pdk 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +25 -0
  3. data/README.md +41 -0
  4. data/lib/pdk.rb +0 -13
  5. data/lib/pdk/analytics.rb +18 -2
  6. data/lib/pdk/analytics/client/google_analytics.rb +3 -0
  7. data/lib/pdk/answer_file.rb +4 -1
  8. data/lib/pdk/cli.rb +7 -2
  9. data/lib/pdk/cli/build.rb +2 -2
  10. data/lib/pdk/cli/bundle.rb +7 -1
  11. data/lib/pdk/cli/console.rb +148 -0
  12. data/lib/pdk/cli/convert.rb +2 -2
  13. data/lib/pdk/cli/exec.rb +14 -14
  14. data/lib/pdk/cli/exec/command.rb +16 -11
  15. data/lib/pdk/cli/exec/interactive_command.rb +4 -0
  16. data/lib/pdk/cli/exec_group.rb +5 -5
  17. data/lib/pdk/cli/module/build.rb +0 -2
  18. data/lib/pdk/cli/module/generate.rb +1 -2
  19. data/lib/pdk/cli/new.rb +1 -1
  20. data/lib/pdk/cli/new/defined_type.rb +2 -0
  21. data/lib/pdk/cli/new/provider.rb +2 -0
  22. data/lib/pdk/cli/new/task.rb +2 -0
  23. data/lib/pdk/cli/new/{unit_test.rb → test.rb} +16 -12
  24. data/lib/pdk/cli/new/transport.rb +2 -0
  25. data/lib/pdk/cli/test/unit.rb +5 -3
  26. data/lib/pdk/cli/update.rb +2 -3
  27. data/lib/pdk/cli/util.rb +45 -14
  28. data/lib/pdk/cli/util/spinner.rb +2 -2
  29. data/lib/pdk/cli/validate.rb +6 -2
  30. data/lib/pdk/config.rb +20 -8
  31. data/lib/pdk/config/analytics_schema.json +26 -0
  32. data/lib/pdk/config/json.rb +14 -3
  33. data/lib/pdk/config/json_schema_namespace.rb +143 -0
  34. data/lib/pdk/config/json_schema_setting.rb +53 -0
  35. data/lib/pdk/config/json_with_schema.rb +50 -0
  36. data/lib/pdk/config/namespace.rb +84 -76
  37. data/lib/pdk/config/setting.rb +132 -0
  38. data/lib/pdk/config/yaml.rb +15 -3
  39. data/lib/pdk/config/yaml_with_schema.rb +59 -0
  40. data/lib/pdk/generate.rb +0 -2
  41. data/lib/pdk/generate/module.rb +29 -16
  42. data/lib/pdk/generate/puppet_object.rb +31 -28
  43. data/lib/pdk/module.rb +2 -2
  44. data/lib/pdk/module/build.rb +21 -8
  45. data/lib/pdk/module/convert.rb +64 -7
  46. data/lib/pdk/module/metadata.rb +5 -1
  47. data/lib/pdk/module/templatedir.rb +24 -7
  48. data/lib/pdk/module/update.rb +5 -1
  49. data/lib/pdk/module/update_manager.rb +21 -13
  50. data/lib/pdk/report.rb +4 -3
  51. data/lib/pdk/report/event.rb +5 -3
  52. data/lib/pdk/tests/unit.rb +36 -7
  53. data/lib/pdk/util.rb +20 -8
  54. data/lib/pdk/util/bundler.rb +14 -6
  55. data/lib/pdk/util/filesystem.rb +5 -0
  56. data/lib/pdk/util/git.rb +6 -0
  57. data/lib/pdk/util/puppet_strings.rb +24 -2
  58. data/lib/pdk/util/puppet_version.rb +25 -10
  59. data/lib/pdk/util/ruby_version.rb +13 -1
  60. data/lib/pdk/util/template_uri.rb +23 -2
  61. data/lib/pdk/util/vendored_file.rb +28 -24
  62. data/lib/pdk/util/version.rb +5 -5
  63. data/lib/pdk/validate/base_validator.rb +5 -4
  64. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -4
  65. data/lib/pdk/validate/metadata/metadata_syntax.rb +5 -3
  66. data/lib/pdk/validate/metadata_validator.rb +0 -2
  67. data/lib/pdk/validate/puppet/puppet_epp.rb +4 -4
  68. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -3
  69. data/lib/pdk/validate/puppet/puppet_syntax.rb +4 -4
  70. data/lib/pdk/validate/puppet_validator.rb +0 -2
  71. data/lib/pdk/validate/ruby/rubocop.rb +0 -5
  72. data/lib/pdk/validate/ruby_validator.rb +0 -2
  73. data/lib/pdk/validate/tasks/metadata_lint.rb +9 -5
  74. data/lib/pdk/validate/tasks/name.rb +4 -2
  75. data/lib/pdk/validate/tasks_validator.rb +0 -2
  76. data/lib/pdk/validate/yaml/syntax.rb +4 -4
  77. data/lib/pdk/validate/yaml_validator.rb +0 -2
  78. data/lib/pdk/version.rb +1 -1
  79. data/locales/pdk.pot +351 -311
  80. metadata +11 -7
  81. data/lib/pdk/config/validator.rb +0 -31
  82. data/lib/pdk/config/value.rb +0 -94
