pdk 1.0.1 → 1.1.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +11 -0
  4. data/lib/pdk/answer_file.rb +1 -1
  5. data/lib/pdk/cli.rb +16 -6
  6. data/lib/pdk/cli/bundle.rb +5 -3
  7. data/lib/pdk/cli/errors.rb +10 -1
  8. data/lib/pdk/cli/exec.rb +9 -4
  9. data/lib/pdk/cli/module.rb +14 -0
  10. data/lib/pdk/cli/module/generate.rb +40 -0
  11. data/lib/pdk/cli/new.rb +2 -1
  12. data/lib/pdk/cli/new/class.rb +1 -1
  13. data/lib/pdk/cli/new/defined_type.rb +27 -0
  14. data/lib/pdk/cli/new/module.rb +1 -11
  15. data/lib/pdk/cli/util.rb +3 -3
  16. data/lib/pdk/cli/util/command_redirector.rb +26 -0
  17. data/lib/pdk/cli/util/interview.rb +17 -6
  18. data/lib/pdk/cli/util/option_normalizer.rb +3 -3
  19. data/lib/pdk/cli/validate.rb +9 -9
  20. data/lib/pdk/generate.rb +1 -0
  21. data/lib/pdk/generators/defined_type.rb +49 -0
  22. data/lib/pdk/generators/module.rb +113 -23
  23. data/lib/pdk/generators/puppet_class.rb +2 -2
  24. data/lib/pdk/generators/puppet_object.rb +3 -3
  25. data/lib/pdk/module/metadata.rb +7 -7
  26. data/lib/pdk/module/templatedir.rb +3 -3
  27. data/lib/pdk/report/event.rb +43 -12
  28. data/lib/pdk/tests/unit.rb +51 -17
  29. data/lib/pdk/util.rb +3 -3
  30. data/lib/pdk/util/bundler.rb +5 -5
  31. data/lib/pdk/util/version.rb +5 -2
  32. data/lib/pdk/util/windows.rb +6 -0
  33. data/lib/pdk/validators/base_validator.rb +1 -1
  34. data/lib/pdk/validators/metadata/metadata_json_lint.rb +2 -2
  35. data/lib/pdk/validators/metadata/metadata_syntax.rb +2 -2
  36. data/lib/pdk/validators/puppet/puppet_lint.rb +1 -1
  37. data/lib/pdk/validators/puppet/puppet_syntax.rb +1 -1
  38. data/lib/pdk/validators/ruby/rubocop.rb +1 -1
  39. data/lib/pdk/version.rb +1 -1
  40. data/lib/puppet/util/windows/api_types.rb +9 -5
  41. data/locales/pdk.pot +314 -180
  42. metadata +16 -10
@@ -29,7 +29,7 @@ module PDK
29
29
  begin
30
30
  OptionValidator.enum(format, PDK::Report.formats)
31
31
  rescue ArgumentError
