pdk 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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