pdk 1.16.0 → 1.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/lib/pdk.rb +25 -18
  4. data/lib/pdk/answer_file.rb +2 -93
  5. data/lib/pdk/cli.rb +1 -5
  6. data/lib/pdk/cli/config.rb +3 -1
  7. data/lib/pdk/cli/config/get.rb +3 -1
  8. data/lib/pdk/cli/convert.rb +1 -1
  9. data/lib/pdk/cli/exec/command.rb +13 -0
  10. data/lib/pdk/cli/exec_group.rb +78 -43
  11. data/lib/pdk/cli/get.rb +20 -0
  12. data/lib/pdk/cli/get/config.rb +24 -0
  13. data/lib/pdk/cli/util.rb +6 -3
  14. data/lib/pdk/cli/validate.rb +26 -44
  15. data/lib/pdk/config.rb +178 -4
  16. data/lib/pdk/config/ini_file.rb +183 -0
  17. data/lib/pdk/config/ini_file_setting.rb +39 -0
  18. data/lib/pdk/config/namespace.rb +25 -5
  19. data/lib/pdk/config/setting.rb +3 -2
  20. data/lib/pdk/context.rb +96 -0
  21. data/lib/pdk/context/control_repo.rb +60 -0
  22. data/lib/pdk/context/module.rb +28 -0
  23. data/lib/pdk/context/none.rb +22 -0
  24. data/lib/pdk/control_repo.rb +40 -0
  25. data/lib/pdk/generate/module.rb +8 -12
  26. data/lib/pdk/module/release.rb +2 -8
  27. data/lib/pdk/util.rb +35 -5
  28. data/lib/pdk/util/bundler.rb +1 -0
  29. data/lib/pdk/util/changelog_generator.rb +6 -1
  30. data/lib/pdk/util/template_uri.rb +4 -3
  31. data/lib/pdk/validate.rb +72 -25
  32. data/lib/pdk/validate/external_command_validator.rb +208 -0
  33. data/lib/pdk/validate/internal_ruby_validator.rb +100 -0
  34. data/lib/pdk/validate/invokable_validator.rb +216 -0
  35. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -0
  36. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -0
  37. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  38. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -0
  39. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  40. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -0
  41. data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -0
  42. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -0
  43. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  44. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -0
  45. data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -0
  46. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  47. data/lib/pdk/validate/validator.rb +111 -0
  48. data/lib/pdk/validate/validator_group.rb +103 -0
  49. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -0
  50. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  51. data/lib/pdk/version.rb +1 -1
  52. data/locales/pdk.pot +161 -125
  53. metadata +29 -17
  54. data/lib/pdk/validate/base_validator.rb +0 -215
  55. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -82
  56. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -111
  57. data/lib/pdk/validate/metadata_validator.rb +0 -26
  58. data/lib/pdk/validate/puppet/puppet_epp.rb +0 -135
  59. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -64
  60. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -135
  61. data/lib/pdk/validate/puppet_validator.rb +0 -26
  62. data/lib/pdk/validate/ruby/rubocop.rb +0 -72
  63. data/lib/pdk/validate/ruby_validator.rb +0 -26
  64. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -130
  65. data/lib/pdk/validate/tasks/name.rb +0 -90
  66. data/lib/pdk/validate/tasks_validator.rb +0 -29
  67. data/lib/pdk/validate/yaml/syntax.rb +0 -125
  68. data/lib/pdk/validate/yaml_validator.rb +0 -28
@@ -0,0 +1,24 @@
1
+ module PDK::CLI
2
+ @get_config_cmd = @get_cmd.define_command do
3
+ name 'config'
4
+ usage _('config [name]')
5
+ summary _('Retrieve the configuration for <name>. If not specified, retrieve all configuration settings')
6
+
7
+ run do |_opts, args, _cmd|
8
+ item_name = args[0]
9
+ resolved_config = PDK.config.resolve(item_name)
10
+ # If the user wanted to know a setting but it doesn't exist, raise an error
11
+ if resolved_config.empty? && !item_name.nil?
12
+ PDK.logger.error(_("Configuration item '%{name}' does not exist") % { name: item_name })
13
+ exit 1
14
+ end
15
+ # If the user requested a setting and it's the only one resolved, then just output the value
16
+ if resolved_config.count == 1 && resolved_config.keys[0] == item_name
17
+ puts _('%{value}') % { value: resolved_config.values[0] }
18
+ exit 0
19
+ end
20
+ # Otherwise just output everything
21
+ resolved_config.keys.sort.each { |key| puts _('%{name}=%{value}') % { name: key, value: resolved_config[key] } }
22
+ end
23
+ end
24
+ end
@@ -129,13 +129,14 @@ module PDK
129
129
  end
