Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(PDK-470) Validation of task metadata. #301

Merged
merged 11 commits into from
Oct 4, 2017
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ pdk new defined_type defined_type_name

PDK creates the new defined\_type manifest and a test file (as `defined_type_name_spec.rb`) in your module's `/spec/defines` directory.

### Generate a task

To generate a task in your module, use the `pdk new task` command, specifying the name of your new task.

1. From the command line, in your module's directory, run:
```
pdk new task task_name
```

PDK creates the new task file and metadata.

### Validating a module

Expand Down
7 changes: 4 additions & 3 deletions lib/pdk/validators/base_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ def self.parse_targets(options)
matched = targets.map { |target|
if respond_to?(:pattern)
if File.directory?(target)
target_list = Array[pattern].flatten.map { |p| Dir.glob(File.join(target, p)) }
pattern_glob = Array(pattern).map { |p| Dir.glob(File.join(PDK::Util.module_root, p)) }
target_list = pattern_glob.flatten.select { |file| File.fnmatch(File.join(File.expand_path(target), '*'), file) }
skipped << target if target_list.flatten.empty?
target_list
elsif File.file?(target)
if target.eql? pattern
if Array(pattern).include? target
target
elsif Array[pattern].flatten.map { |p| File.fnmatch(p, File.expand_path(target)) }.include? true
elsif Array(pattern).any? { |p| File.fnmatch(File.expand_path(p), File.expand_path(target)) }
target
else
skipped << target
Expand Down
2 changes: 1 addition & 1 deletion lib/pdk/validators/metadata/metadata_json_lint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def self.cmd
end

def self.spinner_text(targets = [])
_('Checking metadata style (%{targets}).') % {
_('Checking module metadata style (%{targets}).') % {
targets: PDK::Util.targets_relative_to_pwd(targets).join(' '),
}
end
Expand Down
6 changes: 3 additions & 3 deletions lib/pdk/validators/metadata/metadata_syntax.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ def self.name
end

def self.pattern
'metadata.json'
['metadata.json', 'tasks/*.json']
end

def self.spinner_text(targets = [])
def self.spinner_text(_targets = [])
_('Checking metadata syntax (%{targets}).') % {
targets: PDK::Util.targets_relative_to_pwd(targets).join(' '),
targets: pattern.join(' '),
}
end

Expand Down
149 changes: 149 additions & 0 deletions lib/pdk/validators/metadata/task_metadata_lint.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
require 'pdk'
require 'pdk/cli/exec'
require 'pdk/validators/base_validator'
require 'pdk/util'
require 'pathname'
require 'json-schema'

module PDK
module Validate
class TaskMetadataLint < BaseValidator
FORGE_SCHEMA_URL = 'https://forgeapi.puppet.com/schemas/task.json'.freeze

def self.name
'task-metadata-lint'
end

def self.pattern
'tasks/*.json'
end

def self.spinner_text(_targets = [])
_('Checking task metadata style (%{targets}).') % {
targets: pattern,
}
end

def self.create_spinner(targets = [], options = {})
return if PDK.logger.debug?
options = options.merge(PDK::CLI::Util.spinner_opts_for_platform)

exec_group = options[:exec_group]
@spinner = if exec_group
exec_group.add_spinner(spinner_text(targets), options)
else
TTY::Spinner.new("[:spinner] #{spinner_text(targets)}", options)
end
@spinner.auto_spin
end

def self.stop_spinner(exit_code)
if exit_code.zero? && @spinner
@spinner.success
elsif @spinner
@spinner.error
end
end

def self.vendored_task_schema_path
@vendored_task_schema_path ||= File.join(PDK::Util.package_cachedir, 'task.json')
end

def self.schema_file
schema = if PDK::Util.package_install? && File.exist?(vendored_task_schema_path)
File.read(vendored_task_schema_path)
else
download_schema_from_forge
end

JSON.parse(schema)
rescue JSON::ParserError
raise PDK::CLI::FatalError, _('Failed to parse Task Metadata Schema file.')
end

def self.download_schema_from_forge
PDK.logger.debug(_('Task Metadata Schema was not found in the cache. Now downloading from the forge.'))
require 'net/https'
require 'openssl'

uri = URI.parse(FORGE_SCHEMA_URL)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if Gem.win_platform?
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)

raise PDK::CLI::FatalError, _('Unable to download Task Metadata Schema file. %{code}: %{message}.') % { code: response.code, message: response.message } unless response.code == '200'

response.body
rescue StandardError => e
raise PDK::CLI::FatalError, _('Unable to download Task Metadata Schema file. Please check internet connectivity and retry this action. %{error}') % { error: e }
end

def self.invoke(report, options = {})
targets, skipped, invalid = parse_targets(options)

process_skipped(report, skipped)
process_invalid(report, invalid)

return 0 if targets.empty?

return_val = 0
create_spinner(targets, options)

targets.each do |target|
unless File.readable?(target)
report.add_event(
file: target,
source: name,
state: :failure,
severity: 'error',
message: _('Could not be read.'),
)
return_val = 1
next
end

begin
# Need to set the JSON Parser and State Generator to the Native one to be
# compatible with the multi_json adapter.
JSON.parser = JSON::Ext::Parser if defined?(JSON::Ext::Parser)
JSON.generator = JSON::Ext::Generator

begin
errors = JSON::Validator.fully_validate(schema_file, File.read(target)) || []
rescue JSON::Schema::SchemaError => e
raise PDK::CLI::FatalError, _('Unable to validate Task Metadata. %{error}.') % { error: e.message }
end

if errors.empty?
report.add_event(
file: target,
source: name,
state: :passed,
severity: 'ok',
)
else
errors.each do |error|
# strip off the trailing parts that aren't relevant
error = error.split('in schema').first if error.include? 'in schema'

report.add_event(
file: target,
source: name,
state: :failure,
severity: 'error',
message: error,
)
end
return_val = 1
end
end
end

stop_spinner(return_val)
return_val
end
end
end
end
3 changes: 2 additions & 1 deletion lib/pdk/validators/metadata_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'pdk/validators/base_validator'
require 'pdk/validators/metadata/metadata_json_lint'
require 'pdk/validators/metadata/metadata_syntax'
require 'pdk/validators/metadata/task_metadata_lint'

module PDK
module Validate
Expand All @@ -12,7 +13,7 @@ def self.name
end

def self.metadata_validators
[MetadataSyntax, MetadataJSONLint]
[MetadataSyntax, MetadataJSONLint, TaskMetadataLint]
end

def self.invoke(report, options = {})
Expand Down
1 change: 1 addition & 0 deletions pdk.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'tty-spinner', '0.5.0'
spec.add_runtime_dependency 'tty-prompt', '0.13.1'
spec.add_runtime_dependency 'json_pure', '~> 2.1.0'
spec.add_runtime_dependency 'json-schema', '2.8.0'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a ticket or PR for adding this to the vanagon repo?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(and it's runtime dependencies)

spec.add_runtime_dependency 'tty-which', '0.3.0'

# Used in the pdk-module-template
Expand Down
21 changes: 10 additions & 11 deletions spec/acceptance/validate_all_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ class validate_all { }
describe command('pdk validate') do
its(:exit_status) { is_expected.to eq(0) }
its(:stderr) { is_expected.to match(%r{Running all available validators}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata style \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking module metadata style}i) }
its(:stderr) { is_expected.to match(%r{Checking Puppet manifest syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking Puppet manifest style}i) }
its(:stderr) { is_expected.to match(%r{Checking Ruby code style}i) }
Expand All @@ -47,8 +47,8 @@ class validate_all {
describe command('pdk validate') do
its(:exit_status) { is_expected.not_to eq(0) }
its(:stderr) { is_expected.to match(%r{Running all available validators}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata style \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking module metadata style}i) }
its(:stderr) { is_expected.to match(%r{Checking Puppet manifest syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking Ruby code style}i) }
end
Expand All @@ -57,8 +57,8 @@ class validate_all {
its(:exit_status) { is_expected.not_to eq(0) }
its(:stderr) { is_expected.to match(%r{Running all available validators}i) }
its(:stderr) { is_expected.to match(%r{Validating module using \d+ threads}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata style \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking module metadata style}i) }
its(:stderr) { is_expected.to match(%r{Checking Puppet manifest syntax}i) }
its(:stdout) { is_expected.to match(%r{Error:.*This Name has no effect}i) }
its(:stdout) { is_expected.to match(%r{Error:.*This Type-Name has no effect}i) }
Expand All @@ -69,8 +69,8 @@ class validate_all {
describe command('pdk validate --format junit') do
its(:exit_status) { is_expected.not_to eq(0) }
its(:stderr) { is_expected.to match(%r{Running all available validators}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata style \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking module metadata style}i) }
its(:stderr) { is_expected.to match(%r{Checking Puppet manifest syntax}i) }
its(:stderr) { is_expected.not_to match(%r{Checking Puppet manifest style}i) }
its(:stderr) { is_expected.to match(%r{Checking Ruby code style}i) }
Expand Down Expand Up @@ -124,11 +124,10 @@ class pdk_in_gemfile { }
describe command('pdk validate') do
its(:exit_status) { is_expected.to eq(0) }
its(:stderr) { is_expected.to match(%r{Running all available validators}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata style \(metadata\.json\)}i) }
its(:stderr) { is_expected.to match(%r{Checking metadata syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking module metadata style}i) }
its(:stderr) { is_expected.to match(%r{Checking Puppet manifest syntax}i) }
its(:stderr) { is_expected.to match(%r{Checking Ruby code style}i) }
its(:stdout) { is_expected.to eq('') }
end
end
end
Loading