Skip to content

Commit

Permalink
Merge pull request #448 from rodjek/pdk-785
Browse files Browse the repository at this point in the history
(PDK-785) Add --puppet-version and --pe-version CLI options
  • Loading branch information
rodjek authored Mar 19, 2018
2 parents f28a9ad + 4b5c441 commit 127450f
Show file tree
Hide file tree
Showing 10 changed files with 611 additions and 15 deletions.
11 changes: 11 additions & 0 deletions lib/pdk/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'pdk/logger'
require 'pdk/report'
require 'pdk/util/version'
require 'pdk/util/puppet_version'

module PDK::CLI
def self.run(args)
Expand Down Expand Up @@ -49,6 +50,16 @@ def self.full_interview_option(dsl)
dsl.option nil, 'full-interview', _('When specified, interactive querying of metadata will include all optional questions.')
end

def self.puppet_version_options(dsl)
dsl.option nil, 'puppet-version', _('Puppet version to run tests or validations against.'), argument: :required do |value|
PDK::Util::PuppetVersion.find_gem_for(value)
end

dsl.option nil, 'pe-version', _('Puppet Enterprise version to run tests or validations against.'), argument: :required do |value|
PDK::Util::PuppetVersion.from_pe_version(value)
end
end

@base_cmd = Cri::Command.define do
name 'pdk'
usage _('pdk command [options]')
Expand Down
18 changes: 3 additions & 15 deletions lib/pdk/cli/exec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

require 'pdk/util'
require 'pdk/util/git'
require 'pdk/util/ruby_version'

module PDK
module CLI
Expand Down Expand Up @@ -115,21 +116,8 @@ def execute!
end

if context == :module
# `bundle install --path` ignores all "system" installed gems and causes unnecessary package installs
# `bundle install` (without --path) installs into GEM_HOME, which by default is non-user writeable
# To still use the pre-installed packages, but allow folks to install additional gems
# we set GEM_HOME to the user's cachedir, and put all other cache locations onto GEM_PATH
# See https://stackoverflow.com/a/11277228 for background
@process.environment['GEM_HOME'] = File.join(PDK::Util.cachedir, 'ruby', RbConfig::CONFIG['ruby_version'])

if PDK::Util.package_install?
# Subprocesses use their own set of gems which are managed by pdk or installed with the package.
@process.environment['GEM_PATH'] = File.join(PDK::Util.package_cachedir, 'ruby', RbConfig::CONFIG['ruby_version'])
else
# This allows the subprocess to find the 'bundler' gem, which isn't in the cachedir above for gem installs.
bundler_gem_path = File.absolute_path(File.join(`gem which bundler`, '..', '..', '..', '..'))
@process.environment['GEM_PATH'] = bundler_gem_path
end
@process.environment['GEM_HOME'] = PDK::Util::RubyVersion.gem_home
@process.environment['GEM_PATH'] = PDK::Util::RubyVersion.gem_path

# Make sure invocation of Ruby prefers our private installation.
package_binpath = PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil
Expand Down
5 changes: 5 additions & 0 deletions lib/pdk/cli/test/unit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module PDK::CLI
usage _('unit [options]')
summary _('Run unit tests.')

PDK::CLI.puppet_version_options(self)
flag nil, :list, _('List all available unit test files.')
flag nil, :parallel, _('Run unit tests in parallel.'), hidden: true
flag :v, :verbose, _('More verbose output. Displays examples in each unit test file.')
Expand All @@ -21,6 +22,10 @@ module PDK::CLI
run do |opts, _args, _cmd|
require 'pdk/tests/unit'

if opts[:'puppet-version'] && opts[:'pe-version']
raise PDK::CLI::ExitWithError, _('You can not specify both --puppet-version and --pe-version at the same time.')
end

PDK::CLI::Util.ensure_in_module!(
message: _('Unit tests can only be run from inside a valid module directory.'),
log_level: :info,
Expand Down
5 changes: 5 additions & 0 deletions lib/pdk/cli/validate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module PDK::CLI
'If not specified, validators are run against all applicable files in the module.',
)

PDK::CLI.puppet_version_options(self)
flag nil, :list, _('List all available validators.')
flag :a, 'auto-correct', _('Automatically correct problems where possible.')
flag nil, :parallel, _('Run validations in parallel.')
Expand All @@ -32,6 +33,10 @@ module PDK::CLI
exit 0
end