130
130
  module_function :check_for_deprecated_puppet
131
131
 
132
- # @param opts [Hash] - the pdk options ot use, defaults to empty hash
132
+ # @param opts [Hash] - the pdk options to use, defaults to empty hash
133
133
  # @option opts [String] :'puppet-dev' Use the puppet development version, default to PDK_PUPPET_DEV env
134
134
  # @option opts [String] :'puppet-version' Puppet version to use, default to PDK_PUPPET_VERSION env
135
135
  # @option opts [String] :'pe-version' PE Puppet version to use, default to PDK_PE_VERSION env
136
136
  # @param logging_disabled [Boolean] - disable logging of PDK info
137
+ # @param context [PDK::Context::AbstractContext] - The context the PDK is running in
137
138
  # @return [Hash] - return hash of { gemset: <>, ruby_version: 2.x.x }
138
- def puppet_from_opts_or_env(opts, logging_disabled = false)
139
+ def puppet_from_opts_or_env(opts, logging_disabled = false, context = PDK.context)
139
140
  opts ||= {}
140
141
  use_puppet_dev = opts.fetch(:'puppet-dev', PDK::Util::Env['PDK_PUPPET_DEV'])
141
142
  desired_puppet_version = opts.fetch(:'puppet-version', PDK::Util::Env['PDK_PUPPET_VERSION'])
@@ -150,8 +151,10 @@ module PDK
150
151
  PDK::Util::PuppetVersion.find_gem_for(desired_puppet_version)
151
152
  elsif desired_pe_version
152
153
  PDK::Util::PuppetVersion.from_pe_version(desired_pe_version)
153
- else
154
+ elsif context.is_a?(PDK::Context::Module)
154
155
  PDK::Util::PuppetVersion.from_module_metadata || PDK::Util::PuppetVersion.latest_available
156
+ else
157
+ PDK::Util::PuppetVersion.latest_available
155
158
  end
156
159
  rescue ArgumentError => e
157
160
  raise PDK::CLI::ExitWithError, e.message
@@ -19,6 +19,9 @@ module PDK::CLI
19
19
  flag nil, :parallel, _('Run validations in parallel.')
20
20
 
21
21
  run do |opts, args, _cmd|
22
+ # Write the context information to the debug log
23
+ PDK.context.to_debug_log
24
+
22
25
  if args == ['help']
23
26
  PDK::CLI.run(['validate', '--help'])
24
27
  exit 0
@@ -26,42 +29,42 @@ module PDK::CLI
26
29
 
27
30
  require 'pdk/validate'
28
31
 
29
- validator_names = PDK::Validate.validators.map { |v| v.name }
30
- validators = PDK::Validate.validators
31
- targets = []
32
-
33
32
  if opts[:list]
34
33
  PDK::CLI::Util.analytics_screen_view('validate', opts)
35
- PDK.logger.info(_('Available validators: %{validator_names}') % { validator_names: validator_names.join(', ') })
34
+ PDK.logger.info(_('Available validators: %{validator_names}') % { validator_names: PDK::Validate.validator_names.join(', ') })
36
35
  exit 0
37
36
  end
38
37
 
39
38
  PDK::CLI::Util.validate_puppet_version_opts(opts)
39
+ unless PDK.feature_flag?('controlrepo') || PDK.context.is_a?(PDK::Context::Module)
40
+ raise PDK::CLI::ExitWithError.new(_('Code validation can only be run from inside a valid module directory'), log_level: :error)
41
+ end
40
42
 
41
- PDK::CLI::Util.ensure_in_module!(
42
- message: _('Code validation can only be run from inside a valid module directory'),
43
- log_level: :info,
44
- )
43
+ PDK::CLI::Util.module_version_check if PDK.context.is_a?(PDK::Context::Module)
45
44
 
