pdk 1.14.1 → 1.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (128) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +155 -2
  3. data/lib/pdk.rb +28 -19
  4. data/lib/pdk/answer_file.rb +2 -95
  5. data/lib/pdk/bolt.rb +19 -0
  6. data/lib/pdk/cli.rb +4 -5
  7. data/lib/pdk/cli/bundle.rb +5 -1
  8. data/lib/pdk/cli/config.rb +3 -1
  9. data/lib/pdk/cli/config/get.rb +3 -1
  10. data/lib/pdk/cli/console.rb +1 -1
  11. data/lib/pdk/cli/convert.rb +16 -10
  12. data/lib/pdk/cli/exec.rb +2 -1
  13. data/lib/pdk/cli/exec/command.rb +45 -4
  14. data/lib/pdk/cli/exec_group.rb +78 -43
  15. data/lib/pdk/cli/get.rb +20 -0
  16. data/lib/pdk/cli/get/config.rb +24 -0
  17. data/lib/pdk/cli/module/build.rb +1 -1
  18. data/lib/pdk/cli/module/generate.rb +1 -1
  19. data/lib/pdk/cli/new/class.rb +2 -2
  20. data/lib/pdk/cli/new/defined_type.rb +2 -2
  21. data/lib/pdk/cli/new/provider.rb +2 -2
  22. data/lib/pdk/cli/new/task.rb +2 -2
  23. data/lib/pdk/cli/new/test.rb +2 -2
  24. data/lib/pdk/cli/new/transport.rb +2 -2
  25. data/lib/pdk/cli/release.rb +192 -0
  26. data/lib/pdk/cli/release/prep.rb +39 -0
  27. data/lib/pdk/cli/release/publish.rb +40 -0
  28. data/lib/pdk/cli/remove.rb +20 -0
  29. data/lib/pdk/cli/remove/config.rb +80 -0
  30. data/lib/pdk/cli/set.rb +20 -0
  31. data/lib/pdk/cli/set/config.rb +119 -0
  32. data/lib/pdk/cli/update.rb +18 -8
  33. data/lib/pdk/cli/util.rb +7 -3
  34. data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
  35. data/lib/pdk/cli/validate.rb +26 -44
  36. data/lib/pdk/config.rb +265 -8
  37. data/lib/pdk/config/ini_file.rb +183 -0
  38. data/lib/pdk/config/ini_file_setting.rb +39 -0
  39. data/lib/pdk/config/namespace.rb +26 -6
  40. data/lib/pdk/config/setting.rb +3 -2
  41. data/lib/pdk/context.rb +99 -0
  42. data/lib/pdk/context/control_repo.rb +60 -0
  43. data/lib/pdk/context/module.rb +28 -0
  44. data/lib/pdk/context/none.rb +22 -0
  45. data/lib/pdk/control_repo.rb +90 -0
  46. data/lib/pdk/generate.rb +1 -0
  47. data/lib/pdk/generate/defined_type.rb +25 -32
  48. data/lib/pdk/generate/module.rb +42 -35
  49. data/lib/pdk/generate/provider.rb +16 -65
  50. data/lib/pdk/generate/puppet_class.rb +25 -31
  51. data/lib/pdk/generate/puppet_object.rb +84 -189
  52. data/lib/pdk/generate/resource_api_object.rb +55 -0
  53. data/lib/pdk/generate/task.rb +28 -46
  54. data/lib/pdk/generate/transport.rb +21 -75
  55. data/lib/pdk/module.rb +1 -1
  56. data/lib/pdk/module/build.rb +38 -25
  57. data/lib/pdk/module/convert.rb +61 -42
  58. data/lib/pdk/module/metadata.rb +1 -3
  59. data/lib/pdk/module/release.rb +254 -0
  60. data/lib/pdk/module/update.rb +24 -16
  61. data/lib/pdk/module/update_manager.rb +8 -1
  62. data/lib/pdk/report.rb +18 -12
  63. data/lib/pdk/report/event.rb +6 -3
  64. data/lib/pdk/template.rb +59 -0
  65. data/lib/pdk/template/fetcher.rb +98 -0
  66. data/lib/pdk/template/fetcher/git.rb +85 -0
  67. data/lib/pdk/template/fetcher/local.rb +28 -0
  68. data/lib/pdk/template/renderer.rb +96 -0
  69. data/lib/pdk/template/renderer/v1.rb +25 -0
  70. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +116 -0
  71. data/lib/pdk/template/renderer/v1/renderer.rb +132 -0
  72. data/lib/pdk/template/renderer/v1/template_file.rb +102 -0
  73. data/lib/pdk/template/template_dir.rb +67 -0
  74. data/lib/pdk/tests/unit.rb +5 -0
  75. data/lib/pdk/util.rb +55 -45
  76. data/lib/pdk/util/bundler.rb +9 -9
  77. data/lib/pdk/util/changelog_generator.rb +120 -0
  78. data/lib/pdk/util/env.rb +28 -11
  79. data/lib/pdk/util/filesystem.rb +62 -2
  80. data/lib/pdk/util/git.rb +60 -8
  81. data/lib/pdk/util/json_finder.rb +84 -0
  82. data/lib/pdk/util/puppet_strings.rb +3 -3
  83. data/lib/pdk/util/puppet_version.rb +4 -5
  84. data/lib/pdk/util/ruby_version.rb +9 -6
  85. data/lib/pdk/util/template_uri.rb +60 -48
  86. data/lib/pdk/util/version.rb +4 -4
  87. data/lib/pdk/validate.rb +79 -25
  88. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
  89. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
  90. data/lib/pdk/validate/external_command_validator.rb +208 -0
  91. data/lib/pdk/validate/internal_ruby_validator.rb +100 -0
  92. data/lib/pdk/validate/invokable_validator.rb +215 -0
  93. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -0
  94. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -0
  95. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  96. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -0
  97. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  98. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -0
  99. data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -0
  100. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -0
  101. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  102. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -0
  103. data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -0
  104. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  105. data/lib/pdk/validate/validator.rb +118 -0
  106. data/lib/pdk/validate/validator_group.rb +104 -0
  107. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -0
  108. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  109. data/lib/pdk/version.rb +1 -1
  110. data/locales/pdk.pot +755 -319
  111. metadata +66 -24
  112. data/lib/pdk/module/templatedir.rb +0 -391
  113. data/lib/pdk/template_file.rb +0 -96
  114. data/lib/pdk/validate/base_validator.rb +0 -215
  115. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -82
  116. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -111
  117. data/lib/pdk/validate/metadata_validator.rb +0 -26
  118. data/lib/pdk/validate/puppet/puppet_epp.rb +0 -137
  119. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -64
  120. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -137
  121. data/lib/pdk/validate/puppet_validator.rb +0 -26
  122. data/lib/pdk/validate/ruby/rubocop.rb +0 -72
  123. data/lib/pdk/validate/ruby_validator.rb +0 -26
  124. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -130
  125. data/lib/pdk/validate/tasks/name.rb +0 -90
  126. data/lib/pdk/validate/tasks_validator.rb +0 -29
  127. data/lib/pdk/validate/yaml/syntax.rb +0 -125
  128. data/lib/pdk/validate/yaml_validator.rb +0 -28
