pdk 2.4.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (153) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +803 -1329
  3. data/LICENSE +201 -201
  4. data/README.md +165 -163
  5. data/exe/pdk +10 -10
  6. data/lib/pdk/analytics/client/google_analytics.rb +143 -143
  7. data/lib/pdk/analytics/client/noop.rb +25 -25
  8. data/lib/pdk/analytics/util.rb +19 -19
  9. data/lib/pdk/analytics.rb +30 -30
  10. data/lib/pdk/answer_file.rb +12 -12
  11. data/lib/pdk/bolt.rb +19 -19
  12. data/lib/pdk/cli/build.rb +82 -82
  13. data/lib/pdk/cli/bundle.rb +48 -48
  14. data/lib/pdk/cli/config/get.rb +26 -26
  15. data/lib/pdk/cli/config.rb +22 -22
  16. data/lib/pdk/cli/console.rb +148 -148
  17. data/lib/pdk/cli/convert.rb +52 -52
  18. data/lib/pdk/cli/env.rb +52 -52
  19. data/lib/pdk/cli/errors.rb +25 -25
  20. data/lib/pdk/cli/exec/command.rb +293 -293
  21. data/lib/pdk/cli/exec/interactive_command.rb +114 -114
  22. data/lib/pdk/cli/exec.rb +84 -84
  23. data/lib/pdk/cli/exec_group.rb +104 -104
  24. data/lib/pdk/cli/get/config.rb +24 -24
  25. data/lib/pdk/cli/get.rb +20 -20
  26. data/lib/pdk/cli/module/build.rb +12 -12
  27. data/lib/pdk/cli/module/generate.rb +47 -47
  28. data/lib/pdk/cli/module.rb +14 -14
  29. data/lib/pdk/cli/new/class.rb +32 -32
  30. data/lib/pdk/cli/new/defined_type.rb +32 -32
  31. data/lib/pdk/cli/new/fact.rb +29 -29
  32. data/lib/pdk/cli/new/function.rb +29 -29
  33. data/lib/pdk/cli/new/module.rb +53 -53
  34. data/lib/pdk/cli/new/provider.rb +29 -29
  35. data/lib/pdk/cli/new/task.rb +34 -34
  36. data/lib/pdk/cli/new/test.rb +52 -52
  37. data/lib/pdk/cli/new/transport.rb +27 -27
  38. data/lib/pdk/cli/new.rb +21 -21
  39. data/lib/pdk/cli/release/prep.rb +39 -39
  40. data/lib/pdk/cli/release/publish.rb +50 -50
  41. data/lib/pdk/cli/release.rb +194 -194
  42. data/lib/pdk/cli/remove/config.rb +80 -80
  43. data/lib/pdk/cli/remove.rb +20 -20
  44. data/lib/pdk/cli/set/config.rb +119 -119
  45. data/lib/pdk/cli/set.rb +20 -20
  46. data/lib/pdk/cli/test/unit.rb +90 -90
  47. data/lib/pdk/cli/test.rb +11 -11
  48. data/lib/pdk/cli/update.rb +64 -64
  49. data/lib/pdk/cli/util/command_redirector.rb +27 -27
  50. data/lib/pdk/cli/util/interview.rb +72 -72
  51. data/lib/pdk/cli/util/option_normalizer.rb +55 -55
  52. data/lib/pdk/cli/util/option_validator.rb +68 -68
  53. data/lib/pdk/cli/util/spinner.rb +13 -13
  54. data/lib/pdk/cli/util/update_manager_printer.rb +82 -82
  55. data/lib/pdk/cli/util.rb +305 -305
  56. data/lib/pdk/cli/validate.rb +116 -116
  57. data/lib/pdk/cli.rb +175 -175
  58. data/lib/pdk/config/analytics_schema.json +26 -26
  59. data/lib/pdk/config/errors.rb +5 -5
  60. data/lib/pdk/config/ini_file.rb +183 -183
  61. data/lib/pdk/config/ini_file_setting.rb +39 -39
  62. data/lib/pdk/config/json.rb +34 -34
  63. data/lib/pdk/config/json_schema_namespace.rb +142 -142
  64. data/lib/pdk/config/json_schema_setting.rb +53 -53
  65. data/lib/pdk/config/json_with_schema.rb +49 -49
  66. data/lib/pdk/config/namespace.rb +354 -354
  67. data/lib/pdk/config/setting.rb +135 -135
  68. data/lib/pdk/config/validator.rb +31 -31
  69. data/lib/pdk/config/yaml.rb +46 -46
  70. data/lib/pdk/config/yaml_with_schema.rb +59 -59
  71. data/lib/pdk/config.rb +390 -390
  72. data/lib/pdk/context/control_repo.rb +60 -60
  73. data/lib/pdk/context/module.rb +28 -28
  74. data/lib/pdk/context/none.rb +22 -22
  75. data/lib/pdk/context.rb +99 -99
  76. data/lib/pdk/control_repo.rb +90 -90
  77. data/lib/pdk/generate/defined_type.rb +43 -43
  78. data/lib/pdk/generate/fact.rb +25 -25
  79. data/lib/pdk/generate/function.rb +48 -48
  80. data/lib/pdk/generate/module.rb +352 -352
  81. data/lib/pdk/generate/provider.rb +28 -28
  82. data/lib/pdk/generate/puppet_class.rb +43 -43
  83. data/lib/pdk/generate/puppet_object.rb +232 -232
  84. data/lib/pdk/generate/task.rb +68 -68
  85. data/lib/pdk/generate/transport.rb +33 -33
  86. data/lib/pdk/generate.rb +24 -24
  87. data/lib/pdk/i18n.rb +4 -4
  88. data/lib/pdk/logger.rb +45 -45
  89. data/lib/pdk/module/build.rb +322 -322
  90. data/lib/pdk/module/convert.rb +296 -296
  91. data/lib/pdk/module/metadata.rb +202 -202
  92. data/lib/pdk/module/release.rb +260 -260
  93. data/lib/pdk/module/update.rb +131 -131
  94. data/lib/pdk/module/update_manager.rb +227 -227
  95. data/lib/pdk/module.rb +30 -30
  96. data/lib/pdk/report/event.rb +370 -370
  97. data/lib/pdk/report.rb +121 -121
  98. data/lib/pdk/template/fetcher/git.rb +85 -85
  99. data/lib/pdk/template/fetcher/local.rb +28 -28
  100. data/lib/pdk/template/fetcher.rb +98 -98
  101. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +116 -116
  102. data/lib/pdk/template/renderer/v1/renderer.rb +132 -132
  103. data/lib/pdk/template/renderer/v1/template_file.rb +102 -102
  104. data/lib/pdk/template/renderer/v1.rb +25 -25
  105. data/lib/pdk/template/renderer.rb +96 -96
  106. data/lib/pdk/template/template_dir.rb +67 -67
  107. data/lib/pdk/template.rb +59 -59
  108. data/lib/pdk/tests/unit.rb +252 -252
  109. data/lib/pdk/util/bundler.rb +259 -259
  110. data/lib/pdk/util/changelog_generator.rb +137 -137
  111. data/lib/pdk/util/env.rb +47 -47
  112. data/lib/pdk/util/filesystem.rb +138 -138
  113. data/lib/pdk/util/git.rb +179 -179
  114. data/lib/pdk/util/json_finder.rb +85 -85
  115. data/lib/pdk/util/puppet_strings.rb +125 -125
  116. data/lib/pdk/util/puppet_version.rb +266 -266
  117. data/lib/pdk/util/ruby_version.rb +179 -179
  118. data/lib/pdk/util/template_uri.rb +295 -295
  119. data/lib/pdk/util/vendored_file.rb +93 -93
  120. data/lib/pdk/util/version.rb +43 -43
  121. data/lib/pdk/util/windows/api_types.rb +82 -82
  122. data/lib/pdk/util/windows/file.rb +36 -36
  123. data/lib/pdk/util/windows/process.rb +79 -79
  124. data/lib/pdk/util/windows/string.rb +16 -16
  125. data/lib/pdk/util/windows.rb +15 -15
  126. data/lib/pdk/util.rb +278 -278
  127. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -23
  128. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -98
  129. data/lib/pdk/validate/external_command_validator.rb +208 -208
  130. data/lib/pdk/validate/internal_ruby_validator.rb +100 -100
  131. data/lib/pdk/validate/invokable_validator.rb +228 -228
  132. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +86 -86
  133. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +78 -78
  134. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -20
  135. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +133 -133
  136. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -66
  137. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +137 -137
  138. data/lib/pdk/validate/puppet/puppet_validator_group.rb +21 -21
  139. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +80 -80
  140. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -19
  141. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +88 -88
  142. data/lib/pdk/validate/tasks/tasks_name_validator.rb +50 -50
  143. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -20
  144. data/lib/pdk/validate/validator.rb +118 -118
  145. data/lib/pdk/validate/validator_group.rb +104 -104
  146. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +95 -95
  147. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -19
  148. data/lib/pdk/validate.rb +94 -94
  149. data/lib/pdk/version.rb +4 -4
  150. data/lib/pdk.rb +76 -76
  151. data/locales/config.yaml +21 -21
  152. data/locales/pdk.pot +2111 -2094
  153. metadata +3 -3
