pdk-akerl 1.8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +826 -0
  3. data/LICENSE +201 -0
  4. data/README.md +133 -0
  5. data/exe/pdk +10 -0
  6. data/lib/pdk.rb +10 -0
  7. data/lib/pdk/answer_file.rb +121 -0
  8. data/lib/pdk/cli.rb +113 -0
  9. data/lib/pdk/cli/build.rb +76 -0
  10. data/lib/pdk/cli/bundle.rb +42 -0
  11. data/lib/pdk/cli/convert.rb +41 -0
  12. data/lib/pdk/cli/errors.rb +23 -0
  13. data/lib/pdk/cli/exec.rb +246 -0
  14. data/lib/pdk/cli/exec_group.rb +67 -0
  15. data/lib/pdk/cli/module.rb +14 -0
  16. data/lib/pdk/cli/module/build.rb +14 -0
  17. data/lib/pdk/cli/module/generate.rb +45 -0
  18. data/lib/pdk/cli/new.rb +17 -0
  19. data/lib/pdk/cli/new/class.rb +32 -0
  20. data/lib/pdk/cli/new/defined_type.rb +30 -0
  21. data/lib/pdk/cli/new/module.rb +41 -0
  22. data/lib/pdk/cli/new/provider.rb +27 -0
  23. data/lib/pdk/cli/new/task.rb +31 -0
  24. data/lib/pdk/cli/test.rb +12 -0
  25. data/lib/pdk/cli/test/unit.rb +88 -0
  26. data/lib/pdk/cli/update.rb +32 -0
  27. data/lib/pdk/cli/util.rb +193 -0
  28. data/lib/pdk/cli/util/command_redirector.rb +26 -0
  29. data/lib/pdk/cli/util/interview.rb +63 -0
  30. data/lib/pdk/cli/util/option_normalizer.rb +53 -0
  31. data/lib/pdk/cli/util/option_validator.rb +56 -0
  32. data/lib/pdk/cli/validate.rb +124 -0
  33. data/lib/pdk/generate.rb +11 -0
  34. data/lib/pdk/generate/defined_type.rb +49 -0
  35. data/lib/pdk/generate/module.rb +318 -0
  36. data/lib/pdk/generate/provider.rb +82 -0
  37. data/lib/pdk/generate/puppet_class.rb +48 -0
  38. data/lib/pdk/generate/puppet_object.rb +288 -0
  39. data/lib/pdk/generate/task.rb +86 -0
  40. data/lib/pdk/i18n.rb +4 -0
  41. data/lib/pdk/logger.rb +28 -0
  42. data/lib/pdk/module.rb +21 -0
  43. data/lib/pdk/module/build.rb +214 -0
  44. data/lib/pdk/module/convert.rb +209 -0
  45. data/lib/pdk/module/metadata.rb +193 -0
  46. data/lib/pdk/module/templatedir.rb +313 -0
  47. data/lib/pdk/module/update.rb +111 -0
  48. data/lib/pdk/module/update_manager.rb +210 -0
  49. data/lib/pdk/report.rb +112 -0
  50. data/lib/pdk/report/event.rb +357 -0
  51. data/lib/pdk/template_file.rb +89 -0
  52. data/lib/pdk/tests/unit.rb +213 -0
  53. data/lib/pdk/util.rb +271 -0
  54. data/lib/pdk/util/bundler.rb +253 -0
  55. data/lib/pdk/util/filesystem.rb +12 -0
  56. data/lib/pdk/util/git.rb +74 -0
  57. data/lib/pdk/util/puppet_version.rb +242 -0
  58. data/lib/pdk/util/ruby_version.rb +147 -0
  59. data/lib/pdk/util/vendored_file.rb +88 -0
  60. data/lib/pdk/util/version.rb +42 -0
  61. data/lib/pdk/util/windows.rb +13 -0
  62. data/lib/pdk/util/windows/api_types.rb +57 -0
  63. data/lib/pdk/util/windows/file.rb +36 -0
  64. data/lib/pdk/util/windows/string.rb +16 -0
  65. data/lib/pdk/validate.rb +14 -0
  66. data/lib/pdk/validate/base_validator.rb +209 -0
  67. data/lib/pdk/validate/metadata/metadata_json_lint.rb +86 -0
  68. data/lib/pdk/validate/metadata/metadata_syntax.rb +109 -0
  69. data/lib/pdk/validate/metadata_validator.rb +30 -0
  70. data/lib/pdk/validate/puppet/puppet_lint.rb +67 -0
  71. data/lib/pdk/validate/puppet/puppet_syntax.rb +112 -0
  72. data/lib/pdk/validate/puppet_validator.rb +30 -0
  73. data/lib/pdk/validate/ruby/rubocop.rb +77 -0
  74. data/lib/pdk/validate/ruby_validator.rb +29 -0
  75. data/lib/pdk/validate/tasks/metadata_lint.rb +126 -0
  76. data/lib/pdk/validate/tasks/name.rb +88 -0
  77. data/lib/pdk/validate/tasks_validator.rb +33 -0
  78. data/lib/pdk/version.rb +4 -0
  79. data/locales/config.yaml +21 -0
  80. data/locales/pdk.pot +1283 -0
  81. metadata +304 -0