@@ -0,0 +1,132 @@
1
+ module PDK
2
+ class Config
3
+ # A class for describing the setting of a {PDK::Config} setting.
4
+ #
5
+ # Generally, this is never instantiated manually, but is instead
6
+ # instantiated by passing a block to {PDK::Config::Namespace#setting}.
7
+ #
8
+ # @example
9
+ #
10
+ # PDK::Config::Namespace.new('analytics') do
11
+ # setting :disabled do
12
+ # validate PDK::Config::Validator.boolean
13
+ # default_to { false }
14
+ # end
15
+ # end
16
+ class Setting
17
+ attr_reader :namespace
18
+
19
+ # It is possible to have multiple setting definitions for the same setting; for example, defining a default value with a lambda, but the
20
+ # the validation is within a JSON schema document. These are expressed as two settings objects, and uses a single linked list to join them
21
+ # together:
22
+ #
23
+ # (PDK::Config::JSONSchemaSetting) --previous_setting--> (PDK::Config::Setting)
24
+ #
25
+ # So in the example above, calling `default` the on the first object in the list will:
26
+ # 1. Look at `default` on PDK::Config::JSONSchemaSetting
27
+ # 2. If a default could not be found then it calls `default` on previous_setting
28
+ # 3. If a default could not be found then it calls `default` on previous_setting.previous_setting
29
+ # 4. and so on down the linked list (chain) of settings
30
+ attr_writer :previous_setting
31
+
32
+ # Initialises an empty setting definition.
33
+ #
34
+ # @param name [String,Symbol] the name of the setting.
35
+ # @param namespace [PDK::Config::Namespace] The namespace this setting belongs to
36
+ def initialize(name, namespace, initial_value = nil)
37
+ @name = name.to_s
38
+ @validators = []
39
+ @namespace = namespace
40
+ @value = initial_value
41
+ end
42
+
43
+ def qualified_name
44
+ [namespace.name, @name].join('.')
45
+ end
46
+
47
+ def value # rubocop:disable Style/TrivialAccessors
48
+ @value
49
+ end
50
+
51
+ def value=(obj)
52
+ validate!(obj)
53
+ @value = obj
54
+ end
55
+
56
+ def to_s
57
+ @value.to_s
58
+ end
59
+
60
+ # Assign a validator to the setting. Subclasses should not override this method.
61
+ #
62
+ # @param validator [Hash{Symbol => [Proc,String]}]
63
+ # @option validator [Proc] :proc a lambda that takes the setting to be
64
+ # validated as the argument and returns `true` if the setting is valid.
65
+ # @option validator [String] :message a description of what the validator
66
+ # is testing for, that is displayed to the user as part of the error
67
+ # message for invalid settings.
68
+ #
69
+ # @raise [ArgumentError] if not passed a Hash.
70
+ # @raise [ArgumentError] if the Hash doesn't have a `:proc` key that
71
+ # contains a Proc.
72
+ # @raise [ArgumentError] if the Hash doesn't have a `:message` key that
73
+ # contains a String.
74
+ #
75
+ # @return [nil]
76
+ def validate(validator)
77
+ raise ArgumentError, _('`validator` must be a Hash') unless validator.is_a?(Hash)
78
+ raise ArgumentError, _('the :proc key must contain a Proc') unless validator.key?(:proc) && validator[:proc].is_a?(Proc)
79
+ raise ArgumentError, _('the :message key must contain a String') unless validator.key?(:message) && validator[:message].is_a?(String)
80
+
81
+ @validators << validator
82
+ end
83
+
84
+ # Validate a setting against the assigned validators.
85
+ #
86
+ # @param setting [Object] the setting being validated.
87
+ #
88
+ # @raise [ArgumentError] if any of the assigned validators fail to
89
+ # validate the setting.
90
+ #
91
+ # @return [nil]
92
+ def validate!(value)
93
+ @validators.each do |validator|
94
+ next if validator[:proc].call(value)
95
+
96
+ raise ArgumentError, _('%{key} %{message}') % {
97
+ key: qualified_name,
98
+ message: validator[:message],
99
+ }
100
+ end
101
+ end
102
+
103
+ # Assign a default value proc for the setting. Subclasses should not override this method.
104
+ #
105
+ # @param block [Proc] a block that is lazy evaluated when necessary in
106
+ # order to determine the default setting.
107
+ #
108
+ # @return [nil]
109
+ def default_to(&block)
110
+ raise ArgumentError, _('must be passed a block') unless block_given?
111
+ @default_to = block
112
+ end
113
+
114
+ # Evaluate the default setting.
115
+ #
116
+ # @return [Object,nil] the result of evaluating the block given to
117
+ # {#default_to}, or `nil` if the setting has no default.
118
+ def default
119
+ return @default_to.call if default_block?
120
+ # If there is a previous setting in the chain, use its default
121
+ @previous_setting.nil? ? nil : @previous_setting.default
122
+ end
123
+
124
+ private
125
+
126
+ # @return [Boolean] true if the setting has a default setting block. Subclasses should not override this method.
127
+ def default_block?
128
+ !@default_to.nil?
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,14 +1,23 @@
1
1
  require 'pdk/config/namespace'