@@ -1,352 +1,352 @@
1
- require 'pdk'
2
-
3
- module PDK
4
- module Generate
5
- class Module
6
- def self.validate_options(opts)
7
- require 'pdk/cli/util/option_validator'
8
-
9
- unless PDK::CLI::Util::OptionValidator.valid_module_name?(opts[:module_name])
10
- error_msg = _(
11
- "'%{module_name}' is not a valid module name.\n" \
12
- 'Module names must begin with a lowercase letter and can only include lowercase letters, digits, and underscores.',
13
- ) % { module_name: opts[:module_name] }
14
- raise PDK::CLI::ExitWithError, error_msg
15
- end
16
-
17
- target_dir = PDK::Util::Filesystem.expand_path(opts[:target_dir])
18
- raise PDK::CLI::ExitWithError, _("The destination directory '%{dir}' already exists") % { dir: target_dir } if PDK::Util::Filesystem.exist?(target_dir)
19
- end
20
-
21
- def self.invoke(opts = {})
22
- require 'pdk/util'
23
- require 'pdk/util/template_uri'
24
- require 'pathname'
25
-
26
- validate_options(opts) unless opts[:module_name].nil?
27
-
28
- metadata = prepare_metadata(opts)
29
-
30
- target_dir = PDK::Util::Filesystem.expand_path(opts[:target_dir] || opts[:module_name])
31
- parent_dir = File.dirname(target_dir)
32
-
33
- begin
34
- test_file = File.join(parent_dir, '.pdk-test-writable')
35
- PDK::Util::Filesystem.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.')
36
- PDK::Util::Filesystem.rm_f(test_file)
37
- rescue Errno::EACCES
38
- raise PDK::CLI::FatalError, _("You do not have permission to write to '%{parent_dir}'") % {
39
- parent_dir: parent_dir,
40
- }
41
- end
42
-
43
- temp_target_dir = PDK::Util.make_tmpdir_name('pdk-module-target')
44
-
45
- prepare_module_directory(temp_target_dir)
46
-
47
- template_uri = PDK::Util::TemplateURI.new(opts)
48
-
49
- if template_uri.default? && template_uri.default_ref?
50
- PDK.logger.info _('Using the default template-url and template-ref.')
51
- else
52
- PDK.logger.info _(
53
- "Using the %{method} template-url and template-ref '%{template_uri}'." % {
54
- method: opts.key?(:'template-url') ? _('specified') : _('saved'),
55
- template_uri: template_uri.metadata_format,
56
- },
57
- )
58
- end
59
-
60
- begin
61
- context = PDK::Context::None.new(temp_target_dir)
62
- PDK::Template.with(template_uri, context) do |template_dir|
63
- template_dir.render_new_module(metadata.data['name'], metadata.data) do |relative_file_path, file_content, file_status|
64
- next if [:delete, :unmanage].include?(file_status)
65
- file = Pathname.new(temp_target_dir) + relative_file_path
66
- file.dirname.mkpath
67
- PDK::Util::Filesystem.write_file(file, file_content)
68
- end
69
-
70
- # Add information about the template used to generate the module to the
71
- # metadata (for a future update command).
72
- metadata.update!(template_dir.metadata)
73
-
74
- metadata.write!(File.join(temp_target_dir, 'metadata.json'))
75
- end
76
- rescue ArgumentError => e
77
- raise PDK::CLI::ExitWithError, e
78
- end
79
-
80
- # Only update the answers files after metadata has been written.
81
- require 'pdk/answer_file'
82
- if template_uri.default? && template_uri.default_ref?
83
- # If the user specifies our default template url via the command
84
- # line, remove the saved template-url answer so that the template_uri
85
- # resolution can find new default URLs in the future.
86
- PDK.config.set(%w[user module_defaults template-url], nil) if opts.key?(:'template-url')
87
- else
88
- # Save the template-url answers if the module was generated using a
89
- # template/reference other than ours.
90
- PDK.config.set(%w[user module_defaults template-url], template_uri.metadata_format)
91
- end
92
-
93
- begin
94
- if PDK::Util::Filesystem.mv(temp_target_dir, target_dir)
95
- unless opts[:'skip-bundle-install']
96
- Dir.chdir(target_dir) do
97
- require 'pdk/util/bundler'
98
- PDK::Util::Bundler.ensure_bundle!
99
- end
100
- end
101
-
102
- PDK.logger.info _("Module '%{name}' generated at path '%{path}'.") % {
103
- name: opts[:module_name],
104
- path: target_dir,
105
- }
106
- PDK.logger.info _(
107
- "In your module directory, add classes with the 'pdk new class' command.",
108
- )
109
- end
110
- rescue Errno::EACCES => e
111
- raise PDK::CLI::FatalError, _("Failed to move '%{source}' to '%{target}': %{message}") % {
112
- source: temp_target_dir,
113
- target: target_dir,
114
- message: e.message,
115
- }
116
- end
117
- end
118
-
119
- def self.username_from_login
120
- require 'etc'
121
-
122
- login = Etc.getlogin || ''
123
- login_clean = login.downcase.gsub(%r{[^0-9a-z]}i, '')
124
- login_clean = 'username' if login_clean.empty?
125
-
126
- if login_clean != login
127
- PDK.logger.debug _('Your username is not a valid Forge username. Proceeding with the username %{username}. You can fix this later in metadata.json.') % {
128
- username: login_clean,
129
- }
130
- end
131
-
132
- login_clean
133
- end
134
-
135
- def self.prepare_metadata(opts = {})
136
- require 'pdk/answer_file'
137
- require 'pdk/module/metadata'
138
-
139
- opts[:username] = (opts[:username] || PDK.config.get_within_scopes('module_defaults.forge_username') || username_from_login).downcase
140
-
141
- defaults = PDK::Module::Metadata::DEFAULTS.dup
142
-
143
- defaults['name'] = "#{opts[:username]}-#{opts[:module_name]}" unless opts[:module_name].nil?
144
- PDK.config.with_scoped_value('module_defaults.author') { |val| defaults['author'] = val }
145
- PDK.config.with_scoped_value('module_defaults.license') { |val| defaults['license'] = val }
146
- defaults['license'] = opts[:license] if opts.key?(:license)
147
-
148
- metadata = PDK::Module::Metadata.new(defaults)
149
- module_interview(metadata, opts) unless opts[:'skip-interview']
150
-
151
- metadata
152
- end
153
-
154
- def self.prepare_module_directory(target_dir)
155
- [
156
- File.join(target_dir, 'examples'),
157
- File.join(target_dir, 'files'),
158
- File.join(target_dir, 'manifests'),
159
- File.join(target_dir, 'templates'),
160
- File.join(target_dir, 'tasks'),
161
- ].each do |dir|
162
- begin
163
- PDK::Util::Filesystem.mkdir_p(dir)
164
- rescue SystemCallError => e
165
- raise PDK::CLI::FatalError, _("Unable to create directory '%{dir}': %{message}") % {
166
- dir: dir,
167
- message: e.message,
168
- }
169
- end
170
- end
171
- end
172
-
173
- def self.module_interview(metadata, opts = {})
174
- require 'pdk/module/metadata'
175
- require 'pdk/cli/util/interview'
176
-
177
- questions = [
178
- {
179
- name: 'module_name',
180
- question: _('If you have a name for your module, add it here.'),
181
- help: _('This is the name that will be associated with your module, it should be relevant to the modules content.'),
182
- required: true,
183
- validate_pattern: %r{\A[a-z][a-z0-9_]*\Z}i,
184
- validate_message: _('Module names must begin with a lowercase letter and can only include lowercase letters, numbers, and underscores.'),
185
- },
186
- {
187
- name: 'forge_username',
188
- question: _('If you have a Puppet Forge username, add it here.'),
189
- help: _('We can use this to upload your module to the Forge when it\'s complete.'),
190
- required: true,
191
- validate_pattern: %r{\A[a-z0-9]+\Z}i,
192
- validate_message: _('Forge usernames can only contain lowercase letters and numbers'),
193
- default: opts[:username],
194
- },
195
- {
196
- name: 'version',
197
- question: _('What version is this module?'),
198
- help: _('Puppet uses Semantic Versioning (semver.org) to version modules.'),
199
- required: true,
200
- validate_pattern: %r{\A[0-9]+\.[0-9]+\.[0-9]+},
201
- validate_message: _('Semantic Version numbers must be in the form MAJOR.MINOR.PATCH'),
202
- default: metadata.data['version'],
203
- forge_only: true,
204
- },
205
- {
206
- name: 'author',
207
- question: _('Who wrote this module?'),
208
- help: _('This is used to credit the module\'s author.'),
209
- required: true,
210
- default: metadata.data['author'],
211
- },
212
- {
213
- name: 'license',
214
- question: _('What license does this module code fall under?'),
215
- help: _('This should be an identifier from https://spdx.org/licenses/. Common values are "Apache-2.0", "MIT", or "proprietary".'),
216
- required: true,
217
- default: metadata.data['license'],
218
- },
219
- {
220
- name: 'operatingsystem_support',
221
- question: _('What operating systems does this module support?'),
222
- help: _('Use the up and down keys to move between the choices, space to select and enter to continue.'),
223
- required: true,
224
- type: :multi_select,
225
- choices: PDK::Module::Metadata::OPERATING_SYSTEMS,
226
- default: PDK::Module::Metadata::DEFAULT_OPERATING_SYSTEMS.map do |os_name|
227
- # tty-prompt uses a 1-index
228
- PDK::Module::Metadata::OPERATING_SYSTEMS.keys.index(os_name) + 1
229
- end,
230
- },
231
- {
232
- name: 'summary',
233
- question: _('Summarize the purpose of this module in a single sentence.'),
234
- help: _('This helps other Puppet users understand what the module does.'),
235
- required: true,
236
- default: metadata.data['summary'],
237
- forge_only: true,
238
- },
239
- {
240
- name: 'source',
241
- question: _('If there is a source code repository for this module, enter the URL here.'),
242
- help: _('Skip this if no repository exists yet. You can update this later in the metadata.json.'),
243
- required: true,
244
- default: metadata.data['source'],
245
- forge_only: true,
246
- },
247
- {
248
- name: 'project_page',
249
- question: _('If there is a URL where others can learn more about this module, enter it here.'),
250
- help: _('Optional. You can update this later in the metadata.json.'),
251
- default: metadata.data['project_page'],
252
- forge_only: true,
253
- },
254
- {
255
- name: 'issues_url',
256
- question: _('If there is a public issue tracker for this module, enter its URL here.'),
257
- help: _('Optional. You can update this later in the metadata.json.'),
258
- default: metadata.data['issues_url'],
259
- forge_only: true,
260
- },
261
- ]
262
-
263
- prompt = TTY::Prompt.new(help_color: :cyan)
264
-
265
- interview = PDK::CLI::Util::Interview.new(prompt)
266
-
267
- if opts[:only_ask]
268
- questions.reject! do |question|
269
- if %w[module_name forge_username].include?(question[:name])
270
- metadata.data['name'] && metadata.data['name'] =~ %r{\A[a-z0-9]+-[a-z][a-z0-9_]*\Z}i
271
- else
272
- !opts[:only_ask].include?(question[:name])
273
- end
274
- end
275
- else
276
- questions.reject! { |q| q[:name] == 'module_name' } if opts.key?(:module_name)
277
- questions.reject! { |q| q[:name] == 'license' } if opts.key?(:license)
278
- questions.reject! { |q| q[:forge_only] } unless opts[:'full-interview']
279
- end
280
-
281
- interview.add_questions(questions)
282
-
283
- if PDK::Util::Filesystem.file?('metadata.json')
284
- puts _(
285
- "\nWe need to update the metadata.json file for this module, so we\'re going to ask you %{count} " \
286
- "questions.\n",
287
- ) % {
288
- count: interview.num_questions,
289
- }
290
- else
291
- puts _(
292
- "\nWe need to create the metadata.json file for this module, so we\'re going to ask you %{count} " \
293
- "questions.\n",
294
- ) % {
295
- count: interview.num_questions,
296
- }
297
- end
298
-
299
- puts _(
300
- 'If the question is not applicable to this module, accept the default option ' \
301
- 'shown after each question. You can modify any answers at any time by manually updating ' \
302
- "the metadata.json file.\n\n",
303
- )
304
-
305
- answers = interview.run
306
-
307
- if answers.nil?
308
- PDK.logger.info _('No answers given, interview cancelled.')
309
- exit 0
310
- end
311
-
312
- unless answers['forge_username'].nil?
313
- opts[:username] = answers['forge_username']
314
-
315
- unless answers['module_name'].nil?
316
- opts[:module_name] = answers['module_name']
317
-
318
- answers.delete('module_name')
319
- end
320
-
321
- answers['name'] = "#{opts[:username]}-" + (opts[:module_name])
322
- answers.delete('forge_username')
323
- end
324
-
325
- answers['license'] = opts[:license] if opts.key?(:license)
326
- answers['operatingsystem_support'].flatten! if answers.key?('operatingsystem_support')
327
-
328
- metadata.update!(answers)
329
-
330
- if opts[:prompt].nil? || opts[:prompt]
331
- require 'pdk/cli/util'
332
-
333
- continue = PDK::CLI::Util.prompt_for_yes(
334
- _('Metadata will be generated based on this information, continue?'),
335
- prompt: prompt,
336
- cancel_message: _('Interview cancelled; exiting.'),
337
- )
338
-
339
- unless continue
340
- PDK.logger.info _('Process cancelled; exiting.')
341
- exit 0
342
- end
343
- end
344
-
345
- require 'pdk/answer_file'
346
- PDK.config.set(%w[user module_defaults forge_username], opts[:username]) unless opts[:username].nil?
347
- PDK.config.set(%w[user module_defaults author], answers['author']) unless answers['author'].nil?
348
- PDK.config.set(%w[user module_defaults license], answers['license']) unless answers['license'].nil?
349
- end
350
- end
351
- end
352
- end
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Generate
5
+ class Module
6
+ def self.validate_options(opts)
7
+ require 'pdk/cli/util/option_validator'
8
+
9
+ unless PDK::CLI::Util::OptionValidator.valid_module_name?(opts[:module_name])
10
+ error_msg = _(
11
+ "'%{module_name}' is not a valid module name.\n" \
12
+ 'Module names must begin with a lowercase letter and can only include lowercase letters, digits, and underscores.',
13
+ ) % { module_name: opts[:module_name] }
14
+ raise PDK::CLI::ExitWithError, error_msg
15
+ end
16
+
17
+ target_dir = PDK::Util::Filesystem.expand_path(opts[:target_dir])
18
+ raise PDK::CLI::ExitWithError, _("The destination directory '%{dir}' already exists") % { dir: target_dir } if PDK::Util::Filesystem.exist?(target_dir)
19
+ end
20
+
21
+ def self.invoke(opts = {})
22
+ require 'pdk/util'
23
+ require 'pdk/util/template_uri'
24
+ require 'pathname'
25
+
26
+ validate_options(opts) unless opts[:module_name].nil?
27
+
28
+ metadata = prepare_metadata(opts)
29
+
30
+ target_dir = PDK::Util::Filesystem.expand_path(opts[:target_dir] || opts[:module_name])
31
+ parent_dir = File.dirname(target_dir)
32
+
33
+ begin
34
+ test_file = File.join(parent_dir, '.pdk-test-writable')
35
+ PDK::Util::Filesystem.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.')
36
+ PDK::Util::Filesystem.rm_f(test_file)
37
+ rescue Errno::EACCES
38
+ raise PDK::CLI::FatalError, _("You do not have permission to write to '%{parent_dir}'") % {
39
+ parent_dir: parent_dir,
40
+ }
41
+ end
42
+
43
+ temp_target_dir = PDK::Util.make_tmpdir_name('pdk-module-target')
44
+
45
+ prepare_module_directory(temp_target_dir)
46
+
47
+ template_uri = PDK::Util::TemplateURI.new(opts)
48
+
49
+ if template_uri.default? && template_uri.default_ref?
50
+ PDK.logger.info _('Using the default template-url and template-ref.')
51
+ else
52
+ PDK.logger.info _(
53
+ "Using the %{method} template-url and template-ref '%{template_uri}'." % {
54
+ method: opts.key?(:'template-url') ? _('specified') : _('saved'),
55
+ template_uri: template_uri.metadata_format,
56
+ },
57
+ )
58
+ end
59
+
60
+ begin
61
+ context = PDK::Context::None.new(temp_target_dir)
62
+ PDK::Template.with(template_uri, context) do |template_dir|
63
+ template_dir.render_new_module(metadata.data['name'], metadata.data) do |relative_file_path, file_content, file_status|
64
+ next if [:delete, :unmanage].include?(file_status)
65
+ file = Pathname.new(temp_target_dir) + relative_file_path
66
+ file.dirname.mkpath
67
+ PDK::Util::Filesystem.write_file(file, file_content)
68
+ end
69
+
70
+ # Add information about the template used to generate the module to the
71
+ # metadata (for a future update command).
72
+ metadata.update!(template_dir.metadata)
73
+
74
+ metadata.write!(File.join(temp_target_dir, 'metadata.json'))
75
+ end
76
+ rescue ArgumentError => e
77
+ raise PDK::CLI::ExitWithError, e
78
+ end
79
+
80
+ # Only update the answers files after metadata has been written.
81
+ require 'pdk/answer_file'
82
+ if template_uri.default? && template_uri.default_ref?
83
+ # If the user specifies our default template url via the command
84
+ # line, remove the saved template-url answer so that the template_uri
85
+ # resolution can find new default URLs in the future.
86
+ PDK.config.set(%w[user module_defaults template-url], nil) if opts.key?(:'template-url')
87
+ else
88
+ # Save the template-url answers if the module was generated using a
89
+ # template/reference other than ours.
90
+ PDK.config.set(%w[user module_defaults template-url], template_uri.metadata_format)
91
+ end
92
+
93
+ begin
94
+ if PDK::Util::Filesystem.mv(temp_target_dir, target_dir)
95
+ unless opts[:'skip-bundle-install']
96
+ Dir.chdir(target_dir) do
97
+ require 'pdk/util/bundler'
98
+ PDK::Util::Bundler.ensure_bundle!
99
+ end
100
+ end
101
+
102
+ PDK.logger.info _("Module '%{name}' generated at path '%{path}'.") % {
103
+ name: opts[:module_name],
104
+ path: target_dir,
105
+ }
106
+ PDK.logger.info _(
107
+ "In your module directory, add classes with the 'pdk new class' command.",
108
+ )
109
+ end
110
+ rescue Errno::EACCES => e
111
+ raise PDK::CLI::FatalError, _("Failed to move '%{source}' to '%{target}': %{message}") % {
112
+ source: temp_target_dir,
113
+ target: target_dir,
114
+ message: e.message,
115
+ }
116
+ end
117
+ end
118
+
119
+ def self.username_from_login
120
+ require 'etc'
121
+
122
+ login = Etc.getlogin || ''
123
+ login_clean = login.downcase.gsub(%r{[^0-9a-z]}i, '')
124
+ login_clean = 'username' if login_clean.empty?
125
+
126
+ if login_clean != login
127
+ PDK.logger.debug _('Your username is not a valid Forge username. Proceeding with the username %{username}. You can fix this later in metadata.json.') % {
128
+ username: login_clean,
129
+ }
130
+ end
131
+
132
+ login_clean
133
+ end
134
+
135
+ def self.prepare_metadata(opts = {})
136
+ require 'pdk/answer_file'
137
+ require 'pdk/module/metadata'
138
+
139
+ opts[:username] = (opts[:username] || PDK.config.get_within_scopes('module_defaults.forge_username') || username_from_login).downcase
140
+
141
+ defaults = PDK::Module::Metadata::DEFAULTS.dup
142
+
143
+ defaults['name'] = "#{opts[:username]}-#{opts[:module_name]}" unless opts[:module_name].nil?
144
+ PDK.config.with_scoped_value('module_defaults.author') { |val| defaults['author'] = val }
145
+ PDK.config.with_scoped_value('module_defaults.license') { |val| defaults['license'] = val }
146
+ defaults['license'] = opts[:license] if opts.key?(:license)
147
+
148
+ metadata = PDK::Module::Metadata.new(defaults)
149
+ module_interview(metadata, opts) unless opts[:'skip-interview']
150
+
151
+ metadata
152
+ end
153
+
154
+ def self.prepare_module_directory(target_dir)
155
+ [
156
+ File.join(target_dir, 'examples'),
157
+ File.join(target_dir, 'files'),
158
+ File.join(target_dir, 'manifests'),
159
+ File.join(target_dir, 'templates'),
160
+ File.join(target_dir, 'tasks'),
161
+ ].each do |dir|
162
+ begin
163
+ PDK::Util::Filesystem.mkdir_p(dir)
164
+ rescue SystemCallError => e
165
+ raise PDK::CLI::FatalError, _("Unable to create directory '%{dir}': %{message}") % {
166
+ dir: dir,
167
+ message: e.message,
168
+ }
169
+ end
170
+ end
171
+ end
172
+
173
+ def self.module_interview(metadata, opts = {})
174
+ require 'pdk/module/metadata'
175
+ require 'pdk/cli/util/interview'
176
+
177
+ questions = [
178
+ {
179
+ name: 'module_name',
180
+ question: _('If you have a name for your module, add it here.'),
181
+ help: _('This is the name that will be associated with your module, it should be relevant to the modules content.'),
182
+ required: true,
183
+ validate_pattern: %r{\A[a-z][a-z0-9_]*\Z}i,
184
+ validate_message: _('Module names must begin with a lowercase letter and can only include lowercase letters, numbers, and underscores.'),
185
+ },
186
+ {
187
+ name: 'forge_username',
188
+ question: _('If you have a Puppet Forge username, add it here.'),
189
+ help: _('We can use this to upload your module to the Forge when it\'s complete.'),
190
+ required: true,
191
+ validate_pattern: %r{\A[a-z0-9]+\Z}i,
192
+ validate_message: _('Forge usernames can only contain lowercase letters and numbers'),
193
+ default: opts[:username],
194
+ },
195
+ {
196
+ name: 'version',
197
+ question: _('What version is this module?'),
198
+ help: _('Puppet uses Semantic Versioning (semver.org) to version modules.'),
199
+ required: true,
200
+ validate_pattern: %r{\A[0-9]+\.[0-9]+\.[0-9]+},
201
+ validate_message: _('Semantic Version numbers must be in the form MAJOR.MINOR.PATCH'),
202
+ default: metadata.data['version'],
203
+ forge_only: true,
204
+ },
205
+ {
206
+ name: 'author',
207
+ question: _('Who wrote this module?'),
208
+ help: _('This is used to credit the module\'s author.'),
209
+ required: true,
210
+ default: metadata.data['author'],
211
+ },
212
+ {
213
+ name: 'license',
214
+ question: _('What license does this module code fall under?'),
215
+ help: _('This should be an identifier from https://spdx.org/licenses/. Common values are "Apache-2.0", "MIT", or "proprietary".'),
216
+ required: true,
217
+ default: metadata.data['license'],
218
+ },
219
+ {
220
+ name: 'operatingsystem_support',
221
+ question: _('What operating systems does this module support?'),
222
+ help: _('Use the up and down keys to move between the choices, space to select and enter to continue.'),
223
+ required: true,
224
+ type: :multi_select,
225
+ choices: PDK::Module::Metadata::OPERATING_SYSTEMS,
226
+ default: PDK::Module::Metadata::DEFAULT_OPERATING_SYSTEMS.map do |os_name|
227
+ # tty-prompt uses a 1-index
228
+ PDK::Module::Metadata::OPERATING_SYSTEMS.keys.index(os_name) + 1
229
+ end,
230
+ },
231
+ {
232
+ name: 'summary',
233
+ question: _('Summarize the purpose of this module in a single sentence.'),
234
+ help: _('This helps other Puppet users understand what the module does.'),
235
+ required: true,
236
+ default: metadata.data['summary'],
237
+ forge_only: true,
238
+ },
239
+ {
240
+ name: 'source',
241
+ question: _('If there is a source code repository for this module, enter the URL here.'),
242
+ help: _('Skip this if no repository exists yet. You can update this later in the metadata.json.'),
243
+ required: true,
244
+ default: metadata.data['source'],
245
+ forge_only: true,
246
+ },
247
+ {
248
+ name: 'project_page',
249
+ question: _('If there is a URL where others can learn more about this module, enter it here.'),
250
+ help: _('Optional. You can update this later in the metadata.json.'),
251
+ default: metadata.data['project_page'],
252
+ forge_only: true,
253
+ },
254
+ {
255
+ name: 'issues_url',
256
+ question: _('If there is a public issue tracker for this module, enter its URL here.'),
257
+ help: _('Optional. You can update this later in the metadata.json.'),
258
+ default: metadata.data['issues_url'],
259
+ forge_only: true,
260
+ },
261
+ ]
262
+
263
+ prompt = TTY::Prompt.new(help_color: :cyan)
264
+
265
+ interview = PDK::CLI::Util::Interview.new(prompt)
266
+
267
+ if opts[:only_ask]
268
+ questions.reject! do |question|
269
+ if %w[module_name forge_username].include?(question[:name])
270
+ metadata.data['name'] && metadata.data['name'] =~ %r{\A[a-z0-9]+-[a-z][a-z0-9_]*\Z}i
271
+ else
272
+ !opts[:only_ask].include?(question[:name])
273
+ end
274
+ end
275
+ else
276
+ questions.reject! { |q| q[:name] == 'module_name' } if opts.key?(:module_name)
277
+ questions.reject! { |q| q[:name] == 'license' } if opts.key?(:license)
278
+ questions.reject! { |q| q[:forge_only] } unless opts[:'full-interview']
279
+ end
280
+
281
+ interview.add_questions(questions)
282
+
283
+ if PDK::Util::Filesystem.file?('metadata.json')
284
+ puts _(
285
+ "\nWe need to update the metadata.json file for this module, so we\'re going to ask you %{count} " \
286
+ "questions.\n",
287
+ ) % {
288
+ count: interview.num_questions,
289
+ }
290
+ else
291
+ puts _(
292
+ "\nWe need to create the metadata.json file for this module, so we\'re going to ask you %{count} " \
293
+ "questions.\n",
294
+ ) % {
295
+ count: interview.num_questions,
296
+ }
297
+ end
298
+
299
+ puts _(
300
+ 'If the question is not applicable to this module, accept the default option ' \
301
+ 'shown after each question. You can modify any answers at any time by manually updating ' \
302
+ "the metadata.json file.\n\n",
303
+ )
304
+
305
+ answers = interview.run
306
+
307
+ if answers.nil?
308
+ PDK.logger.info _('No answers given, interview cancelled.')
309
+ exit 0
310
+ end
311
+
312
+ unless answers['forge_username'].nil?
313
+ opts[:username] = answers['forge_username']
314
+
315
+ unless answers['module_name'].nil?
316
+ opts[:module_name] = answers['module_name']
317
+
318
+ answers.delete('module_name')
319
+ end
320
+
321
+ answers['name'] = "#{opts[:username]}-" + (opts[:module_name])
322
+ answers.delete('forge_username')
323
+ end
324
+
325
+ answers['license'] = opts[:license] if opts.key?(:license)
326
+ answers['operatingsystem_support'].flatten! if answers.key?('operatingsystem_support')
327
+
328
+ metadata.update!(answers)
329
+
330
+ if opts[:prompt].nil? || opts[:prompt]
331
+ require 'pdk/cli/util'
332
+
333
+ continue = PDK::CLI::Util.prompt_for_yes(
334
+ _('Metadata will be generated based on this information, continue?'),
335
+ prompt: prompt,
336
+ cancel_message: _('Interview cancelled; exiting.'),
337
+ )
338
+
339
+ unless continue
340
+ PDK.logger.info _('Process cancelled; exiting.')
341
+ exit 0
342
+ end
343
+ end
344
+
345
+ require 'pdk/answer_file'
346
+ PDK.config.set(%w[user module_defaults forge_username], opts[:username]) unless opts[:username].nil?
347
+ PDK.config.set(%w[user module_defaults author], answers['author']) unless answers['author'].nil?
348
+ PDK.config.set(%w[user module_defaults license], answers['license']) unless answers['license'].nil?
349
+ end
350
+ end
351
+ end
352
+ end