32
- raise PDK::CLI::FatalError, _("'%{name}' is not a valid report format (%{valid})") % {
32
+ raise PDK::CLI::ExitWithError, _("'%{name}' is not a valid report format (%{valid})") % {
33
33
  name: format,
34
34
  valid: PDK::Report.formats.join(', '),
35
35
  }
@@ -53,13 +53,13 @@ module PDK
53
53
  param_type = 'String' if param_type.nil?
54
54
 
55
55
  unless PDK::CLI::Util::OptionValidator.valid_param_name?(param_name)
56
- raise PDK::CLI::FatalError, _("'%{name}' is not a valid parameter name") % {
56
+ raise PDK::CLI::ExitWithError, _("'%{name}' is not a valid parameter name") % {
57
57
  name: param_name,
58
58
  }
59
59
  end
60
60
 
61
61
  unless PDK::CLI::Util::OptionValidator.valid_data_type?(param_type)
62
- raise PDK::CLI::FatalError, _("'%{type}' is not a valid data type") % {
62
+ raise PDK::CLI::ExitWithError, _("'%{type}' is not a valid data type") % {
63
63
  type: param_type,
64
64
  }
65
65
  end
@@ -6,16 +6,16 @@ module PDK::CLI
6
6
  usage _('validate [validators] [options] [targets]')
7
7
  summary _('Run static analysis tests.')
8
8
  description _(
9
- "Run metadata, puppet, or ruby validation.\n\n" \
10
- '[validators] is an optional comma separated list of validators to use. ' \
11
- "If not specified, all validators will be used.\n\n" \
12
- '[targets] is an optional space separated list of files or directories to be validated. ' \
13
- 'If not specified, the validators will be run against all applicable files in the module.',
9
+ "Run metadata, Puppet, or Ruby validation.\n\n" \
10
+ '[validators] is an optional comma-separated list of validators to use. ' \
11
+ "If not specified, all validators are used.\n\n" \
12
+ '[targets] is an optional space-separated list of files or directories to be validated. ' \
13
+ 'If not specified, validators are run against all applicable files in the module.',
14
14
  )
15
15
 
16
- flag nil, :list, _('list all available validators')
17
- flag :a, 'auto-correct', _('automatically correct problems (where possible)')
18
- flag nil, :parallel, _('run validations in parallel')
16
+ flag nil, :list, _('List all available validators.')
17
+ flag :a, 'auto-correct', _('Automatically correct problems where possible.')
18
+ flag nil, :parallel, _('Run validations in parallel.')
19
19
 
20
20
  run do |opts, args, _cmd|
21
21
  if args == ['help']
@@ -44,7 +44,7 @@ module PDK::CLI
44
44
 
45
45
  invalid = vals.reject { |v| validator_names.include?(v) }
46
46
  invalid.each do |v|
47
- PDK.logger.warn(_("Unknown validator '%{v}'. Available validators: %{validators}") % { v: v, validators: validator_names.join(', ') })
47
+ PDK.logger.warn(_("Unknown validator '%{v}'. Available validators: %{validators}.") % { v: v, validators: validator_names.join(', ') })
48
48
  end
49
49
  else
50
50
  # This is a single item. Check if it's a known validator, or otherwise treat it as a target.
@@ -1,4 +1,5 @@
1
1
  require 'pdk/generators/module'
2
+ require 'pdk/generators/defined_type'
2
3
  require 'pdk/generators/puppet_class'
3
4
  require 'pdk/module/metadata'
4
5
  require 'pdk/module/templatedir'
@@ -0,0 +1,49 @@
1
+ require 'pdk/generators/puppet_object'
2
+
3
+ module PDK
4
+ module Generate
5
+ class DefinedType < PuppetObject
6
+ OBJECT_TYPE = :defined_type
7
+
8
+ # Prepares the data needed to render the new defined type template.
9
+ #
10
+ # @return [Hash{Symbol => Object}] a hash of information that will be
11
+ # provided to the defined type and defined type spec templates during
12
+ # rendering.
13
+ def template_data
14
+ data = { name: object_name }
15
+
16
+ data
17
+ end
18
+
19
+ # Calculates the path to the .pp file that the new defined type will be
20
+ # written to.
21
+ #
22
+ # @return [String] the path where the new defined type will be written.
23
+ def target_object_path
24
+ @target_pp_path ||= begin
25
+ define_name_parts = object_name.split('::')[1..-1]
26
+ define_name_parts << 'init' if define_name_parts.empty?
27
+
28
+ "#{File.join(module_dir, 'manifests', *define_name_parts)}.pp"
29
+ end
30
+ end
31
+
32
+ # Calculates the path to the file where the tests for the new defined
33
+ # type will be written.
34
+ #
35
+ # @return [String] the path where the tests for the new defined type
36
+ # will be written.
37
+ def target_spec_path
38
+ @target_spec_path ||= begin
39
+ define_name_parts = object_name.split('::')
40
+
41
+ # drop the module name if the object name contains multiple parts
42
+ define_name_parts.delete_at(0) if define_name_parts.length > 1
43
+
44
+ "#{File.join(module_dir, 'spec', 'defines', *define_name_parts)}_spec.rb"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -9,6 +9,7 @@ require 'pdk/module/metadata'
9
9
  require 'pdk/module/templatedir'
10
10
  require 'pdk/cli/exec'
11
11
  require 'pdk/cli/util/interview'
12
+ require 'pdk/cli/util/option_validator'
12
13
  require 'pdk/util'
13
14
  require 'pdk/util/version'
14
15
 
@@ -18,20 +19,37 @@ module PDK
18
19
  def self.default_template_url
19
20
  if !PDK.answers['template-url'].nil?
20
21
  PDK.answers['template-url']
21
- elsif PDK::Util.package_install?
22
+ else
23
+ puppetlabs_template_url
24
+ end
25
+ end
26
+
27
+ def self.puppetlabs_template_url
28
+ if PDK::Util.package_install?
22
29
  'file://' + File.join(PDK::Util.package_cachedir, 'pdk-module-template.git')
23
30
  else
24
31
  'https://github.com/puppetlabs/pdk-module-template'
25
32
  end
26
33
  end
27
34
 
28
- def self.invoke(opts = {})
35
+ def self.validate_options(opts)
36
+ unless PDK::CLI::Util::OptionValidator.valid_module_name?(opts[:name])
37
+ error_msg = _(
38
+ "'%{module_name}' is not a valid module name.\n" \
39
+ 'Module names must begin with a lowercase letter and can only include lowercase letters, digits, and underscores.',
40
+ ) % { module_name: opts[:name] }
41
+ raise PDK::CLI::ExitWithError, error_msg
42
+ end
43
+
29
44
  target_dir = File.expand_path(opts[:target_dir])
30
45
 
31
- if File.exist?(target_dir)
32
- raise PDK::CLI::FatalError, _("The destination directory '%{dir}' already exists") % { dir: target_dir }
33
- end
46
+ raise PDK::CLI::ExitWithError, _("The destination directory '%{dir}' already exists") % { dir: target_dir } if File.exist?(target_dir)
47
+ end
48
+
49
+ def self.invoke(opts = {})
50
+ validate_options(opts)
34
51
 
52
+ target_dir = File.expand_path(opts[:target_dir])
35
53
  parent_dir = File.dirname(target_dir)
36
54
 
37
55
  begin
@@ -68,10 +86,21 @@ module PDK
68
86
  end
69
87
  end
70
88
 
71
- PDK.answers.update!('template-url' => template_url)
89
+ if template_url == puppetlabs_template_url
90
+ # If the user specifies our template via the command line, remove the
91
+ # saved template-url answer.
92
+ PDK.answers.update!('template-url' => nil) if opts.key?(:'template-url')
93
+ else
94
+ # Save the template-url answer if the module was generated using
95
+ # a template other than ours.
96
+ PDK.answers.update!('template-url' => template_url)
97
+ end
72
98
 
73
99
  begin
74
- FileUtils.mv(temp_target_dir, target_dir)
100
+ if FileUtils.mv(temp_target_dir, target_dir)
101
+ PDK.logger.info(_('Module \'%{name}\' generated at path \'%{path}\'.') % { name: opts[:name], path: target_dir })
102
+ PDK.logger.info(_('In your module directory, add classes with the \'pdk new class\' command.'))
103
+ end
75
104
  rescue Errno::EACCES => e
76
105
  raise PDK::CLI::FatalError, _("Failed to move '%{source}' to '%{target}': %{message}") % {
77
106
  source: temp_target_dir,
@@ -87,7 +116,7 @@ module PDK
87
116
  login_clean = 'username' if login_clean.empty?
88
117
 
89
118
  if login_clean != login
90
- PDK.logger.warn _('Your username is not a valid Forge username, proceeding with the username %{username}. You can fix this afterwards in metadata.json.') % {
119
+ PDK.logger.warn _('Your username is not a valid Forge username. Proceeding with the username %{username}. You can fix this later in metadata.json.') % {
91
120
  username: login_clean,
92
121
  }
93
122
  end
@@ -101,9 +130,7 @@ module PDK
101
130
  defaults = {
102
131
  'name' => "#{username}-#{opts[:name]}",
103
132
  'version' => '0.1.0',
104
- 'dependencies' => [
105
- { 'name' => 'puppetlabs-stdlib', 'version_requirement' => '>= 4.13.1 < 5.0.0' },
106
- ],
133
+ 'dependencies' => [],
107
134
  'requirements' => [
108
135
  { 'name' => 'puppet', 'version_requirement' => '>= 4.7.0 < 6.0.0' },
109
136
  ],
@@ -160,7 +187,7 @@ module PDK
160
187
  {
161
188
  name: 'author',
162
189
  question: _('Who wrote this module?'),
163
- help: _('This will be used to credit the module\'s author.'),
190
+ help: _('This is used to credit the module\'s author.'),
164
191
  required: true,
165
192
  default: metadata.data['author'],
166
193
  },
@@ -171,30 +198,87 @@ module PDK
171
198
  required: true,
172
199
  default: metadata.data['license'],
173
200
  },
201
+ {
202
+ name: 'operatingsystem_support',
203
+ question: _('What operating systems does this module support?'),
204
+ help: _('Use the up and down keys to move between the choices, space to select and enter to continue.'),
205
+ required: true,
206
+ choices: {
207
+ 'RedHat based Linux' => [
208
+ {
209
+ 'operatingsystem' => 'CentOS',
210
+ 'operatingsystemrelease' => ['7'],
211
+ },
212
+ {
213
+ 'operatingsystem' => 'OracleLinux',
214
+ 'operatingsystemrelease' => ['7'],
215
+ },
216
+ {
217
+ 'operatingsystem' => 'RedHat',
218
+ 'operatingsystemrelease' => ['7'],
219
+ },
220
+ {
221
+ 'operatingsystem' => 'Scientific',
222
+ 'operatingsystemrelease' => ['7'],
223
+ },
224
+ ],
225
+ 'Debian based Linux' => [
226
+ {
227
+ 'operatingsystem' => 'Debian',
228
+ 'operatingsystemrelease' => ['8'],
229
+ },
230
+ {
231
+ 'operatingsystem' => 'Ubuntu',
232
+ 'operatingsystemrelease' => ['16.04'],
233
+ },
234
+ ],
235
+ 'Fedora' => {
236
+ 'operatingsystem' => 'Fedora',
237
+ 'operatingsystemrelease' => ['25'],
238
+ },
239
+ 'OSX' => {
240
+ 'operatingsystem' => 'Darwin',
241
+ 'operatingsystemrelease' => ['16'],
242
+ },
243
+ 'SLES' => {
244
+ 'operatingsystem' => 'SLES',
245
+ 'operatingsystemrelease' => ['12'],
246
+ },
247
+ 'Solaris' => {
248
+ 'operatingsystem' => 'Solaris',
249
+ 'operatingsystemrelease' => ['11'],
250
+ },
251
+ 'Windows' => {
252
+ 'operatingsystem' => 'windows',
253
+ 'operatingsystemrelease' => ['2008 R2', '2012 R2', '10'],
254
+ },
255
+ },
256
+ default: [1, 2, 7],
257
+ },
174
258
  {
175
259
  name: 'summary',
176
- question: _('Please summarize the purpose of this module in a single sentence.'),
177
- help: _('This will help other Puppet users understand what the module does.'),
260
+ question: _('Summarize the purpose of this module in a single sentence.'),
261
+ help: _('This helps other Puppet users understand what the module does.'),
178
262
  required: true,
179
263
  default: metadata.data['summary'],
180
264
  },
181
265
  {
182
266
  name: 'source',
183
267
  question: _('If there is a source code repository for this module, enter the URL here.'),
184
- help: _('Skip this if none exists yet, you can update this later in the metadata.json.'),
268
+ help: _('Skip this if no repository exists yet. You can update this later in the metadata.json.'),
185
269
  required: true,
186
270
  default: metadata.data['source'],
187
271
  },
188
272
  {
189
273
  name: 'project_page',
190
274
  question: _('If there is a URL where others can learn more about this module, enter it here.'),
191
- help: _('Optional. As with all questions above, you can update this later in the metadata.json.'),
275
+ help: _('Optional. You can update this later in the metadata.json.'),
192
276
  default: metadata.data['project_page'],
193
277
  },
194
278
  {
195
279
  name: 'issues_url',
196
280
  question: _('If there is a public issue tracker for this module, enter its URL here.'),
197
- help: _('Optional. As with all questions above, you can update this later in the metadata.json.'),
281
+ help: _('Optional. You can update this later in the metadata.json.'),
198
282
  default: metadata.data['issues_url'],
199
283
  },
200
284
  ]
@@ -208,23 +292,24 @@ module PDK
208
292
  interview.add_questions(questions)
209
293
 
210
294
  puts _(
211
- "\nWe need to create a metadata.json file for this module, so we\'re going to ask you %{count} quick " \
295
+ "\nWe need to create a metadata.json file for this module, so we\'re going to ask you %{count} " \
212
296
  "questions.\n" \
213
- 'If the question is not applicable to this module, simply leave the answer blank and skip. A default option ' \
214
- 'is shown after each question. You can modify this or any other answers at any time by manually updating ' \
297
+ 'If the question is not applicable to this module, accept the default option ' \
298
+ 'shown after each question. You can modify any answers at any time by manually updating ' \
215
299
  "the metadata.json file.\n\n",
216
300
  ) % { count: interview.num_questions }
217
301
 
218
302
  answers = interview.run
219
303
 
220
304
  if answers.nil?
221
- PDK.logger.info _('Interview cancelled, not generating the module.')
305
+ PDK.logger.info _('Interview cancelled; not generating the module.')
222
306
  exit 0
223
307
  end
224
308
 
225
309
  forge_username = answers['name']
226
310
  answers['name'] = "#{answers['name']}-#{opts[:name]}"
227
311
  answers['license'] = opts[:license] if opts.key?(:license)
312
+ answers['operatingsystem_support'].flatten!
228
313
  metadata.update!(answers)
229
314
 
230
315
  puts '-' * 40
@@ -234,8 +319,13 @@ module PDK
234
319
  puts '-' * 40
235
320
  puts
236
321
 
237
- continue = prompt.yes?(_('About to generate this module; continue?')) do |q|
238
- q.validate(proc { |value| [true, false].include?(value) || value =~ %r{\A(?:yes|y|no|n)\Z}i }, 'Please answer "yes" or "no"')
322
+ begin
323
+ continue = prompt.yes?(_('About to generate this module; continue?')) do |q|
324
+ q.validate(proc { |value| [true, false].include?(value) || value =~ %r{\A(?:yes|y|no|n)\Z}i }, _('Answer "Y" to continue or "n" to cancel.'))
325
+ end
326
+ rescue TTY::Prompt::Reader::InputInterrupt
327
+ PDK.logger.info _('Interview cancelled; not generating the module.')
328
+ exit 0
239
329
  end
240
330
 
241
331
  unless continue
@@ -28,8 +28,8 @@ module PDK
28
28
  end
29
29
  end
30
30
 
31
- # Calculates the path to the file that the tests for the new class will
32
- # be written to.
31
+ # Calculates the path to the file where the tests for the new class will
32
+ # be written.
33
33
  #
34
34
  # @return [String] the path where the tests for the new class will be
35
35
  # written.
@@ -78,14 +78,14 @@ module PDK
78
78
  # and create the target files from the template. This is the main entry
79
79
  # point for the class.
80
80
  #
81
- # @raise [PDK::CLI::FatalError] if the target files already exist.
81
+ # @raise [PDK::CLI::ExitWithError] if the target files already exist.
82
82
  # @raise [PDK::CLI::FatalError] (see #render_file)
83
83
  #
84
84
  # @api public
85
85
  def run
86
86
  [target_object_path, target_spec_path].each do |target_file|
87
87
  if File.exist?(target_file)
88
- raise PDK::CLI::FatalError, _("Unable to generate class, '%{file}' already exists.") % { file: target_file }
88
+ raise PDK::CLI::ExitWithError, _("Unable to generate %{object_type}; '%{file}' already exists.") % { file: target_file, object_type: object_type }
89
89
  end
90
90
  end
91
91
 
@@ -165,7 +165,7 @@ module PDK
165
165
  # TODO: refactor to a search-and-execute form instead
166
166
  return # work is done # rubocop:disable Lint/NonLocalExitFromIterator
167
167
  elsif template[:allow_fallback]
168
- PDK.logger.debug(_('Unable to find a %{type} template in %{url}, trying next template directory') % { type: object_type, url: template[:url] })
168
+ PDK.logger.debug(_('Unable to find a %{type} template in %{url}; trying next template directory.') % { type: object_type, url: template[:url] })
169
169
  else
170
170
  raise PDK::CLI::FatalError, _('Unable to find the %{type} template in %{url}.') % { type: object_type, url: template[:url] }
171
171
  end
@@ -44,11 +44,11 @@ module PDK
44
44
 
45
45
  def self.from_file(metadata_json_path)
46
46
  unless File.file?(metadata_json_path)
47
- raise ArgumentError, _("'%{file}' does not exist or is not a file") % { file: metadata_json_path }
47
+ raise ArgumentError, _("'%{file}' does not exist or is not a file.") % { file: metadata_json_path }
48
48
  end
49
49
 
50
50
  unless File.readable?(metadata_json_path)
51
- raise ArgumentError, _("Unable to open '%{file}' for reading") % { file: metadata_json_path }
51
+ raise ArgumentError, _("Unable to open '%{file}' for reading.") % { file: metadata_json_path }
52
52
  end
53
53
 
54
54
  begin
@@ -90,16 +90,16 @@ module PDK
90
90
 
91
91
  err = case modname
92
92
  when nil, '', :namespace_missing
93
- 'the field must be a dash-separated username and module name'
93
+ _('Field must be a dash-separated user name and module name.')
94
94
  when %r{[^a-z0-9_]}i
95
- 'the module name contains non-alphanumeric (or underscore) characters'
95
+ _('Module name must contain only alphanumeric or underscore characters.')
96
96
  when %r{^[^a-z]}i
97
- 'the module name must begin with a letter'
97
+ _('Module name must begin with a letter.')
98
98
  else
99
- 'the namespace contains non-alphanumeric characters'
99
+ _('Namespace must contain only alphanumeric characters.')
100
100
  end
101
101
 
102
- raise ArgumentError, "Invalid 'name' field in metadata.json: #{err}"
102
+ raise ArgumentError, _("Invalid 'name' field in metadata.json: %{err}") % { err: err }
103
103
  end
104
104
  end
105
105
  end
@@ -52,7 +52,7 @@ module PDK
52
52
  unless clone_result[:exit_code].zero?
53
53
  PDK.logger.error clone_result[:stdout]
54
54
  PDK.logger.error clone_result[:stderr]
55
- raise PDK::CLI::FatalError, _("Unable to clone git repository '%{repo}' to '%{dest}'") % { repo: path_or_url, dest: temp_dir }
55
+ raise PDK::CLI::FatalError, _("Unable to clone git repository '%{repo}' to '%{dest}'.") % { repo: path_or_url, dest: temp_dir }
56
56
  end
57
57
 
58
58
  @path = PDK::Util.canonical_path(temp_dir)
@@ -178,11 +178,11 @@ module PDK
178
178
  # @api private
179
179
  def validate_module_template!
180
180
  unless File.directory?(@path)
181
- raise ArgumentError, _("The specified template '%{path}' is not a directory") % { path: @path }
181
+ raise ArgumentError, _("The specified template '%{path}' is not a directory.") % { path: @path }
182
182
  end
183
183
 
184
184
  unless File.directory?(@moduleroot_dir) # rubocop:disable Style/GuardClause
185
- raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot/' directory") % { path: @path }
185
+ raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot/' directory.") % { path: @path }
186
186
  end
187
187
  end
188
188