2
+ require 'pdk/config/setting'
2
3
 
3
4
  module PDK
4
5
  class Config
6
+ # Parses a YAML document.
7
+ #
8
+ # @see PDK::Config::Namespace.parse_file
5
9
  class YAML < Namespace
6
- def parse_data(data, filename)
7
- return {} if data.nil? || data.empty?
10
+ def parse_file(filename)
11
+ raise unless block_given?
12
+ data = load_data(filename)
13
+ return if data.nil? || data.empty?
8
14
 
9
15
  require 'yaml'
10
16
 
11
- ::YAML.safe_load(data, [Symbol], [], true)
17
+ data = ::YAML.safe_load(data, [Symbol], [], true)
18
+ return if data.nil?
19
+
20
+ data.each { |k, v| yield k, PDK::Config::Setting.new(k, self, v) }
12
21
  rescue Psych::SyntaxError => e
13
22
  raise PDK::Config::LoadError, _('Syntax error when loading %{file}: %{error}') % {
14
23
  file: filename,
@@ -21,6 +30,9 @@ module PDK
21
30
  }
22
31
  end
23
32
 
33
+ # Serializes object data into a YAML string.
34
+ #
35
+ # @see PDK::Config::Namespace.serialize_data
24
36
  def serialize_data(data)
25
37
  require 'yaml'
26
38
 
