From cbb32ef70076a1a8cc457dadb78bc4c3a92b09ca Mon Sep 17 00:00:00 2001 From: Yury Bushmelev Date: Sat, 28 Jan 2023 17:40:17 +0800 Subject: [PATCH] Add Puppet validator for plans --- lib/pdk/validate.rb | 1 + .../puppet/puppet_plan_syntax_validator.rb | 38 ++++++ .../validate/puppet/puppet_validator_group.rb | 1 + .../puppet_plan_syntax_validator_spec.rb | 109 ++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb create mode 100644 spec/unit/pdk/validate/puppet/puppet_plan_syntax_validator_spec.rb diff --git a/lib/pdk/validate.rb b/lib/pdk/validate.rb index b5a688d0e..0ffc654e6 100644 --- a/lib/pdk/validate.rb +++ b/lib/pdk/validate.rb @@ -23,6 +23,7 @@ module Puppet autoload :PuppetEPPValidator, 'pdk/validate/puppet/puppet_epp_validator' autoload :PuppetLintValidator, 'pdk/validate/puppet/puppet_lint_validator' autoload :PuppetSyntaxValidator, 'pdk/validate/puppet/puppet_syntax_validator' + autoload :PuppetPlanSyntaxValidator, 'pdk/validate/puppet/puppet_plan_syntax_validator' autoload :PuppetValidatorGroup, 'pdk/validate/puppet/puppet_validator_group' end diff --git a/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb b/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb new file mode 100644 index 000000000..62ecd08d2 --- /dev/null +++ b/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb @@ -0,0 +1,38 @@ +require 'pdk' + +module PDK + module Validate + module Puppet + class PuppetPlanSyntaxValidator < PuppetSyntaxValidator + def name + 'puppet-plan-syntax' + end + + def pattern + contextual_pattern('plans/**/*.pp') + end + + def pattern_ignore; end + + def spinner_text_for_targets(_targets) + _('Checking Puppet plan syntax (%{pattern}).') % { pattern: pattern.join(' ') } + end + + def parse_options(targets) + # Due to PDK-1266 we need to run `puppet parser validate` with an empty + # modulepath. On *nix, Ruby treats `/dev/null` as an empty directory + # however it doesn't do so with `NUL` on Windows. The workaround for + # this to ensure consistent behaviour is to create an empty temporary + # directory and use that as the modulepath. + ['parser', 'validate', '--tasks', '--config', null_file, '--modulepath', validate_tmpdir].concat(targets) + end + + def validate_tmpdir + require 'tmpdir' + + @validate_tmpdir ||= Dir.mktmpdir('puppet-plan-parser-validate') + end + end + end + end +end diff --git a/lib/pdk/validate/puppet/puppet_validator_group.rb b/lib/pdk/validate/puppet/puppet_validator_group.rb index 4200a92f5..ebbc00076 100644 --- a/lib/pdk/validate/puppet/puppet_validator_group.rb +++ b/lib/pdk/validate/puppet/puppet_validator_group.rb @@ -11,6 +11,7 @@ def name def validators [ PuppetSyntaxValidator, + PuppetPlanSyntaxValidator, PuppetLintValidator, PuppetEPPValidator, ].freeze diff --git a/spec/unit/pdk/validate/puppet/puppet_plan_syntax_validator_spec.rb b/spec/unit/pdk/validate/puppet/puppet_plan_syntax_validator_spec.rb new file mode 100644 index 000000000..2bd7c933e --- /dev/null +++ b/spec/unit/pdk/validate/puppet/puppet_plan_syntax_validator_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' +require 'pdk/validate/puppet/puppet_plan_syntax_validator' + +describe PDK::Validate::Puppet::PuppetPlanSyntaxValidator do + subject(:validator) { described_class.new(validator_context, options) } + + let(:validator_context) { nil } + let(:options) { {} } + let(:tmpdir) { File.join('/', 'tmp', 'puppet-plan-parser-validate') } + + before(:each) do + allow(Dir).to receive(:mktmpdir).with('puppet-plan-parser-validate').and_return(tmpdir) + allow(PDK::Util::Filesystem).to receive(:remove_entry_secure).with(tmpdir) + end + + it 'defines the ExternalCommandValidator attributes' do + expect(validator).to have_attributes( + name: 'puppet-plan-syntax', + cmd: 'puppet', + ) + expect(validator.spinner_text_for_targets(nil)).to match(%r{puppet plan syntax}i) + end + + describe '.pattern' do + it 'only contextually matches puppet plans' do + expect(validator).to receive(:contextual_pattern).with('plans/**/*.pp') # rubocop:disable RSpec/SubjectStub This is fine + validator.pattern + end + end + + describe '.pattern_ignore' do + it 'ignores nothing' do + expect(validator).not_to receive(:contextual_pattern) + validator.pattern_ignore + end + end + + describe '.invoke' do + context 'when the validator runs correctly' do + before(:each) do + allow(validator).to receive(:parse_targets).and_return([[], [], []]) # rubocop:disable RSpec/SubjectStub + end + + it 'cleans up the temp dir after invoking' do + expect(validator).to receive(:remove_validate_tmpdir) # rubocop:disable RSpec/SubjectStub + validator.invoke(PDK::Report.new) + end + end + + context 'when the validator raises an exception' do + before(:each) do + allow(validator).to receive(:parse_targets).and_raise(PDK::CLI::FatalError) # rubocop:disable RSpec/SubjectStub + end + + it 'cleans up the temp dir after invoking' do + expect(validator).to receive(:remove_validate_tmpdir) # rubocop:disable RSpec/SubjectStub + expect { + validator.invoke(PDK::Report.new) + }.to raise_error(PDK::CLI::FatalError) + end + end + end + + describe '.remove_validate_tmpdir' do + after(:each) do + validator.remove_validate_tmpdir + end + + context 'when a temp dir has been created' do + before(:each) do + validator.validate_tmpdir + end + + context 'and the path is a directory' do + before(:each) do + allow(PDK::Util::Filesystem).to receive(:directory?).with(tmpdir).and_return(true) + end + + it 'removes the directory' do + expect(PDK::Util::Filesystem).to receive(:remove_entry_secure).with(tmpdir) + end + end + + context 'but the path is not a directory' do + before(:each) do + allow(PDK::Util::Filesystem).to receive(:directory?).with(tmpdir).and_return(false) + end + + it 'does not attempt to remove the directory' do + expect(PDK::Util::Filesystem).not_to receive(:remove_entry_secure) + end + end + end + end + + describe '.parse_options' do + subject(:command_args) { validator.parse_options(targets) } + + let(:targets) { %w[target1 target2.pp] } + + before(:each) do + allow(Gem).to receive(:win_platform?).and_return(false) + end + + it 'invokes `puppet parser validate --tasks`' do + expect(command_args.first(3)).to eq(%w[parser validate --tasks]) + end + end +end