metadata-json-lint 2.0.1 → 2.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.
data/README.md CHANGED
@@ -8,8 +8,6 @@ metadata-json-lint is compatible with Ruby versions 2.0.0, 2.1.9, 2.3.1, and 2.4
8
8
 
9
9
  ## Installation
10
10
 
11
- Puppet 4.9.0 and newer:
12
-
13
11
  via `gem` command:
14
12
  ``` shell
15
13
  gem install metadata-json-lint
@@ -20,19 +18,6 @@ via Gemfile:
20
18
  gem 'metadata-json-lint'
21
19
  ```
22
20
 
23
- **Puppet 4.8.x and older:**
24
-
25
- via `gem` command:
26
- ``` shell
27
- gem install metadata-json-lint semantic_puppet
28
- ```
29
-
30
- via Gemfile:
31
- ``` ruby
32
- gem 'metadata-json-lint'
33
- gem 'semantic_puppet
34
- ```
35
-
36
21
  ## Usage
37
22
 
38
23
  ### Testing with metadata-json-lint
@@ -56,7 +41,7 @@ rake metadata_lint
56
41
  To set options for the Rake task, include them when you define the task:
57
42
 
58
43
  ```ruby
59
- require 'metadata-json-lint'
44
+ require 'metadata_json_lint'
60
45
  task :metadata_lint do
61
46
  MetadataJsonLint.parse('metadata.json') do |options|
62
47
  options.strict_license = false
@@ -76,6 +61,22 @@ MetadataJsonLint.options.strict_license = false
76
61
  * `--[no-]strict-dependencies`: Whether to fail if module version dependencies are open-ended. Defaults to `false`.
77
62
  * `--[no-]strict-license`: Whether to fail on strict license check. Defaults to `true`.
78
63
  * `--[no-]fail-on-warnings`: Whether to fail on warnings. Defaults to `true`.
64
+ * `--[no-]strict-puppet-version`: Whether to fail if Puppet version requirements are open-ended or no longer supported. Defaults to `false`.
65
+
66
+
67
+ ## Make a new release
68
+
69
+ To make a new release, we need to install the release gem group:
70
+
71
+ ```sh
72
+ bundle install --path .vendor/ --with release
73
+ ```
74
+
75
+ Afterwards export a GitHub access token (otherwise you might run into API rate limits):
76
+
77
+ ```sh
78
+ export CHANGELOG_GITHUB_TOKEN=...
79
+ ```
79
80
 
80
81
  ## Contributors
81
82
 
data/Rakefile CHANGED
@@ -13,3 +13,17 @@ end
13
13
 
14
14
  require 'rspec/core/rake_task'
15
15
  RSpec::Core::RakeTask.new(:spec)
16
+
17
+ begin
18
+ require 'github_changelog_generator/task'
19
+
20
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
21
+ config.header = "# Changelog\n\nAll notable changes to this project will be documented in this file."
22
+ config.exclude_labels = %w[duplicate question invalid wontfix wont-fix skip-changelog]
23
+ config.user = 'voxpupuli'
24
+ config.project = 'metadata-json-lint'
25
+ config.future_release = "v#{Gem::Specification.load("#{config.project}.gemspec").version}"
26
+ end
27
+ rescue LoadError
28
+ puts 'no github_changelog_generator gem available'
29
+ end
@@ -141,10 +141,28 @@ module MetadataJsonLint
141
141
 
142
142
  private
143
143
 
144
+ def semver_full_regex
145
+ @semver_full_regex ||= begin
146
+ # Version string matching regexes
147
+ numeric = '(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)' # Major . Minor . Patch
148
+ pre = '(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?' # Prerelease
149
+ build = '(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?' # Build
150
+ full = numeric + pre + build
151
+
152
+ /\A#{full}\Z/
153
+ end
154
+ end
155
+
144
156
  def semver_validator(value)
145
- SemanticPuppet::Version.parse(value)
146
- rescue SemanticPuppet::Version::ValidationFailure => e
147
- raise JSON::Schema::CustomFormatError, "must be a valid semantic version: #{e.message}"
157
+ if defined?(SemanticPuppet::Version)
158
+ begin
159
+ SemanticPuppet::Version.parse(value)
160
+ rescue SemanticPuppet::Version::ValidationFailure => e
161
+ raise JSON::Schema::CustomFormatError, "must be a valid semantic version: #{e.message}"
162
+ end
163
+ elsif value.match(semver_full_regex).nil?
164
+ raise JSON::Schema::CustomFormatError, "must be a valid semantic version: Unable to parse '#{value}' as a semantic version identifier"
165
+ end
148
166
  end
149
167
  end
150
168
  end
@@ -0,0 +1,47 @@
1
+ module MetadataJsonLint
2
+ # Attempts the various methods of loading SemanticPuppet.
3
+ module SemanticPuppetLoader
4
+ def try_load
5
+ try_load_puppet
6
+ return if defined?(SemanticPuppet)
7
+
8
+ try_load_semantic
9
+ return if defined?(SemanticPuppet)
10
+
11
+ try_load_semantic_puppet
12
+ return if defined?(SemanticPuppet)
13
+
14
+ warn 'Could not find semantic_puppet gem, falling back to internal functionality. Version checks may be less robust.'
15
+ end
16
+ module_function :try_load
17
+
18
+ # Most modern Puppet versions have SemanticPuppet vendored in the proper
19
+ # namespace and automatically load it at require time.
20
+ def try_load_puppet
21
+ require 'puppet'
22
+ rescue LoadError
23
+ nil
24
+ end
25
+ module_function :try_load_puppet
26
+
27
+ # Older Puppet 4.x versions have SemanticPuppet vendored but under the
28
+ # Semantic namespace and require it on demand, so we need to load it
29
+ # ourselves and then alias it to SemanticPuppet for convenience.
30
+ def try_load_semantic
31
+ require 'semantic'
32
+ Kernel.const_set('SemanticPuppet', Semantic)
33
+ rescue LoadError
34
+ nil
35
+ end
36
+ module_function :try_load_semantic
37
+
38
+ # If Puppet is not available or is a version that does not have
39
+ # SemanticPuppet vendored, try to load the external gem.
40
+ def try_load_semantic_puppet
41
+ require 'semantic_puppet'
42
+ rescue LoadError
43
+ nil
44
+ end
45
+ module_function :try_load_semantic_puppet
46
+ end
47
+ end
@@ -1,13 +1,16 @@
1
- require 'puppet'
2
- require 'semantic_puppet' unless Puppet.version >= '4.9.0'
3
-
4
1
  module MetadataJsonLint
5
2
  # Parses a string module version requirement with semantic_puppet and
6
3
  # provides methods to analyse it for lint warnings
7
4
  class VersionRequirement
8
5
  def initialize(requirement)
9
6
  @requirement = requirement
10
- @range = SemanticPuppet::VersionRange.parse(requirement)
7
+
8
+ if defined?(SemanticPuppet::VersionRange)
9
+ @range = SemanticPuppet::VersionRange.parse(requirement)
10
+ raise ArgumentError, "Range matches no versions: \"#{requirement}\"" if @range == SemanticPuppet::VersionRange::EMPTY_RANGE
11
+ elsif requirement.match(/\A[a-z0-9*.\-^~><=|\t ]*\Z/i).nil?
12
+ raise ArgumentError, "Unparsable version range: \"#{requirement}\""
13
+ end
11
14
  end
12
15
 
13
16
  # Whether the range uses a comparison operator (e.g. >=) with a wildcard
@@ -26,7 +29,38 @@ module MetadataJsonLint
26
29
  end
27
30
 
28
31
  def open_ended?
29
- @range.end == SemanticPuppet::Version::MAX
32
+ if range
33
+ range.end == SemanticPuppet::Version::MAX
34
+ else
35
+ # Empty requirement strings are open-ended.
36
+ return true if requirement.strip.empty?
37
+
38
+ # Strip superfluous whitespace.
39
+ range_set = requirement.gsub(/([><=~^])(?:\s+|\s*v)/, '\1')
40
+
41
+ # Split on logical OR
42
+ ranges = range_set.split(/\s*\|\|\s*/)
43
+
44
+ # Returns true if any range includes a '>' but not a corresponding '<'
45
+ # which should be the only way to declare an open-ended range.
46
+ ranges.select { |r| r.include?('>') }.any? { |r| !r.include?('<') }
47
+ end
48
+ end
49
+
50
+ def puppet_eol?
51
+ true if range.begin < SemanticPuppet::Version.parse(MIN_PUPPET_VER)
52
+ end
53
+
54
+ def ver_range
55
+ range
56
+ end
57
+
58
+ def min
59
+ range.begin
60
+ end
61
+
62
+ def max
63
+ range.end
30
64
  end
31
65
 
32
66
  private
@@ -2,20 +2,27 @@ require 'json'
2
2
  require 'spdx-licenses'
3
3
  require 'optparse'
4
4
 
5
+ require 'metadata-json-lint/semantic_puppet_loader'
6
+ MetadataJsonLint::SemanticPuppetLoader.try_load
7
+
5
8
  require 'metadata-json-lint/schema'
6
9
  require 'metadata-json-lint/version_requirement'
7
10
 
8
11
  module MetadataJsonLint
12
+ MIN_PUPPET_VER = '4.10.0'.freeze
13
+
9
14
  def options
10
15
  @options ||= Struct.new(
11
16
  :fail_on_warnings,
12
17
  :strict_license,
13
18
  :strict_dependencies,
19
+ :strict_puppet_version,
14
20
  :format
15
21
  ).new(
16
22
  true, # fail_on_warnings
17
23
  true, # strict_license
18
24
  false, # strict_dependencies
25
+ false, # strict_puppet_version
19
26
  'text', # format
20
27
  )
21
28
  end
@@ -37,6 +44,10 @@ module MetadataJsonLint
37
44
  options[:fail_on_warnings] = v
38
45
  end
39
46
 
47
+ opts.on('--[no-]strict-puppet-version', "Fail on strict Puppet Version check based on current supported Puppet versions. Defaults to '#{options[:strict_puppet_version]}'.") do |v|
48
+ options[:strict_puppet_version] = v
49
+ end
50
+
40
51
  opts.on('-f', '--format FORMAT', %i[text json], 'The format in which results will be output (text, json)') do |format|
41
52
  options[:format] = format
42
53
  end
@@ -126,15 +137,55 @@ module MetadataJsonLint
126
137
  end
127
138
  module_function :parse
128
139
 
140
+ def validate_requirements_unique(requirements)
141
+ names = requirements.map { |x| x['name'] }
142
+ counts = Hash.new(0)
143
+
144
+ names.each { |name| counts[name.downcase] += 1 }
145
+
146
+ counts.each do |k, v|
147
+ error :requirements, "Duplicate entries in the 'requirements' list with the name '#{k}'" if v > 1
148
+ end
149
+ end
150
+ module_function :validate_requirements_unique
151
+
129
152
  def validate_requirements!(requirements)
153
+ return unless requirements.is_a?(Array)
154
+
130
155
  requirements.each do |requirement|
131
156
  if requirement['name'] == 'pe'
132
157
  warn :requirements, "The 'pe' requirement is no longer supported by the Forge."
133
158
  end
159
+
160
+ begin
161
+ puppet_req = VersionRequirement.new(requirement.fetch('version_requirement', ''))
162
+ rescue ArgumentError => e
163
+ # Raised when the version_requirement provided could not be parsed
164
+ error :requirements, "Invalid 'version_requirement' field in metadata.json: #{e}"
165
+ end
166
+
167
+ validate_puppet_ver!(puppet_req) unless puppet_req.instance_variable_get('@requirement').nil?
134
168
  end
169
+
170
+ validate_requirements_unique(requirements)
135
171
  end
136
172
  module_function :validate_requirements!
137
173
 
174
+ def validate_puppet_ver!(requirement)
175
+ if options[:strict_puppet_version] && requirement.open_ended?
176
+ warn(:requirement, "Puppet has an open ended version requirement #{requirement.ver_range}")
177
+ end
178
+
179
+ if options[:strict_puppet_version] && requirement.puppet_eol?
180
+ warn(:requirement, "#{requirement.min} is no longer supported. Minimum supported version is #{MIN_PUPPET_VER}")
181
+ end
182
+
183
+ return unless requirement.mixed_syntax?
184
+ warn(:requirement, 'Mixing "x" or "*" version syntax with operators is not recommended in ' \
185
+ "metadata.json, use one style in the puppet version: #{requirement.instance_variable_get('@requirement')}")
186
+ end
187
+ module_function :validate_puppet_ver!
188
+
138
189
  def validate_dependencies!(deps)
139
190
  dep_names = []
140
191
  deps.each do |dep|
@@ -148,6 +199,8 @@ module MetadataJsonLint
148
199
  rescue ArgumentError => e
149
200
  # Raised when the version_requirement provided could not be parsed
150
201
  error :dependencies, "Invalid 'version_requirement' field in metadata.json: #{e}"
202
+ # Skip to the next dependency
203
+ next
151
204
  end
152
205
  validate_version_requirement!(dep, requirement)
153
206
 
@@ -164,10 +217,10 @@ module MetadataJsonLint
164
217
  def validate_version_requirement!(dep, requirement)
165
218
  # Open ended dependency
166
219
  # From: https://docs.puppet.com/puppet/latest/reference/modules_metadata.html#best-practice-set-an-upper-bound-for-dependencies
167
- if requirement.open_ended?
220
+ if options[:strict_dependencies] && requirement.open_ended?
168
221
  msg = "Dependency #{dep['name']} has an open " \
169
222
  "ended dependency version requirement #{dep['version_requirement']}"
170
- options[:strict_dependencies] == true ? error(:dependencies, msg) : warn(:dependencies, msg)
223
+ warn(:dependencies, msg)
171
224
  end
172
225
 
173
226
  # Mixing operator and wildcard version syntax
@@ -2,7 +2,7 @@ require 'date'
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'metadata-json-lint'
5
- s.version = '2.0.1'
5
+ s.version = '2.4.0'
6
6
  s.date = Date.today.to_s
7
7
  s.summary = 'metadata-json-lint /path/to/metadata.json'
8
8
  s.description = 'Utility to verify Puppet metadata.json files'
@@ -19,14 +19,16 @@ Gem::Specification.new do |s|
19
19
  s.required_ruby_version = '>= 2.0.0'
20
20
  s.add_runtime_dependency 'spdx-licenses', '~> 1.0'
21
21
  s.add_runtime_dependency 'json-schema', '~> 2.8'
22
- s.add_runtime_dependency 'puppet', '>= 4.7.0', '< 6.0.0'
22
+ s.add_development_dependency 'pry'
23
23
  s.add_development_dependency 'rake'
24
+ s.add_development_dependency 'semantic_puppet'
24
25
  s.add_development_dependency 'rspec'
25
- s.add_development_dependency 'rubocop'
26
+ s.add_development_dependency 'rubocop', '~> 0.50.0'
26
27
  s.post_install_message = '
27
- -------------------------------------------------
28
- semantic_puppet must be installed within
29
- your Gemfile if you use Puppet <= 4.8.x!!
30
- -------------------------------------------------
31
- '
28
+ ----------------------------------------------------------
29
+ For the most accurate results, the semantic_puppet
30
+ gem should be included within your Gemfile if you
31
+ use Puppet <= 4.8.x
32
+ ----------------------------------------------------------
33
+ '.gsub(/^ /, '')
32
34
  end
@@ -0,0 +1,37 @@
1
+ describe MetadataJsonLint do
2
+ describe '.validate_requirements!' do
3
+ context 'empty requirements' do
4
+ let :requirements do
5
+ []
6
+ end
7
+
8
+ it { expect { described_class.validate_requirements!(requirements) }.not_to raise_error }
9
+ end
10
+
11
+ context 'with pe' do
12
+ let :requirements do
13
+ [
14
+ { 'name' => 'pe' }
15
+ ]
16
+ end
17
+
18
+ it do
19
+ expect(described_class).to receive('warn').with(:requirements, "The 'pe' requirement is no longer supported by the Forge.")
20
+ expect { described_class.validate_requirements!(requirements) }.not_to raise_error
21
+ end
22
+ end
23
+
24
+ context 'with invalid requirement' do
25
+ let :requirements do
26
+ [
27
+ { 'name' => 'puppet', 'version_requirement' => 'a' }
28
+ ]
29
+ end
30
+
31
+ it do
32
+ expect(described_class).to receive('error').with(:requirements, "Invalid 'version_requirement' field in metadata.json: Unparsable version range: \"a\"")
33
+ expect { described_class.validate_requirements!(requirements) }.not_to raise_error
34
+ end
35
+ end
36
+ end
37
+ end
@@ -57,13 +57,9 @@
57
57
  }
58
58
  ],
59
59
  "requirements": [
60
- {
61
- "name": "pe",
62
- "version_requirement": ">= 3.2.0 < 3.4.0"
63
- },
64
60
  {
65
61
  "name": "puppet",
66
- "version_requirement": "3.x"
62
+ "version_requirement": "5.5.1"
67
63
  }
68
64
  ],
69
65
  "dependencies": [
@@ -0,0 +1,2 @@
1
+ $LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
2
+ require 'metadata-json-lint/rake_task'
@@ -0,0 +1 @@
1
+ Duplicate entries in the 'requirements' list with the name 'puppet'
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "puppetlabs-postgresql",
3
+ "version": "3.4.1",
4
+ "author": "Inkling/Puppet Labs",
5
+ "summary": "PostgreSQL defined resource types",
6
+ "license": "Apache-2.0",
7
+ "source": "git://github.com/puppetlabs/puppet-postgresql.git",
8
+ "project_page": "https://github.com/puppetlabs/puppet-postgresql",
9
+ "issues_url": "https://github.com/puppetlabs/puppet-postgresql/issues",
10
+ "operatingsystem_support": [
11
+ {
12
+ "operatingsystem": "RedHat",
13
+ "operatingsystemrelease": [
14
+ "5",
15
+ "6",
16
+ "7"
17
+ ]
18
+ },
19
+ {
20
+ "operatingsystem": "CentOS",
21
+ "operatingsystemrelease": [
22
+ "5",
23
+ "6",
24
+ "7"
25
+ ]
26
+ },
27
+ {
28
+ "operatingsystem": "OracleLinux",
29
+ "operatingsystemrelease": [
30
+ "5",
31
+ "6",
32
+ "7"
33
+ ]
34
+ },
35
+ {
36
+ "operatingsystem": "Scientific",
37
+ "operatingsystemrelease": [
38
+ "5",
39
+ "6",
40
+ "7"
41
+ ]
42
+ },
43
+ {
44
+ "operatingsystem": "Debian",
45
+ "operatingsystemrelease": [
46
+ "6",
47
+ "7"
48
+ ]
49
+ },
50
+ {
51
+ "operatingsystem": "Ubuntu",
52
+ "operatingsystemrelease": [
53
+ "10.04",
54
+ "12.04",
55
+ "14.04"
56
+ ]
57
+ }
58
+ ],
59
+ "requirements": [
60
+ {
61
+ "name": "puppet",
62
+ "version_requirement": "5.5.1"
63
+ },
64
+ {
65
+ "name": "puppet",
66
+ "version_requirement": "6.11.0"
67
+ }
68
+ ],
69
+ "dependencies": [
70
+ {
71
+ "name": "puppetlabs/stdlib",
72
+ "version_requirement": "4.x"
73
+ },
74
+ {
75
+ "name": "puppetlabs/firewall",
76
+ "version_requirement": ">= 0.0.4"
77
+ },
78
+ {
79
+ "name": "puppetlabs/apt",
80
+ "version_requirement": ">=1.1.0 <2.0.0"
81
+ },
82
+ {
83
+ "name": "puppetlabs/concat",
84
+ "version_requirement": ">= 1.1.0 <2.0.0"
85
+ }
86
+ ]
87
+ }