pdk 0.3.0 → 0.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5501e7effa501cd2ba488735cc9abb8257ffef6a
4
- data.tar.gz: e4f7a88496280db89b0ee46a42a7b8feacc24594
3
+ metadata.gz: eaa8d8a0b82102681c9b61fe39ba2222cd252a3a
4
+ data.tar.gz: 586bc04ec9a42581020f88a03d5ed58f01b52d62
5
5
  SHA512:
6
- metadata.gz: 736c543069aa1b1d3eb79c8de901e91cf2491328fc3104f7bd660c37cafd88712583d471cbd2f4afd37b9c49890bfcb60c5b2857121f3cabb81841662fa53557
7
- data.tar.gz: 5b5b03c6ac83fca4d730cba0db0277441d9bb062ed5f13ebecf24836d237db11856e6811f276ec2dcfafe28fd5c86a009958ef9e894021d14ba8a1e4d0a15af8
6
+ metadata.gz: 33b02d12161649fd01c1b5e9c259646cfff45e0b0ffd4625ac28caea26aa995ce0346866eb81042c29f900d8dd99a5cb1f71d5347416da332a0376cf9f6ed166
7
+ data.tar.gz: 9f17d85f76f08d2feb31a9b6e5c2f8c7c39240be48f6a9c51f06cf3f441dddaf239b6da10d45ae075ce38b15d356c3a9572b9305691f3d069e4af5d83fb2576a
@@ -3,6 +3,19 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
5
 