46
- PDK::CLI::Util.module_version_check
45
+ # Set the ruby version we're going to use early. Must be set before the validators are created.
46
+ # Note that this is a bit of code-smell and should be fixed
47
+ puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
48
+ PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
47
49
 
50
+ targets = []
51
+ validators_to_run = nil
48
52
  if args[0]
49
53
  # This may be a single validator, a list of validators, or a target.
50
54
  if Util::OptionValidator.comma_separated_list?(args[0])
51
55
  # This is a comma separated list. Treat each item as a validator.
52
-
53
56
  vals = Util::OptionNormalizer.comma_separated_list_to_array(args[0])
54
- validators = PDK::Validate.validators.select { |v| vals.include?(v.name) }
57
+ validators_to_run = PDK::Validate.validator_names.select { |name| vals.include?(name) }
55
58
 
56
- invalid = vals.reject { |v| validator_names.include?(v) }
57
- invalid.each do |v|
58
- PDK.logger.warn(_("Unknown validator '%{v}'. Available validators: %{validators}.") % { v: v, validators: validator_names.join(', ') })
59
+ vals.reject { |v| PDK::Validate.validator_names.include?(v) }
60
+ .each do |v|
61
+ PDK.logger.warn(_("Unknown validator '%{v}'. Available validators: %{validators}.") % { v: v, validators: PDK::Validate.validator_names.join(', ') })
59
62
  end
60
63
  else
61
64
  # This is a single item. Check if it's a known validator, or otherwise treat it as a target.
62
- val = PDK::Validate.validators.find { |v| args[0] == v.name }
65
+ val = PDK::Validate.validator_names.find { |name| args[0] == name }
63
66
  if val
64
- validators = [val]
67
+ validators_to_run = [val]
65
68
  else
66
69
  targets = [args[0]]
67
70
  # We now know that no validators were passed, so let the user know we're using all of them by default.
@@ -71,11 +74,12 @@ module PDK::CLI
71
74
  else
72
75
  PDK.logger.info(_('Running all available validators...'))
73
76
  end
77
+ validators_to_run = PDK::Validate.validator_names if validators_to_run.nil?
74
78
 
75
- if validators == PDK::Validate.validators
79
+ if validators_to_run.sort == PDK::Validate.validator_names.sort
76
80
  PDK::CLI::Util.analytics_screen_view('validate', opts)
77
81
  else
78
- PDK::CLI::Util.analytics_screen_view(['validate', validators.map(&:name).sort].flatten.join('_'), opts)
82
+ PDK::CLI::Util.analytics_screen_view(['validate', validators_to_run.sort].flatten.join('_'), opts)
79
83
  end
80
84
 
81
85
  # Subsequent arguments are targets.
@@ -93,36 +97,14 @@ module PDK::CLI
93
97
 
94
98
  options = targets.empty? ? {} : { targets: targets }
95
99
  options[:auto_correct] = true if opts[:'auto-correct']
96
-
97
- # Ensure that the bundled gems are up to date and correct Ruby is activated before running any validations.
98
- puppet_env = PDK::CLI::Util.puppet_from_opts_or_env(opts)
99
- PDK::Util::RubyVersion.use(puppet_env[:ruby_version])
100
-
101
100
  options.merge!(puppet_env[:gemset])
102
101
 
102
+ # Ensure that the bundled gems are up to date and correct Ruby is activated before running any validations.
103
+ # Note that if no Gemfile exists, then ensure_bundle! will log a debug message and exit gracefully
103
104
  require 'pdk/util/bundler'
104
-
105
105
  PDK::Util::Bundler.ensure_bundle!(puppet_env[:gemset])
106
106
 
107
- exit_code = 0
108
- if opts[:parallel]
109
- require 'pdk/cli/exec_group'
110
-
111
- exec_group = PDK::CLI::ExecGroup.new(_('Validating module using %{num_of_threads} threads' % { num_of_threads: validators.count }), opts)
112
-
113
- validators.each do |validator|
114
- exec_group.register do
115
- validator.invoke(report, options.merge(exec_group: exec_group))
116
- end
117
- end
118
-
119
- exit_code = exec_group.exit_code
120
- else
121
- validators.each do |validator|
122
- validator_exit_code = validator.invoke(report, options.dup)
123
- exit_code = validator_exit_code if validator_exit_code != 0
124
- end
125
- end
107
+ exit_code, report = PDK::Validate.invoke_validators_by_name(PDK.context, validators_to_run, opts.fetch(:parallel, false), options)
126
108
 