@@ -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,50 @@ module PDK
11
13
  autoload :Validator, 'pdk/config/validator'
12
14
  autoload :YAML, 'pdk/config/yaml'
13
15
 
14
- 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)
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 system level configuration settings.
37
+ # @return [PDK::Config::Namespace]
38
+ # @api private
39
+ def system_config
40
+ local_options = @config_options
41
+ @system_config ||= PDK::Config::JSON.new('system', file: local_options['system.path']) do
42
+ mount :module_defaults, PDK::Config::JSON.new(file: local_options['system.module_defaults.path'])
43
+ end
44
+ end
45
+
46
+ # The user level configuration settings.
47
+ # @return [PDK::Config::Namespace]
48
+ # @api private
49
+ def user_config
50
+ local_options = @config_options
51
+ @user_config ||= PDK::Config::JSON.new('user', file: local_options['user.path']) do
52
+ mount :module_defaults, PDK::Config::JSON.new(file: local_options['user.module_defaults.path'])
17
53
 
18
54
  # Due to the json-schema gem having issues with Windows based paths, and only supporting Draft 05 (or less) do
19
55
  # not use JSON validation yet. Once PDK drops support for EOL rubies, we will be able to use the json_schemer gem
20
56
  # Which has much more modern support
21
57
  # Reference - https://github.com/puppetlabs/pdk/pull/777
