Skip to content

Commit

Permalink
(PDK-1107) Add pdk config get CLI command
Browse files Browse the repository at this point in the history
Previously there was no way to display what the current configuration the PDK
was using.  This commit adds a `pdk config` command, with a single `get`
sub-command.

This commit also adds the ability to filter the settings either by a specific
name or setting treename.

This commit also adds tests for the new resolve method in the Namespace object.
  • Loading branch information
glennsarti committed Jul 31, 2019
1 parent 050fbc1 commit f613c8c
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 3 deletions.
1 change: 1 addition & 0 deletions lib/pdk/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ def self.puppet_dev_option(dsl)

require 'pdk/cli/bundle'
require 'pdk/cli/build'
require 'pdk/cli/config'
require 'pdk/cli/convert'
require 'pdk/cli/new'
require 'pdk/cli/test'
Expand Down
20 changes: 20 additions & 0 deletions lib/pdk/cli/config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module PDK::CLI
@config_cmd = @base_cmd.define_command do
name 'config'
usage _('config [subcommand] [options]')
summary _('Configure the Puppet Development Kit.')
default_subcommand 'get'

run do |_opts, args, _cmd|
if args == ['help']
PDK::CLI.run(%w[config --help])
exit 0
end

PDK::CLI.run(%w[config get]) if args.empty?
end
end
@config_cmd.add_command Cri::Command.new_basic_help
end

require 'pdk/cli/config/get'
18 changes: 18 additions & 0 deletions lib/pdk/cli/config/get.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module PDK::CLI
@config_get_cmd = @config_cmd.define_command do
name 'get'
usage _('config get [name]')
summary _('Retrieve the configuration for <name>. If not specified, retrieve all configuration settings')

run do |_opts, args, _cmd|
item_name = args[0]
resolved_config = PDK::Config.new.resolve(item_name)
# If the user wanted to know a setting but it doesn't exist, raise an error
if resolved_config.empty? && !item_name.nil?
PDK.logger.error(_("Configuration item '%{name}' does not exist") % { name: item_name })
exit 1
end
resolved_config.keys.sort.each { |key| puts _('%{name}=%{value}') % { name: key, value: resolved_config[key] } }
end
end
end
8 changes: 8 additions & 0 deletions lib/pdk/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ def user
end
end

# Resolves *all* filtered settings from all namespaces
#
# @param filter [String] Only resolve setting names which match the filter. See PDK::Config::Namespace.be_resolved? for matching rules
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
def resolve(filter = nil)
user.resolve(filter)
end

def self.bolt_analytics_config
file = File.expand_path('~/.puppetlabs/bolt/analytics.yaml')
PDK::Config::YAML.new(file: file)
Expand Down
37 changes: 37 additions & 0 deletions lib/pdk/config/namespace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,28 @@ def to_h
end
end

# Resolves all filtered settings, including child namespaces, fully namespaced and filling in default values.
#
# @param filter [String] Only resolve setting names which match the filter. See #be_resolved? for matching rules
# @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
def resolve(filter = nil)
# Explicitly force values to be loaded if they have not already
# done so. This will not cause them to be persisted to disk
(@values.keys - data.keys).each { |key_name| self[key_name] }
resolved = {}
data.each do |data_name, obj|
case obj
when PDK::Config::Namespace
# Query the child namespace
resolved.merge!(obj.resolve(filter))
else
setting_name = [name, data_name.to_s].join('.')
resolved[setting_name] = self[data_name] if be_resolved?(setting_name, filter)
end
end
resolved
end

# @return [Boolean] true if the namespace has a parent, otherwise false.
def child_namespace?
!parent.nil?
Expand Down Expand Up @@ -168,6 +190,21 @@ def include_in_parent?

private

# Determines whether a setting name should be resolved using the filter
# Returns true when filter is nil.
# Returns true if the filter is exactly the same name as the setting.
# Returns true if the name is a sub-key of the filter e.g.
# Given a filter of user.module_defaults, `user.module_defaults.author` will return true, but `user.analytics.disabled` will return false.
#
# @param name [String] The setting name to test.
# @param filter [String] The filter used to test on the name.
# @return [Boolean] Whether the name passes the filter.
def be_resolved?(name, filter = nil)
return true if filter.nil? # If we're not filtering, this value should always be resolved
return true if name == filter # If it's exactly the same name then it should be resolved
name.start_with?(filter + '.') # If name is a subkey of the filter then it should be resolved
end

# @abstract Subclass and override {#parse_data} to implement parsing logic
# for a particular config file format.
#
Expand Down
40 changes: 40 additions & 0 deletions spec/acceptance/config_get_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require 'spec_helper_acceptance'
require 'fileutils'

describe 'pdk config get' do
include_context 'with a fake TTY'

context 'when run outside of a module' do
describe command('pdk config get') do
its(:exit_status) { is_expected.to eq 0 }
# This setting should appear in all pdk versions
its(:stdout) { is_expected.to match(%r{user\.analytics\.user-id=}) }
its(:stderr) { is_expected.to be_empty }
end