@@ -0,0 +1,59 @@
1
+ require 'pdk/config/json_schema_namespace'
2
+
3
+ module PDK
4
+ class Config
5
+ # Parses a YAML document with a JSON schema.
6
+ #
7
+ # @see PDK::Config::Namespace.parse_file
8
+ class YAMLWithSchema < JSONSchemaNamespace
9
+ def parse_file(filename)
10
+ raise unless block_given?
11
+ data = load_data(filename)
12
+ data = '' if data.nil?
13
+ require 'yaml'
14
+ require 'json-schema'
15
+
16
+ @raw_data = ::YAML.safe_load(data, [Symbol], [], true)
17
+ @raw_data = {} if @raw_data.nil?
18
+
19
+ begin
20
+ # Ensure the parsed document is actually valid
21
+ validate_document!(@raw_data)
22
+ rescue ::JSON::Schema::ValidationError => e
23
+ raise PDK::Config::LoadError, _('The configuration file %{filename} is not valid: %{message}') % {
24
+ filename: filename,
25
+ message: e.message,
26
+ }
27
+ end
28
+
29
+ require 'pdk/config/json_schema_setting'
30
+
31
+ schema_property_names.each do |key|
32
+ yield key, PDK::Config::JSONSchemaSetting.new(key, self, @raw_data[key])
33
+ end
34
+
35
+ # Remove all of the "known" settings from the schema and
36
+ # we're left with the settings that we don't manage.
37
+ self.unmanaged_settings = @raw_data.reject { |k, _| schema_property_names.include?(k) }
38
+ rescue Psych::SyntaxError => e
39
+ raise PDK::Config::LoadError, _('Syntax error when loading %{file}: %{error}') % {
40
+ file: filename,
41
+ error: "#{e.problem} #{e.context}",
42
+ }
43
+ rescue Psych::DisallowedClass => e
44
+ raise PDK::Config::LoadError, _('Unsupported class in %{file}: %{error}') % {
45
+ file: filename,
46
+ error: e.message,
47
+ }
48
+ end
49
+
50
+ # Serializes object data into a YAML string.
51
+ #
52
+ # @see PDK::Config::Namespace.serialize_data
53
+ def serialize_data(data)
54
+ require 'yaml'
55
+ ::YAML.dump(data)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -4,8 +4,6 @@ require 'pdk/generate/provider'
4
4
  require 'pdk/generate/puppet_class'
5
5
  require 'pdk/generate/task'
6
6
  require 'pdk/generate/transport'
7
- require 'pdk/module/metadata'
8
- require 'pdk/module/templatedir'
9
7
 
10
8
  module PDK
11
9
  module Generate
@@ -1,18 +1,4 @@
1
- require 'etc'
2
- require 'pathname'
3
- require 'fileutils'
4
- require 'tty-prompt'
5
-
6
- require 'pdk'
7
- require 'pdk/logger'
8
- require 'pdk/module/metadata'
9
- require 'pdk/module/templatedir'
10
- require 'pdk/cli/exec'
11
- require 'pdk/cli/util'
12
- require 'pdk/cli/util/interview'
13
- require 'pdk/cli/util/option_validator'
14
- require 'pdk/util'
15
- require 'pdk/util/version'
1
+ require 'pdk/util/filesystem'
16
2
 
17
3
  module PDK
18
4
  module Generate
@@ -20,6 +6,8 @@ module PDK
20
6
  extend PDK::Util::Filesystem
21
7
 
22
8
  def self.validate_options(opts)
9
+ require 'pdk/cli/util/option_validator'
10
+
23
11
  unless PDK::CLI::Util::OptionValidator.valid_module_name?(opts[:module_name])