22
58
  # 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
59
+ mount :analytics, PDK::Config::YAML.new(file: local_options['user.analytics.path'], persistent_defaults: true) do
24
60
  setting :disabled do
25
61
  validate PDK::Config::Validator.boolean
26
62
  default_to { PDK::Config.bolt_analytics_config.fetch('disabled', true) }
@@ -35,6 +71,29 @@ module PDK
35
71
  end
36
72
  end
37
73
  end
74
+
75
+ # Display the feature flags
76
+ mount :pdk_feature_flags, PDK::Config::Namespace.new('pdk_feature_flags') do
77
+ setting 'available' do
78
+ default_to { PDK.available_feature_flags }
79
+ end
80
+
81
+ setting 'requested' do
82
+ default_to { PDK.requested_feature_flags }
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ # The project level configuration settings.
89
+ # @return [PDK::Config::Namespace]
90
+ # @api private
91
+ def project_config
92
+ context = @config_options['context']
93
+ @project ||= PDK::Config::Namespace.new('project') do
94
+ if context.is_a?(PDK::Context::ControlRepo)
95
+ mount :environment, PDK::ControlRepo.environment_conf_as_config(File.join(context.root_path, 'environment.conf'))
96
+ end
38
97
  end
39
98
  end
40
99
 
@@ -43,11 +102,89 @@ module PDK
43
102
  # @param filter [String] Only resolve setting names which match the filter. See PDK::Config::Namespace.be_resolved? for matching rules
44
103
  # @return [Hash{String => Object}] All resolved settings for example {'user.module_defaults.author' => 'johndoe'}
45
104
  def resolve(filter = nil)
46
- user.resolve(filter)
105
+ all_scopes.values.reverse.reduce({}) do |result, method_name|
106
+ result.merge(send(method_name).resolve(filter))
107
+ end
108
+ end
109
+
110
+ # Returns a configuration setting by name. This name can either be a String, Array or parameters e.g. These are equivalent
111
+ # - PDK.config.get('user.a.b.c')
112
+ # - PDK.config.get(['user', 'a', 'b', 'c'])
113
+ # - PDK.config.get('user', 'a', 'b', 'c')
114
+ # @param root [Array[String], String] The root setting name or the entire setting name as a single string
115
+ # @param keys [String] The child names of the setting
116
+ # @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
117
+ def get(root, *keys)
118
+ return nil if root.nil? || root.empty?
119
+
120
+ if keys.empty?
121
+ if root.is_a?(Array)
122
+ name = root
123
+ elsif root.is_a?(String)
124
+ name = split_key_string(root)
125
+ else
126
+ return nil
127
+ end
128
+ else
129
+ name = [root].concat(keys)
130
+ end
131
+
132
+ get_within_scopes(name[1..-1], [name[0]])
133
+ end
134
+
135
+ # 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
136
+ # @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
137
+ # @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
138
+ # @return [PDK::Config::Namespace, Object, nil] The value of the configuration setting. Returns nil if it does no exist
139
+ def get_within_scopes(setting_name, scopes = nil)
140
+ raise ArgumentError, _('Expected an Array but got \'%{klass}\' for scopes') % { klass: scopes.class } unless scopes.nil? || scopes.is_a?(Array)
141
+ 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)
142
+
143
+ setting_arr = setting_name.is_a?(String) ? split_key_string(setting_name) : setting_name
144
+ all_scope_names = all_scopes.keys
145
+
146
+ # Use only valid scope names
147
+ scopes = scopes.nil? ? all_scope_names : scopes & all_scope_names
148
+
149
+ scopes.each do |scope_name|
150
+ value = traverse_object(send(all_scopes[scope_name]), *setting_arr)
151
+ return value unless value.nil?
152
+ end
153
+ nil
154
+ end
155
+
156
+ # 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
157
+ # @setting_name [String, Array[String]] The setting name to retrieve without the leading scope name e.g. Use 'setting' instead of 'system.setting'
158
+ # @scopes [Nil, Array[String]] The list of scopes, in order, to query in turn for the setting_name. Invalid or missing scopes are ignored.
159
+ # @yield [PDK::Config::Namespace, Object] The value of the configuration setting. Does not yield if the setting does not exist or is nil
160
+ def with_scoped_value(setting_name, scopes = nil)
161
+ raise ArgumentError, _('must be passed a block') unless block_given?
162
+ value = get_within_scopes(setting_name, scopes)
163
+ yield value unless value.nil?
164
+ end
165
+
166
+ # Sets a configuration setting by name. This name can either be a String or an Array
167
+ # - PDK.config.set('user.a.b.c', ...)
168
+ # - PDK.config.set(['user', 'a', 'b', 'c'], ...)
169
+ # @param key [String, Array[String]] The name of the configuration key to change
170
+ # @param value [Object] The value to set the configuration setting to
171
+ # @param options [Hash] Changes the behaviour of the setting process
172
+ # @option options [Boolean] :force Disables any munging or array processing, and sets the value as it is. Default is false
173
+ # @return [Object] The new value of the configuration setting
174
+ def set(key, value, options = {})
175
+ options = {
176
+ force: false,
177
+ }.merge(options)
178
+
179
+ names = key.is_a?(String) ? split_key_string(key) : key
180
+ raise ArgumentError, _('Invalid configuration names') if names.nil? || !names.is_a?(Array) || names.empty?
181
+ scope_name = names[0]
182
+ raise ArgumentError, _("Unknown configuration root '%{name}'") % { name: scope_name } if all_scopes[scope_name].nil?
183
+ deep_set_object(value, options[:force], send(all_scopes[scope_name]), *names[1..-1])
47
184
  end