if opts[:'puppet-version'] && opts[:'pe-version']
raise PDK::CLI::ExitWithError, _('You can not specify both --puppet-version and --pe-version at the same time.')
end

PDK::CLI::Util.ensure_in_module!(
message: _('Code validation can only be run from inside a valid module directory'),
log_level: :info,
Expand Down
130 changes: 130 additions & 0 deletions lib/pdk/util/puppet_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
require 'pdk/util'

module PDK
module Util
class PuppetVersion
class << self
extend Forwardable

def_delegators :instance, :find_gem_for, :from_pe_version

attr_writer :instance

def instance
@instance ||= new
end
end

def find_gem_for(version_str)
ensure_semver_version!(version_str)
version = Gem::Version.new(version_str)

exact_requirement = Gem::Requirement.create(version)
gem_version = find_gem(exact_requirement)
return gem_version.version unless gem_version.nil?

latest_requirement = Gem::Requirement.create("#{version.approximate_recommendation}.0")
gem_version = find_gem(latest_requirement)
unless gem_version.nil?
PDK.logger.info _('Unable to find Puppet %{requested_version}, using %{found_version} instead') % {
requested_version: version_str,
found_version: gem_version.version,
}
return gem_version.version
end

raise ArgumentError, _('Unable to find a Puppet version matching %{requirement}') % {
requirement: latest_requirement,
}
end

def from_pe_version(version_str)
ensure_semver_version!(version_str)

version = Gem::Version.new(version_str)
gem_version = pe_version_map.find do |version_map|
version_map[:requirement].satisfied_by?(version)
end

if gem_version.nil?
raise ArgumentError, _('Unable to map Puppet Enterprise version %{pe_version} to a Puppet version') % {
pe_version: version_str,
}
end

PDK.logger.info _('Puppet Enterprise %{pe_version} maps to Puppet %{puppet_version}') % {
pe_version: version_str,
puppet_version: gem_version[:gem_version],
}
find_gem_for(gem_version[:gem_version])
end

private

def ensure_semver_version!(version_str)
return if version_str =~ %r{\A\d+\.\d+\.\d+\Z}

raise ArgumentError, _('%{version} is not a valid version number') % {
version: version_str,
}
end

def pe_version_map
@pe_version_map ||= fetch_pe_version_map.map do |version_map|
{
requirement: requirement_from_forge_range(version_map['name']),
gem_version: version_map['puppet'],
}
end
end

# TODO: Replace this with a cached forge lookup like we do for the task
# metadata schema (PDK-828)
def fetch_pe_version_map
[
{ 'name' => '2017.3.x', 'puppet_range' => '5.3.x', 'puppet' => '5.3.2' },
{ 'name' => '2017.2.x', 'puppet_range' => '4.10.x', 'puppet' => '4.10.1' },
{ 'name' => '2017.1.x', 'puppet_range' => '4.9.x', 'puppet' => '4.9.4' },
{ 'name' => '2016.5.x', 'puppet_range' => '4.8.x', 'puppet' => '4.8.1' },
{ 'name' => '2016.4.x', 'puppet_range' => '4.7.x', 'puppet' => '4.7.0' },
{ 'name' => '2016.2.x', 'puppet_range' => '4.5.x', 'puppet' => '4.5.2' },
{ 'name' => '2016.1.x', 'puppet_range' => '4.4.x', 'puppet' => '4.4.1' },
{ 'name' => '2015.3.x', 'puppet_range' => '4.3.x', 'puppet' => '4.3.2' },
{ 'name' => '2015.2.x', 'puppet_range' => '4.2.x', 'puppet' => '4.2.3' },
]
end

def requirement_from_forge_range(range_str)
range_str.gsub!(%r{\.x\Z}, '.0')
Gem::Requirement.create("~> #{range_str}")
end

def rubygems_puppet_versions
return @rubygems_puppet_versions unless @rubygems_puppet_versions.nil?

fetcher = Gem::SpecFetcher.fetcher
puppet_tuples = fetcher.detect(:released) do |spec_tuple|
spec_tuple.name == 'puppet' && Gem::Platform.match(spec_tuple.platform)
end
puppet_versions = puppet_tuples.map { |name, _| name.version }.uniq
@rubygems_puppet_versions = puppet_versions.sort { |a, b| b <=> a }
end

def find_gem(requirement)
if PDK::Util.package_install?
find_in_package_cache(requirement)
else
find_in_rubygems(requirement)
end
end

def find_in_rubygems(requirement)
rubygems_puppet_versions.find { |r| requirement.satisfied_by?(r) }
end

def find_in_package_cache(requirement)
PDK::Util::RubyVersion.available_puppet_versions.find { |r| requirement.satisfied_by?(r) }
end
end
end
end
77 changes: 77 additions & 0 deletions lib/pdk/util/ruby_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
require 'pdk/util'

module PDK
module Util
class RubyVersion
class << self
extend Forwardable

def_delegators :instance, :gem_path, :gem_home, :versions, :available_puppet_versions

attr_writer :instance

def instance
@instance ||= new
end
end

attr_reader :active_ruby_version

def initialize
@active_ruby_version = default_ruby_version
end

def gem_path
if PDK::Util.package_install?
# Subprocesses use their own set of gems which are managed by pdk or
# installed with the package.
File.join(PDK::Util.package_cachedir, 'ruby', versions[active_ruby_version])
else
# This allows the subprocess to find the 'bundler' gem, which isn't
# in the cachedir above for gem installs.
# TODO: There must be a better way to do this than shelling out to
# gem...
File.absolute_path(File.join(`gem which bundler`, '..', '..', '..', '..'))
end
end

def gem_home
# `bundle install --path` ignores all "system" installed gems and
# causes unnecessary package installs. `bundle install` (without
# --path) installs into GEM_HOME, which by default is non-user
# writeable.
# To still use the pre-installed packages, but allow folks to install
# additional gems, we set GEM_HOME to the user's cachedir and put all
# other cache locations onto GEM_PATH.
# See https://stackoverflow.com/a/11277228 for background
File.join(PDK::Util.cachedir, 'ruby', versions[active_ruby_version])
end

def versions
@versions ||= if PDK::Util.package_install?
scan_for_packaged_rubies
else
{ RbConfig::CONFIG['RUBY_PROGRAM_VERSION'] => RbConfig::CONFIG['ruby_version'] }
end
end

def available_puppet_versions
return @available_puppet_versions unless @available_puppet_versions.nil?
puppet_spec_files = Dir[File.join(gem_path, 'specifications', '**', 'puppet*.gemspec')]
puppet_spec_files += Dir[File.join(gem_home, 'specifications', '**', 'puppet*.gemspec')]
puppet_specs = puppet_spec_files.map { |r| Gem::Specification.load(r) }
@available_puppet_versions = puppet_specs.select { |r| r.name == 'puppet' }.map { |r| r.version }.sort { |a, b| b <=> a }
end

private

def scan_for_packaged_rubies
{ '2.1.9' => '2.1.0' }
end

def default_ruby_version
versions.keys.sort { |a, b| Gem::Version.new(b) <=> Gem::Version.new(a) }.first
end
end
end
end
15 changes: 15 additions & 0 deletions spec/unit/pdk/cli/test/unit_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,19 @@
end
end
end

context 'when --puppet-version and --pe-version are specified' do
before(:each) do
allow(PDK::Util::PuppetVersion).to receive(:find_gem_for).with('4.10.10').and_return('4.10.10')
allow(PDK::Util::PuppetVersion).to receive(:from_pe_version).with('2018.1.1').and_return('4.10.10')
end

it 'exits with an error' do
expect(logger).to receive(:error).with(a_string_matching(%r{both --puppet-version and --pe-version}i))

expect {
PDK::CLI.run(%w[test unit --puppet-version 4.10.10 --pe-version 2018.1.1])
}.to exit_nonzero
end
end
end
15 changes: 15 additions & 0 deletions spec/unit/pdk/cli/validate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,19 @@
}.to exit_zero
end
end

context 'when --puppet-version and --pe-version are specified' do
before(:each) do
allow(PDK::Util::PuppetVersion).to receive(:find_gem_for).with('4.10.10').and_return('4.10.10')
allow(PDK::Util::PuppetVersion).to receive(:from_pe_version).with('2018.1.1').and_return('4.10.10')
end

it 'exits with an error' do
expect(logger).to receive(:error).with(a_string_matching(%r{both --puppet-version and --pe-version}i))

expect {
PDK::CLI.run(%w[validate --puppet-version 4.10.10 --pe-version 2018.1.1])
}.to exit_nonzero
end
end
end
Loading

0 comments on commit 127450f

Please sign in to comment.