24
12
  error_msg = _(
25
13
  "'%{module_name}' is not a valid module name.\n" \
@@ -33,6 +21,12 @@ module PDK
33
21
  end
34
22
 
35
23
  def self.invoke(opts = {})
24
+ require 'pdk/module/templatedir'
25
+ require 'pdk/util'
26
+ require 'pdk/util/template_uri'
27
+ require 'fileutils'
28
+ require 'pathname'
29
+
36
30
  validate_options(opts) unless opts[:module_name].nil?
37
31
 
38
32
  metadata = prepare_metadata(opts)
@@ -76,6 +70,7 @@ module PDK
76
70
  end
77
71
 
78
72
  # Only update the answers files after metadata has been written.
73
+ require 'pdk/answer_file'
79
74
  if template_uri.default?
80
75
  # If the user specifies our default template url via the command
81
76
  # line, remove the saved template-url answer so that the template_uri
@@ -89,7 +84,12 @@ module PDK
89
84
 
90
85
  begin
91
86
  if FileUtils.mv(temp_target_dir, target_dir)
92
- Dir.chdir(target_dir) { PDK::Util::Bundler.ensure_bundle! } unless opts[:'skip-bundle-install']
87
+ unless opts[:'skip-bundle-install']
88
+ Dir.chdir(target_dir) do
89
+ require 'pdk/util/bundler'
90
+ PDK::Util::Bundler.ensure_bundle!
91
+ end
92
+ end
93
93
 
94
94
  PDK.logger.info _('Module \'%{name}\' generated at path \'%{path}\', from template \'%{url}\'.') % { name: opts[:module_name], path: target_dir, url: template_uri.git_remote }
95
95
  PDK.logger.info(_('In your module directory, add classes with the \'pdk new class\' command.'))
@@ -104,6 +104,8 @@ module PDK
104
104
  end
105
105
 
106
106
  def self.username_from_login
107
+ require 'etc'
108
+
107
109
  login = Etc.getlogin || ''
108
110
  login_clean = login.downcase.gsub(%r{[^0-9a-z]}i, '')
109
111
  login_clean = 'username' if login_clean.empty?
@@ -118,6 +120,9 @@ module PDK
118
120
  end
119
121
 
120
122
  def self.prepare_metadata(opts = {})
123
+ require 'pdk/answer_file'
124
+ require 'pdk/module/metadata'
125
+
121
126
  opts[:username] = (opts[:username] || PDK.answers['forge_username'] || username_from_login).downcase
122
127
 
123
128
  defaults = PDK::Module::Metadata::DEFAULTS.dup
@@ -134,6 +139,8 @@ module PDK
134
139
  end
135
140
 
136
141
  def self.prepare_module_directory(target_dir)
142
+ require 'fileutils'
143
+
137
144
  [
138
145
  File.join(target_dir, 'examples'),
139
146
  File.join(target_dir, 'files'),
@@ -153,6 +160,9 @@ module PDK
153
160
  end
154
161
 
155
162
  def self.module_interview(metadata, opts = {})
163
+ require 'pdk/module/metadata'
164
+ require 'pdk/cli/util/interview'
165
+
156
166
  questions = [
157
167
  {
158
168
  name: 'module_name',
@@ -307,6 +317,8 @@ module PDK
307
317
  metadata.update!(answers)
308
318
 
309
319
  if opts[:prompt].nil? || opts[:prompt]
320
+ require 'pdk/cli/util'
321
+
310
322
  continue = PDK::CLI::Util.prompt_for_yes(
311
323
  _('Metadata will be generated based on this information, continue?'),
312
324
  prompt: prompt,
@@ -319,6 +331,7 @@ module PDK
319
331
  end
320
332
  end
321
333
 
334
+ require 'pdk/answer_file'
322
335
  PDK.answers.update!(
323
336
  {
324
337
  'forge_username' => opts[:username],
@@ -1,12 +1,3 @@
1
- require 'fileutils'
2
-
3
- require 'pdk'
4
- require 'pdk/logger'
5
- require 'pdk/module/metadata'
6
- require 'pdk/module/templatedir'
7
- require 'pdk/template_file'
8
- require 'pdk/util/filesystem'
9
-
10
1
  module PDK
11
2
  module Generate
12
3
  class PuppetObject
@@ -141,8 +132,10 @@ module PDK
141
132
  #
142
133
  # @api public
143
134
  def check_preconditions
135
+ require 'pdk/util/filesystem'
136
+
144
137
  targets.each do |target_file|
145
- next unless File.exist?(target_file)
138
+ next unless PDK::Util::Filesystem.exist?(target_file)
146
139
 
147
140
  raise PDK::CLI::ExitWithError, _("Unable to generate %{object_type}; '%{file}' already exists.") % {
148
141
  file: target_file,
@@ -151,6 +144,18 @@ module PDK
151
144
  end
152
145
  end
153
146
 
147
+ # Check the preconditions of this template group, behaving as a
148
+ # predicate rather than raising an exception.
149
+ #
150
+ # @return [Boolean] true if the generator is safe to run, otherwise
151
+ # false.
152
+ def can_run?
153
+ check_preconditions
154
+ true
155
+ rescue PDK::CLI::ExitWithError
156
+ false
157
+ end
158
+
154
159
  # Check that the templates can be rendered. Find an appropriate template
155
160
  # and create the target files from the template. This is the main entry
156
161
  # point for the class.
@@ -191,6 +196,8 @@ module PDK
191
196
  #
192
197
  # @api private
193
198
  def render_file(dest_path, template_path, data)
199
+ require 'pdk/template_file'
200
+
194
201
  write_file(dest_path) do
195
202
  PDK::TemplateFile.new(template_path, data).render
196
203
  end
@@ -211,12 +218,15 @@ module PDK
211
218
  #
212
219
  # @api private
213
220
  def write_file(dest_path)
221
+ require 'pdk/logger'
222
+ require 'pdk/util/filesystem'
223
+
214
224
  PDK.logger.info(_("Creating '%{file}' from template.") % { file: dest_path })
215
225
 
216
226
  file_content = yield
217
227
 
218
228
  begin
219
- FileUtils.mkdir_p(File.dirname(dest_path))
229
+ PDK::Util::Filesystem.mkdir_p(File.dirname(dest_path))
220
230
  rescue SystemCallError => e
221
231
  raise PDK::CLI::FatalError, _("Unable to create directory '%{path}': %{message}") % {
222
232
  path: File.dirname(dest_path),
@@ -247,6 +257,10 @@ module PDK
247
257
  #
248
258
  # @api private
249
259
  def with_templates
260
+ require 'pdk/logger'
261
+ require 'pdk/module/templatedir'
262
+ require 'pdk/util/template_uri'
263
+
250
264
  templates.each do |template|
251
265
  if template[:uri].nil?
252
266
  PDK.logger.debug(_('No %{dir_type} template found; trying next template directory.') % { dir_type: template[:type] })
@@ -293,34 +307,23 @@ module PDK
293
307
  #
294
308
  # @api private
295
309
  def templates
310
+ require 'pdk/util/template_uri'
311
+
296
312
  @templates ||= PDK::Util::TemplateURI.templates(@options)
297
313
  end
298
314
 
299
315
  # Retrieves the name of the module (without the forge username) from the
300
316
  # module metadata.
301
317
  #
302
- # @raise (see #module_metadata)
303
318
  # @return [String] The name of the module.
304
319
  #
305
320
  # @api private
306
321
  def module_name
307
- @module_name ||= module_metadata.data['name'].rpartition('-').last
308
- end
322
+ require 'pdk/util'
309
323
 
310
- # Parses the metadata.json file for the module.
311
- #
312
- # @raise [PDK::CLI::FatalError] if the metadata.json file does not exist,
313
- # can not be read, or contains invalid metadata.
314
- #
315
- # @return [PDK::Module::Metadata] the parsed module metadata.
316
- #
317
- # @api private
318
- def module_metadata
319
- @module_metadata ||= begin
320
- PDK::Module::Metadata.from_file(File.join(module_dir, 'metadata.json'))
321
- rescue ArgumentError => e
322
- raise PDK::CLI::FatalError, _("'%{dir}' does not contain valid Puppet module metadata: %{msg}") % { dir: module_dir, msg: e.message }
323
- end
324
+ @module_name ||= PDK::Util.module_metadata['name'].rpartition('-').last
325
+ rescue ArgumentError => e
326
+ raise PDK::CLI::FatalError, e
324
327
  end
325
328
 
326
329
  # transform a object name into a ruby class name