48
185
 
49
186
  def self.bolt_analytics_config
50
- file = File.expand_path('~/.puppetlabs/bolt/analytics.yaml')
187
+ file = PDK::Util::Filesystem.expand_path('~/.puppetlabs/bolt/analytics.yaml')
51
188
  PDK::Config::YAML.new(file: file)
52
189
  rescue PDK::Config::LoadError => e
53
190
  PDK.logger.debug _('Unable to load %{file}: %{message}') % {
@@ -65,6 +202,14 @@ module PDK
65
202
  File.join(PDK::Util.configdir, 'user_config.json')
66
203
  end
67
204
 
205
+ def self.system_config_path
206
+ File.join(PDK::Util.system_configdir, 'system_config.json')
207
+ end
208
+
209
+ def self.system_answers_path
210
+ File.join(PDK::Util.system_configdir, 'answers.json')
211
+ end
212
+
68
213
  def self.json_schemas_path
69
214
  File.join(__dir__, 'config')
70
215
  end
@@ -116,12 +261,124 @@ module PDK
116
261
 
117
262
  if answers.nil?
118
263
  PDK.logger.info _('No answer given, opting out of analytics collection.')
119
- PDK.config.user['analytics']['disabled'] = true
264
+ PDK.config.set(%w[user analytics disabled], true)
120
265
  else
121
- PDK.config.user['analytics']['disabled'] = !answers['enabled']
266
+ PDK.config.set(%w[user analytics disabled], !answers['enabled'])
122
267
  end
123
268
 
124
269
  PDK.logger.info(text: post_message, wrap: true)
125
270
  end
271
+
272
+ private
273
+
274
+ #:nocov: This is a private method and is tested elsewhere
275
+ def traverse_object(object, *names)
276
+ return nil if object.nil? || !object.respond_to?(:[])
277
+ return nil if names.nil?
278
+ # It's possible to pass in empty names at the root traversal layer
279
+ # but this should _only_ happen at the root namespace level
280
+ if names.empty?
281
+ return (object.is_a?(PDK::Config::Namespace) ? object : nil)
282
+ end
283
+
284
+ name = names.shift
285
+ value = object[name]
286
+ if names.empty?
287
+ return value if value.is_a?(PDK::Config::Namespace)
288
+ # Duplicate arrays and hashes so that they are isolated from changes being made
289
+ (value.is_a?(Hash) || value.is_a?(Array)) ? value.dup : value
290
+ else
291
+ traverse_object(value, *names)
292
+ end
293
+ end
294
+ #:nocov:
295
+
296
+ #:nocov: This is a private method and is tested elsewhere
297
+ # Takes a string representation of a setting and splits into its constituent setting parts e.g.
298
+ # 'user.a.b.c' becomes ['user', 'a', 'b', 'c']
299
+ # @return [Array[String]] The string split into each setting name as an array
300
+ def split_key_string(key)
301
+ raise ArgumentError, _('Expected a String but got \'%{klass}\'') % { klass: key.class } unless key.is_a?(String)
302
+ key.split('.')
303
+ end
304
+ #:nocov:
305
+
306
+ #:nocov:
307
+ # Returns all known scope names and their associated method name to call, to query the scope
308
+ # Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
309
+ # @return [Hash[String, Symbol]] A hash of the scope name then method name to call to query the scope (as a Symbol)
310
+ def all_scopes
311
+ # Note - Order is important. This dictates the resolution precedence order (topmost is processed first)
312
+ {
313
+ 'project' => :project_config,
314
+ 'user' => :user_config,
315
+ 'system' => :system_config,
316
+ }.freeze
317
+ end
318
+
319
+ #:nocov: This is a private method and is tested elsewhere
320
+ # Deeply traverses an object tree via `[]` and sets the last
321
+ # element to the value specified.
322
+ #
323
+ # Creating any missing parent hashes during the traversal
324
+ def deep_set_object(value, force, namespace, *names)
325
+ raise ArgumentError, _('Missing or invalid namespace') unless namespace.is_a?(PDK::Config::Namespace)
326
+ raise ArgumentError, _('Missing a name to set') if names.nil? || names.empty?
327
+
328
+ name = names.shift
329
+ current_value = namespace[name]
330
+
331
+ # If the next thing in the traversal chain is another namespace, set the value using that child namespace.
332
+ if current_value.is_a?(PDK::Config::Namespace)
333
+ return deep_set_object(value, force, current_value, *names)
334
+ end
335
+
336
+ # We're at the end of the name traversal
337
+ if names.empty?
338
+ if force || !current_value.is_a?(Array)
339
+ namespace[name] = value
340
+ return value
341
+ end
342
+
343
+ # Arrays are a special case if we're not forcing the value
344
+ namespace[name] = current_value << value unless current_value.include?(value)
345
+ return value
346
+ end
347
+
348
+ # Need to generate a deep hash using the current remaining names
349
+ # So given an origin *names of ['a', 'b', 'c', 'd'] and a value 'foo',
350
+ # we eventually want a hash of `{"b"=>{"c"=>{"d"=>"foo"}}}`
351
+ #
352
+ # The code above has already shifted the first element so we currently have
353
+ # name : 'a'
354
+ # names: ['b', 'c', 'd']
355
+ #
356
+ #
357
+ # First we need to pop off the last element ('d') in this case as we need to set that in the `reduce` call below
358
+ # So now we have:
359
+ # name : 'a'
360
+ # names: ['b', 'c']
361
+ # last_name : 'd'
362
+ last_name = names.pop
363
+ # Using reduce and an accumulator, we create the nested hash from the deepest value first. In this case the deepest value
364
+ # is the last_name, so the starting condition is {"d"=>"foo"}
365
+ # After the first iteration ('c'), the accumulator has {"c"=>{"d"=>"foo"}}}
366
+ # After the last iteration ('b'), the accumulator has {"b"=>{"c"=>{"d"=>"foo"}}}
367
+ hash_value = names.reverse.reduce(last_name => value) { |accumulator, item| { item => accumulator } }
368
+
369
+ # If the current value is nil, then it can't be a namespace or an existing value
370
+ # or
371
+ # If the current value is not a Hash and are forcing the change.
372
+ if current_value.nil? || (force && !current_value.is_a?(Hash))
373
+ namespace[name] = hash_value
374
+ return value
375
+ end
376
+
377
+ raise ArgumentError, _("Unable to set '%{key}' to '%{value}' as it is not a Hash") % { key: namespace.name + '.' + name, value: hash_value } unless current_value.is_a?(Hash)
378
+
379
+ namespace[name] = current_value.merge(hash_value)
380
+ value
381
+ end
382
+ #:nocov:
126
383
  end
127
384
  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