127
109
  report_formats.each do |format|
128
110
  report.send(format[:method], format[:target])
@@ -2,6 +2,8 @@ require 'pdk'
2
2
 
3
3
  module PDK
4
4
  class Config
5
+ autoload :IniFile, 'pdk/config/ini_file'
6
+ autoload :IniFileSetting, 'pdk/config/ini_file_setting'
5
7
  autoload :JSON, 'pdk/config/json'
6
8
  autoload :JSONSchemaNamespace, 'pdk/config/json_schema_namespace'
7
9
  autoload :JSONSchemaSetting, 'pdk/config/json_schema_setting'
@@ -11,16 +13,58 @@ module PDK
11
13
  autoload :Validator, 'pdk/config/validator'
12
14
  autoload :YAML, 'pdk/config/yaml'
13
15
 
16
+ # Create a new instance of the PDK Configuration
17
+ # @param options [Hash[String => Object]] Optional hash to override configuration options
18
+ # @option options [String] 'system.path' Path to the system PDK configuration file
19
+ # @option options [String] 'system.module_defaults.path' Path to the system module answers PDK configuration file
20
+ # @option options [String] 'user.path' Path to the user PDK configuration file
21
+ # @option options [String] 'user.module_defaults.path' Path to the user module answers PDK configuration file
22
+ # @option options [String] 'user.analytics.path' Path to the user analytics PDK configuration file
23
+ # @option options [PDK::Context::AbstractContext] 'context' The context that the configuration should be created in
24
+ def initialize(options = nil)
25
+ options = {} if options.nil?
26
+ @config_options = {
27
+ 'system.path' => PDK::Config.system_config_path,
28
+ 'system.module_defaults.path' => PDK::Config.system_answers_path,
29
+ 'user.path' => PDK::Config.user_config_path,
30
+ 'user.module_defaults.path' => PDK::AnswerFile.default_answer_file_path,
31
+ 'user.analytics.path' => PDK::Config.analytics_config_path,
32
+ 'context' => PDK.context,
33
+ }.merge(options)
34
+ end
35
+
36
+ # The user configuration settings.
37
+ # @deprecated This method is only provided as a courtesy until the `pdk set config` CLI and associated changes in this class, are completed.
38
+ # Any read-only operations should be using `.get` or `.get_within_scopes`
39
+ # @return [PDK::Config::Namespace]
14
40
  def user
15
- @user ||= PDK::Config::JSON.new('user', file: PDK::Config.user_config_path) do
16
- mount :module_defaults, PDK::Config::JSON.new(file: PDK.answers.answer_file_path)
41
+ user_config
42
+ end
43
+
44
+ # The system level configuration settings.
45
+ # @return [PDK::Config::Namespace]
46
+ # @api private
47
+ def system_config
48
+ local_options = @config_options
49
+ @system ||= PDK::Config::JSON.new('system', file: local_options['system.path']) do
50
+ mount :module_defaults, PDK::Config::JSON.new(file: local_options['system.module_defaults.path'])
51
+ end
52
+ end
53
+
54
+ # The user level configuration settings.
55
+ # @return [PDK::Config::Namespace]
56
+ # @api private
57
+ def user_config
58
+ local_options = @config_options
59
+ @user ||= PDK::Config::JSON.new('user', file: local_options['user.path']) do
60
+ mount :module_defaults, PDK::Config::JSON.new(file: local_options['user.module_defaults.path'])
17
61
 
18
62
  # Due to the json-schema gem having issues with Windows based paths, and only supporting Draft 05 (or less) do
19
63
  # not use JSON validation yet. Once PDK drops support for EOL rubies, we will be able to use the json_schemer gem
20
64
  # Which has much more modern support
21
65
  # Reference - https://github.com/puppetlabs/pdk/pull/777
22
66
  # Reference - https://tickets.puppetlabs.com/browse/PDK-1526
23
- mount :analytics, PDK::Config::YAML.new(file: PDK::Config.analytics_config_path, persistent_defaults: true) do
67
+ mount :analytics, PDK::Config::YAML.new(file: local_options['user.analytics.path'], persistent_defaults: true) do
24
68
  setting :disabled do