describe command('pdk config get user.analytics.disabled') do
its(:exit_status) { is_expected.to eq 0 }
# This setting, and only, this setting should appear in output
its(:stdout) { is_expected.to eq("user.analytics.disabled=true\n") }
its(:stderr) { is_expected.to be_empty }
end

describe command('pdk config get user.analytics') do
its(:exit_status) { is_expected.to eq 0 }
# There should be two configuration items returned
its(:stdout) { expect(is_expected.target.split("\n").count).to eq(2) }
its(:stdout) do
result = is_expected.target.split("\n").sort
expect(result[0]).to match('user.analytics.disabled=true')
expect(result[1]).to match(%r{user.analytics.user-id=.+})
end
its(:stderr) { is_expected.to be_empty }
end

describe command('pdk config get does.not.exist') do
its(:exit_status) { is_expected.not_to eq(0) }
its(:stdout) { is_expected.to be_empty }
its(:stderr) { is_expected.to match(%r{does\.not\.exist}) }
end
end
end
15 changes: 15 additions & 0 deletions spec/acceptance/config_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'spec_helper_acceptance'
require 'fileutils'

describe 'pdk config' do
include_context 'with a fake TTY'

context 'when run outside of a module' do
describe command('pdk config') do
its(:exit_status) { is_expected.to eq 0 }
# This setting should appear in all pdk versions
its(:stdout) { is_expected.to match(%r{user\.analytics\.user-id=}) }
its(:stderr) { is_expected.to be_empty }
end
end
end
55 changes: 55 additions & 0 deletions spec/unit/pdk/config/namespace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,61 @@
end
end

describe '#resolve' do
let(:config_options) { { persistent_defaults: false } }

include_context :with_a_mounted_file_with_content, 'mounted', '{"setting1": "value1"}'

before(:each) do
# Add a value with a default value
config.value('spec_test') do
default_to { 'spec_default' }
end
# The resolver should not trigger any saves unless persistent_defaults is set to true
expect(PDK::Util::Filesystem).to receive(:write_file).never
end

context 'with an empty filter' do
let(:filter) { nil }

it 'resolves all settings' do
result = config.resolve(filter)
expect(result.count).to eq(2)
expect(result['config.spec_test']).to eq('spec_default')
expect(result['config.mounted.setting1']).to eq('value1')
end
end

context 'with a setting name' do
let(:filter) { 'config.spec_test' }

it 'resolves only one setting' do
result = config.resolve(filter)
expect(result.count).to eq(1)
expect(result['config.spec_test']).to eq('spec_default')
end
end

context 'with a tree name' do
let(:filter) { 'config.mounted' }

it 'resolves only settings in the tree' do
result = config.resolve(filter)
expect(result.count).to eq(1)
expect(result['config.mounted.setting1']).to eq('value1')
end
end

context 'with a name that cannot be resolved' do
let(:filter) { 'does.not.exist' }

it 'returns an empty hash' do
result = config.resolve(filter)
expect(result).to eq({})
end
end
end

describe '#namespace' do
before(:each) do
config.namespace('test')
Expand Down
11 changes: 8 additions & 3 deletions spec/unit/pdk/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ def mock_file(path, content)
end
end

def uuid_regex(uuid)
# Depending on the YAML or JSON generator, it may, or may not have quotes
%r{user-id: (?:#{uuid}|'#{uuid}'|\"#{uuid}\")}
end

context 'default value' do
context 'when there is no pre-existing bolt configuration' do
it 'generates a new UUID' do
Expand All @@ -107,7 +112,7 @@ def mock_file(path, content)
new_id = SecureRandom.uuid
expect(SecureRandom).to receive(:uuid).and_return(new_id)
# Expect that the user-id is saved to the config file
expect(PDK::Util::Filesystem).to receive(:write_file).with(File.expand_path(described_class.analytics_config_path), %r{user-id: #{new_id}})
expect(PDK::Util::Filesystem).to receive(:write_file).with(File.expand_path(described_class.analytics_config_path), uuid_regex(new_id))
# ... and that it returns the new id
expect(config.user['analytics']['user-id']).to eq(new_id)
end
Expand All @@ -123,7 +128,7 @@ def mock_file(path, content)

it 'saves the UUID to the analytics config file' do
# Expect that the user-id is saved to the config file
expect(PDK::Util::Filesystem).to receive(:write_file).with(File.expand_path(described_class.analytics_config_path), %r{user-id: #{uuid}})
expect(PDK::Util::Filesystem).to receive(:write_file).with(File.expand_path(described_class.analytics_config_path), uuid_regex(uuid))
config.user['analytics']['user-id']
end

Expand All @@ -144,7 +149,7 @@ def mock_file(path, content)
new_id = SecureRandom.uuid
expect(SecureRandom).to receive(:uuid).and_return(new_id)
# Expect that the user-id is saved to the config file
expect(PDK::Util::Filesystem).to receive(:write_file).with(File.expand_path(described_class.analytics_config_path), %r{user-id: #{new_id}})
expect(PDK::Util::Filesystem).to receive(:write_file).with(File.expand_path(described_class.analytics_config_path), uuid_regex(new_id))
# ... and that it returns the new id
expect(config.user['analytics']['user-id']).to eq(new_id)
end
Expand Down

0 comments on commit f613c8c

Please sign in to comment.