6
+ ## [v0.4.0](https://github.com/puppetlabs/pdk/tree/v0.4.0) (2017-07-14)
7
+ [Full Changelog](https://github.com/puppetlabs/pdk/compare/v0.3.0...v0.4.0)
8
+
9
+ **Implemented enhancements:**
10
+
11
+ - \(SDK-299\) Check metadata.json syntax before linting [\#133](https://github.com/puppetlabs/pdk/pull/133) ([rodjek](https://github.com/rodjek))
12
+ - \(SDK-305\) Answer file to cache module interview answers, template-url etc [\#132](https://github.com/puppetlabs/pdk/pull/132) ([rodjek](https://github.com/rodjek))
13
+ - \(SDK-296\) Allow target selection for the metadata validator [\#124](https://github.com/puppetlabs/pdk/pull/124) ([rodjek](https://github.com/rodjek))
14
+
15
+ **Fixed bugs:**
16
+
17
+ - \(SDK-298\) Handle exception raised when an invalid report format is specified on the CLI [\#125](https://github.com/puppetlabs/pdk/pull/125) ([rodjek](https://github.com/rodjek))
18
+
6
19
  ## [v0.3.0](https://github.com/puppetlabs/pdk/tree/v0.3.0) (2017-06-29)
7
20
  [Full Changelog](https://github.com/puppetlabs/pdk/compare/v0.2.0...v0.3.0)
8
21
 
data/README.md CHANGED
@@ -25,21 +25,7 @@ To get started, generate a new module from the default template.
25
25
  pdk new module my_module
26
26
  ```
27
27
 
28
- This generates the basic components of a new module and initializes Git for you. The `pdk new module` command sets some default values based on your environment. Check the `metadata.json` to make sure that these values are correct. The new module now contains all the infrastructure to use the other capabilities of `pdk`.
29
-
30
- ### Add a new resource provider
31
-
32
- If you need to manage a specific resource that is not covered by either Puppet's basic resource types or an existing module, create a new resource provider.
33
-
34
- 1. From within an existing module, run `pdk add provider`, specifying the new provider name as well as any attributes along with their data types.
35
-
36
- For example:
37
-
38
- ```
39
- pdk add provider new_provider String:content 'Enum[absent, present]:ensure'
40
- ```
41
-
42
- This creates all the files required to define a resource type, its provider, and the associated basic tests. In this example, the resource type has an `ensure` property with the expected values, and a `String` property named `content`. If your types use Bash special characters, such as 'Enum[absent, present]:ensure' above, you must quote to avoid issues with the shell.
28
+ This generates the basic components of a new module. The `pdk new module` command sets some default metadata values based on your environment. Check the `metadata.json` to make sure that these values are correct. The new module now contains all the infrastructure to use the other capabilities of `pdk`.
43
29
 
44
30
  ### Running validations
45
31
 
@@ -299,6 +285,31 @@ bundle binstubs pdk --path ~/bin
299
285
 
300
286
  Bug reports and pull requests are welcome on GitHub at https://github.com/puppetlabs/pdk.
301
287
 
288
+ ### Running tests
289
+
290
+ pdk has three testing rake tasks
291
+
292
+ #### spec
293
+
294
+ Run unit tests.
295
+
296
+ #### acceptance:local
297
+
298
+ Run acceptance tests on the current pdk code. These tests are executed on commits and pull requests to this repo using both travis and appveyor.
299
+
300
+ #### acceptance:package
301
+
302
+ Run acceptance tests against a package install. This task is for Puppet's packaging CI, and contributors outside of Puppet, Inc. don't need to worry about executing it. It uses [beaker](https://github.com/puppetlabs/beaker) to provision a VM, fetch and install a pdk installation package, and then run the acceptance tests on that VM.
303
+ It requires some environment variables to be set in order to specify what beaker will set up:
304
+
305
+ Environment Variable | Usage
306
+ ---------------------|------
307
+ **SHA** | The SHA or tag of a package build i.e. the folder name on the build server that packages will be found in.
308
+ **TEST_TARGET** | A beaker-hostgenerator string for the OS of the VM you want to test on e.g. _redhat7-64workstation._ or _windows2012r2-64workstation._ (The period character after workstation is required by beaker-hostgenerator).
309
+ **BUILD_SERVER** | (Only required if the tests will run on a Windows VM). The hostname of the build server that hosts packages. A Puppet JIRA ticket ([BKR-1109](https://tickets.puppetlabs.com/browse/BKR-1109)) has been filed to update beaker so this would never be required.
310
+
311
+ On completion of this testing task, the results from the VM will be available in a folder named _archive_.
312
+
302
313
  ### Release Process
303
314
 
304
315
  1. Bump the version in `lib/pdk/version.rb`.
@@ -307,4 +318,4 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/puppet
307
318
  1. Commit and PR the changes.
308
319
  1. When the PR is merged, get a clean checkout of the merged commit, and run `bundle exec rake release[upstream]` (where "upstream" is your local name of the puppetlabs remote)
309
320
  1. Profit!
310
- 1. Update `lib/pdk/version.rb` with `x.y.z-pre`, commit, and PR to prepare for next release.
321
+ 1. Update `lib/pdk/version.rb` with `x.y.z.pre` version bump, commit, and PR to prepare for next release.
data/lib/pdk.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'pdk/answer_file'
1
2
  require 'pdk/generate'
2
3
  require 'pdk/i18n'
3
4
  require 'pdk/logger'
@@ -0,0 +1,120 @@
1
+ require 'json'
2
+
3
+ module PDK
4
+ # Singleton accessor to the current answer file being used by the PDK.
5
+ #
6
+ # @return [PDK::AnswerFile] The AnswerFile instance currently being used by
7
+ # the PDK.
8
+ def self.answers
9
+ @answer_file ||= PDK::AnswerFile.new
10
+ end
11
+
12
+ # Specify the path to a custom answer file that the PDK should use.
13
+ #
14
+ # @param path [String] A path on disk to the file where the PDK should store
15
+ # answers to interactive questions.
16
+ def self.answer_file=(value)
17
+ @answer_file = PDK::AnswerFile.new(value)
18
+ end
19
+
20
+ class AnswerFile
21
+ attr_reader :answers
22
+ attr_reader :answer_file_path
23
+
24
+ # Initialises the AnswerFile object, which stores the responses to certain
25
+ # interactive questions.
26
+ #
27
+ # @param answer_file_path [String, nil] The path on disk to the file where
28
+ # the answers will be stored and read from. If not specified (or `nil`),
29
+ # the default path will be used (see #default_answer_file_path).
30
+ #
31
+ # @raise (see #read_from_disk)
32
+ def initialize(answer_file_path = nil)
33
+ @answer_file_path = answer_file_path || default_answer_file_path
34
+ @answers = read_from_disk
35
+ end
36
+
37
+ # Retrieve the stored answer to a question.
38
+ #
39
+ # @param question [String] The question name/identifying string.
40
+ #
41
+ # @return [Object] The answer to the question, or `nil` if no answer found.
42
+ def [](question)
43
+ answers[question]
44
+ end
45
+
46
+ # Update the stored answers in memory and then save them to disk.
47
+ #
48
+ # @param new_answers [Hash{String => Object}] The new questions and answers
49
+ # to be merged into the existing answers.
50
+ #
51
+ # @raise [PDK::CLI::FatalError] if the new answers are not provided as
52
+ # a Hash.
53
+ # @raise (see #save_to_disk)
54
+ def update!(new_answers = {})
55
+ unless new_answers.is_a?(Hash)
56
+ raise PDK::CLI::FatalError, _('Answer file can only be updated with a Hash')
57
+ end
58
+
59
+ answers.merge!(new_answers)
60
+
61
+ save_to_disk
62
+ end
63
+
64
+ private
65
+
66
+ # Determine the default path to the answer file.
67
+ #
68
+ # @return [String] The path on disk to the default answer file.
69
+ def default_answer_file_path
70
+ File.join(PDK::Util.cachedir, 'answers.json')
71
+ end
72
+
73
+ # Read existing answers into memory from the answer file on disk.
74
+ #
75
+ # @raise [PDK::CLI::FatalError] If the answer file exists but can not be
76
+ # read.
77
+ #
78
+ # @return [Hash{String => Object}] The existing questions and answers.
79
+ def read_from_disk
80
+ return {} if !File.file?(answer_file_path) || File.zero?(answer_file_path)
81
+
82
+ unless File.readable?(answer_file_path)
83
+ raise PDK::CLI::FatalError, _("Unable to open '%{file}' for reading") % {
84
+ file: answer_file_path,
85
+ }
86
+ end
87
+
88
+ answers = JSON.parse(File.read(answer_file_path))
89
+ if answers.is_a?(Hash)
90
+ answers
91
+ else
92
+ PDK.logger.warn _("Answer file '%{path}' did not contain a valid set of answers, recreating it") % {
93
+ path: answer_file_path,
94
+ }
95
+ {}
96
+ end
97
+ rescue JSON::JSONError
98
+ PDK.logger.warn _("Answer file '%{path}' did not contain valid JSON, recreating it") % {
99
+ path: answer_file_path,
100
+ }
101
+ {}
102
+ end
103
+
104
+ # Save the in memory answer set to the answer file on disk.
105
+ #
106
+ # @raise [PDK::CLI::FatalError] if the answer file can not be written to.
107
+ def save_to_disk
108
+ FileUtils.mkdir_p(File.dirname(answer_file_path))
109
+
110
+ File.open(answer_file_path, 'w') do |answer_file|
111
+ answer_file.puts JSON.pretty_generate(answers)
112
+ end
113
+ rescue SystemCallError, IOError => e
114
+ raise PDK::CLI::FatalError, _("Unable to write '%{file}': %{msg}") % {
115
+ file: answer_file_path,
116
+ msg: e.message,
117
+ }
118
+ end
119
+ end
120
+ end
@@ -60,20 +60,16 @@ module PDK::CLI
60
60
  ) % { available_formats: PDK::Report.formats.join("', '") }
61
61
 
62
62
  option :f, :format, format_desc, argument: :required, multiple: true do |values|
63
- values.compact.each do |v|
64
- if v.include?(':')
65
- format = v.split(':', 2).first
66
-
67
- Util::OptionValidator.enum(format, PDK::Report.formats)
68
- else
69
- Util::OptionValidator.enum(v, PDK::Report.formats)
70
- end
71
- end
63
+ PDK::CLI::Util::OptionNormalizer.report_formats(values.compact)
72
64
  end
73
65
 
74
66
  flag :d, :debug, _('Enable debug output.') do |_, _|
75
67
  PDK.logger.enable_debug_output
76
68
  end
69
+
70
+ option nil, 'answer-file', _('Path to an answer file'), argument: :required, hidden: true do |value|
71
+ PDK.answer_file = value
72
+ end
77
73
  end
78
74
 
79
75
  require 'pdk/cli/new'
@@ -3,6 +3,8 @@ require 'childprocess'
3
3
  require 'tempfile'
4
4
  require 'tty-spinner'
5
5
 
6
+ require 'pdk/util'
7
+
6
8
  module PDK
7
9
  module CLI
8
10
  module Exec
@@ -10,12 +12,8 @@ module PDK
10
12
  Command.new(*cmd).execute!
11
13
  end
12
14
 
13
- def self.pdk_basedir
14
- @pdk_basedir ||= Gem.win_platform? ? 'C:/Program Files/Puppet Labs/DevelopmentKit' : '/opt/puppetlabs/sdk'
15
- end
16
-
17
15
  def self.git_bindir
18
- @git_dir ||= File.join(pdk_basedir, 'private', 'git', Gem.win_platform? ? 'cmd' : 'bin')
16
+ @git_dir ||= File.join('private', 'git', Gem.win_platform? ? 'cmd' : 'bin')
19
17
  end
20
18
 
21
19
  def self.git(*args)
@@ -27,24 +25,29 @@ module PDK
27
25
 
28
26
  def self.bundle(*args)
29
27
  bundle_bin = Gem.win_platform? ? 'bundle.bat' : 'bundle'
30
- vendored_bin_path = File.join(pdk_basedir, 'private', 'ruby', '2.1.9', 'bin', bundle_bin)
28
+ vendored_bin_path = File.join('private', 'ruby', '2.1.9', 'bin', bundle_bin)
31
29
 
32
30
  execute(try_vendored_bin(vendored_bin_path, bundle_bin), *args)
33
31
  end
34
32
 
35
33
  def self.bundle_bin
36
34
  bundle_bin = Gem.win_platform? ? 'bundle.bat' : 'bundle'
37
- vendored_bin_path = File.join(pdk_basedir, 'private', 'ruby', '2.1.9', 'bin', bundle_bin)
35
+ vendored_bin_path = File.join('private', 'ruby', '2.1.9', 'bin', bundle_bin)
38
36
 
39
37
  try_vendored_bin(vendored_bin_path, bundle_bin)
40
38
  end
41
39
 
42
40
  def self.try_vendored_bin(vendored_bin_path, fallback)
43
- if File.exist?(vendored_bin_path)
44
- PDK.logger.debug(_("Using '%{vendored_bin_path}'") % { fallback: fallback, vendored_bin_path: vendored_bin_path })
45
- vendored_bin_path
41
+ unless PDK::Util.package_install?
42
+ PDK.logger.debug(_("PDK package installation not found, trying '%{fallback}' from the system PATH instead") % { fallback: fallback })
43
+ return fallback
44
+ end
45
+
46
+ if File.exist?(File.join(PDK::Util.pdk_package_basedir, vendored_bin_path))
47
+ PDK.logger.debug(_("Using '%{vendored_bin_path}' from PDK package") % { vendored_bin_path: vendored_bin_path })
48
+ File.join(PDK::Util.pdk_package_basedir, vendored_bin_path)
46
49
  else
47
- PDK.logger.debug(_("Trying '%{fallback}' from the system PATH, instead of '%{vendored_bin_path}'") % { fallback: fallback, vendored_bin_path: vendored_bin_path })
50
+ PDK.logger.debug(_("Could not find '%{vendored_bin_path}' in PDK package, trying '%{fallback}' from the system PATH instead") % { fallback: fallback, vendored_bin_path: vendored_bin_path })
48
51
  fallback
49
52
  end
50
53
  end
@@ -101,9 +104,17 @@ module PDK
101
104
  end
102
105
 
103
106
  if context == :module
107
+ # Subprocesses use their own set of gems which are managed by pdk or installed with the package.
108
+ @process.environment['GEM_HOME'] = File.join(PDK::Util.cachedir, 'ruby', RbConfig::CONFIG['ruby_version'])
109
+
110
+ if PDK::Util.gem_install?
111
+ # This allows the subprocess to find the 'bundler' gem, which isn't in the cachedir above for gem installs.
112
+ # bundler_gem_path = File.absolute_path(File.join(Gem.loaded_specs['bundler'].gem_dir, '..', '..', '..', '..', '..'))
113
+ bundler_gem_path = File.absolute_path(File.join(`bundle show bundler`, '..', '..'))
114
+ @process.environment['GEM_PATH'] = bundler_gem_path
115
+ end
116
+
104
117
  # TODO: we should probably more carefully manage PATH and maybe other things too
105
- @process.environment['GEM_HOME'] = File.join(PDK::Util.cachedir, 'bundler', 'ruby', RbConfig::CONFIG['ruby_version'])
106
- @process.environment['GEM_PATH'] = pdk_gem_path
107
118
 
108
119
  mod_root = PDK::Util.module_root
109
120
 
@@ -168,21 +179,6 @@ module PDK
168
179
  @process.wait
169
180
  end
170
181
  end
171
-
172
- def pdk_gem_path
173
- @pdk_gem_path ||= find_pdk_gem_path
174
- end
175
-
176
- def find_pdk_gem_path
177
- package_gem_path = File.join(PDK::CLI::Exec.pdk_basedir, 'private', 'ruby', RUBY_VERSION, 'lib', 'ruby', 'gems', RbConfig::CONFIG['ruby_version'])
178
-
179
- if File.directory?(package_gem_path)
180
- package_gem_path
181
- else
182
- # FIXME: calculate this more reliably
183
- File.absolute_path(File.join(`bundle show bundler`, '..', '..'))
184
- end
185
- end
186
182
  end
187
183
 
188
184
  # This is a placeholder until we integrate ansicon into Windows packaging
@@ -27,9 +27,9 @@ module PDK
27
27
  num_questions = @questions.count
28
28
  @questions.each do |question_name, question|
29
29
  @name = question_name
30
- puts pastel.bold(_('[Q %{current_number}/%{questions_total}]') % { current_number: i, questions_total: num_questions })
31
- puts pastel.bold(question[:question])
32
- puts question[:help]
30
+ @prompt.print pastel.bold(_('[Q %{current_number}/%{questions_total}]') % { current_number: i, questions_total: num_questions })
31
+ @prompt.print pastel.bold(question[:question])
32
+ @prompt.print question[:help]
33
33
  ask(_('-->')) do |q|
34
34
  q.required(question.fetch(:required, false))
35
35
 
@@ -40,7 +40,7 @@ module PDK
40
40
  q.default(question[:default]) if question.key?(:default)
41
41
  end
42
42
  i += 1
43
- puts ''
43
+ @prompt.print ''
44
44
  end
45
45
  @answers
46
46
  rescue TTY::Prompt::Reader::InputInterrupt
@@ -28,7 +28,7 @@ module PDK
28
28
 
29
29
  begin
30
30
  OptionValidator.enum(format, PDK::Report.formats)
31
- rescue
31
+ rescue ArgumentError
32
32
  raise PDK::CLI::FatalError, _("'%{name}' is not a valid report format (%{valid})") % {
33
33
  name: format,
34
34
  valid: PDK::Report.formats.join(', '),
@@ -40,6 +40,8 @@ module PDK
40
40
  target = $stdout
41
41
  when 'stderr'
42
42
  target = $stderr
43
+ when nil
44
+ target = PDK::Report.default_target
43
45
  end
44
46
 
45
47
  { method: "write_#{format}".to_sym, target: target }
@@ -11,7 +11,7 @@ module PDK
11
11
  invalid_entries = vals.reject { |e| valid_entries.include?(e) }
12
12
 
13
13
  unless invalid_entries.empty?
14
- raise _('Error: the following values are invalid: %{invalid_entries}') % { invalid_entries: invalid_entries }
14
+ raise ArgumentError, _('Error: the following values are invalid: %{invalid_entries}') % { invalid_entries: invalid_entries }
15
15
  end
16
16
 
17
17
  val
@@ -30,7 +30,7 @@ module PDK
30
30
 
31
31
  prepare_module_directory(temp_target_dir)
32
32
 
33
- template_url = opts.fetch(:'template-url', DEFAULT_TEMPLATE)
33
+ template_url = opts.fetch(:'template-url', PDK.answers['template-url'] || DEFAULT_TEMPLATE)
34
34
 
35
35
  PDK::Module::TemplateDir.new(template_url) do |templates|
36
36
  templates.render do |file_path, file_content|
@@ -48,16 +48,28 @@ module PDK
48
48
  end
49
49
  end
50
50
 
51
+ PDK.answers.update!('template-url' => template_url)
52
+
51
53
  FileUtils.mv(temp_target_dir, target_dir)
52
54
  end
53
55
 
54
- def self.prepare_metadata(opts)
55
- username = Etc.getlogin.gsub(%r{[^0-9a-z]}i, '')
56
- username = 'username' if username == ''
57
- if Etc.getlogin != username
58
- PDK.logger.warn(_('Your username is not a valid Forge username, proceeding with the username %{username}' % { username: username }))
56
+ def self.username_from_login
57
+ login = Etc.getlogin
58
+ login_clean = login.gsub(%r{[^0-9a-z]}i, '')
59
+ login_clean = 'username' if login_clean.empty?
60
+
61
+ if login_clean != login
62
+ PDK.logger.warn _('You username is not a valid Forge username, proceeding with the username %{username}') % {
63
+ username: login_clean,
64
+ }
59
65
  end
60
66
 
67
+ login_clean
68
+ end
69
+
70
+ def self.prepare_metadata(opts)
71
+ username = PDK.answers['forge-username'] || username_from_login
72
+
61
73
  defaults = {
62
74
  'name' => "#{username}-#{opts[:name]}",
63
75
  'version' => '0.1.0',
@@ -65,11 +77,13 @@ module PDK
65
77
  { 'name' => 'puppetlabs-stdlib', 'version_requirement' => '>= 4.13.1 < 5.0.0' },
66
78
  ],
67
79
  }
80
+ defaults['author'] = PDK.answers['author'] unless PDK.answers['author'].nil?
81
+ defaults['license'] = PDK.answers['license'] unless PDK.answers['license'].nil?
68
82
  defaults['license'] = opts[:license] if opts.key? :license
69
83
 
70
84
  metadata = PDK::Module::Metadata.new(defaults)
71
85
 
72
- module_interview(metadata, opts) unless opts[:'skip-interview'] # @todo Build way to get info by answers file
86
+ module_interview(metadata, opts) unless opts[:'skip-interview']
73
87
 
74
88
  metadata.update!('pdk-version' => PDK::Util::Version.version_string)
75
89
 
@@ -98,7 +112,7 @@ module PDK
98
112
  required: true,
99
113
  validate_pattern: %r{\A[a-z0-9]+\Z}i,
100
114
  validate_message: _('Forge usernames can only contain lowercase letters and numbers'),
101
- default: metadata.data['author'],
115
+ default: PDK.answers['forge-username'] || metadata.data['author'],
102
116
  },
103
117
  {
104
118
  name: 'version',
@@ -167,11 +181,13 @@ module PDK
167
181
  answers = interview.run
168
182
 
169
183
  if answers.nil?
170
- puts _('Interview cancelled, aborting...')
184
+ PDK.logger.info _('Interview cancelled, not generating the module.')
171
185
  exit 0
172
186
  end
173
187
 
188
+ forge_username = answers['name']
174
189
  answers['name'] = "#{answers['name']}-#{opts[:name]}"
190
+ answers['license'] = opts[:license] if opts.key?(:license)
175
191
  metadata.update!(answers)
176
192
 
177
193
  puts '-' * 40
@@ -181,10 +197,16 @@ module PDK
181
197
  puts '-' * 40
182
198
  puts
183
199
 
184
- unless prompt.yes?(_('About to generate this module; continue?')) # rubocop:disable Style/GuardClause
185
- puts _('Aborting...')
200
+ unless prompt.yes?(_('About to generate this module; continue?'))
201
+ PDK.logger.info _('Module not generated.')
186
202
  exit 0
187
203
  end
204
+
205
+ PDK.answers.update!(
206
+ 'forge-username' => forge_username,
207
+ 'author' => answers['author'],
208
+ 'license' => answers['license'],
209
+ )
188
210
  end
189
211
  end
190
212
  end
@@ -67,7 +67,7 @@ module PDK
67
67
  end
68
68
 
69
69
  def to_json
70
- JSON.pretty_generate(@data)
70
+ JSON.pretty_generate(@data.dup.delete_if { |_key, value| value.nil? })
71
71
  end
72
72
 
73
73
  private
@@ -48,9 +48,6 @@ module PDK
48
48
  # @param target [#write] an IO object that the report will be written to.
49
49
  # Defaults to PDK::Report.default_target.
50
50
  def write_junit(target = self.class.default_target)
51
- # Extra defaulting here, b/c the Class.send method will pass in nil
52
- target ||= self.class.default_target
53
-
54
51
  # Open a File Object for IO if target is a string containing a filename or path
55
52
  target = File.open(target, 'w') if target.is_a? String
56
53
 
@@ -94,9 +91,6 @@ module PDK
94
91
  # @param target [#write] an IO object that the report will be written to.
95
92
  # Defaults to PDK::Report.default_target.
96
93
  def write_text(target = self.class.default_target)
97
- # Extra defaulting here, b/c the Class.send method will pass in nil
98
- target ||= self.class.default_target
99
-
100
94
  # Open a File Object for IO if target is a string containing a filename or path
101
95
  target = File.open(target, 'w') if target.is_a? String
102
96
 
@@ -2,17 +2,20 @@ require 'tmpdir'
2
2
  require 'tempfile'
3
3
  require 'puppet/util/windows'
4
4
 
5
+ require 'pdk/util/version'
6
+
5
7
  module PDK
6
8
  module Util
7
9
  # Searches upwards from current working directory for the given target file.
8
10
  #
9
11
  # @param target [String] Name of file to search for.
12
+ # @param start_dir [String] Directory to start searching from, defaults to Dir.pwd
10
13
  #
11
14
  # @return [String, nil] Fully qualified path to the given target file if found,
12
15
  # nil if the target file could not be found.
13
- def find_upwards(target)
16
+ def find_upwards(target, start_dir = nil)
14
17
  previous = nil
15
- current = File.expand_path(Dir.pwd)
18
+ current = File.expand_path(start_dir || Dir.pwd)
16
19
 
17
20
  until !File.directory?(current) || current == previous
18
21
  filename = File.join(current, target)
@@ -50,17 +53,34 @@ module PDK
50
53
  end
51
54
  module_function :canonical_path
52
55
 
56
+ def package_install?
57
+ !PDK::Util::Version.version_file.nil?
58
+ end
59
+ module_function :package_install?
60
+
61
+ def gem_install?
62
+ !package_install?
63
+ end
64
+ module_function :gem_install?
65
+
66
+ def pdk_package_basedir
67
+ raise PDK::CLI::FatalError, _('Package basedir requested for non-package install') unless package_install?
68
+
69
+ File.dirname(PDK::Util::Version.version_file)
70
+ end
71
+ module_function :pdk_package_basedir
72
+
53
73
  # Returns the fully qualified path to a per-user PDK cachedir.
54
74
  #
55
75
  # @return [String] Fully qualified path to per-user PDK cachedir.
56
76
  def cachedir
57
- basedir = if Gem.win_platform?
58
- ENV['LOCALAPPDATA']
59
- else
60
- Dir.home
61
- end
62
-
63
- File.join(basedir, '.pdk', 'cache')
77
+ if package_install?
78
+ File.join(pdk_package_basedir, 'share', 'cache')
79
+ elsif Gem.win_platform?
80
+ File.join(ENV['LOCALAPPDATA'], 'PDK', 'cache')
81
+ else
82
+ File.join(Dir.home, '.pdk', 'cache')
83
+ end
64
84
  end
65
85
  module_function :cachedir
66
86
 
@@ -62,7 +62,10 @@ module PDK
62
62
  end
63
63
 
64
64
  def installed?
65
- command = bundle_command('check', "--gemfile=#{gemfile}", "--path=#{bundle_cachedir}").tap do |c|
65
+ argv = ['check', "--gemfile=#{gemfile}"]
66
+ argv << "--path=#{bundle_cachedir}" if PDK::Util.gem_install?
67
+
68
+ command = bundle_command(*argv).tap do |c|
66
69
  c.add_spinner(_('Checking for missing Gemfile dependencies'))
67
70
  end
68
71
 
@@ -92,7 +95,10 @@ module PDK
92
95
  end
93
96
 
94
97
  def install!
95
- command = bundle_command('install', "--gemfile=#{gemfile}", "--path=#{bundle_cachedir}").tap do |c|
98
+ argv = ['install', "--gemfile=#{gemfile}"]
99
+ argv << "--path=#{bundle_cachedir}" if PDK::Util.gem_install?
100
+
101
+ command = bundle_command(*argv).tap do |c|
96
102
  c.add_spinner(_('Installing missing Gemfile dependencies'))
97
103
  end
98
104
 
@@ -107,7 +113,10 @@ module PDK
107
113
  end
108
114
 
109
115
  def binstubs!(gems)
110
- command = bundle_command('binstubs', gems.join(' '), '--force')
116
+ binstub_dir = File.join(File.dirname(gemfile), 'bin')
117
+ return true if gems.all? { |gem| File.file?(File.join(binstub_dir, gem)) }
118
+
119
+ command = bundle_command('binstubs', *gems, '--force')
111
120
 
112
121
  result = command.execute!
113
122
 
@@ -137,7 +146,7 @@ module PDK
137
146
  end
138
147
 
139
148
  def bundle_cachedir
140
- @bundle_cachedir ||= File.join(PDK::Util.cachedir, 'bundler')
149
+ @bundle_cachedir ||= File.join(PDK::Util.cachedir)
141
150
  end
142
151
  end
143
152
  end
@@ -14,11 +14,9 @@ module PDK
14
14
  end
15
15
 
16
16
  def self.pkg_sha
17
- version_file = File.join(File.expand_path('../../..', File.dirname(__FILE__)), 'VERSION')
18
-
19
- if File.exist? version_file
17
+ if version_file && File.exist?(version_file)
20
18
  ver = File.read(version_file)
21
- sha = ver.strip.split('.')[-1] unless ver.nil?
19
+ sha = ver.strip.split('.')[5] unless ver.nil?
22
20
  end
23
21
 
24
22
  sha
@@ -29,6 +27,10 @@ module PDK
29
27
 
30
28
  ref_result[:stdout].strip if ref_result[:exit_code].zero?
31
29
  end
30
+
31
+ def self.version_file
32
+ PDK::Util.find_upwards('PDK_VERSION', File.dirname(__FILE__))
33
+ end
32
34
  end
33
35
  end
34
36
  end
@@ -1,11 +1,11 @@
1
- require 'pdk/validators/metadata'
1
+ require 'pdk/validators/metadata_validator'
2
2
  require 'pdk/validators/puppet_validator'
3
3
  require 'pdk/validators/ruby_validator'
4
4
 
5
5
  module PDK
6
6
  module Validate
7
7
  def self.validators
8
- @validators ||= [Metadata, PuppetValidator, RubyValidator].freeze
8
+ @validators ||= [MetadataValidator, PuppetValidator, RubyValidator].freeze
9
9
  end
10
10
  end
11
11
  end
@@ -4,6 +4,14 @@ require 'pdk/cli/exec'
4
4
  module PDK
5
5
  module Validate
6
6
  class BaseValidator
7
+ # Controls how many times the validator is invoked.
8
+ #
9
+ # :once - The validator will be invoked once and passed all the
10
+ # targets.
11
+ # :per_target - The validator will be invoked for each target
12
+ # separately.
13
+ INVOKE_STYLE = :once
14
+
7
15
  def self.cmd_path
8
16
  File.join(PDK::Util.module_root, 'bin', cmd)
9
17
  end
@@ -34,7 +42,7 @@ module PDK
34
42
  targets
35
43
  end
36
44
 
37
- def self.spinner_text
45
+ def self.spinner_text(_targets = nil)
38
46
  _('Invoking %{cmd}') % { cmd: cmd }
39
47
  end
40
48
 
@@ -44,19 +52,31 @@ module PDK
44
52
  return 0 if targets.empty?
45
53
 
46
54
  PDK::Util::Bundler.ensure_binstubs!(cmd)
47
- cmd_argv = parse_options(options, targets).unshift(cmd_path)
48
- cmd_argv.unshift('ruby', '-W0') if Gem.win_platform?
49
55
 
50
- command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
51
- c.context = :module
52
- c.add_spinner(spinner_text)
53
- end
56
+ # If invoking :per_target, split the targets array into an array of
57
+ # single element arrays (one per target). If invoking :once, wrap the
58
+ # targets array in another array. This is so we can loop through the
59
+ # invokes with the same logic, regardless of which invoke style is
60
+ # needed.
61
+ targets = (self::INVOKE_STYLE == :per_target) ? targets.combination(1).to_a : Array[targets]
62
+ exit_codes = []
63
+
64
+ targets.each do |invokation_targets|
65
+ cmd_argv = parse_options(options, invokation_targets).unshift(cmd_path)
66
+ cmd_argv.unshift('ruby', '-W0') if Gem.win_platform?
54
67
 
55
- result = command.execute!
68
+ command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
69
+ c.context = :module
70
+ c.add_spinner(spinner_text(invokation_targets))
71
+ end
72
+
73
+ result = command.execute!
74
+ exit_codes << result[:exit_code]
56
75
 
57
- parse_output(report, result, targets)
76
+ parse_output(report, result, invokation_targets)
77
+ end
58
78
 
59
- result[:exit_code]
79
+ exit_codes.sort.last
60
80
  end
61
81
  end
62
82
  end
@@ -2,24 +2,31 @@ require 'pdk'
2
2
  require 'pdk/cli/exec'
3
3
  require 'pdk/validators/base_validator'
4
4
  require 'pdk/util/bundler'
5
+ require 'pathname'
5
6
 
6
7
  module PDK
7
8
  module Validate
8
- class Metadata < BaseValidator
9
+ class MetadataJSONLint < BaseValidator
10
+ # Validate each metadata file separately, as metadata-json-lint does not
11
+ # support multiple targets.
12
+ INVOKE_STYLE = :per_target
13
+
9
14
  def self.name
10
- 'metadata'
15
+ 'metadata-json-lint'
11
16
  end
12
17
 
13
18
  def self.cmd
14
19
  'metadata-json-lint'
15
20
  end
16
21
 
17
- def self.spinner_text
18
- _('Checking metadata.json')
22
+ def self.spinner_text(targets = nil)
23
+ _('Checking metadata (%{targets})') % {
24
+ targets: targets.map { |t| Pathname.new(t).absolute? ? Pathname.new(t).relative_path_from(Pathname.pwd) : t }.join(' '),
25
+ }
19
26
  end
20
27
 
21
- def self.parse_targets(_options)
22
- [File.join(PDK::Util.module_root, 'metadata.json')]
28
+ def self.pattern
29
+ 'metadata.json'
23
30
  end
24
31
 
25
32
  def self.parse_options(_options, targets)
@@ -28,16 +35,18 @@ module PDK
28
35
  cmd_options.concat(targets)
29
36
  end
30
37
 
31
- def self.parse_output(report, result, _targets)
38
+ def self.parse_output(report, result, targets)
32
39
  begin
33
40
  json_data = JSON.parse(result[:stdout])
34
41
  rescue JSON::ParserError
35
42
  json_data = []
36
43
  end
37
44
 
45
+ raise ArgumentError, 'More that 1 target provided to PDK::Validate::MetadataJSONLint' if targets.count > 1
46
+
38
47
  if json_data.empty?
39
48
  report.add_event(
40
- file: 'metadata.json',
49
+ file: targets.first,
41
50
  source: cmd,
42
51
  state: :passed,
43
52
  severity: :ok,
@@ -52,7 +61,7 @@ module PDK
52
61
  event_type = type[%r{\A(.+?)s?\Z}, 1]
53
62
 
54
63
  report.add_event(
55
- file: 'metadata.json',
64
+ file: targets.first,
56
65
  source: cmd,
57
66
  message: offense['msg'],
58
67
  test: offense['check'],
@@ -0,0 +1,89 @@
1
+ require 'pdk'
2
+ require 'pdk/cli/exec'
3
+ require 'pdk/validators/base_validator'
4
+ require 'pathname'
5
+
6
+ module PDK
7
+ module Validate
8
+ class MetadataSyntax < BaseValidator
9
+ def self.name
10
+ 'metadata-syntax'
11
+ end
12
+
13
+ def self.pattern
14
+ 'metadata.json'
15
+ end
16
+
17
+ def self.invoke(report, options = {})
18
+ targets = parse_targets(options)
19
+
20
+ return 0 if targets.empty?
21
+
22
+ return_val = 0
23
+
24
+ # The pure ruby JSON parser gives much nicer parse error messages than
25
+ # the C extension at the cost of slightly slower parsing. We require it
26
+ # here and restore the C extension at the end of the method (if it was
27
+ # being used).
28
+ require 'json/pure'
29
+ JSON.parser = JSON::Pure::Parser
30
+
31
+ targets.each do |target|
32
+ unless File.file?(target)
33
+ report.add_event(
34
+ file: target,
35
+ source: name,
36
+ state: :failure,
37
+ severity: 'error',
38
+ message: _('not a file'),
39
+ )
40
+ return_val = 1
41
+ next
42
+ end
43
+
44
+ unless File.readable?(target)
45
+ report.add_event(
46
+ file: target,
47
+ source: name,
48
+ state: :failure,
49
+ severity: 'error',
50
+ message: _('could not be read'),
51
+ )
52
+ return_val = 1
53
+ next
54
+ end
55
+
56
+ begin
57
+ JSON.parse(File.read(target))
58
+
59
+ report.add_event(
60
+ file: target,
61
+ source: name,
62
+ state: :passed,
63
+ severity: 'ok',
64
+ )
65
+ rescue JSON::ParserError => e
66
+ # Because the message contains a raw segment of the file, we use
67
+ # String#dump here to unescape any escape characters like newlines.
68
+ # We then strip out the surrounding quotes and the exclaimation
69
+ # point that json_pure likes to put in exception messages.
70
+ sane_message = e.message.dump[%r{\A"(.+?)!?"\Z}, 1]
71
+
72
+ report.add_event(
73
+ file: target,
74
+ source: name,
75
+ state: :failure,
76
+ severity: 'error',
77
+ message: sane_message,
78
+ )
79
+ return_val = 1
80
+ end
81
+ end
82
+
83
+ return_val
84
+ ensure
85
+ JSON.parser = JSON::Ext::Parser if defined?(JSON::Ext::Parser)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,30 @@
1
+ require 'pdk'
2
+ require 'pdk/cli/exec'
3
+ require 'pdk/validators/base_validator'
4
+ require 'pdk/validators/metadata/metadata_json_lint'
5
+ require 'pdk/validators/metadata/metadata_syntax'
6
+
7
+ module PDK
8
+ module Validate
9
+ class MetadataValidator < BaseValidator
10
+ def self.name
11
+ 'metadata'
12
+ end
13
+
14
+ def self.metadata_validators
15
+ [MetadataSyntax, MetadataJSONLint]
16
+ end
17
+
18
+ def self.invoke(report, options = {})
19
+ exit_code = 0
20
+
21
+ metadata_validators.each do |validator|
22
+ exit_code = validator.invoke(report, options)
23
+ break if exit_code != 0
24
+ end
25
+
26
+ exit_code
27
+ end
28
+ end
29
+ end
30
+ end
@@ -18,7 +18,7 @@ module PDK
18
18
  '**/*.pp'
19
19
  end
20
20
 
21
- def self.spinner_text
21
+ def self.spinner_text(_targets = nil)
22
22
  _('Checking Puppet manifest style')
23
23
  end
24
24
 
@@ -32,7 +32,7 @@ module PDK
32
32
 
33
33
  def self.parse_output(report, result, targets)
34
34
  begin
35
- json_data = JSON.parse(result[:stdout])
35
+ json_data = JSON.parse(result[:stdout]).flatten
36
36
  rescue JSON::ParserError
37
37
  json_data = []
38
38
  end
@@ -17,7 +17,7 @@ module PDK
17
17
  '**/**.pp'
18
18
  end
19
19
 
20
- def self.spinner_text
20
+ def self.spinner_text(_targets = nil)
21
21
  _('Checking Puppet manifest syntax')
22
22
  end
23
23
 
@@ -16,7 +16,7 @@ module PDK
16
16
  'rubocop'
17
17
  end
18
18
 
19
- def self.spinner_text
19
+ def self.spinner_text(_targets = nil)
20
20
  _('Checking Ruby code style')
21
21
  end
22
22
 
@@ -31,10 +31,12 @@ module PDK
31
31
  end
32
32
 
33
33
  def self.parse_output(report, result, _targets)
34
+ return if result[:stdout].empty?
35
+
34
36
  begin
35
37
  json_data = JSON.parse(result[:stdout])
36
38
  rescue JSON::ParserError
37
- json_data = []
39
+ json_data = {}
38
40
  end
39
41
 
40
42
  return unless json_data.key?('files')
@@ -1,3 +1,3 @@
1
1
  module PDK
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-29 00:00:00.000000000 Z
11
+ date: 2017-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0.12'
97
+ - !ruby/object:Gem::Dependency
98
+ name: json_pure
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.1.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.1.0
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: deep_merge
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -121,6 +135,7 @@ files:
121
135
  - README.md
122
136
  - exe/pdk
123
137
  - lib/pdk.rb
138
+ - lib/pdk/answer_file.rb
124
139
  - lib/pdk/cli.rb
125
140
  - lib/pdk/cli/errors.rb
126
141
  - lib/pdk/cli/exec.rb
@@ -151,7 +166,9 @@ files:
151
166
  - lib/pdk/util/version.rb
152
167
  - lib/pdk/validate.rb
153
168
  - lib/pdk/validators/base_validator.rb
154
- - lib/pdk/validators/metadata.rb
169
+ - lib/pdk/validators/metadata/metadata_json_lint.rb
170
+ - lib/pdk/validators/metadata/metadata_syntax.rb
171
+ - lib/pdk/validators/metadata_validator.rb
155
172
  - lib/pdk/validators/puppet/puppet_lint.rb
156
173
  - lib/pdk/validators/puppet/puppet_syntax.rb
157
174
  - lib/pdk/validators/puppet_validator.rb
@@ -184,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
201
  version: '0'
185
202
  requirements: []
186
203
  rubyforge_project:
187
- rubygems_version: 2.4.8
204
+ rubygems_version: 2.6.12
188
205
  signing_key:
189
206
  specification_version: 4
190
207
  summary: A key part of the Puppet Development Kit, the shortest path to better modules