25
69
  validate PDK::Config::Validator.boolean
26
70
  default_to { PDK::Config.bolt_analytics_config.fetch('disabled', true) }
@@ -35,6 +79,29 @@ module PDK
35
79
  end
36
80
  end
37
81
  end
82
+
83
+ # Display the feature flags
84
+ mount :pdk_feature_flags, PDK::Config::Namespace.new('pdk_feature_flags') do
85
+ setting 'available' do
86
+ default_to { PDK.available_feature_flags }
87
+ end
88
+
89
+ setting 'requested' do
90
+ default_to { PDK.requested_feature_flags }
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ # The project level configuration settings.
97
+ # @return [PDK::Config::Namespace]
98
+ # @api private
99
+ def project_config
100
+ context = @config_options['context']
101
+ @project ||= PDK::Config::Namespace.new('project') do
102
+ if context.is_a?(PDK::Context::ControlRepo)
103
+ mount :environment, PDK::ControlRepo.environment_conf_as_config(File.join(context.root_path, 'environment.conf'))
104
+ end
38
105
  end
39
106
  end
40
107
 
@@ -43,7 +110,63 @@ module PDK
43
110
  # @param filter [String] Only resolve setting names which match the filter. See PDK::Config::Namespace.be_resolved? for matching rules
44
111
  # @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
45
112
  def resolve(filter = nil)
46
- user.resolve(filter)
113
+ all_scopes.values.reverse.reduce({}) do |result, method_name|
114
+ result.merge(send(method_name).resolve(filter))
115
+ end
116
+ end
117
+
118
+ # Returns a configuration setting by name. This name can either be a String, Array or parameters e.g. These are equivalent
119
+ # - PDK.config.get('user.a.b.c')
120
+ # - PDK.config.get(['user', 'a', 'b', 'c'])
121
+ # - PDK.config.get('user', 'a', 'b', 'c')
122
+ # @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
123
+ def get(root, *keys)
124
+ return nil if root.nil? || root.empty?
125
+
126
+ if keys.empty?
127
+ if root.is_a?(Array)
128
+ name = root
129
+ elsif root.is_a?(String)
130
+ name = split_key_string(root)
131
+ else
132
+ return nil
133
+ end
134
+ else
135
+ name = [root].concat(keys)
136
+ end
137
+
138
+ get_within_scopes(name[1..-1], [name[0]])
139
+ end
140
+
141
+ # Returns a configuration setting by name, using scope precedence rules. If no scopes are passed, then all scopes are queried using the default precedence rules
142
+ # @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
143
+ # @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
144
+ # @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
145
+ def get_within_scopes(setting_name, scopes = nil)
146
+ raise ArgumentError, _('Expected an Array but got \'%{klass}\' for scopes') % { klass: scopes.class } unless scopes.nil? || scopes.is_a?(Array)
147
+ raise ArgumentError, _('Expected an Array or String but got \'%{klass}\' for setting_name') % { klass: setting_name.class } unless setting_name.is_a?(Array) || setting_name.is_a?(String)
148
+
149
+ setting_arr = setting_name.is_a?(String) ? split_key_string(setting_name) : setting_name
150
+ all_scope_names = all_scopes.keys
151
+
152
+ # Use only valid scope names
153
+ scopes = scopes.nil? ? all_scope_names : scopes & all_scope_names
154
+
155
+ scopes.each do |scope_name|
156
+ value = traverse_object(send(all_scopes[scope_name]), *setting_arr)
157
+ return value unless value.nil?
158
+ end
159
+ nil
160
+ end
161
+
162
+ # Yields a configuration setting value by name, using scope precedence rules. If no scopes are passed, then all scopes are queried using the default precedence rules
163
+ # @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
164
+ # @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
165
+ # @yield [PDK::Config::Namespace, Object] The value of the configuration setting. Does not yield if the setting does not exist or is nil
166
+ def with_scoped_value(setting_name, scopes = nil)
167
+ raise ArgumentError, _('must be passed a block') unless block_given?
168
+ value = get_within_scopes(setting_name, scopes)
169
+ yield value unless value.nil?
47
170
  end
48
171
 
49
172
  def self.bolt_analytics_config