@@ -0,0 +1,11 @@
1
+ require 'pdk/generate/defined_type'
2
+ require 'pdk/generate/module'
3
+ require 'pdk/generate/provider'
4
+ require 'pdk/generate/puppet_class'
5
+ require 'pdk/generate/task'
6
+ require 'pdk/module/metadata'
7
+ require 'pdk/module/templatedir'
8
+
9
+ module PDK
10
+ module Generate; end
11
+ end
@@ -0,0 +1,49 @@
1
+ require 'pdk/generate/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
@@ -0,0 +1,318 @@
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'
16
+
17
+ module PDK
18
+ module Generate
19
+ class Module
20
+ extend PDK::Util::Filesystem
21
+
22
+ def self.validate_options(opts)
23
+ unless PDK::CLI::Util::OptionValidator.valid_module_name?(opts[:module_name])
24
+ error_msg = _(
25
+ "'%{module_name}' is not a valid module name.\n" \
26
+ 'Module names must begin with a lowercase letter and can only include lowercase letters, digits, and underscores.',
27
+ ) % { module_name: opts[:module_name] }
28
+ raise PDK::CLI::ExitWithError, error_msg
29
+ end
30
+
31
+ target_dir = File.expand_path(opts[:target_dir])
32
+ raise PDK::CLI::ExitWithError, _("The destination directory '%{dir}' already exists") % { dir: target_dir } if File.exist?(target_dir)
33
+ end
34
+
35
+ def self.invoke(opts = {})
36
+ validate_options(opts) unless opts[:module_name].nil?
37
+
38
+ metadata = prepare_metadata(opts)
39
+
40
+ target_dir = File.expand_path(opts[:target_dir] || opts[:module_name])
41
+ parent_dir = File.dirname(target_dir)
42
+
43
+ begin
44
+ test_file = File.join(parent_dir, '.pdk-test-writable')
45
+ write_file(test_file, 'This file was created by the Puppet Development Kit to test if this folder was writable, you can safely remove this file.')
46
+ FileUtils.rm_f(test_file)
47
+ rescue Errno::EACCES
48
+ raise PDK::CLI::FatalError, _("You do not have permission to write to '%{parent_dir}'") % {
49
+ parent_dir: parent_dir,
50
+ }
51
+ end
52
+
53
+ temp_target_dir = PDK::Util.make_tmpdir_name('pdk-module-target')
54
+
55
+ prepare_module_directory(temp_target_dir)
56
+
57
+ template_url = opts.fetch(:'template-url', PDK::Util.default_template_url)
58
+
59
+ begin
60
+ PDK::Module::TemplateDir.new(template_url, metadata.data, true) do |templates|
61
+ templates.render do |file_path, file_content|
62
+ file = Pathname.new(temp_target_dir) + file_path
63
+ file.dirname.mkpath
64
+ write_file(file, file_content)
65
+ end
66
+
67
+ # Add information about the template used to generate the module to the
68
+ # metadata (for a future update command).
69
+ metadata.update!(templates.metadata)
70
+
71
+ metadata.write!(File.join(temp_target_dir, 'metadata.json'))
72
+ end
73
+ rescue ArgumentError => e
74
+ raise PDK::CLI::ExitWithError, e
75
+ end
76
+
77
+ if template_url == PDK::Util.puppetlabs_template_url
78
+ # If the user specifies our template via the command line, remove the
79
+ # saved template-url answer.
80
+ PDK.answers.update!('template-url' => nil) if opts.key?(:'template-url')
81
+ else
82
+ # Save the template-url answer if the module was generated using
83
+ # a template other than ours.
84
+ PDK.answers.update!('template-url' => template_url)
85
+ end
86
+
87
+ begin
88
+ if FileUtils.mv(temp_target_dir, target_dir)
89
+ Dir.chdir(target_dir) { PDK::Util::Bundler.ensure_bundle! } unless opts[:'skip-bundle-install']
90
+
91
+ PDK.logger.info(_('Module \'%{name}\' generated at path \'%{path}\', from template \'%{template_url}\'.') % { name: opts[:module_name], path: target_dir, template_url: template_url })
92
+ PDK.logger.info(_('In your module directory, add classes with the \'pdk new class\' command.'))
93
+ end
94
+ rescue Errno::EACCES => e
95
+ raise PDK::CLI::FatalError, _("Failed to move '%{source}' to '%{target}': %{message}") % {
96
+ source: temp_target_dir,
97
+ target: target_dir,
98
+ message: e.message,
99
+ }
100
+ end
101
+ end
102
+
103
+ def self.username_from_login
104
+ login = Etc.getlogin || ''
105
+ login_clean = login.downcase.gsub(%r{[^0-9a-z]}i, '')
106
+ login_clean = 'username' if login_clean.empty?
107
+
108
+ if login_clean != login
109
+ PDK.logger.debug _('Your username is not a valid Forge username. Proceeding with the username %{username}. You can fix this later in metadata.json.') % {
110
+ username: login_clean,
111
+ }
112
+ end
113
+
114
+ login_clean
115
+ end
116
+
117
+ def self.prepare_metadata(opts = {})
118
+ opts[:username] = (opts[:username] || PDK.answers['forge_username'] || username_from_login).downcase
119
+
120
+ defaults = PDK::Module::Metadata::DEFAULTS.dup
121
+
122
+ defaults['name'] = "#{opts[:username]}-#{opts[:module_name]}" unless opts[:module_name].nil?
123
+ defaults['author'] = PDK.answers['author'] unless PDK.answers['author'].nil?
124
+ defaults['license'] = PDK.answers['license'] unless PDK.answers['license'].nil?
125
+ defaults['license'] = opts[:license] if opts.key? :license
126
+
127
+ metadata = PDK::Module::Metadata.new(defaults)
128
+ module_interview(metadata, opts) unless opts[:'skip-interview']
129
+
130
+ metadata
131
+ end
132
+
133
+ def self.prepare_module_directory(target_dir)
134
+ [
135
+ File.join(target_dir, 'examples'),
136
+ File.join(target_dir, 'files'),
137
+ File.join(target_dir, 'manifests'),
138
+ File.join(target_dir, 'templates'),
139
+ File.join(target_dir, 'tasks'),
140
+ ].each do |dir|
141
+ begin
142
+ FileUtils.mkdir_p(dir)
143
+ rescue SystemCallError => e
144
+ raise PDK::CLI::FatalError, _("Unable to create directory '%{dir}': %{message}") % {
145
+ dir: dir,
146
+ message: e.message,
147
+ }
148
+ end
149
+ end
150
+ end
151
+
152
+ def self.module_interview(metadata, opts = {})
153
+ questions = [
154
+ {
155
+ name: 'module_name',
156
+ question: _('If you have a name for your module, add it here.'),
157
+ help: _('This is the name that will be associated with your module, it should be relevant to the modules content.'),
158
+ required: true,
159
+ validate_pattern: %r{\A[a-z][a-z0-9_]*\Z}i,
160
+ validate_message: _('Module names must begin with a lowercase letter and can only include lowercase letters, numbers, and underscores.'),
161
+ },
162
+ {
163
+ name: 'forge_username',
164
+ question: _('If you have a Puppet Forge username, add it here.'),
165
+ help: _('We can use this to upload your module to the Forge when it\'s complete.'),
166
+ required: true,
167
+ validate_pattern: %r{\A[a-z0-9]+\Z}i,
168
+ validate_message: _('Forge usernames can only contain lowercase letters and numbers'),
169
+ default: opts[:username],
170
+ },
171
+ {
172
+ name: 'version',
173
+ question: _('What version is this module?'),
174
+ help: _('Puppet uses Semantic Versioning (semver.org) to version modules.'),
175
+ required: true,
176
+ validate_pattern: %r{\A[0-9]+\.[0-9]+\.[0-9]+},
177
+ validate_message: _('Semantic Version numbers must be in the form MAJOR.MINOR.PATCH'),
178
+ default: metadata.data['version'],
179
+ forge_only: true,
180
+ },
181
+ {
182
+ name: 'author',
183
+ question: _('Who wrote this module?'),
184
+ help: _('This is used to credit the module\'s author.'),
185
+ required: true,
186
+ default: metadata.data['author'],
187
+ },
188
+ {
189
+ name: 'license',
190
+ question: _('What license does this module code fall under?'),
191
+ help: _('This should be an identifier from https://spdx.org/licenses/. Common values are "Apache-2.0", "MIT", or "proprietary".'),
192
+ required: true,
193
+ default: metadata.data['license'],
194
+ },
195
+ {
196
+ name: 'operatingsystem_support',
197
+ question: _('What operating systems does this module support?'),
198
+ help: _('Use the up and down keys to move between the choices, space to select and enter to continue.'),
199
+ required: true,
200
+ choices: PDK::Module::Metadata::OPERATING_SYSTEMS,
201
+ default: PDK::Module::Metadata::DEFAULT_OPERATING_SYSTEMS.map do |os_name|
202
+ # tty-prompt uses a 1-index
203
+ PDK::Module::Metadata::OPERATING_SYSTEMS.keys.index(os_name) + 1
204
+ end,
205
+ },
206
+ {
207
+ name: 'summary',
208
+ question: _('Summarize the purpose of this module in a single sentence.'),
209
+ help: _('This helps other Puppet users understand what the module does.'),
210
+ required: true,
211
+ default: metadata.data['summary'],
212
+ forge_only: true,
213
+ },
214
+ {
215
+ name: 'source',
216
+ question: _('If there is a source code repository for this module, enter the URL here.'),
217
+ help: _('Skip this if no repository exists yet. You can update this later in the metadata.json.'),
218
+ required: true,
219
+ default: metadata.data['source'],
220
+ forge_only: true,
221
+ },
222
+ {
223
+ name: 'project_page',
224
+ question: _('If there is a URL where others can learn more about this module, enter it here.'),
225
+ help: _('Optional. You can update this later in the metadata.json.'),
226
+ default: metadata.data['project_page'],
227
+ forge_only: true,
228
+ },
229
+ {
230
+ name: 'issues_url',
231
+ question: _('If there is a public issue tracker for this module, enter its URL here.'),
232
+ help: _('Optional. You can update this later in the metadata.json.'),
233
+ default: metadata.data['issues_url'],
234
+ forge_only: true,
235
+ },
236
+ ]
237
+
238
+ prompt = TTY::Prompt.new(help_color: :cyan)
239
+
240
+ interview = PDK::CLI::Util::Interview.new(prompt)
241
+
242
+ if opts[:only_ask]
243
+ questions.reject! do |question|
244
+ if %w[module_name forge_username].include?(question[:name])
245
+ metadata.data['name'] && metadata.data['name'] =~ %r{\A[a-z0-9]+-[a-z][a-z0-9_]*\Z}i
246
+ else
247
+ !opts[:only_ask].include?(question[:name])
248
+ end
249
+ end
250
+ else
251
+ questions.reject! { |q| q[:name] == 'module_name' } if opts.key?(:module_name)
252
+ questions.reject! { |q| q[:name] == 'license' } if opts.key?(:license)
253
+ questions.reject! { |q| q[:forge_only] } unless opts.key?(:'full-interview')
254
+ end
255
+
256
+ interview.add_questions(questions)
257
+
258
+ action = File.file?('metadata.json') ? _('update') : _('create')
259
+ puts _(
260
+ "\nWe need to %{action} the metadata.json file for this module, so we\'re going to ask you %{count} " \
261
+ "questions.\n" \
262
+ 'If the question is not applicable to this module, accept the default option ' \
263
+ 'shown after each question. You can modify any answers at any time by manually updating ' \
264
+ "the metadata.json file.\n\n",
265
+ ) % {
266
+ count: interview.num_questions,
267
+ action: action,
268
+ }
269
+
270
+ answers = interview.run
271
+
272
+ if answers.nil?
273
+ PDK.logger.info _('No answers given, interview cancelled.')
274
+ exit 0
275
+ end
276
+
277
+ unless answers['forge_username'].nil?
278
+ opts[:username] = answers['forge_username']
279
+
280
+ unless answers['module_name'].nil?
281
+ opts[:module_name] = answers['module_name']
282
+
283
+ answers.delete('module_name')
284
+ end
285
+
286
+ answers['name'] = "#{opts[:username]}-" + (opts[:module_name])
287
+ answers.delete('forge_username')
288
+ end
289
+
290
+ answers['license'] = opts[:license] if opts.key?(:license)
291
+ answers['operatingsystem_support'].flatten! if answers.key?('operatingsystem_support')
292
+
293
+ metadata.update!(answers)
294
+
295
+ if opts[:prompt].nil? || opts[:prompt]
296
+ continue = PDK::CLI::Util.prompt_for_yes(
297
+ _('Metadata will be generated based on this information, continue?'),
298
+ prompt: prompt,
299
+ cancel_message: _('Interview cancelled; exiting.'),
300
+ )
301
+
302
+ unless continue
303
+ PDK.logger.info _('Process cancelled; exiting.')
304
+ exit 0
305
+ end
306
+ end
307
+
308
+ PDK.answers.update!(
309
+ {
310
+ 'forge_username' => opts[:username],
311
+ 'author' => answers['author'],
312
+ 'license' => answers['license'],
313
+ }.delete_if { |_key, value| value.nil? },
314
+ )
315
+ end
316
+ end
317
+ end
318
+ end
@@ -0,0 +1,82 @@
1
+ require 'pdk/generate/puppet_object'
2
+
3
+ module PDK
4
+ module Generate
5
+ class Provider < PuppetObject
6
+ OBJECT_TYPE = :provider
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 = {
15
+ name: object_name,
16
+ provider_class: Provider.class_name_from_object_name(object_name),
17
+ }
18
+
19
+ data
20
+ end
21
+
22
+ def raise_precondition_error(error)
23
+ raise PDK::CLI::ExitWithError, _('%{error}: Creating a provider needs some local configuration in your module.' \
24
+ ' Please follow the docs at https://github.com/puppetlabs/puppet-resource_api#getting-started.') % { error: error }
25
+ end
26
+
27
+ def check_preconditions
28
+ super
29
+ # These preconditions can be removed once the pdk-templates are carrying the puppet-resource_api gem by default, and have switched
30
+ # the default mock_with value.
31
+ sync_path = PDK::Util.find_upwards('.sync.yml')
32
+ if sync_path.nil?
33
+ raise_precondition_error(_('.sync.yml not found'))
34
+ end
35
+ sync = YAML.load_file(sync_path)
36
+ if !sync.is_a? Hash
37
+ raise_precondition_error(_('.sync.yml contents is not a Hash'))
38
+ elsif !sync.key? 'Gemfile'
39
+ raise_precondition_error(_('Gemfile configuration not found'))
40
+ elsif !sync['Gemfile'].key? 'optional'
41
+ raise_precondition_error(_('Gemfile.optional configuration not found'))
42
+ elsif !sync['Gemfile']['optional'].key? ':development'
43
+ raise_precondition_error(_('Gemfile.optional.:development configuration not found'))
44
+ elsif sync['Gemfile']['optional'][':development'].none? { |g| g['gem'] == 'puppet-resource_api' }
45
+ raise_precondition_error(_('puppet-resource_api not found in the Gemfile config'))
46
+ elsif !sync.key? 'spec/spec_helper.rb'
47
+ raise_precondition_error(_('spec/spec_helper.rb configuration not found'))
48
+ elsif !sync['spec/spec_helper.rb'].key? 'mock_with'
49
+ raise_precondition_error(_('spec/spec_helper.rb.mock_with configuration not found'))
50
+ elsif !sync['spec/spec_helper.rb']['mock_with'] == ':rspec'
51
+ raise_precondition_error(_('spec/spec_helper.rb.mock_with not set to \':rspec\''))
52
+ end
53
+ end
54
+
55
+ # @return [String] the path where the new provider will be written.
56
+ def target_object_path
57
+ @target_object_path ||= File.join(module_dir, 'lib', 'puppet', 'provider', object_name, object_name) + '.rb'
58
+ end
59
+
60
+ # @return [String] the path where the new type will be written.
61
+ def target_type_path
62
+ @target_type_path ||= File.join(module_dir, 'lib', 'puppet', 'type', object_name) + '.rb'
63
+ end
64
+
65
+ # @return [String] the path where the tests for the new provider
66
+ # will be written.
67
+ def target_spec_path
68
+ @target_spec_path ||= File.join(module_dir, 'spec', 'unit', 'puppet', 'provider', object_name, object_name) + '_spec.rb'
69
+ end
70
+
71
+ # @return [String] the path where the tests for the new type will be written.
72
+ def target_type_spec_path
73
+ @target_type_spec_path ||= File.join(module_dir, 'spec', 'unit', 'puppet', 'type', object_name) + '_spec.rb'
74
+ end
75
+
76
+ # transform a object name into a ruby class name
77
+ def self.class_name_from_object_name(object_name)
78
+ object_name.to_s.split('_').map(&:capitalize).join
79
+ end
80
+ end
81
+ end
82
+ end