metadata-json-lint 2.0.1 → 2.4.0

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