@@ -65,6 +188,14 @@ module PDK
65
188
  File.join(PDK::Util.configdir, 'user_config.json')
66
189
  end
67
190
 
191
+ def self.system_config_path
192
+ File.join(PDK::Util.system_configdir, 'system_config.json')
193
+ end
194
+
195
+ def self.system_answers_path
196
+ File.join(PDK::Util.system_configdir, 'answers.json')
197
+ end
198
+
68
199
  def self.json_schemas_path
69
200
  File.join(__dir__, 'config')
70
201
  end
@@ -123,5 +254,48 @@ module PDK
123
254
 
124
255
  PDK.logger.info(text: post_message, wrap: true)
125
256
  end
257
+
258
+ private
259
+
260
+ #:nocov: This is a private method and is tested elsewhere
261
+ def traverse_object(object, *names)
262
+ return nil if object.nil? || !object.respond_to?(:[])
263
+ return nil if names.nil? || names.empty?
264
+
265
+ name = names.shift
266
+ value = object[name]
267
+ if names.empty?
268
+ return value if value.is_a?(PDK::Config::Namespace)
269
+ # Duplicate arrays and hashes so that they are isolated from changes being made
270
+ (value.is_a?(Hash) || value.is_a?(Array)) ? value.dup : value
271
+ else
272
+ traverse_object(value, *names)
273
+ end
274
+ end
275
+ #:nocov:
276
+
277
+ #:nocov: This is a private method and is tested elsewhere
278
+ # Takes a string representation of a setting and splits into its constituent setting parts e.g.
279
+ # 'user.a.b.c' becomes ['user', 'a', 'b', 'c']
280
+ # @return [Array[String]] The string split into each setting name as an array
281
+ def split_key_string(key)
282
+ raise ArgumentError, _('Expected a String but got \'%{klass}\'') % { klass: key.class } unless key.is_a?(String)
283
+ key.split('.')
284
+ end
285
+ #:nocov:
286
+
287
+ #:nocov:
288
+ # Returns all known scope names and their associated method name to call, to query the scope
289
+ # Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
290
+ # @return [Hash[String, Symbol]] A hash of the scope name then method name to call to query the scope (as a Symbol)
291
+ def all_scopes
292
+ # Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
293
+ {
294
+ 'project' => :project_config,
295
+ 'user' => :user_config,
296
+ 'system' => :system_config,
297
+ }.freeze
298
+ end
299
+ #:nocov:
126
300
  end
127
301
  end
