diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index f3ffb57..2240a97 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,279 +1,281 @@ # Contribution guidelines ## Table of contents * [Contributing](#contributing) * [Writing proper commits - short version](#writing-proper-commits-short-version) * [Writing proper commits - long version](#writing-proper-commits-long-version) * [Dependencies](#dependencies) * [Note for OS X users](#note-for-os-x-users) * [The test matrix](#the-test-matrix) * [Syntax and style](#syntax-and-style) * [Running the unit tests](#running-the-unit-tests) * [Unit tests in docker](#unit-tests-in-docker) * [Integration tests](#integration-tests) This module has grown over time based on a range of contributions from people using it. If you follow these contributing guidelines your patch will likely make it into a release a little more quickly. ## Contributing Please note that this project is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. [Contributor Code of Conduct](https://voxpupuli.org/coc/). * Fork the repo. * Create a separate branch for your change. * We only take pull requests with passing tests, and documentation. [travis-ci](http://travis-ci.org) runs the tests for us. You can also execute them locally. This is explained [in a later section](#the-test-matrix). * Checkout [our docs](https://voxpupuli.org/docs/reviewing_pr/) we use to review a module and the [official styleguide](https://puppet.com/docs/puppet/6.0/style_guide.html). They provide some guidance for new code that might help you before you submit a pull request. * Add a test for your change. Only refactoring and documentation changes require no new tests. If you are adding functionality or fixing a bug, please add a test. * Squash your commits down into logical components. Make sure to rebase against our current master. * Push the branch to your fork and submit a pull request. Please be prepared to repeat some of these steps as our contributors review your code. ## Writing proper commits - short version * Make commits of logical units. * Check for unnecessary whitespace with "git diff --check" before committing. * Commit using Unix line endings (check the settings around "crlf" in git-config(1)). * Do not check in commented out code or unneeded files. * The first line of the commit message should be a short description (50 characters is the soft limit, excluding ticket number(s)), and should skip the full stop. * Associate the issue in the message. The first line should include the issue number in the form "(#XXXX) Rest of message". * The body should provide a meaningful commit message, which: *uses the imperative, present tense: `change`, not `changed` or `changes`. * includes motivation for the change, and contrasts its implementation with the previous behavior. * Make sure that you have tests for the bug you are fixing, or feature you are adding. * Make sure the test suites passes after your commit: * When introducing a new feature, make sure it is properly documented in the README.md ## Writing proper commits - long version 1. Make separate commits for logically separate changes. Please break your commits down into logically consistent units which include new or changed tests relevant to the rest of the change. The goal of doing this is to make the diff easier to read for whoever is reviewing your code. In general, the easier your diff is to read, the more likely someone will be happy to review it and get it into the code base. If you are going to refactor a piece of code, please do so as a separate commit from your feature or bug fix changes. We also really appreciate changes that include tests to make sure the bug is not re-introduced, and that the feature is not accidentally broken. Describe the technical detail of the change(s). If your description starts to get too long, that is a good sign that you probably need to split up your commit into more finely grained pieces. Commits which plainly describe the things which help reviewers check the patch and future developers understand the code are much more likely to be merged in with a minimum of bike-shedding or requested changes. Ideally, the commit message would include information, and be in a form suitable for inclusion in the release notes for the version of Puppet that includes them. Please also check that you are not introducing any trailing whitespace or other "whitespace errors". You can do this by running "git diff --check" on your changes before you commit. 2. Sending your patches To submit your changes via a GitHub pull request, we _highly_ recommend that you have them on a topic branch, instead of directly on `master`. It makes things much easier to keep track of, especially if you decide to work on another thing before your first change is merged in. GitHub has some pretty good [general documentation](http://help.github.com/) on using their site. They also have documentation on [creating pull requests](http://help.github.com/send-pull-requests/). In general, after pushing your topic branch up to your repository on GitHub, you can switch to the branch in the GitHub UI and click "Pull Request" towards the top of the page in order to open a pull request. 3. Update the related GitHub issue. If there is a GitHub issue associated with the change you submitted, then you should update the ticket to include the location of your branch, along with any other commentary you may wish to make. ## Dependencies The testing and development tools have a bunch of dependencies, all managed by [bundler](http://bundler.io/) according to the [Puppet support matrix](http://docs.puppetlabs.com/guides/platforms.html#ruby-versions). By default the tests use a baseline version of Puppet. If you have Ruby 2.x or want a specific version of Puppet, you must set an environment variable such as: ```sh export PUPPET_VERSION="~> 5.5.6" ``` You can install all needed gems for spec tests into the modules directory by running: ```sh bundle install --path .vendor/ --without development system_tests release --jobs "$(nproc)" ``` If you also want to run acceptance tests: ```sh bundle install --path .vendor/ --with system_tests --without development release --jobs "$(nproc)" ``` Our all in one solution if you don't know if you need to install or update gems: ```sh bundle install --path .vendor/ --with system_tests --without development release --jobs "$(nproc)"; bundle update; bundle clean ``` As an alternative to the `--jobs "$(nproc)` parameter, you can set an environment variable: ```sh BUNDLE_JOBS="$(nproc)" ``` ### Note for OS X users `nproc` isn't a valid command under OS x. As an alternative, you can do: ```sh --jobs "$(sysctl -n hw.ncpu)" ``` ## The test matrix ### Syntax and style The test suite will run [Puppet Lint](http://puppet-lint.com/) and [Puppet Syntax](https://github.com/gds-operations/puppet-syntax) to check various syntax and style things. You can run these locally with: ```sh bundle exec rake lint bundle exec rake validate ``` It will also run some [Rubocop](http://batsov.com/rubocop/) tests against it. You can run those locally ahead of time with: ```sh bundle exec rake rubocop ``` ### Running the unit tests The unit test suite covers most of the code, as mentioned above please add tests if you're adding new functionality. If you've not used [rspec-puppet](http://rspec-puppet.com/) before then feel free to ask about how best to test your new feature. To run the linter, the syntax checker and the unit tests: ```sh bundle exec rake test ``` To run your all the unit tests ```sh bundle exec rake spec ``` To run a specific spec test set the `SPEC` variable: ```sh bundle exec rake spec SPEC=spec/foo_spec.rb ``` #### Unit tests in docker Some people don't want to run the dependencies locally or don't want to install ruby. We ship a Dockerfile that enables you to run all unit tests and linting. You only need to run: ```sh docker build . ``` Please ensure that a docker daemon is running and that your user has the permission to talk to it. You can specify a remote docker host by setting the `DOCKER_HOST` environment variable. it will copy the content of the module into the docker image. So it will not work if a Gemfile.lock exists. ### Integration tests The unit tests just check the code runs, not that it does exactly what we want on a real machine. For that we're using [beaker](https://github.com/puppetlabs/beaker). This fires up a new virtual machine (using vagrant) and runs a series of simple tests against it after applying the module. You can run this with: ```sh bundle exec rake acceptance ``` This will run the tests on the module's default nodeset. You can override the nodeset used, e.g., ```sh BEAKER_set=centos-7-x64 bundle exec rake acceptance ``` There are default rake tasks for the various acceptance test modules, e.g., ```sh bundle exec rake beaker:centos-7-x64 bundle exec rake beaker:ssh:centos-7-x64 ``` If you don't want to have to recreate the virtual machine every time you can use `BEAKER_destroy=no` and `BEAKER_provision=no`. On the first run you will at least need `BEAKER_provision` set to yes (the default). The Vagrantfile for the created virtual machines will be in `.vagrant/beaker_vagrant_files`. Beaker also supports docker containers. We also use that in our automated CI pipeline at [travis-ci](http://travis-ci.org). To use that instead of Vagrant: ```sh -PUPPET_INSTALL_TYPE=agent BEAKER_IS_PE=no BEAKER_PUPPET_COLLECTION=puppet5 BEAKER_debug=true BEAKER_setfile=debian9-64{hypervisor=docker} BEAKER_destroy=yes bundle exec rake beaker +PUPPET_INSTALL_TYPE=agent BEAKER_IS_PE=no BEAKER_PUPPET_COLLECTION=puppet6 BEAKER_debug=true BEAKER_setfile=debian10-64{hypervisor=docker} BEAKER_destroy=yes bundle exec rake beaker ``` -You can replace the string `debian9` with any common operating system. +You can replace the string `debian10` with any common operating system. The following strings are known to work: * ubuntu1604 * ubuntu1804 * debian8 * debian9 +* debian10 * centos6 * centos7 +* centos8 The easiest way to debug in a docker container is to open a shell: ```sh docker exec -it -u root ${container_id_or_name} bash ``` The source of this file is in our [modulesync_config](https://github.com/voxpupuli/modulesync_config/blob/master/moduleroot/.github/CONTRIBUTING.md.erb) repository. diff --git a/.msync.yml b/.msync.yml index 11aed5f..8864fc0 100644 --- a/.msync.yml +++ b/.msync.yml @@ -1 +1 @@ -modulesync_config_version: '2.10.0' +modulesync_config_version: '2.12.0' diff --git a/.rubocop.yml b/.rubocop.yml index 5984ccc..c2ebc88 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,545 +1,546 @@ require: rubocop-rspec AllCops: - TargetRubyVersion: 2.1 +# Puppet Server 5 defaults to jruby 1.7 so TargetRubyVersion must stay at 1.9 until we drop support for puppet 5 + TargetRubyVersion: 1.9 Include: - ./**/*.rb Exclude: - files/**/* - vendor/**/* - .vendor/**/* - pkg/**/* - spec/fixtures/**/* - Gemfile - Rakefile - Guardfile - Vagrantfile Lint/ConditionPosition: Enabled: True Lint/ElseLayout: Enabled: True Lint/UnreachableCode: Enabled: True Lint/UselessComparison: Enabled: True Lint/EnsureReturn: Enabled: True Lint/HandleExceptions: Enabled: True Lint/LiteralInCondition: Enabled: True Lint/ShadowingOuterLocalVariable: Enabled: True Lint/LiteralInInterpolation: Enabled: True Style/HashSyntax: Enabled: True Style/RedundantReturn: Enabled: True Layout/EndOfLine: Enabled: False Lint/AmbiguousOperator: Enabled: True Lint/AssignmentInCondition: Enabled: True Layout/SpaceBeforeComment: Enabled: True Style/AndOr: Enabled: True Style/RedundantSelf: Enabled: True Metrics/BlockLength: Enabled: False # Method length is not necessarily an indicator of code quality Metrics/MethodLength: Enabled: False # Module length is not necessarily an indicator of code quality Metrics/ModuleLength: Enabled: False Style/WhileUntilModifier: Enabled: True Lint/AmbiguousRegexpLiteral: Enabled: True Security/Eval: Enabled: True Lint/BlockAlignment: Enabled: True Lint/DefEndAlignment: Enabled: True Lint/EndAlignment: Enabled: True Lint/DeprecatedClassMethods: Enabled: True Lint/Loop: Enabled: True Lint/ParenthesesAsGroupedExpression: Enabled: True Lint/RescueException: Enabled: True Lint/StringConversionInInterpolation: Enabled: True Lint/UnusedBlockArgument: Enabled: True Lint/UnusedMethodArgument: Enabled: True Lint/UselessAccessModifier: Enabled: True Lint/UselessAssignment: Enabled: True Lint/Void: Enabled: True Layout/AccessModifierIndentation: Enabled: True Style/AccessorMethodName: Enabled: True Style/Alias: Enabled: True Layout/AlignArray: Enabled: True Layout/AlignHash: Enabled: True Layout/AlignParameters: Enabled: True Metrics/BlockNesting: Enabled: True Style/AsciiComments: Enabled: True Style/Attr: Enabled: True Style/BracesAroundHashParameters: Enabled: True Style/CaseEquality: Enabled: True Layout/CaseIndentation: Enabled: True Style/CharacterLiteral: Enabled: True Style/ClassAndModuleCamelCase: Enabled: True Style/ClassAndModuleChildren: Enabled: False Style/ClassCheck: Enabled: True # Class length is not necessarily an indicator of code quality Metrics/ClassLength: Enabled: False Style/ClassMethods: Enabled: True Style/ClassVars: Enabled: True Style/WhenThen: Enabled: True Style/WordArray: Enabled: True Style/UnneededPercentQ: Enabled: True Layout/Tab: Enabled: True Layout/SpaceBeforeSemicolon: Enabled: True Layout/TrailingBlankLines: Enabled: True Layout/SpaceInsideBlockBraces: Enabled: True Layout/SpaceInsideBrackets: Enabled: True Layout/SpaceInsideHashLiteralBraces: Enabled: True Layout/SpaceInsideParens: Enabled: True Layout/LeadingCommentSpace: Enabled: True Layout/SpaceBeforeFirstArg: Enabled: True Layout/SpaceAfterColon: Enabled: True Layout/SpaceAfterComma: Enabled: True Layout/SpaceAfterMethodName: Enabled: True Layout/SpaceAfterNot: Enabled: True Layout/SpaceAfterSemicolon: Enabled: True Layout/SpaceAroundEqualsInParameterDefault: Enabled: True Layout/SpaceAroundOperators: Enabled: True Layout/SpaceBeforeBlockBraces: Enabled: True Layout/SpaceBeforeComma: Enabled: True Style/CollectionMethods: Enabled: True Layout/CommentIndentation: Enabled: True Style/ColonMethodCall: Enabled: True Style/CommentAnnotation: Enabled: True # 'Complexity' is very relative Metrics/CyclomaticComplexity: Enabled: False Style/ConstantName: Enabled: True Style/Documentation: Enabled: False Style/DefWithParentheses: Enabled: True Style/PreferredHashMethods: Enabled: True Layout/DotPosition: EnforcedStyle: trailing Style/DoubleNegation: Enabled: True Style/EachWithObject: Enabled: True Layout/EmptyLineBetweenDefs: Enabled: True Layout/IndentArray: Enabled: True Layout/IndentHash: Enabled: True Layout/IndentationConsistency: Enabled: True Layout/IndentationWidth: Enabled: True Layout/EmptyLines: Enabled: True Layout/EmptyLinesAroundAccessModifier: Enabled: True Style/EmptyLiteral: Enabled: True # Configuration parameters: AllowURI, URISchemes. Metrics/LineLength: Enabled: False Style/MethodCallWithoutArgsParentheses: Enabled: True Style/MethodDefParentheses: Enabled: True Style/LineEndConcatenation: Enabled: True Layout/TrailingWhitespace: Enabled: True Style/StringLiterals: Enabled: True Style/TrailingCommaInArguments: Enabled: True Style/TrailingCommaInLiteral: Enabled: True Style/GlobalVars: Enabled: True Style/GuardClause: Enabled: True Style/IfUnlessModifier: Enabled: True Style/MultilineIfThen: Enabled: True Style/NegatedIf: Enabled: True Style/NegatedWhile: Enabled: True Style/Next: Enabled: True Style/SingleLineBlockParams: Enabled: True Style/SingleLineMethods: Enabled: True Style/SpecialGlobalVars: Enabled: True Style/TrivialAccessors: Enabled: True Style/UnlessElse: Enabled: True Style/VariableInterpolation: Enabled: True Style/VariableName: Enabled: True Style/WhileUntilDo: Enabled: True Style/EvenOdd: Enabled: True Style/FileName: Enabled: True Style/For: Enabled: True Style/Lambda: Enabled: True Style/MethodName: Enabled: True Style/MultilineTernaryOperator: Enabled: True Style/NestedTernaryOperator: Enabled: True Style/NilComparison: Enabled: True Style/FormatString: Enabled: True Style/MultilineBlockChain: Enabled: True Style/Semicolon: Enabled: True Style/SignalException: Enabled: True Style/NonNilCheck: Enabled: True Style/Not: Enabled: True Style/NumericLiterals: Enabled: True Style/OneLineConditional: Enabled: True Style/OpMethod: Enabled: True Style/ParenthesesAroundCondition: Enabled: True Style/PercentLiteralDelimiters: Enabled: True Style/PerlBackrefs: Enabled: True Style/PredicateName: Enabled: True Style/RedundantException: Enabled: True Style/SelfAssignment: Enabled: True Style/Proc: Enabled: True Style/RaiseArgs: Enabled: True Style/RedundantBegin: Enabled: True Style/RescueModifier: Enabled: True # based on https://github.com/voxpupuli/modulesync_config/issues/168 Style/RegexpLiteral: EnforcedStyle: percent_r Enabled: True Lint/UnderscorePrefixedVariableName: Enabled: True Metrics/ParameterLists: Enabled: False Lint/RequireParentheses: Enabled: True Style/ModuleFunction: Enabled: True Lint/Debugger: Enabled: True Style/IfWithSemicolon: Enabled: True Style/Encoding: Enabled: True Style/BlockDelimiters: Enabled: True Layout/MultilineBlockLayout: Enabled: True # 'Complexity' is very relative Metrics/AbcSize: Enabled: False # 'Complexity' is very relative Metrics/PerceivedComplexity: Enabled: False Lint/UselessAssignment: Enabled: True Layout/ClosingParenthesisIndentation: Enabled: True # RSpec RSpec/BeforeAfterAll: Exclude: - spec/acceptance/**/* # We don't use rspec in this way RSpec/DescribeClass: Enabled: False # Example length is not necessarily an indicator of code quality RSpec/ExampleLength: Enabled: False RSpec/NamedSubject: Enabled: False # disabled for now since they cause a lot of issues # these issues aren't easy to fix RSpec/RepeatedDescription: Enabled: False RSpec/NestedGroups: Enabled: False # this is broken on ruby1.9 Layout/IndentHeredoc: Enabled: False # disable Yaml safe_load. This is needed to support ruby2.0.0 development envs Security/YAMLLoad: Enabled: false # This affects hiera interpolation, as well as some configs that we push. Style/FormatStringToken: Enabled: false # This is useful, but sometimes a little too picky about where unit tests files # are located. RSpec/FilePath: Enabled: false diff --git a/Gemfile b/Gemfile index 50a9030..4d187cc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,85 +1,68 @@ source ENV['GEM_SOURCE'] || "https://rubygems.org" def location_for(place, fake_version = nil) if place =~ /^(git[:@][^#]*)#(.*)/ [fake_version, { :git => $1, :branch => $2, :require => false }].compact elsif place =~ /^file:\/\/(.*)/ ['>= 0', { :path => File.expand_path($1), :require => false }] else [place, { :require => false }] end end group :test do - gem 'puppetlabs_spec_helper', '>= 2.14.0', :require => false - gem 'rspec-puppet-facts', '>= 1.9.5', :require => false - gem 'rspec-puppet-utils', :require => false - gem 'puppet-lint-leading_zero-check', :require => false - gem 'puppet-lint-trailing_comma-check', :require => false - gem 'puppet-lint-version_comparison-check', :require => false - gem 'puppet-lint-classes_and_types_beginning_with_digits-check', :require => false - gem 'puppet-lint-unquoted_string-check', :require => false - gem 'puppet-lint-variable_contains_upcase', :require => false - gem 'puppet-lint-absolute_classname-check', '>= 2.0.0', :require => false - gem 'puppet-lint-topscope-variable-check', :require => false - gem 'puppet-lint-legacy_facts-check', :require => false - gem 'puppet-lint-anchor-check', :require => false - gem 'metadata-json-lint', :require => false - gem 'redcarpet', :require => false - gem 'rubocop', '~> 0.49.1', :require => false - gem 'rubocop-rspec', '~> 1.15.0', :require => false - gem 'mocha', '~> 1.4.0', :require => false - gem 'coveralls', :require => false - gem 'simplecov-console', :require => false - gem 'parallel_tests', :require => false + gem 'voxpupuli-test', :require => false + gem 'coveralls', :require => false + gem 'simplecov-console', :require => false end group :development do gem 'travis', :require => false gem 'travis-lint', :require => false gem 'guard-rake', :require => false gem 'overcommit', '>= 0.39.1', :require => false end group :system_tests do gem 'winrm', :require => false if beaker_version = ENV['BEAKER_VERSION'] gem 'beaker', *location_for(beaker_version) else gem 'beaker', '>= 4.2.0', :require => false end if beaker_rspec_version = ENV['BEAKER_RSPEC_VERSION'] gem 'beaker-rspec', *location_for(beaker_rspec_version) else gem 'beaker-rspec', :require => false end gem 'serverspec', :require => false gem 'beaker-hostgenerator', '>= 1.1.22', :require => false gem 'beaker-docker', :require => false gem 'beaker-puppet', :require => false gem 'beaker-puppet_install_helper', :require => false gem 'beaker-module_install_helper', :require => false gem 'rbnacl', '>= 4', :require => false gem 'rbnacl-libsodium', :require => false gem 'bcrypt_pbkdf', :require => false + gem 'ed25519', :require => false end group :release do gem 'github_changelog_generator', :require => false, :git => 'https://github.com/voxpupuli/github-changelog-generator', :branch => 'voxpupuli_essential_fixes' gem 'puppet-blacksmith', :require => false gem 'voxpupuli-release', :require => false gem 'puppet-strings', '>= 2.2', :require => false end if facterversion = ENV['FACTER_GEM_VERSION'] gem 'facter', facterversion.to_s, :require => false, :groups => [:test] else gem 'facter', :require => false, :groups => [:test] end ENV['PUPPET_VERSION'].nil? ? puppetversion = '~> 6.0' : puppetversion = ENV['PUPPET_VERSION'].to_s gem 'puppet', puppetversion, :require => false, :groups => [:test] # vim: syntax=ruby diff --git a/Rakefile b/Rakefile index c0f2d37..b450fe7 100644 --- a/Rakefile +++ b/Rakefile @@ -1,85 +1,54 @@ -require 'puppetlabs_spec_helper/rake_tasks' +require 'voxpupuli/test/rake' # load optional tasks for releases # only available if gem group releases is installed begin require 'voxpupuli/release/rake_tasks' rescue LoadError end -PuppetLint.configuration.log_format = '%{path}:%{line}:%{check}:%{KIND}:%{message}' - -desc 'Auto-correct puppet-lint offenses' -task 'lint:auto_correct' do - Rake::Task[:lint_fix].invoke -end - -desc 'Run acceptance tests' -RSpec::Core::RakeTask.new(:acceptance) do |t| - t.pattern = 'spec/acceptance' -end - -desc 'Run tests' -task test: [:release_checks] - -namespace :check do - desc 'Check for trailing whitespace' - task :trailing_whitespace do - Dir.glob('**/*.md', File::FNM_DOTMATCH).sort.each do |filename| - next if filename =~ %r{^((modules|acceptance|\.?vendor|spec/fixtures|pkg)/|REFERENCE.md)} - File.foreach(filename).each_with_index do |line, index| - if line =~ %r{\s\n$} - puts "#{filename} has trailing whitespace on line #{index + 1}" - exit 1 - end - end - end - end -end -Rake::Task[:release_checks].enhance ['check:trailing_whitespace'] - desc "Run main 'test' task and report merged results to coveralls" task test_with_coveralls: [:test] do if Dir.exist?(File.expand_path('../lib', __FILE__)) require 'coveralls/rake/task' Coveralls::RakeTask.new Rake::Task['coveralls:push'].invoke else puts 'Skipping reporting to coveralls. Module has no lib dir' end end desc 'Generate REFERENCE.md' task :reference, [:debug, :backtrace] do |t, args| patterns = '' Rake::Task['strings:generate:reference'].invoke(patterns, args[:debug], args[:backtrace]) end begin require 'github_changelog_generator/task' GitHubChangelogGenerator::RakeTask.new :changelog do |config| version = (Blacksmith::Modulefile.new).version config.future_release = "v#{version}" if version =~ /^\d+\.\d+.\d+$/ config.header = "# Changelog\n\nAll notable changes to this project will be documented in this file.\nEach new release typically also includes the latest modulesync defaults.\nThese should not affect the functionality of the module." config.exclude_labels = %w{duplicate question invalid wontfix wont-fix modulesync skip-changelog} config.user = 'voxpupuli' metadata_json = File.join(File.dirname(__FILE__), 'metadata.json') metadata = JSON.load(File.read(metadata_json)) config.project = metadata['name'] end # Workaround for https://github.com/github-changelog-generator/github-changelog-generator/issues/715 require 'rbconfig' if RbConfig::CONFIG['host_os'] =~ /linux/ task :changelog do puts 'Fixing line endings...' changelog_file = File.join(__dir__, 'CHANGELOG.md') changelog_txt = File.read(changelog_file) new_contents = changelog_txt.gsub(%r{\r\n}, "\n") File.open(changelog_file, "w") {|file| file.puts new_contents } end end rescue LoadError end # vim: syntax=ruby diff --git a/lib/puppet/type/archive.rb b/lib/puppet/type/archive.rb index b3c85fb..b6285c6 100644 --- a/lib/puppet/type/archive.rb +++ b/lib/puppet/type/archive.rb @@ -1,284 +1,284 @@ require 'pathname' require 'uri' require 'puppet/util' require 'puppet/parameter/boolean' Puppet::Type.newtype(:archive) do @doc = 'Manage archive file download, extraction, and cleanup.' ensurable do desc 'whether archive file should be present/absent (default: present)' newvalue(:present) do provider.create end newvalue(:absent) do provider.destroy end defaultto(:present) # The following changes allows us to notify if the resource is being replaced def is_to_s(value) # rubocop:disable Style/PredicateName return "(#{resource[:checksum_type]})#{provider.archive_checksum}" if provider.archive_checksum super end def should_to_s(value) return "(#{resource[:checksum_type]})#{resource[:checksum]}" if provider.archive_checksum super end def change_to_s(currentvalue, newvalue) if currentvalue == :absent || currentvalue.nil? extract = resource[:extract] == :true ? "and extracted in #{resource[:extract_path]}" : '' cleanup = resource[:cleanup] == :true ? 'with cleanup' : 'without cleanup' if provider.archive_checksum "replace archive: #{provider.archive_filepath} from #{is_to_s(currentvalue)} to #{should_to_s(newvalue)}" else "download archive from #{resource[:source]} to #{provider.archive_filepath} #{extract} #{cleanup}" end elsif newvalue == :absent "remove archive: #{provider.archive_filepath} " else super end rescue StandardError super end end newparam(:path, namevar: true) do desc 'namevar, archive file fully qualified file path.' validate do |value| unless Puppet::Util.absolute_path? value raise ArgumentError, "archive path must be absolute: #{value}" end end end newparam(:filename) do desc 'archive file name (derived from path).' end newparam(:extract) do desc 'whether archive will be extracted after download (true|false).' newvalues(:true, :false) defaultto(:false) end newparam(:extract_path) do desc 'target folder path to extract archive.' validate do |value| unless Puppet::Util.absolute_path? value raise ArgumentError, "archive extract_path must be absolute: #{value}" end end end newparam(:target) do desc 'target folder path to extract archive. (this parameter is for camptocamp/archive compatibility)' validate do |value| unless Puppet::Util.absolute_path? value raise ArgumentError, "archive extract_path must be absolute: #{value}" end end munge do |val| resource[:extract_path] = val end end newparam(:extract_command) do desc "custom extraction command ('tar xvf example.tar.gz'), also support sprintf format ('tar xvf %s') which will be processed with the filename: sprintf('tar xvf %s', filename)" end newparam(:temp_dir) do desc 'Specify an alternative temporary directory to use for copying files, if unset then the operating system default will be used.' validate do |value| unless Puppet::Util.absolute_path?(value) raise ArgumentError, "Invalid temp_dir #{value}" end end end newparam(:extract_flags) do desc "custom extraction options, this replaces the default flags. A string such as 'xvf' for a tar file would replace the default xf flag. A hash is useful when custom flags are needed for different platforms. {'tar' => 'xzf', '7z' => 'x -aot'}." defaultto(:undef) end newproperty(:creates) do desc 'if file/directory exists, will not download/extract archive.' def should_to_s(value) "extracting in #{resource[:extract_path]} to create #{value}" end end newparam(:cleanup) do desc 'whether archive file will be removed after extraction (true|false).' newvalues(:true, :false) defaultto(:true) end newparam(:source) do desc 'archive file source, supports puppet|http|https|ftp|file|s3 uri.' validate do |value| unless value =~ URI.regexp(%w[puppet http https ftp file s3]) || Puppet::Util.absolute_path?(value) raise ArgumentError, "invalid source url: #{value}" end end end newparam(:url) do desc 'archive file source, supports http|https|ftp|file uri. (for camptocamp/archive compatibility)' validate do |value| unless value =~ URI.regexp(%w[http https file ftp]) raise ArgumentError, "invalid source url: #{value}" end end munge do |val| resource[:source] = val end end newparam(:cookie) do desc 'archive file download cookie.' end newparam(:checksum) do desc 'archive file checksum (match checksum_type).' newvalues(%r{\b[0-9a-f]{5,128}\b}, :true, :false, :undef, nil, '') munge do |val| if val.nil? || val.empty? || val == :undef :false - elsif %i[true false].include? val + elsif [:true, :false].include? val resource[:checksum_verify] = val else val end end end newparam(:digest_string) do desc 'archive file checksum (match checksum_type) (this parameter is for camptocamp/archive compatibility).' newvalues(%r{\b[0-9a-f]{5,128}\b}) munge do |val| if !val.nil? && !val.empty? resource[:checksum] = val else val end end end newparam(:checksum_url) do desc 'archive file checksum source (instead of specifying checksum)' end newparam(:digest_url) do desc 'archive file checksum source (instead of specifying checksum) (this parameter is for camptocamp/archive compatibility)' munge do |val| resource[:checksum_url] = val end end newparam(:checksum_type) do desc 'archive file checksum type (none|md5|sha1|sha2|sha256|sha384|sha512).' newvalues(:none, :md5, :sha1, :sha2, :sha256, :sha384, :sha512) defaultto(:none) end newparam(:digest_type) do desc 'archive file checksum type (none|md5|sha1|sha2|sha256|sha384|sha512) (this parameter is camptocamp/archive compatibility).' newvalues(:none, :md5, :sha1, :sha2, :sha256, :sha384, :sha512) munge do |val| resource[:checksum_type] = val end end newparam(:checksum_verify) do desc 'whether checksum wil be verified (true|false).' newvalues(:true, :false) defaultto(:true) end newparam(:username) do desc 'username to download source file.' end newparam(:password) do desc 'password to download source file.' end newparam(:user) do desc 'extract command user (using this option will configure the archive file permission to 0644 so the user can read the file).' end newparam(:group) do desc 'extract command group (using this option will configure the archive file permisison to 0644 so the user can read the file).' end newparam(:proxy_type) do desc 'proxy type (none|ftp|http|https)' newvalues(:none, :ftp, :http, :https) end newparam(:proxy_server) do desc 'proxy address to use when accessing source' end newparam(:allow_insecure, boolean: true, parent: Puppet::Parameter::Boolean) do desc 'ignore HTTPS certificate errors' defaultto :false end newparam(:download_options) do desc 'provider download options (affects curl, wget, and only s3 downloads for ruby provider)' validate do |val| unless val.is_a?(::String) || val.is_a?(::Array) raise ArgumentError, "download_options should be String or Array: #{val}" end end munge do |val| case val when ::String [val] else val end end end autorequire(:file) do [ Pathname.new(self[:path]).parent.to_s, self[:extract_path], '/root/.aws/config', '/root/.aws/credentials' ].compact end autorequire(:exec) do ['install_aws_cli'] end validate do filepath = Pathname.new(self[:path]) self[:filename] = filepath.basename.to_s if !self[:source].nil? && !self[:url].nil? && self[:source] != self[:url] raise ArgumentError, "invalid parameter: url (#{self[:url]}) and source (#{self[:source]}) are mutually exclusive." end if !self[:checksum_url].nil? && !self[:digest_url].nil? && self[:checksum_url] != self[:digest_url] raise ArgumentError, "invalid parameter: checksum_url (#{self[:checksum_url]}) and digest_url (#{self[:digest_url]}) are mutually exclusive." end if self[:proxy_server] self[:proxy_type] ||= URI(self[:proxy_server]).scheme.to_sym else self[:proxy_type] = :none end end end diff --git a/lib/puppet_x/bodeco/util.rb b/lib/puppet_x/bodeco/util.rb index 90cca6c..b7da192 100644 --- a/lib/puppet_x/bodeco/util.rb +++ b/lib/puppet_x/bodeco/util.rb @@ -1,180 +1,180 @@ module PuppetX module Bodeco module Util def self.download(url, filepath, options = {}) uri = URI(url) @connection = PuppetX::Bodeco.const_get(uri.scheme.upcase).new("#{uri.scheme}://#{uri.host}:#{uri.port}", options) @connection.download(uri, filepath) end def self.content(url, options = {}) uri = URI(url) @connection = PuppetX::Bodeco.const_get(uri.scheme.upcase).new("#{uri.scheme}://#{uri.host}:#{uri.port}", options) @connection.content(uri) end # # This allows you to use a puppet syntax for a file and return its content. # # @example # puppet_download 'puppet:///modules/my_module_name/my_file.dat # # @param [String] url this is the puppet url of the file to be fetched # @param [String] filepath this is path of the file to create # # @raise [ArgumentError] when the file doesn't exist # def self.puppet_download(url, filepath) # Somehow there is no consistent way to determine what terminus to use. So we switch to a # trial and error method. First we start withe the default. And if it doesn't work, we try the # other ones status = load_file_with_any_terminus(url) raise ArgumentError, "Could not retrieve information from environment #{Puppet['environment']} source(s) #{url}'" unless status File.open(filepath, 'w') { |file| file.write(status.content) } end # @private # rubocop:disable HandleExceptions def self.load_file_with_any_terminus(url) - termini_to_try = %i[file_server rest] + termini_to_try = [:file_server, :rest] termini_to_try.each do |terminus| with_terminus(terminus) do begin content = Puppet::FileServing::Content.indirection.find(url) rescue SocketError, Timeout::Error, Errno::ECONNREFUSED, Errno::EHOSTDOWN, Errno::EHOSTUNREACH, Errno::ETIMEDOUT # rescue any network error end return content if content end end nil end # rubocop:enable HandleExceptions def self.with_terminus(terminus) old_terminus = Puppet[:default_file_terminus] Puppet[:default_file_terminus] = terminus value = yield Puppet[:default_file_terminus] = old_terminus value end end class HTTP require 'net/http' FOLLOW_LIMIT = 5 URI_UNSAFE = %r{[^\-_.!~*'()a-zA-Z\d;\/?:@&=+$,\[\]%]} def initialize(_url, options) @username = options[:username] @password = options[:password] @cookie = options[:cookie] @insecure = options[:insecure] if options[:proxy_server] uri = URI(options[:proxy_server]) unless uri.scheme uri = URI("#{options[:proxy_type]}://#{options[:proxy_server]}") end @proxy_addr = uri.hostname @proxy_port = uri.port end ENV['SSL_CERT_FILE'] = File.expand_path(File.join(__FILE__, '..', 'cacert.pem')) if Facter.value(:osfamily) == 'windows' && !ENV.key?('SSL_CERT_FILE') end def generate_request(uri) header = @cookie && { 'Cookie' => @cookie } request = Net::HTTP::Get.new(uri.request_uri, header) request.basic_auth(@username, @password) if @username && @password request end def follow_redirect(uri, option = { limit: FOLLOW_LIMIT }, &block) http_opts = if uri.scheme == 'https' { use_ssl: true, verify_mode: (@insecure ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER) } else { use_ssl: false } end Net::HTTP.start(uri.host, uri.port, @proxy_addr, @proxy_port, http_opts) do |http| http.request(generate_request(uri)) do |response| case response when Net::HTTPSuccess yield response when Net::HTTPRedirection limit = option[:limit] - 1 raise Puppet::Error, "Redirect limit exceeded, last url: #{uri}" if limit < 0 location = safe_escape(response['location']) new_uri = URI(location) new_uri = URI(uri.to_s + location) if new_uri.relative? follow_redirect(new_uri, limit: limit, &block) else raise Puppet::Error, "HTTP Error Code #{response.code}\nURL: #{uri}\nContent:\n#{response.body}" end end end end def download(uri, file_path, option = { limit: FOLLOW_LIMIT }) follow_redirect(uri, option) do |response| File.open file_path, 'wb' do |io| response.read_body do |chunk| io.write chunk end end end end def content(uri, option = { limit: FOLLOW_LIMIT }) follow_redirect(uri, option) do |response| return response.body end end def safe_escape(uri) uri.to_s.gsub(URI_UNSAFE) do |match| '%' + match.unpack('H2' * match.bytesize).join('%').upcase end end end class HTTPS < HTTP end class FTP require 'net/ftp' def initialize(url, options) uri = URI(url) username = options[:username] password = options[:password] proxy_server = options[:proxy_server] proxy_type = options[:proxy_type] ENV["#{proxy_type}_proxy"] = proxy_server @ftp = Net::FTP.new @ftp.connect(uri.host, uri.port) if username @ftp.login(username, password) else @ftp.login end end def download(uri, file_path) @ftp.getbinaryfile(uri.path, file_path) end end class FILE def initialize(_url, _options) end def download(uri, file_path) FileUtils.copy(uri.path, file_path) end end end end diff --git a/spec/functions/archive_go_md5_spec.rb b/spec/functions/archive_go_md5_spec.rb index d880bdb..8081de0 100644 --- a/spec/functions/archive_go_md5_spec.rb +++ b/spec/functions/archive_go_md5_spec.rb @@ -1,21 +1,21 @@ require 'spec_helper' describe 'archive::go_md5' do let(:example_md5) { File.read(fixtures('checksum', 'gocd.md5')) } let(:url) { 'https://gocd.lan/path/file.md5' } let(:uri) { URI(url) } it { is_expected.not_to eq(nil) } it 'retreives file md5' do - PuppetX::Bodeco::Util.stubs(:content).with(uri, username: 'user', password: 'pass').returns(example_md5) + allow(PuppetX::Bodeco::Util).to receive(:content).with(uri, username: 'user', password: 'pass').and_return(example_md5) is_expected.to run.with_params('user', 'pass', 'filea', url).and_return('283158c7da8c0ada74502794fa8745eb') end context 'when file doesn\'t exist' do it 'raises error' do - PuppetX::Bodeco::Util.stubs(:content).with(uri, username: 'user', password: 'pass').returns(example_md5) + allow(PuppetX::Bodeco::Util).to receive(:content).with(uri, username: 'user', password: 'pass').and_return(example_md5) is_expected.to run.with_params('user', 'pass', 'non-existent-file', url).and_raise_error(RuntimeError, "Could not parse md5 from url https://gocd\.lan/path/file\.md5 response: #{example_md5}") end end end diff --git a/spec/functions/artifactory_checksum_spec.rb b/spec/functions/artifactory_checksum_spec.rb index e6ad09a..e8ea2b1 100644 --- a/spec/functions/artifactory_checksum_spec.rb +++ b/spec/functions/artifactory_checksum_spec.rb @@ -1,21 +1,20 @@ require 'spec_helper' describe 'archive::artifactory_checksum' do + let(:example_json) { File.read(fixtures('checksum', 'artifactory.json')) } + let(:url) { 'https://repo.jfrog.org/artifactory/distributions/images/Artifactory_120x75.png' } + let(:uri) { URI(url.sub('/artifactory/', '/artifactory/api/storage/')) } + it { is_expected.not_to eq(nil) } it { is_expected.to run.with_params.and_raise_error(ArgumentError) } it { is_expected.to run.with_params('not_a_url').and_raise_error(ArgumentError) } - - example_json = File.read(fixtures('checksum', 'artifactory.json')) - url = 'https://repo.jfrog.org/artifactory/distributions/images/Artifactory_120x75.png' - uri = URI(url.sub('/artifactory/', '/artifactory/api/storage/')) - it 'defaults to and parses sha1' do - PuppetX::Bodeco::Util.stubs(:content).with(uri).returns(example_json) + allow(PuppetX::Bodeco::Util).to receive(:content).with(uri).and_return(example_json) is_expected.to run.with_params(url).and_return('a359e93636e81f9dd844b2dfb4b89fa876e5d4fa') end it 'parses md5' do - PuppetX::Bodeco::Util.stubs(:content).with(uri).returns(example_json) + allow(PuppetX::Bodeco::Util).to receive(:content).with(uri).and_return(example_json) is_expected.to run.with_params(url, 'md5').and_return('00f32568be85929fe95be38f9f5f3519') end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e7cb1d5..60c7ce7 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,20 +1,21 @@ +RSpec.configure do |c| + c.formatter = 'documentation' + c.mock_framework = :rspec +end + require 'puppetlabs_spec_helper/module_spec_helper' + require 'rspec-puppet-utils' require 'rspec/mocks' require 'rspec-puppet-facts' include RspecPuppetFacts unless RUBY_VERSION =~ %r{^1.9} require 'coveralls' Coveralls.wear! end # # Require all support files # Dir['./spec/support/**/*.rb'].sort.each { |f| require f } - -RSpec.configure do |c| - c.formatter = 'documentation' - c.mock_framework = :rspec -end diff --git a/spec/support/shared_contexts.rb b/spec/support/shared_contexts.rb index 7147b9f..c5141dd 100644 --- a/spec/support/shared_contexts.rb +++ b/spec/support/shared_contexts.rb @@ -1,6 +1,24 @@ +require 'tmpdir' +require 'pathname' + shared_context :some_context do # example only, let(:hiera_data) do {} end end + +shared_context 'uses temp dir' do + around do |example| + Dir.mktmpdir('rspec-') do |dir| + @temp_dir = dir + example.run + end + end + + attr_reader :temp_dir + + def temp_dir_path + Pathname(temp_dir) + end +end diff --git a/spec/unit/facter/archive_windir_spec.rb b/spec/unit/facter/archive_windir_spec.rb index 36725d2..9c5c5d5 100644 --- a/spec/unit/facter/archive_windir_spec.rb +++ b/spec/unit/facter/archive_windir_spec.rb @@ -1,25 +1,26 @@ require 'spec_helper' require 'facter/archive_windir' describe 'archive_windir fact specs', type: :fact do before { Facter.clear } after { Facter.clear } + subject { Facter.fact(:archive_windir).value } context 'RedHat' do before do - Facter.fact(:osfamily).stubs(:value).returns 'RedHat' + allow(Facter.fact(:osfamily)).to receive(:value).and_return('RedHat') end it 'is nil on RedHat' do - expect(Facter.fact(:archive_windir).value).to be_nil + is_expected.to be_nil end end context 'Windows' do before do - Facter.fact(:osfamily).stubs(:value).returns 'windows' + allow(Facter.fact(:osfamily)).to receive(:value).and_return('windows') end it 'defaults to C:\\staging on windows' do - expect(Facter.fact(:archive_windir).value).to eq('C:\\staging') + is_expected.to eq('C:\\staging') end end end diff --git a/spec/unit/puppet/type/archive_spec.rb b/spec/unit/puppet/type/archive_spec.rb index fa8517b..a6806eb 100644 --- a/spec/unit/puppet/type/archive_spec.rb +++ b/spec/unit/puppet/type/archive_spec.rb @@ -1,161 +1,161 @@ require 'spec_helper' require 'puppet' describe Puppet::Type.type(:archive) do let(:resource) do Puppet::Type.type(:archive).new( path: '/tmp/example.zip', source: 'http://home.lan/example.zip' ) end context 'resource defaults' do it { expect(resource[:path]).to eq '/tmp/example.zip' } it { expect(resource[:name]).to eq '/tmp/example.zip' } it { expect(resource[:filename]).to eq 'example.zip' } it { expect(resource[:extract]).to eq :false } it { expect(resource[:cleanup]).to eq :true } it { expect(resource[:checksum_type]).to eq :none } it { expect(resource[:digest_type]).to eq nil } it { expect(resource[:checksum_verify]).to eq :true } it { expect(resource[:extract_flags]).to eq :undef } it { expect(resource[:allow_insecure]).to eq false } it { expect(resource[:download_options]).to eq nil } it { expect(resource[:temp_dir]).to eq nil } end it 'verify resource[:path] is absolute filepath' do expect do resource[:path] = 'relative/file' end.to raise_error(Puppet::Error, %r{archive path must be absolute: }) end it 'verify resource[:temp_dir] is absolute filetemp_dir' do expect do resource[:temp_dir] = 'relative/file' end.to raise_error(Puppet::Error, %r{Invalid temp_dir}) end describe 'on posix', if: Puppet.features.posix? do it 'accepts valid resource[:source]' do expect do resource[:source] = 'http://home.lan/example.zip' resource[:source] = 'https://home.lan/example.zip' resource[:source] = 'ftp://home.lan/example.zip' resource[:source] = 's3://home.lan/example.zip' resource[:source] = '/tmp/example.zip' end.not_to raise_error end %w[ afp://home.lan/example.zip \tmp D:/example.zip ].each do |s| it 'rejects invalid resource[:source]' do expect do resource[:source] = s end.to raise_error(Puppet::Error, %r{invalid source url: }) end end end describe 'on windows', if: Puppet.features.microsoft_windows? do it 'accepts valid windows resource[:source]' do expect do resource[:source] = 'D:/example.zip' end.not_to raise_error end %w[ /tmp/example.zip \Z: ].each do |s| it 'rejects invalid windows resource[:source]' do expect do resource[:source] = s end.to raise_error(Puppet::Error, %r{invalid source url: }) end end end %w[ 557e2ebb67b35d1fddff18090b6bc26b 557e2ebb67b35d1fddff18090b6bc26557e2ebb67b35d1fddff18090b6bc26bb ].each do |cs| it 'accepts valid resource[:checksum]' do expect do resource[:checksum] = cs end.not_to raise_error end end %w[ z57e2ebb67b35d1fddff18090b6bc26b 557e ].each do |cs| it 'rejects bad checksum' do expect do resource[:checksum] = cs end.to raise_error(Puppet::Error, %r{Invalid value}) end end it 'accepts valid resource[:checksum_type]' do expect do - %i[none md5 sha1 sha2 sha256 sha384 sha512].each do |type| + [:none, :md5, :sha1, :sha2, :sha256, :sha384, :sha512].each do |type| resource[:checksum_type] = type end end.not_to raise_error end it 'rejects invalid resource[:checksum_type]' do expect do resource[:checksum_type] = :crc32 end.to raise_error(Puppet::Error, %r{Invalid value}) end it 'verify resource[:allow_insecure] is valid' do expect do - %i[true false yes no].each do |type| + [:true, :false, :yes, :no].each do |type| resource[:allow_insecure] = type end end.not_to raise_error end it 'verify resource[:download_options] is valid' do expect do ['--tlsv1', ['--region', 'eu-central-1']].each do |type| resource[:download_options] = type end end.not_to raise_error end describe 'archive autorequire' do let(:file_resource) { Puppet::Type.type(:file).new(name: '/tmp') } let(:archive_resource) do described_class.new( path: '/tmp/example.zip', source: 'http://home.lan/example.zip' ) end let(:auto_req) do catalog = Puppet::Resource::Catalog.new catalog.add_resource file_resource catalog.add_resource archive_resource archive_resource.autorequire end it 'creates relationship' do expect(auto_req.size).to be 1 end it 'links to archive resource' do expect(auto_req[0].target).to eql archive_resource end it 'autorequires parent directory' do expect(auto_req[0].source).to eql file_resource end end end diff --git a/spec/unit/puppet_x/bodeco/archive_spec.rb b/spec/unit/puppet_x/bodeco/archive_spec.rb index 8d96e1b..f4efd0f 100644 --- a/spec/unit/puppet_x/bodeco/archive_spec.rb +++ b/spec/unit/puppet_x/bodeco/archive_spec.rb @@ -1,101 +1,152 @@ -# rubocop:disable RSpec/MultipleExpectations require 'spec_helper' require 'puppet_x/bodeco/archive' describe PuppetX::Bodeco::Archive do let(:zipfile) do - File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'files', 'test.zip')) + File.expand_path(File.join(__dir__, '..', '..', '..', '..', 'files', 'test.zip')) end - it '#checksum' do - Dir.mktmpdir do |dir| - tempfile = File.join(dir, 'test.zip') - FileUtils.cp(zipfile, tempfile) + describe '#checksum' do + include_context 'uses temp dir' - archive = described_class.new(tempfile) - expect(archive.checksum(:none)).to be nil - expect(archive.checksum(:md5)).to eq '557e2ebb67b35d1fddff18090b6bc26b' - expect(archive.checksum(:sha1)).to eq '377ec712d7fdb7266221db3441e3af2055448ead' - end - end + subject { described_class.new(tempfile) } + + let(:tempfile) { File.join(temp_dir, 'test.zip') } - it '#parse_flags' do - archive = described_class.new('test.tar.gz') - expect(archive.send(:parse_flags, 'xf', :undef, 'tar')).to eq 'xf' - expect(archive.send(:parse_flags, 'xf', 'xvf', 'tar')).to eq 'xvf' - expect(archive.send(:parse_flags, 'xf', { 'tar' => 'xzf', '7z' => '-y x' }, 'tar')).to eq 'xzf' + before { FileUtils.cp(zipfile, tempfile) } + it { expect(subject.checksum(:none)).to be nil } + it { expect(subject.checksum(:md5)).to eq '557e2ebb67b35d1fddff18090b6bc26b' } + it { expect(subject.checksum(:sha1)).to eq '377ec712d7fdb7266221db3441e3af2055448ead' } end - it '#command on RedHat' do - Facter.stubs(:value).with(:osfamily).returns 'RedHat' - - tar = described_class.new('test.tar.gz') - expect(tar.send(:command, :undef)).to eq 'tar xzf test.tar.gz' - expect(tar.send(:command, 'xvf')).to eq 'tar xvf test.tar.gz' - tar = described_class.new('test.tar.bz2') - expect(tar.send(:command, :undef)).to eq 'tar xjf test.tar.bz2' - expect(tar.send(:command, 'xjf')).to eq 'tar xjf test.tar.bz2' - tar = described_class.new('test.tar.xz') - expect(tar.send(:command, :undef)).to eq 'unxz -dc test.tar.xz | tar xf -' - gunzip = described_class.new('test.gz') - expect(gunzip.send(:command, :undef)).to eq 'gunzip -d test.gz' - bunzip2 = described_class.new('test.bz2') - expect(bunzip2.send(:command, :undef)).to eq 'bunzip2 -d test.bz2' - zip = described_class.new('test.zip') - expect(zip.send(:command, :undef)).to eq 'unzip -o test.zip' - expect(zip.send(:command, '-a')).to eq 'unzip -a test.zip' - - zip = described_class.new('/tmp/fun folder/test.zip') - expect(zip.send(:command, :undef)).to eq 'unzip -o /tmp/fun\ folder/test.zip' - expect(zip.send(:command, '-a')).to eq 'unzip -a /tmp/fun\ folder/test.zip' + describe '#parse_flags' do + subject { described_class.new('test.tar.gz') } + + it { expect(subject.send(:parse_flags, 'xf', :undef, 'tar')).to eq 'xf' } + it { expect(subject.send(:parse_flags, 'xf', 'xvf', 'tar')).to eq 'xvf' } + it { expect(subject.send(:parse_flags, 'xf', { 'tar' => 'xzf', '7z' => '-y x' }, 'tar')).to eq 'xzf' } end - system_v = %w[Solaris AIX] - system_v.each do |os| - it "#command on #{os}" do - Facter.stubs(:value).with(:osfamily).returns os - - tar = described_class.new('test.tar.gz') - expect(tar.send(:command, :undef)).to eq 'gunzip -dc test.tar.gz | tar xf -' - expect(tar.send(:command, 'gunzip' => '-dc', 'tar' => 'xvf')).to eq 'gunzip -dc test.tar.gz | tar xvf -' - tar = described_class.new('test.tar.bz2') - expect(tar.send(:command, :undef)).to eq 'bunzip2 -dc test.tar.bz2 | tar xf -' - expect(tar.send(:command, 'bunzip' => '-dc', 'tar' => 'xvf')).to eq 'bunzip2 -dc test.tar.bz2 | tar xvf -' - tar = described_class.new('test.tar.xz') - expect(tar.send(:command, :undef)).to eq 'unxz -dc test.tar.xz | tar xf -' - gunzip = described_class.new('test.gz') - expect(gunzip.send(:command, :undef)).to eq 'gunzip -d test.gz' - zip = described_class.new('test.zip') - expect(zip.send(:command, :undef)).to eq 'unzip -o test.zip' - expect(zip.send(:command, '-a')).to eq 'unzip -a test.zip' - - zip = described_class.new('/tmp/fun folder/test.zip') - expect(zip.send(:command, :undef)).to eq 'unzip -o /tmp/fun\ folder/test.zip' - expect(zip.send(:command, '-a')).to eq 'unzip -a /tmp/fun\ folder/test.zip' - - tar = described_class.new('test.tar.Z') - expect(tar.send(:command, :undef)).to eq 'uncompress -c test.tar.Z | tar xf -' + describe '#command' do + subject { |example| described_class.new(example.metadata[:filename]) } + + before { allow(Facter).to receive(:value).with(:osfamily).and_return(os) } + after { expect(Facter).to have_received(:value).with(:osfamily).at_least(:twice) } + + describe 'on RedHat' do + let(:os) { 'RedHat' } + + describe 'tar.gz', filename: 'test.tar.gz' do + it { expect(subject.send(:command, :undef)).to eq 'tar xzf test.tar.gz' } + it { expect(subject.send(:command, 'xvf')).to eq 'tar xvf test.tar.gz' } + end + + describe 'tar.bz2', filename: 'test.tar.bz2' do + it { expect(subject.send(:command, :undef)).to eq 'tar xjf test.tar.bz2' } + it { expect(subject.send(:command, 'xjf')).to eq 'tar xjf test.tar.bz2' } + end + + describe 'tar.xz', filename: 'test.tar.xz' do + it { expect(subject.send(:command, :undef)).to eq 'unxz -dc test.tar.xz | tar xf -' } + end + + describe 'gz', filename: 'test.gz' do + it { expect(subject.send(:command, :undef)).to eq 'gunzip -d test.gz' } + end + + describe 'bz2', filename: 'test.bz2' do + it { expect(subject.send(:command, :undef)).to eq 'bunzip2 -d test.bz2' } + end + + describe 'zip' do + describe 'filename', filename: 'test.zip' do + it { expect(subject.send(:command, :undef)).to eq 'unzip -o test.zip' } + it { expect(subject.send(:command, '-a')).to eq 'unzip -a test.zip' } + end + + describe 'path with space', filename: '/tmp/fun folder/test.zip' do + it { expect(subject.send(:command, :undef)).to eq 'unzip -o /tmp/fun\ folder/test.zip' } + it { expect(subject.send(:command, '-a')).to eq 'unzip -a /tmp/fun\ folder/test.zip' } + end + end end - end - it '#command on Windows' do - Facter.stubs(:value).with(:osfamily).returns 'windows' + system_v = %w[Solaris AIX] + system_v.each do |os| + describe "on #{os}" do + let(:os) { os } + + describe 'tar.gz', filename: 'test.tar.gz' do + it { expect(subject.send(:command, :undef)).to eq 'gunzip -dc test.tar.gz | tar xf -' } + it { expect(subject.send(:command, 'gunzip' => '-dc', 'tar' => 'xvf')).to eq 'gunzip -dc test.tar.gz | tar xvf -' } + end + + describe 'tar.bz2', filename: 'test.tar.bz2' do + it { expect(subject.send(:command, :undef)).to eq 'bunzip2 -dc test.tar.bz2 | tar xf -' } + it { expect(subject.send(:command, 'bunzip' => '-dc', 'tar' => 'xvf')).to eq 'bunzip2 -dc test.tar.bz2 | tar xvf -' } + end + + describe 'tar.xz', filename: 'test.tar.xz' do + it { expect(subject.send(:command, :undef)).to eq 'unxz -dc test.tar.xz | tar xf -' } + end + + describe 'gz', filename: 'test.gz' do + it { expect(subject.send(:command, :undef)).to eq 'gunzip -d test.gz' } + end + + describe 'zip' do + describe 'filename', filename: 'test.zip' do + it { expect(subject.send(:command, :undef)).to eq 'unzip -o test.zip' } + it { expect(subject.send(:command, '-a')).to eq 'unzip -a test.zip' } + end + + describe 'path with space' do + subject { described_class.new('/tmp/fun folder/test.zip') } + + it { expect(subject.send(:command, :undef)).to eq 'unzip -o /tmp/fun\ folder/test.zip' } + it { expect(subject.send(:command, '-a')).to eq 'unzip -a /tmp/fun\ folder/test.zip' } + end + end + + describe 'tar.Z' do + subject { described_class.new('test.tar.Z') } + + it { expect(subject.send(:command, :undef)).to eq 'uncompress -c test.tar.Z | tar xf -' } + end + end + end + + describe 'on Windows' do + let(:os) { 'windows' } + + # rubocop:disable RSpec/SubjectStub + before { allow(subject).to receive(:win_7zip).and_return(zip_cmd) } + # rubocop:enable RSpec/SubjectStub + + context '7z.exe' do + let(:zip_cmd) { '7z.exe' } - tar = described_class.new('test.tar.gz') - tar.stubs(:win_7zip).returns('7z.exe') - expect(tar.send(:command, :undef)).to eq '7z.exe x -aoa "test.tar.gz"' - expect(tar.send(:command, 'x -aot')).to eq '7z.exe x -aot "test.tar.gz"' + describe 'tar.gz', filename: 'test.tar.gz' do + it { expect(subject.send(:command, :undef)).to eq '7z.exe x -aoa "test.tar.gz"' } + it { expect(subject.send(:command, 'x -aot')).to eq '7z.exe x -aot "test.tar.gz"' } + end - zip = described_class.new('test.zip') - zip.stubs(:win_7zip).returns('7z.exe') - expect(zip.send(:command, :undef)).to eq '7z.exe x -aoa "test.zip"' + describe 'zip' do + describe 'filename', filename: 'test.zip' do + it { expect(subject.send(:command, :undef)).to eq '7z.exe x -aoa "test.zip"' } + end - zip = described_class.new('C:/Program Files/test.zip') - zip.stubs(:win_7zip).returns('7z.exe') - expect(zip.send(:command, :undef)).to eq '7z.exe x -aoa "C:/Program Files/test.zip"' + describe 'path with space', filename: 'C:/Program Files/test.zip' do + it { expect(subject.send(:command, :undef)).to eq '7z.exe x -aoa "C:/Program Files/test.zip"' } + end + end + end - zip = described_class.new('C:/Program Files/test.zip') - zip.stubs(:win_7zip).returns('powershell') - expect(zip.send(:command, :undef)).to eq 'powershell' + describe 'powershell', filename: 'C:/Program Files/test.zip' do + let(:zip_cmd) { 'powershell' } + + it { expect(subject.send(:command, :undef)).to eq 'powershell' } + end + end end end