Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PDK-1107) Add pdk config get CLI command #715

Merged
merged 1 commit into from
Aug 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 'help'

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

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

require 'pdk/cli/config/get'
24 changes: 24 additions & 0 deletions lib/pdk/cli/config/get.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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.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
# If the user requested a setting and it's the only one resolved, then just output the value
if resolved_config.count == 1 && resolved_config.keys[0] == item_name
puts _('%{value}') % { value: resolved_config.values[0] }
exit 0
end
# Otherwise just output everything
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 @@ -141,6 +141,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 @@ -171,6 +193,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 have_no_output }
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("true\n") }
its(:stderr) { is_expected.to have_no_output }
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 have_no_output }
end

describe command('pdk config get does.not.exist') do
its(:exit_status) { is_expected.not_to eq(0) }
its(:stdout) { is_expected.to have_no_output }
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 }
# Should show the command help
its(:stdout) { is_expected.to match(%r{pdk config \[subcommand\] \[options\]}) }
its(:stderr) { is_expected.to have_no_output }
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