@@ -0,0 +1,183 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ class Config
5
+ # Represents a configuration file using the INI file format
6
+ class IniFile < Namespace
7
+ # Ini Files have very strict valdiation rules which are set in the IniFileSetting class
8
+ # @see PDK::Config::Namespace.default_setting_class
9
+ def default_setting_class
10
+ PDK::Config::IniFileSetting
11
+ end
12
+
13
+ # Parses an IniFile document.
14
+ #
15
+ # @see PDK::Config::Namespace.parse_file
16
+ def parse_file(filename)
17
+ raise unless block_given?
18
+ data = load_data(filename)
19
+ return if data.nil? || data.empty?
20
+
21
+ ini_file = IniFileImpl.parse(data)
22
+ ini_file.to_hash.each do |name, value|
23
+ begin
24
+ new_setting = PDK::Config::IniFileSetting.new(name, self, value)
25
+ rescue StandardError
26
+ # We just ignore invalid initial settings
27
+ new_setting = PDK::Config::IniFileSetting.new(name, self, nil)
28
+ end
29
+
30
+ yield name, new_setting
31
+ end
32
+ end
33
+
34
+ # Serializes object data into an INI file string.
35
+ #
36
+ # @see PDK::Config::Namespace.serialize_data
37
+ def serialize_data(data)
38
+ default_lines = ''
39
+ lines = ''
40
+ data.each do |name, value|
41
+ next if value.nil?
42
+ if value.is_a?(Hash)
43
+ # Hashes are an INI section
44
+ lines += "\n[#{name}]\n"
45
+ value.each do |child_name, child_value|
46
+ next if child_value.nil?
47
+ lines += "#{child_name} = #{munge_serialized_value(child_value)}\n"
48
+ end
49
+ else
50
+ default_lines += "#{name} = #{munge_serialized_value(value)}\n"
51
+ end
52
+ end
53
+
54
+ default_lines + lines
55
+ end
56
+
57
+ private
58
+
59
+ def munge_serialized_value(value)
60
+ value = value.to_s unless value.is_a?(String)
61
+ # Add enclosing quotes if there's a space in the value
62
+ value = '"' + value + '"' if value.include?(' ')
63
+ value
64
+ end
65
+
66
+ # Adapted from https://raw.githubusercontent.com/puppetlabs/puppet/6c257fc7827989c2af2901f974666f0f23611153/lib/puppet/settings/ini_file.rb
67
+ # rubocop:disable Style/RegexpLiteral
68
+ # rubocop:disable Style/PerlBackrefs
69
+ # rubocop:disable Style/RedundantSelf
70
+ # rubocop:disable Style/StringLiterals
71
+ class IniFileImpl
72
+ DEFAULT_SECTION_NAME = 'default_section_name'.freeze
73
+
74
+ def self.parse(config_fh)
75
+ config = new([DefaultSection.new])
76
+ config_fh.each_line do |line|
77
+ case line.chomp
78
+ when /^(\s*)\[([[:word:]]+)\](\s*)$/
79
+ config.append(SectionLine.new($1, $2, $3))
80
+ when /^(\s*)([[:word:]]+)(\s*=\s*)(.*?)(\s*)$/
81
+ config.append(SettingLine.new($1, $2, $3, $4, $5))
82
+ else
83
+ config.append(Line.new(line))
84
+ end
85
+ end
86
+
87
+ config
88
+ end
89
+
90
+ def initialize(lines = [])
91
+ @lines = lines
92
+ end
93
+
94
+ def to_hash
95
+ result = {}
96
+
97
+ current_section_name = nil
98
+ @lines.each do |line|
99
+ if line.instance_of?(SectionLine)
100
+ current_section_name = line.name
101
+ result[current_section_name] = {}
102
+ elsif line.instance_of?(SettingLine)
103
+ if current_section_name.nil?
104
+ result[line.name] = munge_value(line.value)
105
+ else
106
+ result[current_section_name][line.name] = munge_value(line.value)
107
+ end
108
+ end
109
+ end
110
+
111
+ result
112
+ end
113
+
114
+ def munge_value(value)
115
+ value = value.to_s unless value.is_a?(String)
116
+ # Strip enclosing quotes
117
+ value = value.slice(1...-1) if value.start_with?('"') && value.end_with?('"')
118
+ value
119
+ end
120
+
121
+ def append(line)
122
+ line.previous = @lines.last
123
+ @lines << line
124
+ end
125
+
126
+ module LineNumber
127
+ attr_accessor :previous
128
+
129
+ def line_number
130
+ line = 0
131
+ previous_line = previous
132
+ while previous_line
133
+ line += 1
134
+ previous_line = previous_line.previous
135
+ end
136
+ line
137
+ end
138
+ end
139
+
140
+ Line = Struct.new(:text) do
141
+ include LineNumber
142
+
143
+ def to_s
144
+ text
145
+ end
146
+ end
147
+
148
+ SettingLine = Struct.new(:prefix, :name, :infix, :value, :suffix) do
149
+ include LineNumber
150
+
151
+ def to_s
152
+ "#{prefix}#{name}#{infix}#{value}#{suffix}"
153
+ end
154
+
155
+ def ==(other)
156
+ super(other) && self.line_number == other.line_number
157
+ end
158
+ end
159
+
160
+ SectionLine = Struct.new(:prefix, :name, :suffix) do
161
+ include LineNumber
162
+
163
+ def to_s
164
+ "#{prefix}[#{name}]#{suffix}"
165
+ end
166
+ end
167
+
168
+ class DefaultSection < SectionLine
169
+ attr_accessor :write_sectionline
170
+
171
+ def initialize
172
+ @write_sectionline = false
173
+ super("", DEFAULT_SECTION_NAME, "")
174
+ end
175
+ end
176
+ end
177
+ # rubocop:enable Style/StringLiterals
178
+ # rubocop:enable Style/RedundantSelf
179
+ # rubocop:enable Style/PerlBackrefs
180
+ # rubocop:enable Style/RegexpLiteral
181
+ end
182
+ end
183
+ end