test-kitchen-1.23.2/0000755000004100000410000000000013377651062014243 5ustar www-datawww-datatest-kitchen-1.23.2/.travis.yml0000644000004100000410000000302713377651062016356 0ustar www-datawww-datasudo: required dist: trusty services: docker language: ruby rvm: - 2.3.7 - 2.4.4 - 2.5.1 - ruby-head before_install: - gem --version bundler_args: --with integration --without changelog debug docs script: - bundle exec rake - export KITCHEN_YAML=.kitchen.dokken.yml - bundle exec kitchen test branches: only: - master matrix: include: - rvm: 2.5.1 before_install: # https://github.com/travis-ci/travis-ci/issues/8978 - gem update --system - rvm: 2.4.4 # To run the proxy tests we need additional gems than what Test Kitchen normally uses # for testing gemfile: Gemfile.proxy_tests before_install: - sudo usermod -p `openssl passwd -1 'travis'` travis - gem install bundler - sudo apt-get update - sudo apt-get -y install squid3 git curl env: - global: - machine_user=travis - machine_pass=travis - machine_port=22 - PROXY_TESTS_DIR=proxy_tests/files/default/scripts - PROXY_TESTS_REPO=$PROXY_TESTS_DIR/repo - KITCHEN_YAML=/home/travis/build/test-kitchen/test-kitchen/.kitchen.proxy.yml script: - bundle exec kitchen --version - git clone https://github.com/chef/proxy_tests.git - rvmsudo -E bundle exec bash $PROXY_TESTS_DIR/run_tests.sh kitchen \* \* /tmp/out.txt after_script: - cat /tmp/out.txt - sudo cat /var/log/squid3/cache.log - sudo cat /var/log/squid3/access.log allow_failures: - rvm: ruby-head test-kitchen-1.23.2/test/0000755000004100000410000000000013377651062015222 5ustar www-datawww-datatest-kitchen-1.23.2/test/cookbooks/0000755000004100000410000000000013377651062017213 5ustar www-datawww-datatest-kitchen-1.23.2/test/cookbooks/test_cookbook/0000755000004100000410000000000013377651062022060 5ustar www-datawww-datatest-kitchen-1.23.2/test/cookbooks/test_cookbook/recipes/0000755000004100000410000000000013377651062023512 5ustar www-datawww-datatest-kitchen-1.23.2/test/cookbooks/test_cookbook/recipes/default.rb0000644000004100000410000000003713377651062025463 0ustar www-datawww-datadirectory "/tk_test_directory" test-kitchen-1.23.2/test/cookbooks/test_cookbook/metadata.rb0000644000004100000410000000025013377651062024162 0ustar www-datawww-dataname "test_cookbook" maintainer "Maintainer" maintainer_email "Maintainer@example.com" license "Apache 2.0" description "Used for testing test-kitchen" version "0.1.0" test-kitchen-1.23.2/test/integration/0000755000004100000410000000000013377651062017545 5ustar www-datawww-datatest-kitchen-1.23.2/test/integration/default/0000755000004100000410000000000013377651062021171 5ustar www-datawww-datatest-kitchen-1.23.2/test/integration/default/default_spec.rb0000644000004100000410000000010613377651062024151 0ustar www-datawww-datadescribe directory("/tk_test_directory") do it { should exist } end test-kitchen-1.23.2/testing_windows.md0000644000004100000410000000303613377651062020016 0ustar www-datawww-data## Testing test-kitchen contributions against windows test instances ### Choose a windows based cookbook The [windows cookbook](https://github.com/chef-cookbooks/windows) is a grand choice. ### Edit the `Gemfile` Ensure that the cookbook's root directory includes a `Gemfile` that includes your local test-kitchen repo on the branch you would like to test as well as required windows-only needed gems: ``` gem 'test-kitchen', git: 'https://github.com/mwrock/test-kitchen', branch: 'winrm-fs' gem 'winrm', '~> 1.6' gem 'winrm-fs', '~> 0.4.1' gem 'winrm-elevated', '~> 0.4.0' ``` The above would target the `winrm-fs` branch in mwrock's test-kitchen repo. ### Finding a windows image Make sure you have a windows test image handy. You can use your favorite cloud or hypervisor. An easy vagrant option is `mwrock/Windows2012R2` which is publicly available on atlas. To use that, edit your cookbook's `.kitchen.yml` to include: ``` platforms: - name: win2012r2-standard driver: box: mwrock/Windows2012R2 ``` For other windows OS versions, you can spin up instances in your favorite cloud provider or create your own vagrant box. The windows packer templates found in the [boxcutter repo](https://github.com/boxcutter/windows) provide a good place to start here. ### `bundle install` From the root of your cookbook directory run `bundle install` ### Converge and test! Now run `bundle exec kitchen verify`. If your cookbook has multiple suites (like the windows cookbook), you likely just want to run one: ``` bundle exec kitchen verify feature ``` test-kitchen-1.23.2/README.md0000644000004100000410000001044413377651062015525 0ustar www-datawww-data# Test Kitchen [![Gem Version](https://badge.fury.io/rb/test-kitchen.svg)](http://badge.fury.io/rb/test-kitchen) [![Build Status](https://secure.travis-ci.org/test-kitchen/test-kitchen.svg?branch=master)](https://travis-ci.org/test-kitchen/test-kitchen) | | | | ----------- | --------------------------------------------------------------------------------| | Website | [http://kitchen.ci][website] | | Source Code | [http://kitchen.ci/docs/getting-started/][guide] | | Slack | [#test-kitchen][slack] channel on Chef Community Slack, [transcript][slack_log] | | Twitter | [@kitchenci][twitter] | > **Test Kitchen is an integration tool for developing and testing > infrastructure code and software on isolated target platforms.** ## Getting Started Guide To learn how to install and setup Test Kitchen for developing infrastructure code, check out the [Getting Started Guide][guide]. If you want to get going super fast, then try the Quick Start next... ## Quick Start Test Kitchen is a RubyGem and can be installed with: ``` $ gem install test-kitchen ``` If you use Bundler, you can add `gem "test-kitchen"` to your Gemfile and make sure to run `bundle install`. Next add support to your library, Chef cookbook, or empty project with `kitchen init`: ``` $ kitchen init ``` A `.kitchen.yml` will be created in your project base directory. This file describes your testing configuration; what you want to test and on which target platforms. Each of these suite and platform combinations are called instances. By default your instances will be converged with Chef Solo and run in Vagrant virtual machines. Get a listing of your instances with: ``` $ kitchen list ``` Run Chef on an instance, in this case `default-ubuntu-1204`, with: ``` $ kitchen converge default-ubuntu-1204 ``` Destroy all instances with: ``` $ kitchen destroy ``` You can clone a Chef cookbook project that contains Test Kitchen support and run through all the instances in serial by running: ``` $ kitchen test ``` ## Usage There is help included with the `kitchen help` subcommand which will list all subcommands and their usage: ``` $ kitchen help test ``` More verbose logging for test-kitchen can be specified when running test-kitchen from the command line using: ``` $ kitchen test -l debug ``` For the provisioner (e.g. chef-solo or chef-zero) add a `log_level` item to the provisioner section of the `.kitchen.yml` For more information see the Documentation. This is a change since version 1.7.0 ## Documentation Documentation is being added on the Test Kitchen [website][website]. Please read and contribute to improve them! ## Versioning Test Kitchen aims to adhere to [Semantic Versioning 2.0.0][semver]. ## Community and Ecosystem If you would like to see a few of the plugins or ecosystem helpers, please look at [ECOSYSTEM.md][ecosystem]. ## Development * Source hosted at [GitHub][repo] * Report issues/questions/feature requests on [GitHub Issues][issues] Pull requests are very welcome! Make sure your patches are well tested. Ideally create a topic branch for every separate change you make. For example: 1. Fork the repo 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Added some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## Authors Created and maintained by [Fletcher Nichol][fnichol] () and a growing community of [contributors][contributors]. ## License Apache License, Version 2.0 (see [LICENSE][license]) [contributors]: https://github.com/test-kitchen/test-kitchen/graphs/contributors [ecosystem]: https://github.com/test-kitchen/test-kitchen/blob/master/ECOSYSTEM.md [fnichol]: https://github.com/fnichol [guide]: http://kitchen.ci/docs/getting-started/ [issues]: https://github.com/test-kitchen/test-kitchen/issues [license]: https://github.com/test-kitchen/test-kitchen/blob/master/LICENSE [repo]: https://github.com/test-kitchen/test-kitchen [semver]: http://semver.org/ [slack]: https://chefcommunity.slack.com/messages/testkitchen/details/ [slack_log]: https://chefcommunity.slackarchive.io/test-kitchen [twitter]: https://twitter.com/kitchenci [website]: http://kitchen.ci test-kitchen-1.23.2/bin/0000755000004100000410000000000013377651062015013 5ustar www-datawww-datatest-kitchen-1.23.2/bin/kitchen0000755000004100000410000000054713377651062016374 0ustar www-datawww-data#!/usr/bin/env ruby # -*- encoding: utf-8 -*- # Trap interrupts to quit cleanly. See # https://twitter.com/mitchellh/status/283014103189053442 Signal.trap("INT") { exit 1 } $LOAD_PATH.unshift File.join(File.dirname(__FILE__), %w{.. lib}) require "rubygems" require "kitchen/cli" require "kitchen/errors" Kitchen.with_friendly_errors { Kitchen::CLI.start } test-kitchen-1.23.2/spec/0000755000004100000410000000000013377651062015175 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen_spec.rb0000644000004100000410000000600613377651062020163 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "spec_helper" require "kitchen" describe "Kitchen" do let(:stdout) { StringIO.new } before do FakeFS.activate! FileUtils.mkdir_p(Dir.pwd) stdout.stubs(:tty?).returns(true) @orig_stdout = $stdout $stdout = stdout end after do $stdout = @orig_stdout FakeFS.deactivate! FakeFS::FileSystem.clear end describe "defaults" do it "sets DEFAULT_LOG_LEVEL to :info" do Kitchen::DEFAULT_LOG_LEVEL.must_equal :info end it "sets DEFAULT_TEST_DIR to test/integration, which is frozen" do Kitchen::DEFAULT_TEST_DIR.must_equal "test/integration" Kitchen::DEFAULT_TEST_DIR.frozen?.must_equal true end it "sets DEFAULT_LOG_DIR to .kitchen/logs, which is frozen" do Kitchen::DEFAULT_LOG_DIR.must_equal ".kitchen/logs" Kitchen::DEFAULT_LOG_DIR.frozen?.must_equal true end end it ".tty? returns true if $stdout.tty? is true" do Kitchen.tty?.must_equal true end it ".tty? returns flse is $stdout.tty? is false" do stdout.stubs(:tty?).returns(false) Kitchen.tty?.must_equal false end it ".source_root returns the root path of the gem" do Kitchen.source_root .must_equal Pathname.new(File.expand_path("../..", __FILE__)) end it ".default_logger is a Kitchen::Logger" do Kitchen.default_logger.must_be_instance_of Kitchen::Logger end it ".default_logger returns a $stdout logger" do Kitchen.default_logger.warn("uhoh") stdout.string.must_match %r{ uhoh$} end it ".default_file_logger is a Kitchen::Logger" do Kitchen.default_file_logger.must_be_instance_of Kitchen::Logger end it ".default_file_logger returns a logger that uses $stdout" do Kitchen.default_logger.warn("uhoh") stdout.string.must_match %r{ uhoh$} end it ".default_file_logger returns a logger that uses a file" do Kitchen.default_file_logger.warn("uhoh") IO.read(File.join(%w{.kitchen logs kitchen.log})) .must_match %r{ -- Kitchen: uhoh$} end it ".default_file_logger accepts a level and log_overwrite" do l = Kitchen.default_file_logger(:error, false) l.level.must_equal 3 l.log_overwrite.must_equal false end it "sets Kitchen.logger to a Kitchen::Logger" do Kitchen.default_logger.must_be_instance_of Kitchen::Logger end it "sets Kitchen.mutex to a Mutex" do Kitchen.mutex.must_be_instance_of Mutex end end test-kitchen-1.23.2/spec/spec_helper.rb0000644000004100000410000000532213377651062020015 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. gem "minitest" begin require "simplecov" SimpleCov.profiles.define "gem" do command_name "Specs" add_filter ".gem/" add_filter "/spec/" add_filter "/lib/vendor/" add_group "Libraries", "/lib/" end SimpleCov.start "gem" rescue LoadError puts "add simplecov to Gemfile.local or GEMFILE_MOD to generate code coverage" end require "fakefs/safe" require "minitest/autorun" require "mocha/setup" require "tempfile" # Nasty hack to redefine IO.read in terms of File#read for fakefs class IO def self.read(*args) length = args[1] offset = args[2] opt = args[3] if length.is_a? Hash opt = length length = nil elsif offset.is_a? Hash opt = offset end if opt && opt.key?(:mode) File.open(args[0], opt) { |f| f.read(length) } else File.open(args[0], "rb", opt) { |f| f.read(length) } end end end # Hack to sort results in `Dir.entries` only within the yielded block, to limit # the "behavior pollution" to other code. This was needed for Net::SCP, as # recursive directory upload doesn't sort the file and directory upload # candidates which leads to different results based on the underlying # filesystem (i.e. lexically sorted, inode insertion, mtime/atime, total # randomness, etc.) # # See: https://github.com/net-ssh/net-scp/blob/a24948/lib/net/scp/upload.rb#L52 $_sort_dir_entries = false Dir.singleton_class.prepend(Module.new do def entries(*args) super.tap do |rv| rv.sort! if $_sort_dir_entries end end end) def with_sorted_dir_entries(&block) old_sort_dir_entries = $_sort_dir_entries $_sort_dir_entries = true yield ensure $_sort_dir_entries = old_sort_dir_entries end def with_fake_fs FakeFS.activate! FileUtils.mkdir_p("/tmp") yield FakeFS.deactivate! FakeFS::FileSystem.clear end def running_tests_on_windows? ENV["OS"] == "Windows_NT" end def os_safe_root_path(root_path) if running_tests_on_windows? File.join(ENV["SystemDrive"], root_path).to_s else root_path end end def padded_octal_string(integer) integer.to_s(8).rjust(4, "0") end test-kitchen-1.23.2/spec/support/0000755000004100000410000000000013377651062016711 5ustar www-datawww-datatest-kitchen-1.23.2/spec/support/powershell_max_size_spec.rb0000644000004100000410000000303513377651062024334 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" # There is some double Base64 encoding going on when a PowerShell script is # given to a CMD to invoke. We're in a battle with Windows' CMD max length and # that what these specs are trying to ensure. This does lead to some less than # ideal "code golfing" to reduce the code payload, but at the end of the day # it's easier to see the entire context of the code vs. uploading partial code # fragements and calling them on the remote side (not to mention more expensive # in terms of PowerShell invocations). describe "PowerShell script max size" do MAX_POWERSHELL_SIZE = 3010 Dir.glob(File.join(File.dirname(__FILE__), "../../support/*.ps1*")).each do |script| base = File.basename(script) it "support/#{base} size must be less than #{MAX_POWERSHELL_SIZE} bytes" do (IO.read(script).size < MAX_POWERSHELL_SIZE).must_equal true end end end test-kitchen-1.23.2/spec/kitchen/0000755000004100000410000000000013377651062016622 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/driver/0000755000004100000410000000000013377651062020115 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/driver/base_spec.rb0000644000004100000410000000710413377651062022370 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "logger" require "stringio" require "kitchen" module Kitchen module Driver class Speedy < Base end class Dodgy < Base no_parallel_for :setup end class Slow < Base no_parallel_for :create, :destroy no_parallel_for :verify end end end describe Kitchen::Driver::Base do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:state) { Hash.new } let(:busser) do stub(setup_cmd: "setup", sync_cmd: "sync", run_cmd: "run") end let(:instance) do stub( name: "coolbeans", logger: logger, busser: busser, to_str: "instance" ) end let(:driver) do Kitchen::Driver::Base.new(config).finalize_config!(instance) end it "#instance returns its instance" do driver.instance.must_equal instance end it "#puts calls logger.info" do driver.send(:puts, "yo") logged_output.string.must_match(/I, /) logged_output.string.must_match(/yo\n/) end it "#print calls logger.info" do driver.send(:print, "yo") logged_output.string.must_match(/I, /) logged_output.string.must_match(/yo\n/) end [:create, :setup, :verify, :destroy].each do |action| it "has a #{action} method that takes state" do # TODO: revert back # state = Hash.new # driver.public_send(action, state).must_be_nil driver.respond_to?(action) end end describe ".pre_create_command" do it "run command" do Kitchen::Driver::Base.any_instance.stubs(:run_command).returns(true) config[:pre_create_command] = "echo works 2&>1 > /dev/null" driver.expects(:run_command).with("echo works 2&>1 > /dev/null") driver.send(:pre_create_command) end it "error if cannot run" do class ShellCommandFailed < Kitchen::ShellOut::ShellCommandFailed; end Kitchen::Driver::Base.any_instance.stubs(:run_command).raises(ShellCommandFailed, "Expected process to exit with [0], but received '1'") config[:pre_create_command] = "touch /this/dir/does/not/exist 2&>1 > /dev/null" proc { driver.send(:pre_create_command) }.must_raise Kitchen::ActionFailed end end describe ".no_parallel_for" do it "registers no serial actions when none are declared" do Kitchen::Driver::Speedy.serial_actions.must_be_nil end it "registers a single serial action method" do Kitchen::Driver::Dodgy.serial_actions.must_equal [:setup] end it "registers multiple serial action methods" do actions = Kitchen::Driver::Slow.serial_actions actions.must_include :create actions.must_include :verify actions.must_include :destroy end it "raises a ClientError if value is not an action method" do proc do Class.new(Kitchen::Driver::Base) { no_parallel_for :telling_stories } end.must_raise Kitchen::ClientError end end end test-kitchen-1.23.2/spec/kitchen/driver/dummy_spec.rb0000644000004100000410000001217413377651062022614 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "logger" require "stringio" require "kitchen/driver/dummy" describe Kitchen::Driver::Dummy do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:state) { Hash.new } let(:instance) do stub(name: "coolbeans", logger: logger, to_str: "instance") end let(:driver) do Kitchen::Driver::Dummy.new(config).finalize_config!(instance) end it "driver api_version is 2" do driver.diagnose_plugin[:api_version].must_equal 2 end it "plugin_version is set to Kitchen::VERSION" do driver.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "default_config" do it "sets :sleep to 0 by default" do driver[:sleep].must_equal 0 end it "sets :random_failure to false by default" do driver[:random_failure].must_equal false end end describe "#create" do it "sets :my_id to a unique value as an example" do driver.create(state) state[:my_id].must_match(/^coolbeans-/) end it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 12.5 driver.expects(:sleep).with(12.5).returns(true) driver.create(state) end it "raises ActionFailed if :fail_create is set" do config[:fail_create] = true proc { driver.create(state) }.must_raise Kitchen::ActionFailed end it "randomly raises ActionFailed if :random_failure is set" do config[:random_failure] = true driver.stubs(:randomly_fail?).returns(true) proc { driver.create(state) }.must_raise Kitchen::ActionFailed end it "will only raise ActionFailed if :random_failure is set" do config[:random_failure] = true begin driver.create(state) rescue Kitchen::ActionFailed # If exception is anything other than Kitchen::ActionFailed, this spec # will fail true end end it "logs a create event to INFO" do driver.create(state) logged_output.string.must_match(/^.+ INFO .+ \[Dummy\] Create on .+$/) end end describe "#setup" do it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 12.5 driver.expects(:sleep).with(12.5).returns(true) driver.setup(state) end it "raises ActionFailed if :fail_setup is set" do config[:fail_setup] = true proc { driver.setup(state) }.must_raise Kitchen::ActionFailed end it "randomly raises ActionFailed if :random_failure is set" do config[:random_failure] = true driver.stubs(:randomly_fail?).returns(true) proc { driver.setup(state) }.must_raise Kitchen::ActionFailed end it "logs a setup event to INFO" do driver.setup(state) logged_output.string.must_match(/^.+ INFO .+ \[Dummy\] Setup on .+$/) end end describe "#verify" do it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 12.5 driver.expects(:sleep).with(12.5).returns(true) driver.verify(state) end it "raises ActionFailed if :fail_verify is set" do config[:fail_verify] = true proc { driver.verify(state) }.must_raise Kitchen::ActionFailed end it "randomly raises ActionFailed if :random_failure is set" do config[:random_failure] = true driver.stubs(:randomly_fail?).returns(true) proc { driver.verify(state) }.must_raise Kitchen::ActionFailed end it "logs a verify event to INFO" do driver.verify(state) logged_output.string.must_match(/^.+ INFO .+ \[Dummy\] Verify on .+$/) end end describe "#destroy" do it "removes :my_id from the state hash" do state[:my_id] = "90210" driver.destroy(state) state[:my_id].must_be_nil end it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 12.5 driver.expects(:sleep).with(12.5).returns(true) driver.verify(state) end it "raises ActionFailed if :fail_destroy is set" do config[:fail_destroy] = true proc { driver.destroy(state) }.must_raise Kitchen::ActionFailed end it "randomly raises ActionFailed if :random_failure is set" do config[:random_failure] = true driver.stubs(:randomly_fail?).returns(true) proc { driver.destroy(state) }.must_raise Kitchen::ActionFailed end it "logs a destroy event to INFO" do driver.destroy(state) logged_output.string.must_match(/^.+ INFO .+ \[Dummy\] Destroy on .+$/) end end end test-kitchen-1.23.2/spec/kitchen/driver/proxy_spec.rb0000644000004100000410000000630313377651062022637 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen/driver/proxy" describe Kitchen::Driver::Proxy do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:state) { Hash.new } let(:config) do { host: "foobnoobs.com", reset_command: "mulligan" } end let(:instance) do stub(name: "coolbeans", logger: logger, to_str: "instance") end let(:driver) do Kitchen::Driver::Proxy.new(config).finalize_config!(instance) end it "plugin_version is set to Kitchen::VERSION" do driver.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "non-parallel action" do it "create must be serially executed" do Kitchen::Driver::Proxy.serial_actions.must_include :create end it "destroy must be serially executed" do Kitchen::Driver::Proxy.serial_actions.must_include :destroy end end describe "required_config" do it "requires host" do config.delete(:host) err = assert_raises(Kitchen::UserError) { driver } err.message.must_include "config[:host] cannot be blank" end it "does not require reset_command" do config.delete(:reset_command) driver # Just make sure it doesn't raise end end describe "#create" do it "sets :hostname in state config" do driver.stubs(:ssh) driver.create(state) state[:hostname].must_equal "foobnoobs.com" end it "calls the reset command over ssh" do driver.expects(:ssh).with do |ssh_args, cmd| ssh_args[0].must_equal "foobnoobs.com" cmd.must_equal "mulligan" end driver.create(state) end it "skips ssh call if :reset_command is falsey" do config[:reset_command] = false driver.expects(:ssh).never driver.create(state) end end describe "#destroy" do before do state[:hostname] = "beep" end it "deletes :hostname in state config" do driver.stubs(:ssh) driver.destroy(state) state[:hostname].must_be_nil end it "calls the reset command over ssh" do driver.expects(:ssh).with do |ssh_args, cmd| ssh_args[0].must_equal "beep" cmd.must_equal "mulligan" end driver.destroy(state) end it "skips ssh call if :hostname is not in state config" do state.delete(:hostname) driver.expects(:ssh).never driver.destroy(state) end it "skips ssh call if :reset_command is falsey" do config[:reset_command] = false driver.expects(:ssh).never driver.destroy(state) end end end test-kitchen-1.23.2/spec/kitchen/driver/ssh_base_spec.rb0000644000004100000410000010012513377651062023242 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/transport/ssh" require "kitchen/verifier/busser" module Kitchen module Driver class BackCompat < Kitchen::Driver::SSHBase def use_run_remote(state, command) connection = Kitchen::SSH.new(*build_ssh_args(state)) run_remote(command, connection) end def use_transfer_path(state, locals, remote) connection = Kitchen::SSH.new(*build_ssh_args(state)) transfer_path(locals, remote, connection) end end class SpeedyCompat < Kitchen::Driver::SSHBase end class DodgyCompat < Kitchen::Driver::SSHBase no_parallel_for :converge end class SlowCompat < Kitchen::Driver::SSHBase no_parallel_for :create, :destroy no_parallel_for :verify end end end describe Kitchen::Driver::SSHBase do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:state) { Hash.new } let(:verifier) do v = mock("busser") v.responds_like_instance_of(Kitchen::Verifier::Busser) v.stubs(:install_command).returns("install") v.stubs(:init_command).returns("init") v.stubs(:prepare_command).returns("prepare") v.stubs(:run_command).returns("run") v.stubs(:create_sandbox).returns(true) v.stubs(:cleanup_sandbox).returns(true) v.stubs(:sandbox_path).returns("/tmp/sandbox") v.stubs(:[]).with(:root_path).returns("/tmp/verifier") v end let(:provisioner) do stub( install_command: "install", init_command: "init", prepare_command: "prepare", run_command: "run", create_sandbox: true, cleanup_sandbox: true, sandbox_path: "/tmp/sandbox" ) end let(:transport) do t = mock("transport") t.responds_like_instance_of(Kitchen::Transport::Base) t end let(:instance) do stub( name: "coolbeans", logger: logger, verifier: verifier, provisioner: provisioner, transport: transport, to_str: "instance" ) end let(:driver) do Kitchen::Driver::SSHBase.new(config).finalize_config!(instance) end it "plugin_version is not set" do driver.diagnose_plugin[:version].must_be_nil end describe "configuration" do it ":sudo defaults to true" do driver[:sudo].must_equal true end it ":port defaults to 22" do driver[:port].must_equal 22 end end it "#create raises a ClientError" do proc { driver.create(state) }.must_raise Kitchen::ClientError end it "#destroy raises a ClientError" do proc { driver.destroy(state) }.must_raise Kitchen::ClientError end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.constructs_an_ssh_connection describe "constructs an SSH connection" do it "with hostname set from state" do transport.expects(:connection).with do |state| state[:hostname].must_equal "fizzy" end.returns(stub(login_command: stub)) cmd end it "with username set from state" do transport.expects(:connection).with do |state| state[:username].must_equal "bork" end.returns(stub(login_command: stub)) cmd end it "with :ssh_key option set from config" do config[:ssh_key] = "wicked" transport.expects(:connection).with do |state| state[:ssh_key].must_equal "wicked" end.returns(stub(login_command: stub)) cmd end it "with :ssh_key option set from state" do state[:ssh_key] = "wicked" transport.expects(:connection).with do |state| state[:ssh_key].must_equal "wicked" end.returns(stub(login_command: stub)) cmd end it "with :password option set to falsey by default" do transport.expects(:connection).with do |state| state[:password].nil? end.returns(stub(login_command: stub)) cmd end it "with :password option set if given in config" do config[:password] = "psst" transport.expects(:connection).with do |state| state[:password].must_equal "psst" end.returns(stub(login_command: stub)) cmd end it "with :password option set if given in state" do state[:password] = "psst" transport.expects(:connection).with do |state| state[:password].must_equal "psst" end.returns(stub(login_command: stub)) cmd end it "with :forward_agent option set to falsey by default" do transport.expects(:connection).with do |state| state[:forward_agent].nil? end.returns(stub(login_command: stub)) cmd end it "with :forward_agent option set if given in config" do config[:forward_agent] = "yeah?" transport.expects(:connection).with do |state| state[:forward_agent].must_equal "yeah?" end.returns(stub(login_command: stub)) cmd end it "with :forward_agent option set if given in state" do state[:forward_agent] = "yeah?" transport.expects(:connection).with do |state| state[:forward_agent].must_equal "yeah?" end.returns(stub(login_command: stub)) cmd end it "with :port option set to 22 by default" do transport.expects(:connection).with do |state| state[:port].must_equal 22 end.returns(stub(login_command: stub)) cmd end it "with :port option set if customized in config" do config[:port] = 1234 transport.expects(:connection).with do |state| state[:port].must_equal 1234 end.returns(stub(login_command: stub)) cmd end it "with :port option set if customized in state" do state[:port] = 9999 transport.expects(:connection).with do |state| state[:port].must_equal 9999 end.returns(stub(login_command: stub)) cmd end end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "#login_command" do let(:cmd) { driver.login_command(state) } before do state[:hostname] = "fizzy" state[:username] = "bork" end it "returns a LoginCommand" do transport.stubs(:connection).returns(stub(login_command: "command")) cmd.must_equal "command" end constructs_an_ssh_connection end describe "#converge" do let(:cmd) { driver.converge(state) } let(:connection) { stub(execute: true, upload: true, download: true) } before do state[:hostname] = "fizzy" state[:username] = "bork" provisioner.stubs(:[]).with(:root_path).returns("/rooty") provisioner.stubs(:[]).with(:downloads).returns( ["/tmp/kitchen/nodes", "/tmp/kitchen/data_bags"] => "./test/fixtures", "/remote" => "/local" ) FakeFS.activate! FileUtils.mkdir_p("/tmp") @original_env = ENV.to_hash ENV.replace("http_proxy" => nil, "HTTP_PROXY" => nil, "https_proxy" => nil, "HTTPS_PROXY" => nil, "ftp_proxy" => nil, "FTP_PROXY" => nil, "no_proxy" => nil, "NO_PROXY" => nil) end after do FakeFS.deactivate! FakeFS::FileSystem.clear ENV.clear ENV.replace(@original_env) end constructs_an_ssh_connection it "creates the sandbox" do transport.stubs(:connection).yields(connection) provisioner.expects(:create_sandbox) cmd end it "ensures that the sandbox is cleaned up" do transport.stubs(:connection).raises provisioner.expects(:cleanup_sandbox) begin cmd rescue # rubocop:disable Lint/HandleExceptions end end it "invokes the provisioner commands over ssh" do transport.stubs(:connection).yields(connection) order = sequence("order") connection.expects(:execute).with("install").in_sequence(order) connection.expects(:execute).with("init").in_sequence(order) connection.expects(:execute).with("prepare").in_sequence(order) connection.expects(:execute).with("run").in_sequence(order) cmd end it "invokes the #install_command with :http_proxy set in config" do config[:http_proxy] = "http://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with("env http_proxy=http://proxy install") cmd end it 'invokes the #install_command with ENV["http_proxy"] set' do ENV["http_proxy"] = "http://proxy" transport.stubs(:connection).yields(connection) if running_tests_on_windows? connection.expects(:execute) .with("env http_proxy=http://proxy HTTP_PROXY=http://proxy install") else connection.expects(:execute).with("env http_proxy=http://proxy install") end cmd end it 'invokes the #install_command with ENV["http_proxy"] and ENV["no_proxy"] set' do ENV["http_proxy"] = "http://proxy" ENV["no_proxy"] = "http://no" transport.stubs(:connection).yields(connection) if running_tests_on_windows? connection.expects(:execute) .with("env http_proxy=http://proxy HTTP_PROXY=http://proxy " \ "no_proxy=http://no NO_PROXY=http://no install") else connection.expects(:execute).with("env http_proxy=http://proxy " \ "no_proxy=http://no install") end cmd end it "invokes the #install_command with :https_proxy set in config" do config[:https_proxy] = "https://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with("env https_proxy=https://proxy install") cmd end it 'invokes the #install_command with ENV["https_proxy"] set' do ENV["https_proxy"] = "https://proxy" transport.stubs(:connection).yields(connection) if running_tests_on_windows? connection.expects(:execute) .with("env https_proxy=https://proxy HTTPS_PROXY=https://proxy install") else connection.expects(:execute).with("env https_proxy=https://proxy install") end cmd end it 'invokes the #install_command with ENV["https_proxy"] and ENV["no_proxy"] set' do ENV["https_proxy"] = "https://proxy" ENV["no_proxy"] = "https://no" transport.stubs(:connection).yields(connection) if running_tests_on_windows? connection.expects(:execute) .with("env https_proxy=https://proxy HTTPS_PROXY=https://proxy " \ "no_proxy=https://no NO_PROXY=https://no install") else connection.expects(:execute).with("env https_proxy=https://proxy " \ "no_proxy=https://no install") end cmd end it "invokes the #install_command with :ftp_proxy set in config" do config[:ftp_proxy] = "ftp://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with("env ftp_proxy=ftp://proxy install") cmd end it 'invokes the #install_command with ENV["ftp_proxy"] set' do ENV["ftp_proxy"] = "ftp://proxy" transport.stubs(:connection).yields(connection) if running_tests_on_windows? connection.expects(:execute) .with("env ftp_proxy=ftp://proxy FTP_PROXY=ftp://proxy install") else connection.expects(:execute).with("env ftp_proxy=ftp://proxy install") end cmd end it 'invokes the #install_command with ENV["ftp_proxy"] and ENV["no_proxy"] set' do ENV["ftp_proxy"] = "ftp://proxy" ENV["no_proxy"] = "http://no" transport.stubs(:connection).yields(connection) if running_tests_on_windows? connection.expects(:execute) .with("env ftp_proxy=ftp://proxy FTP_PROXY=http://proxy " \ "no_proxy=http://no NO_PROXY=http://no install") else connection.expects(:execute).with("env ftp_proxy=ftp://proxy " \ "no_proxy=http://no install") end cmd end it "invokes the #install_command with :http_proxy & :https_proxy & :ftp_proxy set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with( "env http_proxy=http://proxy https_proxy=https://proxy ftp_proxy=ftp://proxy install") cmd end describe "transferring files" do before do transport.stubs(:connection).yields(connection) connection.stubs(:upload) FileUtils.mkdir_p "/tmp/sandbox/stuff" end it "uploads files" do connection.expects(:upload).with(["/tmp/sandbox/stuff"], "/rooty") cmd end it "logs to info" do cmd logged_output.string .must_match(/INFO -- : Transferring files to instance$/) end it "logs to debug" do cmd logged_output.string.must_match(/DEBUG -- : Transfer complete$/) end it "raises an ActionFailed on transfer when SshFailed is raised" do connection.stubs(:upload).raises(Kitchen::Transport::SshFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end describe "downloading files" do before do transport.stubs(:connection).yields(connection) connection.stubs(:download) end it "downloads files" do connection.expects(:download).with( ["/tmp/kitchen/nodes", "/tmp/kitchen/data_bags"], "./test/fixtures" ) connection.expects(:download).with("/remote", "/local") cmd end it "logs to info" do cmd logged_output.string.must_match( /INFO -- : Downloading files from instance$/ ) end it "logs to debug" do cmd logged_output.string.must_match( %r{DEBUG -- : Downloading /tmp/kitchen/nodes, /tmp/kitchen/data_bags to ./test/fixtures$} ) logged_output.string.must_match( %r{DEBUG -- : Downloading /remote to /local$} ) logged_output.string.must_match(/DEBUG -- : Download complete$/) end end it "raises an ActionFailed on execute when SshFailed is raised" do transport.stubs(:connection).yields(connection) connection.stubs(:execute).raises(Kitchen::Transport::SshFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end describe "#setup" do let(:cmd) { driver.setup(state) } let(:connection) { mock } before do state[:hostname] = "fizzy" state[:username] = "bork" end constructs_an_ssh_connection it "invokes the Verifier#install_command over ssh" do transport.stubs(:connection).yields(connection) connection.expects(:execute).with("install") cmd end it "invokes the Verifier#install_command with :http_proxy set in config" do config[:http_proxy] = "http://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with("env http_proxy=http://proxy install") cmd end it "invokes the Verifier#install_command with :https_proxy set in config" do config[:https_proxy] = "https://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with("env https_proxy=https://proxy install") cmd end it "invokes the Verifier#install_command with :ftp_proxy set in config" do config[:ftp_proxy] = "ftp://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with("env ftp_proxy=ftp://proxy install") cmd end it "invokes the Verifier#install_command with :http_proxy & :https_proxy & :ftp_proxy set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" transport.stubs(:connection).yields(connection) connection.expects(:execute).with( "env http_proxy=http://proxy https_proxy=https://proxy ftp_proxy=ftp://proxy install") cmd end it "raises an ActionFailed when SshFailed is raised" do transport.stubs(:connection).yields(connection) connection.stubs(:execute).raises(Kitchen::Transport::SshFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end describe "#verify" do let(:cmd) { driver.verify(state) } let(:connection) { stub(execute: true, upload: true) } before do state[:hostname] = "fizzy" state[:username] = "bork" transport.stubs(:connection).yields(connection) end constructs_an_ssh_connection it "creates the sandbox" do verifier.expects(:create_sandbox) cmd end it "ensures that the sandbox is cleanup up" do transport.stubs(:connection).raises verifier.expects(:cleanup_sandbox) begin cmd rescue # rubocop:disable Lint/HandleExceptions end end it "invokes the verifier commands over the transport" do order = sequence("order") connection.expects(:execute).with("init").in_sequence(order) connection.expects(:execute).with("prepare").in_sequence(order) connection.expects(:execute).with("run").in_sequence(order) cmd end %w{init prepare run}.each do |phase| it "invokes Verifier##{phase}_command over ssh" do connection.expects(:execute).with(phase) cmd end it "invokes Verifier##{phase}_command with :http_proxy set in config" do config[:http_proxy] = "http://proxy" connection.expects(:execute).with("env http_proxy=http://proxy #{phase}") cmd end it "invokes Verifier##{phase}_command with :https_proxy set in config" do config[:https_proxy] = "https://proxy" connection.expects(:execute).with("env https_proxy=https://proxy #{phase}") cmd end it "invokes Verifier##{phase}_command with :ftp_proxy set in config" do config[:ftp_proxy] = "ftp://proxy" connection.expects(:execute).with("env ftp_proxy=ftp://proxy #{phase}") cmd end it "invokes Verifier##{phase}_command with :http_proxy & :https_proxy & :ftp_proxy set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" connection.expects(:execute).with( "env http_proxy=http://proxy https_proxy=https://proxy ftp_proxy=ftp://proxy #{phase}") cmd end end it "logs to info" do cmd logged_output.string .must_match(/INFO -- : Transferring files to instance$/) end it "uploads sandbox files" do connection.expects(:upload).with([], "/tmp/verifier") cmd end it "logs to debug" do cmd logged_output.string.must_match(/DEBUG -- : Transfer complete$/) end it "raises an ActionFailed on transfer when TransportFailed is raised" do connection.stubs(:upload) .raises(Kitchen::Transport::TransportFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end it "raises an ActionFailed when SSHFailed is raised" do connection.stubs(:execute).raises(Kitchen::Transport::SshFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end describe "#ssh" do let(:cmd) { driver.ssh(["host", "user", { one: "two" }], "go") } let(:connection) { mock } it "creates an SSH connection" do connection.stubs(:execute) transport.expects(:connection).with( hostname: "host", username: "user", port: 22, one: "two" ).yields(connection) cmd end it "invokes the command over ssh" do transport.expects(:connection).yields(connection) connection.expects(:execute).with("go") cmd end end describe "#remote_command" do let(:cmd) { driver.remote_command(state, "shipit") } let(:connection) { mock } before do state[:hostname] = "fizzy" state[:username] = "bork" end it "creates an SSH connection" do transport.expects(:connection).with( hostname: "fizzy", username: "bork", port: 22 ) cmd end it "invokes the command over ssh" do transport.expects(:connection).yields(connection) connection.expects(:execute).with("shipit") cmd end end describe "#wait_for_sshd" do let(:cmd) do driver.send(:wait_for_sshd, "host", "user", one: "two") end it "creates an SSH connection with merged options" do transport.expects(:connection).with( hostname: "host", username: "user", port: 22, one: "two" ).returns(stub(wait_until_ready: true)) cmd end it "calls wait on the SSH connection" do connection = mock transport.expects(:connection).returns(connection) connection.expects(:wait_until_ready) cmd end end describe "to maintain backwards compatibility" do let(:driver) do Kitchen::Driver::BackCompat.new(config).finalize_config!(instance) end it "#instance returns its instance" do driver.instance.must_equal instance end it "#name returns the name of the driver" do driver.name.must_equal "BackCompat" end describe "#logger" do before { @klog = Kitchen.logger } after { Kitchen.logger = @klog } it "returns the instance's logger if defined" do driver.send(:logger).must_equal logger end it "returns the default logger if instance's logger is not set" do driver = Kitchen::Driver::BackCompat.new(config) Kitchen.logger = "yep" driver.send(:logger).must_equal Kitchen.logger end end it "#puts calls logger.info" do driver.send(:puts, "yo") logged_output.string.must_match(/I, /) logged_output.string.must_match(/yo\n/) end it "#print calls logger.info" do driver.send(:print, "yo") logged_output.string.must_match(/I, /) logged_output.string.must_match(/yo\n/) end it "has a default verify dependencies method" do driver.verify_dependencies.must_be_nil end it "#busser returns the instance's verifier" do driver.send(:busser).must_equal verifier end describe ".no_parallel_for" do it "registers no serial actions when none are declared" do Kitchen::Driver::SpeedyCompat.serial_actions.must_be_nil end it "registers a single serial action method" do Kitchen::Driver::DodgyCompat.serial_actions.must_equal [:converge] end it "registers multiple serial action methods" do actions = Kitchen::Driver::SlowCompat.serial_actions actions.must_include :create actions.must_include :verify actions.must_include :destroy end it "raises a ClientError if value is not an action method" do proc do Class.new(Kitchen::Driver::BackCompat) do no_parallel_for :telling_stories end end.must_raise Kitchen::ClientError end end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.constructs_an_ssh_object it "with hostname set from state" do Kitchen::SSH.expects(:new).with do |hostname, _username, _opts| hostname.must_equal "fizzy" end.returns(connection) cmd end it "with username set from state" do Kitchen::SSH.expects(:new).with do |_hostname, username, _opts| username.must_equal "bork" end.returns(connection) cmd end it "with :user_known_hosts_file option set to /dev/null" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:user_known_hosts_file].must_equal "/dev/null" end.returns(connection) cmd end it "with :verify_host_key option set to false" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:verify_host_key].must_equal false end.returns(connection) cmd end it "with :keys_only option set to falsey by default" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:keys_only].nil? end.returns(connection) cmd end it "with :keys_only option set to true if :ssh_key is set in config" do config[:ssh_key] = "wicked" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:keys_only].must_equal true end.returns(connection) cmd end it "with :keys_only option set to true if :ssh_key is set in state" do state[:ssh_key] = "wicked" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:keys_only].must_equal true end.returns(connection) cmd end it "with :keys option set to falsey by default" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:keys].nil? end.returns(connection) cmd end it "with :keys option set to an array if :ssh_key is set in config" do config[:ssh_key] = "wicked" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:keys].must_equal ["wicked"] end.returns(connection) cmd end it "with :keys option set to an array if :ssh_key is set in state" do state[:ssh_key] = "wicked" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:keys].must_equal ["wicked"] end.returns(connection) cmd end it "with :password option set to falsey by default" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:password].nil? end.returns(connection) cmd end it "with :password option set if given in config" do config[:password] = "psst" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:password].must_equal "psst" end.returns(connection) cmd end it "with :password option set if given in state" do state[:password] = "psst" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:password].must_equal "psst" end.returns(connection) cmd end it "with :forward_agent option set to falsey by default" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:forward_agent].nil? end.returns(connection) cmd end it "with :forward_agent option set if given in config" do config[:forward_agent] = "yeah?" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:forward_agent].must_equal "yeah?" end.returns(connection) cmd end it "with :forward_agent option set if given in state" do state[:forward_agent] = "yeah?" Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:forward_agent].must_equal "yeah?" end.returns(connection) cmd end it "with :port option set to 22 by default" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:port].must_equal 22 end.returns(connection) cmd end it "with :port option set if customized in config" do config[:port] = 1234 Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:port].must_equal 1234 end.returns(connection) cmd end it "with :port option set if customized in state" do state[:port] = 9999 Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:port].must_equal 9999 end.returns(connection) cmd end it "with :logger option set to driver's logger" do Kitchen::SSH.expects(:new).with do |_hostname, _username, opts| opts[:logger].must_equal logger end.returns(connection) cmd end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "#run_remote" do let(:cmd) { driver.use_run_remote(state, "huh") } let(:connection) { stub(exec: true) } before do state[:hostname] = "fizzy" state[:username] = "bork" end constructs_an_ssh_object it "invokes the #install_command with :http_proxy set in config" do config[:http_proxy] = "http://proxy" Kitchen::SSH.stubs(:new).returns(connection) connection.expects(:exec).with("env http_proxy=http://proxy huh") cmd end it "invokes the #install_command with :https_proxy set in config" do config[:https_proxy] = "https://proxy" Kitchen::SSH.stubs(:new).returns(connection) connection.expects(:exec).with("env https_proxy=https://proxy huh") cmd end it "invokes the #install_command with :ftp_proxy set in config" do config[:ftp_proxy] = "ftp://proxy" Kitchen::SSH.stubs(:new).returns(connection) connection.expects(:exec).with("env ftp_proxy=ftp://proxy huh") cmd end it "invokes the #install_command with :http_proxy & :https_proxy & :ftp_proxy set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" Kitchen::SSH.stubs(:new).returns(connection) connection.expects(:exec).with( "env http_proxy=http://proxy https_proxy=https://proxy ftp_proxy=ftp://proxy huh") cmd end it "doesn't invoke an ssh command if command is nil" do Kitchen::SSH.stubs(:new).returns(mock) driver.use_run_remote(state, nil) end it "raises an ActionFailed on transfer when SSHFailed is raised" do Kitchen::SSH.stubs(:new).returns(connection) connection.stubs(:exec).raises(Kitchen::SSHFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end it "raises an ActionFailed on exec when Net::SSH:Exception is raised" do Kitchen::SSH.stubs(:new).returns(connection) connection.stubs(:exec).raises(Net::SSH::Exception.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end describe "#transfer_path" do let(:cmd) { driver.use_transfer_path(state, ["nope"], "nadda") } let(:channel) { stub(wait: true) } let(:connection) { stub(upload_path!: true, upload_path: channel) } before do state[:hostname] = "fizzy" state[:username] = "bork" end constructs_an_ssh_object it "doesn't invoke an scp command if locals is nil" do Kitchen::SSH.stubs(:new).returns(mock) driver.use_transfer_path(state, nil, "nope") end it "doesn't invoke an scp command if locals is an empty array" do Kitchen::SSH.stubs(:new).returns(mock) driver.use_transfer_path(state, [], "nope") end it "raises an ActionFailed on transfer when SSHFailed is raised" do Kitchen::SSH.stubs(:new).returns(connection) connection.stubs(:upload_path).raises(Kitchen::SSHFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end it "raises an ActionFailed on exec when Net::SSH:Exception is raised" do Kitchen::SSH.stubs(:new).returns(connection) connection.stubs(:upload_path).raises(Net::SSH::Exception.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end end end test-kitchen-1.23.2/spec/kitchen/driver/exec_spec.rb0000644000004100000410000000366413377651062022411 0ustar www-datawww-data# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require_relative "../../spec_helper" require "kitchen/driver/exec" describe Kitchen::Driver::Exec do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:state) { Hash.new } let(:config) do { reset_command: "mulligan" } end let(:instance) do stub(name: "coolbeans", logger: logger, to_str: "instance", :"transport=" => nil) end let(:driver) do Kitchen::Driver::Exec.new(config).finalize_config!(instance) end it "plugin_version is set to Kitchen::VERSION" do driver.diagnose_plugin[:version].must_equal Kitchen::VERSION end it "sets the transport to exec" do instance.expects(:"transport=").with { |v| v.is_a?(Kitchen::Transport::Exec) } driver end describe "#create" do it "runs the reset command" do driver.expects(:run_command).with("mulligan") driver.create(state) end it "skips the reset command if :reset_command is falsey" do config[:reset_command] = false driver.expects(:run_command).never driver.create(state) end end describe "#destroy" do it "calls the reset command" do driver.expects(:run_command).with("mulligan") driver.destroy(state) end it "skips reset command if :reset_command is falsey" do config[:reset_command] = false driver.expects(:run_command).never driver.destroy(state) end end end test-kitchen-1.23.2/spec/kitchen/driver_spec.rb0000644000004100000410000000576213377651062021466 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/configurable" require "kitchen/errors" require "kitchen/logging" require "kitchen/shell_out" require "kitchen/driver" require "kitchen/driver/base" module Kitchen module Driver class Coolbeans < Kitchen::Driver::Base end class ItDepends < Kitchen::Driver::Base attr_reader :verify_call_count def initialize(config = {}) @verify_call_count = 0 super end def verify_dependencies @verify_call_count += 1 end end class UnstableDepends < Kitchen::Driver::Base def verify_dependencies raise UserError, "Oh noes, you don't have software!" end end end end describe Kitchen::Driver do describe ".for_plugin" do before do Kitchen::Driver.stubs(:require).returns(true) end it "returns a driver object of the correct class" do driver = Kitchen::Driver.for_plugin("coolbeans", {}) driver.must_be_kind_of Kitchen::Driver::Coolbeans end it "returns a driver initialized with its config" do driver = Kitchen::Driver.for_plugin("coolbeans", jelly: "beans") driver[:jelly].must_equal "beans" end it "calls #verify_dependencies on the driver object" do driver = Kitchen::Driver.for_plugin("it_depends", {}) driver.verify_call_count.must_equal 1 end it "calls #verify_dependencies once per driver require" do Kitchen::Driver.stubs(:require).returns(true, false) driver1 = Kitchen::Driver.for_plugin("it_depends", {}) driver1.verify_call_count.must_equal 1 driver2 = Kitchen::Driver.for_plugin("it_depends", {}) driver2.verify_call_count.must_equal 0 end it "raises ClientError if the driver could not be required" do Kitchen::Driver.stubs(:require).raises(LoadError) proc { Kitchen::Driver.for_plugin("coolbeans", {}) } .must_raise Kitchen::ClientError end it "raises ClientError if the driver's class constant could not be found" do Kitchen::Driver.stubs(:require).returns(true) # pretend require worked proc { Kitchen::Driver.for_plugin("nope", {}) } .must_raise Kitchen::ClientError end it "raises UserError if #verify_dependencies fails" do proc { Kitchen::Driver.for_plugin("unstable_depends", {}) } .must_raise Kitchen::UserError end end end test-kitchen-1.23.2/spec/kitchen/provisioner/0000755000004100000410000000000013377651062021201 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/provisioner/base_spec.rb0000644000004100000410000002417713377651062023465 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "logger" require "stringio" require "kitchen" module Kitchen module Provisioner class TestingDummy < Kitchen::Provisioner::Base attr_reader :called_create_sandbox, :called_cleanup_sandbox def install_command "install" end def init_command "init" end def prepare_command "prepare" end def run_command "run" end def create_sandbox @called_create_sandbox = true end def cleanup_sandbox @called_cleanup_sandbox = true end def sandbox_path "/tmp/sandbox" end end end end describe Kitchen::Provisioner::Base do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil, shell_type: nil) } let(:config) { Hash.new } let(:transport) do t = mock("transport") t.responds_like_instance_of(Kitchen::Transport::Base) t end let(:instance) do stub( name: "coolbeans", to_str: "instance", logger: logger, platform: platform, transport: transport ) end let(:provisioner) do Kitchen::Provisioner::Base.new(config).finalize_config!(instance) end describe "configuration" do describe "for unix operating systems" do before { platform.stubs(:os_type).returns("unix") } it ":root_path defaults to /tmp/kitchen" do provisioner[:root_path].must_equal "/tmp/kitchen" end it ":sudo defaults to true" do provisioner[:sudo].must_equal true end it ":sudo_command defaults to sudo -E" do provisioner[:sudo_command].must_equal "sudo -E" end end describe "for windows operating systems" do before { platform.stubs(:os_type).returns("windows") } it ':root_path defaults to $env:TEMP\\kitchen' do provisioner[:root_path].must_equal '$env:TEMP\\kitchen' end it ":sudo defaults to nil" do provisioner[:sudo].must_be_nil end it ":sudo_command defaults to nil" do provisioner[:sudo_command].must_be_nil end end it ":http_proxy defaults to nil" do provisioner[:http_proxy].must_be_nil end it ":http_proxys defaults to nil" do provisioner[:https_proxy].must_be_nil end it ":ftp_proxy defaults to nil" do provisioner[:ftp_proxy].must_be_nil end end describe "#call" do let(:state) { Hash.new } let(:cmd) { provisioner.call(state) } let(:connection) do c = mock("transport_connection") c.responds_like_instance_of(Kitchen::Transport::Base::Connection) c end let(:provisioner) do Kitchen::Provisioner::TestingDummy.new(config).finalize_config!(instance) end before do FakeFS.activate! FileUtils.mkdir_p(File.join(provisioner.sandbox_path, "stuff")) transport.stubs(:connection).yields(connection) connection.stubs(:execute) connection.stubs(:execute_with_retry) connection.stubs(:upload) connection.stubs(:download) config[:downloads] = { ["/tmp/kitchen/nodes", "/tmp/kitchen/data_bags"] => "./test/fixtures", "/remote" => "/local", } end after do FakeFS.deactivate! FakeFS::FileSystem.clear end it "creates the sandbox" do provisioner.expects(:create_sandbox) cmd end it "ensures that the sandbox is cleanup up" do transport.stubs(:connection).raises provisioner.expects(:cleanup_sandbox) begin cmd rescue # rubocop:disable Lint/HandleExceptions end end it "yields a connection given the state" do state[:a] = "b" transport.expects(:connection).with(state).yields(connection) cmd end it "invokes the provisioner commands over the transport" do order = sequence("order") connection.expects(:execute).with("install").in_sequence(order) connection.expects(:execute).with("init").in_sequence(order) connection.expects(:execute).with("prepare").in_sequence(order) connection.expects(:execute_with_retry).with("run", [], 1, 30).in_sequence(order) cmd end it "logs to info" do cmd logged_output.string .must_match(/INFO -- : Transferring files to instance$/) logged_output.string .must_match(/INFO -- : Downloading files from instance$/) end it "uploads sandbox files" do connection.expects(:upload).with(["/tmp/sandbox/stuff"], "/tmp/kitchen") cmd end it "logs to debug" do cmd logged_output.string.must_match(/DEBUG -- : Transfer complete$/) logged_output.string.must_match( %r{DEBUG -- : Downloading /tmp/kitchen/nodes, /tmp/kitchen/data_bags to ./test/fixtures$} ) logged_output.string.must_match( %r{DEBUG -- : Downloading /remote to /local$} ) logged_output.string.must_match(/DEBUG -- : Download complete$/) end it "downloads files" do connection.expects(:download).with( ["/tmp/kitchen/nodes", "/tmp/kitchen/data_bags"], "./test/fixtures" ) connection.expects(:download).with("/remote", "/local") cmd end it "raises an ActionFailed on transfer when TransportFailed is raised" do connection.stubs(:upload) .raises(Kitchen::Transport::TransportFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end it "raises an ActionFailed on execute when TransportFailed is raised" do connection.stubs(:execute) .raises(Kitchen::Transport::TransportFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end [:init_command, :install_command, :prepare_command, :run_command].each do |cmd| it "has a #{cmd} method" do provisioner.public_send(cmd).must_be_nil end end describe "sandbox" do after do begin provisioner.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end it "raises ClientError if #sandbox_path is called before #create_sandbox" do proc { provisioner.sandbox_path }.must_raise Kitchen::ClientError end it "#create_sandbox creates a temporary directory" do provisioner.create_sandbox File.directory?(provisioner.sandbox_path).must_equal true format("%o", File.stat(provisioner.sandbox_path).mode)[1, 4] .must_equal "0755" end it "#create_sandbox logs an info message" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing files for transfer") end it "#create_sandbox logs a debug message" do provisioner.create_sandbox logged_output.string .must_match debug_line_starting_with("Creating local sandbox in ") end it "#cleanup_sandbox deletes the sandbox directory" do provisioner.create_sandbox provisioner.cleanup_sandbox File.directory?(provisioner.sandbox_path).must_equal false end it "#cleanup_sandbox logs a debug message" do provisioner.create_sandbox provisioner.cleanup_sandbox logged_output.string .must_match debug_line_starting_with("Cleaning up local sandbox in ") end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def debug_line_starting_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end end describe "#sudo" do describe "with :sudo set" do before { config[:sudo] = true } it "prepends sudo command" do provisioner.send(:sudo, "wakka").must_equal("sudo -E wakka") end it "customizes sudo when :sudo_command is set" do config[:sudo_command] = "blueto -Ohai" provisioner.send(:sudo, "wakka").must_equal("blueto -Ohai wakka") end end describe "with :sudo falsey" do before { config[:sudo] = false } it "does not include sudo command" do provisioner.send(:sudo, "wakka").must_equal("wakka") end it "does not include sudo command, even when :sudo_command is set" do config[:sudo_command] = "blueto -Ohai" provisioner.send(:sudo, "wakka").must_equal("wakka") end end end describe "#sudo_command" do describe "with :sudo set" do before { config[:sudo] = true } it "returns the default sudo_command" do provisioner.send(:sudo_command).must_equal("sudo -E") end it "returns the custom sudo_command" do config[:sudo_command] = "mysudo" provisioner.send(:sudo_command).must_equal("mysudo") end end describe "with :sudo falsey" do before { config[:sudo] = false } it "returns empty string for default sudo_command" do provisioner.send(:sudo_command).must_equal("") end it "returns empty string for custom sudo_command" do config[:sudo_command] = "mysudo" provisioner.send(:sudo_command).must_equal("") end end end describe "#prefix_command" do describe "with :command_prefix set" do before { config[:command_prefix] = "my_prefix" } it "prepends the command with the prefix" do provisioner.send(:prefix_command, "my_command").must_equal("my_prefix my_command") end end describe "with :command_prefix unset" do before { config[:command_prefix] = nil } it "returns an unaltered command" do provisioner.send(:prefix_command, "my_command").must_equal("my_command") end end end end test-kitchen-1.23.2/spec/kitchen/provisioner/chef_base_spec.rb0000644000004100000410000014476413377651062024457 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/provisioner/chef_base" describe Kitchen::Provisioner::ChefBase do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil) } let(:driver) { stub(cache_directory: nil) } let(:suite) { stub(name: "fries") } let(:default_version) { true } let(:config) do { test_base_path: "/basist", kitchen_root: "/rooty" } end let(:instance) do stub( name: "coolbeans", logger: logger, suite: suite, platform: platform, driver: driver ) end let(:provisioner) do Class.new(Kitchen::Provisioner::ChefBase) do def calculate_path(path, _opts = {}) "/#{path}" end end.new(config).finalize_config!(instance) end describe "configuration" do describe "for unix operating systems" do before { platform.stubs(:os_type).returns("unix") } it ":chef_omnibus_url has a default" do provisioner[:chef_omnibus_url] .must_equal "https://omnitruck.chef.io/install.sh" end it ":chef_metadata_url defaults to nil" do provisioner[:chef_metadata_url].must_be_nil end end describe "for windows operating systems" do before { platform.stubs(:os_type).returns("windows") } it ":chef_omnibus_url has a default" do provisioner[:chef_omnibus_url] .must_equal "https://omnitruck.chef.io/install.sh" end end it ":require_chef_omnibus defaults to true" do provisioner[:require_chef_omnibus].must_equal true end it ":chef_omnibus_install_options defaults to nil" do provisioner[:chef_omnibus_install_options].must_be_nil end it ":run_list defaults to an empty array" do provisioner[:run_list].must_equal [] end it ":attributes defaults to an empty hash" do provisioner[:attributes].must_equal Hash.new end it ":log_level defaults to auto" do provisioner[:log_level].must_equal "auto" end it ":log_level is debug when in debug mode" do config[:debug] = true provisioner[:log_level].must_equal "debug" end it ":log_file defaults to nil" do provisioner[:log_file].must_be_nil end it ":cookbook_files_glob includes recipes" do provisioner[:cookbook_files_glob].must_match %r{,recipes/} end it ":data_path uses calculate_path and is expanded" do provisioner[:data_path] .must_equal os_safe_root_path("/rooty//data") end it ":data_bags_path uses calculate_path and is expanded" do provisioner[:data_bags_path] .must_equal os_safe_root_path("/rooty//data_bags") end it ":environments_path uses calculate_path and is expanded" do provisioner[:environments_path] .must_equal os_safe_root_path("/rooty//environments") end it ":nodes_path uses calculate_path and is expanded" do provisioner[:nodes_path] .must_equal os_safe_root_path("/rooty//nodes") end it ":roles_path uses calculate_path and is expanded" do provisioner[:roles_path] .must_equal os_safe_root_path("/rooty//roles") end it ":clients_path uses calculate_path and is expanded" do provisioner[:clients_path] .must_equal os_safe_root_path("/rooty//clients") end it "...secret_key_path uses calculate_path and is expanded" do provisioner[:encrypted_data_bag_secret_key_path] .must_equal os_safe_root_path("/rooty//encrypted_data_bag_secret_key") end it ":product_name default to nil" do provisioner[:product_name].must_be_nil end it ":product_version defaults to :latest" do provisioner[:product_version].must_equal :latest end it ":channel defaults to :stable" do provisioner[:channel].must_equal :stable end it ":platform default to nil" do provisioner[:platform].must_be_nil end it ":platform_version default to nil" do provisioner[:platform_version].must_be_nil end it ":architecture default to nil" do provisioner[:architecture].must_be_nil end it ":download_url default to nil" do provisioner[:download_url].must_be_nil end it ":checksum default to nil" do provisioner[:checksum].must_be_nil end it ":retry_on_exit_code defaults to standard values" do provisioner[:retry_on_exit_code].must_equal [35, 213] end end describe "#install_command" do before do platform.stubs(:shell_type).returns("bourne") Mixlib::Install::ScriptGenerator.stubs(:new).returns(installer) end let(:installer) { stub(root: "/rooty", install_command: "make_it_so") } let(:cmd) { provisioner.install_command } let(:install_opts) do { omnibus_url: "https://omnitruck.chef.io/install.sh", project: nil, install_flags: nil, sudo_command: "sudo -E", http_proxy: nil, https_proxy: nil } end it "returns nil if :require_chef_omnibus is falsey" do config[:require_chef_omnibus] = false installer.expects(:root).never installer.expects(:install_command).never cmd.must_be_nil end describe "common behaviour" do before do installer.expects(:root).at_least_once.returns("/opt/chef") installer.expects(:install_command) end it "passes sensible defaults" do Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" install_opts[:http_proxy] = "http://proxy" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" install_opts[:https_proxy] = "https://proxy" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" install_opts[:http_proxy] = "http://proxy" install_opts[:https_proxy] = "https://proxy" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "installs chef using :chef_omnibus_url, if necessary" do config[:chef_omnibus_url] = "FROM_HERE" install_opts[:omnibus_url] = "FROM_HERE" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "will install a specific version of chef, if necessary" do config[:require_chef_omnibus] = "1.2.3" Mixlib::Install::ScriptGenerator.expects(:new) .with("1.2.3", false, install_opts).returns(installer) cmd end it "will install a major/minor version of chef, if necessary" do config[:require_chef_omnibus] = "11.10" Mixlib::Install::ScriptGenerator.expects(:new) .with("11.10", false, install_opts).returns(installer) cmd end it "will install a major version of chef, if necessary" do config[:require_chef_omnibus] = "12" Mixlib::Install::ScriptGenerator.expects(:new) .with("12", false, install_opts).returns(installer) cmd end it "will install a nightly, if necessary" do config[:require_chef_omnibus] = "12.5.0-current.0+20150721082808.git.14.c91b337-1" Mixlib::Install::ScriptGenerator.expects(:new).with( "12.5.0-current.0+20150721082808.git.14.c91b337-1", false, install_opts ).returns(installer) cmd end it "will install the latest chef, if necessary" do config[:require_chef_omnibus] = "latest" Mixlib::Install::ScriptGenerator.expects(:new) .with("latest", false, install_opts).returns(installer) cmd end it "will install a version of chef, unless it exists" do config[:require_chef_omnibus] = true Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "will pass a project, when given" do config[:chef_omnibus_install_options] = "-P chefdk" install_opts[:install_flags] = "-P chefdk" install_opts[:project] = "chefdk" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "will pass install options and version info, when given" do config[:require_chef_omnibus] = "11" config[:chef_omnibus_install_options] = "-d /tmp/place" install_opts[:install_flags] = "-d /tmp/place" Mixlib::Install::ScriptGenerator.expects(:new) .with("11", false, install_opts).returns(installer) cmd end it "will set the install root" do config[:chef_omnibus_root] = "/tmp/test" install_opts[:root] = "/tmp/test" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "will set the msi url" do config[:install_msi_url] = "http://blah/blah.msi" install_opts[:install_msi_url] = "http://blah/blah.msi" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end describe "when driver implements the cache_directory interface" do before { driver.stubs(:cache_directory).returns("/tmp/custom/place") } it "will use driver.cache_directory to provide a cache directory" do install_opts[:install_flags] = "-d /tmp/custom/place" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "will use driver.cache_directory even if other options are given" do config[:chef_omnibus_install_options] = "-P cool -v 123" install_opts[:install_flags] = "-P cool -v 123 -d /tmp/custom/place" install_opts[:project] = "cool" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end it "will not use driver.cache_directory if -d options is given" do config[:chef_omnibus_install_options] = "-P cool -d /path -v 123" install_opts[:install_flags] = "-P cool -d /path -v 123" install_opts[:project] = "cool" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts).returns(installer) cmd end end end describe "for product" do before do installer.expects(:root).at_least_once.returns("/opt/chef") installer.expects(:install_command) config[:product_name] = "my_product" end it "will set the product name, version and channel" do config[:product_version] = "version" config[:channel] = "channel" Mixlib::Install.expects(:new).with do |opts| opts[:product_name].must_equal "my_product" opts[:product_version].must_equal "version" opts[:channel].must_equal :channel end.returns(installer) cmd end it "will set the architecture if given" do config[:architecture] = "architecture" Mixlib::Install.expects(:new).with do |opts| opts[:architecture].must_equal "architecture" end.returns(installer) cmd end it "will set the platform if given" do config[:platform] = "platform" Mixlib::Install.expects(:new).with do |opts| opts[:platform].must_equal "platform" end.returns(installer) cmd end it "will set the platform_version if given" do config[:platform_version] = "platform_version" Mixlib::Install.expects(:new).with do |opts| opts[:platform_version].must_equal "platform_version" end.returns(installer) cmd end it "will omit the architecture if not given" do Mixlib::Install.expects(:new).with do |opts| opts.key?(:architecture).must_equal false end.returns(installer) cmd end it "will omit the platform if not given" do Mixlib::Install.expects(:new).with do |opts| opts.key?(:platform).must_equal false end.returns(installer) cmd end it "will omit the platform_version if not given" do Mixlib::Install.expects(:new).with do |opts| opts.key?(:platform_version).must_equal false end.returns(installer) cmd end it "will use stable channel when none specified" do Mixlib::Install.expects(:new).with do |opts| opts[:channel].must_equal :stable end.returns(installer) cmd end it "will set install_strategy to once when not given" do Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:install_strategy].must_equal "once" end.returns(installer) cmd end it "will set install_strategy when given" do config[:install_strategy] = "always" Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:install_strategy].must_equal "always" end.returns(installer) cmd end it "will set the download_url and checksum if given" do config[:download_url] = "http://url/path" config[:checksum] = "abcd" Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:download_url_override].must_equal "http://url/path" opts[:install_command_options][:checksum].must_equal "abcd" end.returns(installer) cmd end it "will set the http_proxy and https_proxy if given" do config[:http_proxy] = "http://url/path:8000" config[:https_proxy] = "http://url/path:8000" Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:http_proxy].must_equal "http://url/path:8000" opts[:install_command_options][:https_proxy].must_equal "http://url/path:8000" end.returns(installer) cmd end it "will set the http_proxy only for powershell" do config[:http_proxy] = "http://url/path:8000" config[:https_proxy] = "http://url/path:8000" platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:http_proxy].must_equal "http://url/path:8000" opts[:install_command_options][:https_proxy].must_be_nil end.returns(installer) cmd end it "will not set proxies when not given" do Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:http_proxy].must_be_nil end.returns(installer) cmd end describe "when driver implements the cache_directory" do describe "for windows" do before { driver.stubs(:cache_directory).returns('$env:TEMP\\dummy\\place') } it "will have the set behavior on windows" do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:download_directory].must_equal '$env:TEMP\\dummy\\place' end.returns(installer) cmd end end describe "for shell" do before { driver.stubs(:cache_directory).returns("/tmp") } it "will have the set behavior on non-windows" do Mixlib::Install.expects(:new).with do |opts| opts[:install_command_options][:cmdline_dl_dir].must_equal "/tmp" end.returns(installer) cmd end end end end describe "when install_strategy is skipped" do before do config[:product_name] = "my_product" config[:install_strategy] = "skip" end it "will not return installer when install_strategy is set to skip" do Mixlib::Install.expects(:new).never cmd end end describe "for bourne shells" do before do installer.expects(:root).at_least_once.returns("/opt/chef") installer.expects(:install_command).returns("my_install_command") end it "prepends sudo for sh commands when :sudo is set" do config[:sudo] = true config[:sudo_command] = "my_sudo_command" install_opts_clone = install_opts.clone install_opts_clone[:sudo_command] = config[:sudo_command] Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts_clone).returns(installer) cmd.must_equal "my_sudo_command my_install_command" end it "does not pass shell type for product based command" do config[:product_name] = "product_name" Mixlib::Install.expects(:new).with do |opts| opts.key?(:shell_type).must_equal false end.returns(installer) cmd end it "does not sudo for sh commands when :sudo is falsey" do config[:sudo] = false install_opts_clone = install_opts.clone install_opts_clone[:sudo_command] = "" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, false, install_opts_clone).returns(installer) cmd.must_equal "my_install_command" end end describe "for powershell shells on windows os types" do before do installer.expects(:root).at_least_once.returns("/opt/chef") installer.expects(:install_command) platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "sets the powershell flag for Mixlib::Install" do install_opts_clone = install_opts.clone install_opts_clone[:sudo_command] = "" Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, true, install_opts_clone).returns(installer) cmd end it "passes ps1 shell type for product based command" do config[:product_name] = "product_name" Mixlib::Install.expects(:new).with do |opts| opts[:shell_type].must_equal :ps1 end.returns(installer) cmd end describe "when driver implements the cache_directory" do before { driver.stubs(:cache_directory).returns('$env:TEMP\\dummy\\place') } it "will have the same behavior on windows" do config[:chef_omnibus_install_options] = "-version 123" install_opts_clone = install_opts.clone install_opts_clone[:sudo_command] = "" install_opts_clone[:install_flags] = "-version 123" install_opts_clone[:install_flags] << ' -download_directory $env:TEMP\\dummy\\place' Mixlib::Install::ScriptGenerator.expects(:new) .with(default_version, true, install_opts_clone).returns(installer) cmd end end end end describe "#init_command" do let(:cmd) { provisioner.init_command } describe "common behavior" do before { platform.stubs(:shell_type).returns("fake") } it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end end describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[1..2].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..2].must_equal([ %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..4].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "prepends sudo for rm when :sudo is set" do config[:sudo] = true cmd.must_match regexify(%{sudo_rm="sudo -E rm"}) end it "does not sudo for sh commands when :sudo is falsey" do config[:sudo] = false cmd.must_match regexify(%{sudo_rm="rm"}) end it "sets chef component dirs for deletion" do config[:root_path] = "/route" dirs = %w{ /route/clients /route/cookbooks /route/data /route/data_bags /route/encrypted_data_bag_secret /route/environments /route/roles }.join(" ") cmd.must_match regexify(%{dirs="#{dirs}"}) end it "sets the root_path from :root_path" do config[:root_path] = "RIGHT_HERE" cmd.must_match regexify(%{root_path="RIGHT_HERE"}) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..3].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "sets chef component dirs for deletion" do config[:root_path] = '\\route' dirs = %w{ clients cookbooks data data_bags encrypted_data_bag_secret environments roles }.map do |dir| "\\route\\#{dir}" end.join(", ") cmd.include? "$dirs = @(#{dirs})" end it "sets the root_path from :root_path" do config[:root_path] = "RIGHT_HERE" cmd.must_match regexify(%{$root_path = "RIGHT_HERE"}) end end end describe "#create_sandbox" do before do @root = Dir.mktmpdir config[:kitchen_root] = @root end after do FileUtils.remove_entry(@root) begin provisioner.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end let(:provisioner) do Class.new(Kitchen::Provisioner::ChefBase) do default_config :generic_rb, {} def create_sandbox super data = default_config_rb.merge(config[:generic_rb]) File.open(File.join(sandbox_path, "generic.rb"), "wb") do |file| file.write(format_config_file(data)) end end end.new(config).finalize_config!(instance) end describe "json file" do let(:json) { JSON.parse(IO.read(sandbox_path("dna.json"))) } it "creates a json file with node attributes" do config[:attributes] = { "one" => { "two" => "three" } } provisioner.create_sandbox json["one"].must_equal("two" => "three") end it "creates a json file with run_list" do config[:run_list] = %w{alpha bravo charlie} provisioner.create_sandbox json["run_list"].must_equal %w{alpha bravo charlie} end it "creates a json file with an empty run_list" do config[:run_list] = [] provisioner.create_sandbox json["run_list"].must_equal [] end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing dna.json") end it "logs a message on debug" do config[:run_list] = ["yo"] provisioner.create_sandbox logged_output.string .must_match debug_line(%(Creating dna.json from {:run_list=>["yo"]})) end end it "creates a cache directory" do provisioner.create_sandbox sandbox_path("cache").directory?.must_equal true end %w{data data_bags environments nodes roles clients}.each do |thing| describe "#{thing} files" do before do create_files_under("#{config[:kitchen_root]}/my_#{thing}") config[:"#{thing}_path"] = "#{config[:kitchen_root]}/my_#{thing}" end it "skips directory creation if :#{thing}_path is not set" do config[:"#{thing}_path"] = nil provisioner.create_sandbox sandbox_path(thing).directory?.must_equal false end it "copies tree from :#{thing}_path into sandbox" do provisioner.create_sandbox sandbox_path("#{thing}/alpha.txt").file?.must_equal true IO.read(sandbox_path("#{thing}/alpha.txt")).must_equal "stuff" sandbox_path("#{thing}/sub").directory?.must_equal true sandbox_path("#{thing}/sub/bravo.txt").file?.must_equal true IO.read(sandbox_path("#{thing}/sub/bravo.txt")).must_equal "junk" end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing #{thing}") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string.must_match debug_line( "Using #{thing} from #{config[:kitchen_root]}/my_#{thing}") end end end describe "secret files" do before do config[:encrypted_data_bag_secret_key_path] = "#{config[:kitchen_root]}/my_secret" File.open("#{config[:kitchen_root]}/my_secret", "wb") do |file| file.write("p@ss") end end it "skips file if :encrypted_data_bag_secret_key_path is not set" do config[:encrypted_data_bag_secret_key_path] = nil provisioner.create_sandbox sandbox_path("encrypted_data_bag_secret").file?.must_equal false end it "copies file from :encrypted_data_bag_secret_key_path into sandbox" do provisioner.create_sandbox sandbox_path("encrypted_data_bag_secret").file?.must_equal true IO.read(sandbox_path("encrypted_data_bag_secret")).must_equal "p@ss" end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing secret") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string.must_match debug_line( "Using secret from #{config[:kitchen_root]}/my_secret") end end describe "cookbooks" do let(:kitchen_root) { config[:kitchen_root] } describe "with a cookbooks/ directory under kitchen_root" do it "copies cookbooks/" do create_cookbook("#{kitchen_root}/cookbooks/epache") create_cookbook("#{kitchen_root}/cookbooks/jahva") provisioner.create_sandbox sandbox_path("cookbooks/epache").directory?.must_equal true sandbox_path("cookbooks/epache/recipes/default.rb") .file?.must_equal true sandbox_path("cookbooks/jahva").directory?.must_equal true sandbox_path("cookbooks/jahva/recipes/default.rb") .file?.must_equal true end it "copies from kitchen_root as cookbook if it contains metadata.rb" do File.open("#{kitchen_root}/metadata.rb", "wb") do |file| file.write("name 'wat'") end create_cookbook("#{kitchen_root}/cookbooks/bk") provisioner.create_sandbox sandbox_path("cookbooks/bk").directory?.must_equal true sandbox_path("cookbooks/wat").directory?.must_equal true sandbox_path("cookbooks/wat/metadata.rb").file?.must_equal true end it "copies site-cookbooks/ if it exists" do create_cookbook("#{kitchen_root}/cookbooks/upstream") create_cookbook("#{kitchen_root}/site-cookbooks/mine") provisioner.create_sandbox sandbox_path("cookbooks/upstream").directory?.must_equal true sandbox_path("cookbooks/mine").directory?.must_equal true sandbox_path("cookbooks/mine/attributes/all.rb").file?.must_equal true end it "logs a message on info for cookbooks/ directory" do create_cookbook("#{kitchen_root}/cookbooks/epache") provisioner.create_sandbox logged_output.string.must_match info_line( "Preparing cookbooks from project directory") end it "logs a meesage on debug for cookbooks/ directory" do create_cookbook("#{kitchen_root}/cookbooks/epache") provisioner.create_sandbox logged_output.string.must_match debug_line( "Using cookbooks from #{kitchen_root}/cookbooks") end it "logs a message on info for site-cookbooks/ directory" do create_cookbook("#{kitchen_root}/cookbooks/epache") create_cookbook("#{kitchen_root}/site-cookbooks/mine") provisioner.create_sandbox logged_output.string.must_match info_line( "Preparing site-cookbooks from project directory") end it "logs a meesage on debug for site-cookbooks/ directory" do create_cookbook("#{kitchen_root}/cookbooks/epache") create_cookbook("#{kitchen_root}/site-cookbooks/mine") provisioner.create_sandbox logged_output.string.must_match debug_line( "Using cookbooks from #{kitchen_root}/site-cookbooks") end end describe "with a cookbook as the project" do before do File.open("#{kitchen_root}/metadata.rb", "wb") do |file| file.write("name 'wat'") end end it "copies from kitchen_root as cookbook if it contains metadata.rb" do provisioner.create_sandbox sandbox_path("cookbooks/wat").directory?.must_equal true sandbox_path("cookbooks/wat/metadata.rb").file?.must_equal true end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line( "Preparing current project directory as a cookbook") end it "logs a meesage on debug" do provisioner.create_sandbox logged_output.string.must_match debug_line( "Using metadata.rb from #{kitchen_root}/metadata.rb") end it "raises a UserError is name cannot be determined from metadata.rb" do File.open("#{kitchen_root}/metadata.rb", "wb") do |file| file.write("nameeeeee 'wat'") end proc { provisioner.create_sandbox }.must_raise Kitchen::UserError end end describe "with no referenced cookbooks" do it "makes a fake cookbook" do name = File.basename(@root) provisioner.create_sandbox sandbox_path("cookbooks/#{name}").directory?.must_equal true sandbox_path("cookbooks/#{name}/metadata.rb").file?.must_equal true IO.read(sandbox_path("cookbooks/#{name}/metadata.rb")) .must_equal %{name "#{name}"\n} end it "logs a warning" do provisioner.create_sandbox logged_output.string.must_match regexify( "Berksfile, Cheffile, cookbooks/, or metadata.rb not found", :partial_line ) end end describe "with a Policyfile under kitchen_root" do let(:policyfile_path) { "#{kitchen_root}/Policyfile.rb" } let(:policyfile_lock_path) { "#{kitchen_root}/Policyfile.lock.json" } let(:resolver) do stub(compile: true, resolve: true, lockfile: policyfile_lock_path) end describe "with the default name `Policyfile.rb`" do before do File.open("#{kitchen_root}/Policyfile.rb", "wb") do |file| file.write(<<-POLICYFILE) name 'wat' run_list 'wat' cookbook 'wat' POLICYFILE end File.open("#{kitchen_root}/Policyfile.lock.json", "wb") do |file| file.write(<<-POLICYFILE) { "name": "wat" } POLICYFILE end Kitchen::Provisioner::Chef::Policyfile.stubs(:new).returns(resolver) end describe "when the chef executable is not in the PATH" do it "raises a UserError" do Kitchen::Provisioner::Chef::Policyfile.stubs(:detect_chef_command!).with do raise Kitchen::UserError, "Load failed" end proc { provisioner }.must_raise Kitchen::UserError end end describe "when using a provisoner that doesn't support policyfiles" do # This is be the default, provisioners must opt-in. it "raises a UserError" do proc { provisioner.create_sandbox }.must_raise Kitchen::UserError end end describe "when the chef executable is in the PATH" do before do Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) provisioner.stubs(:supports_policyfile?).returns(true) end it "logs on debug that it autodetected the policyfile" do provisioner logged_output.string.must_match debug_line( "Policyfile found at #{kitchen_root}/Policyfile.rb, "\ "using Policyfile to resolve dependencies") end it "uses uses the policyfile to resolve dependencies" do resolver.expects(:compile) resolver.expects(:resolve) provisioner.create_sandbox end it "uses Kitchen.mutex for resolving" do Kitchen.mutex.expects(:synchronize).twice provisioner.create_sandbox end it "injects policyfile configuration into the dna.json" do provisioner.create_sandbox dna_json_file = File.join(provisioner.sandbox_path, "dna.json") dna_json_data = JSON.parse(IO.read(dna_json_file)) expected = { "policy_name" => "wat", "policy_group" => "local", } dna_json_data.must_equal(expected) end end end describe "with a custom policyfile_path" do let(:config) do { policyfile_path: "foo-policy.rb", test_base_path: "/basist", kitchen_root: "/rooty", } end before do Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) Kitchen::Provisioner::Chef::Policyfile.stubs(:new).returns(resolver) provisioner.stubs(:supports_policyfile?).returns(true) end describe "when the policyfile exists" do let(:policyfile_path) { "#{kitchen_root}/foo-policy.rb" } let(:policyfile_lock_path) { "#{kitchen_root}/foo-policy.lock.json" } before do File.open(policyfile_path, "wb") do |file| file.write(<<-POLICYFILE) name 'wat' run_list 'wat' cookbook 'wat' POLICYFILE end File.open(policyfile_lock_path, "wb") do |file| file.write(<<-POLICYFILE) { "name": "wat" } POLICYFILE end end it "uses uses the policyfile to resolve dependencies" do Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) resolver.expects(:compile) resolver.expects(:resolve) provisioner.create_sandbox end it "passes the correct path to the policyfile resolver" do Kitchen::Provisioner::Chef::Policyfile .expects(:new) .with(policyfile_path, instance_of(String), anything) .returns(resolver) Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) resolver.expects(:compile) resolver.expects(:resolve) provisioner.create_sandbox end end describe "when the policyfile doesn't exist" do it "raises a UserError" do proc { provisioner.create_sandbox }.must_raise Kitchen::UserError end end describe "when the policyfile lock doesn't exist" do before do File.open("#{kitchen_root}/Policyfile.rb", "wb") do |file| file.write(<<-POLICYFILE) name 'wat' run_list 'wat' cookbook 'wat' POLICYFILE end it "runs `chef install` to generate the lock" do resolver.expects(:compile) provisioner.create_sandbox end end end end describe "with a fallback policyfile" do let(:config) do { policyfile: "foo-policy.rb", test_base_path: "/basist", kitchen_root: "/rooty", } end before do Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) Kitchen::Provisioner::Chef::Policyfile.stubs(:new).returns(resolver) provisioner.stubs(:supports_policyfile?).returns(true) end describe "when the policyfile exists" do let(:policyfile_path) { "#{kitchen_root}/foo-policy.rb" } let(:policyfile_lock_path) { "#{kitchen_root}/foo-policy.lock.json" } before do File.open(policyfile_path, "wb") do |file| file.write(<<-POLICYFILE) name 'wat' run_list 'wat' cookbook 'wat' POLICYFILE end File.open(policyfile_lock_path, "wb") do |file| file.write(<<-POLICYFILE) { "name": "wat" } POLICYFILE end end it "uses uses the policyfile to resolve dependencies" do Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) resolver.expects(:compile) resolver.expects(:resolve) provisioner.create_sandbox end it "passes the correct path to the policyfile resolver" do Kitchen::Provisioner::Chef::Policyfile .expects(:new) .with(policyfile_path, instance_of(String), anything) .returns(resolver) Kitchen::Provisioner::Chef::Policyfile.stubs(:load!) resolver.expects(:compile) resolver.expects(:resolve) provisioner.create_sandbox end end describe "when the policyfile doesn't exist" do it "raises a UserError" do proc { provisioner.create_sandbox }.must_raise Kitchen::UserError end end end end describe "with a Berksfile under kitchen_root" do let(:resolver) { stub(resolve: true) } before do File.open("#{kitchen_root}/Berksfile", "wb") do |file| file.write("cookbook 'wat'") end Kitchen::Provisioner::Chef::Berkshelf.stubs(:new).returns(resolver) end it "raises a UserError if Berkshelf library can't be loaded" do Kitchen::Provisioner::Chef::Berkshelf.stubs(:load_berkshelf!).with do raise Kitchen::UserError, "Load failed" end proc { provisioner }.must_raise Kitchen::UserError end it "logs on debug that Berkshelf is loading" do Kitchen::Provisioner::Chef::Berkshelf.stubs(:load!) provisioner logged_output.string.must_match debug_line( "Berksfile found at #{kitchen_root}/Berksfile, loading Berkshelf") end it "uses Berkshelf" do Kitchen::Provisioner::Chef::Berkshelf.stubs(:load!) resolver.expects(:resolve) provisioner.create_sandbox end it "uses Kitchen.mutex for resolving" do Kitchen::Provisioner::Chef::Berkshelf.stubs(:load!) Kitchen.mutex.expects(:synchronize) provisioner.create_sandbox end end describe "with a Cheffile under kitchen_root" do let(:resolver) { stub(resolve: true) } before do File.open("#{kitchen_root}/Cheffile", "wb") do |file| file.write("cookbook 'wat'") end Kitchen::Provisioner::Chef::Librarian.stubs(:new).returns(resolver) end it "raises a UserError if Librarian library can't be loaded" do proc { provisioner }.must_raise Kitchen::UserError end it "logs on debug that Berkshelf is loading" do Kitchen::Provisioner::Chef::Librarian.stubs(:load!) provisioner logged_output.string.must_match debug_line( "Cheffile found at #{kitchen_root}/Cheffile, loading Librarian-Chef" ) end it "uses Librarian" do Kitchen::Provisioner::Chef::Librarian.stubs(:load!) resolver.expects(:resolve) provisioner.create_sandbox end it "uses Kitchen.mutex for resolving" do Kitchen::Provisioner::Chef::Librarian.stubs(:load!) Kitchen.mutex.expects(:synchronize) provisioner.create_sandbox end end describe "filtering cookbooks files" do it "retains all useful cookbook files" do create_full_cookbook("#{kitchen_root}/cookbooks/full") provisioner.create_sandbox full_cookbook_files.each do |file| sandbox_path("cookbooks/full/#{file}").file?.must_equal true end end it "strips extra cookbook files" do extras = %w{ .gitignore tmp/librarian chefignore .git/info/excludes cookbooks/another/metadata.rb CONTRIBUTING.md metadata.py } create_full_cookbook("#{kitchen_root}/cookbooks/full") extras.each do |file| create_file("#{kitchen_root}/cookbooks/full/#{file}") end provisioner.create_sandbox extras.each do |file| sandbox_path("cookbooks/full/#{file}").file?.must_equal false end end it "logs on info" do create_full_cookbook("#{kitchen_root}/cookbooks/full") provisioner.create_sandbox logged_output.string.must_match info_line( "Removing non-cookbook files before transfer") end end describe "Chef config files" do let(:file) do IO.read(sandbox_path("generic.rb")).lines.map(&:chomp) end it "#create_sanbox creates a generic.rb" do provisioner.create_sandbox sandbox_path("generic.rb").file?.must_equal true end describe "defaults" do before { provisioner.create_sandbox } it "sets node_name to the instance name" do file.must_include %{node_name "#{instance.name}"} end it "sets checksum_path" do file.must_include %{checksum_path "/tmp/kitchen/checksums"} end it "sets file_backup_path" do file.must_include %{file_backup_path "/tmp/kitchen/backup"} end it "sets cookbook_path" do file.must_include %{cookbook_path } + %{["/tmp/kitchen/cookbooks", "/tmp/kitchen/site-cookbooks"]} end it "sets data_bag_path" do file.must_include %{data_bag_path "/tmp/kitchen/data_bags"} end it "sets environment_path" do file.must_include %{environment_path "/tmp/kitchen/environments"} end it "sets node_path" do file.must_include %{node_path "/tmp/kitchen/nodes"} end it "sets role_path" do file.must_include %{role_path "/tmp/kitchen/roles"} end it "sets client_path" do file.must_include %{client_path "/tmp/kitchen/clients"} end it "sets user_path" do file.must_include %{user_path "/tmp/kitchen/users"} end it "sets validation_key" do file.must_include %{validation_key "/tmp/kitchen/validation.pem"} end it "sets client_key" do file.must_include %{client_key "/tmp/kitchen/client.pem"} end it "sets chef_server_url" do file.must_include %{chef_server_url "http://127.0.0.1:8889"} end it "sets encrypted_data_bag_secret" do file.must_include %{encrypted_data_bag_secret } + %{"/tmp/kitchen/encrypted_data_bag_secret"} end it "disables deprecation warnings" do file.must_include %{treat_deprecation_warnings_as_errors false} end end it "supports overwriting defaults" do config[:generic_rb] = { node_name: "eagles", user_path: "/a/b/c/u", chef_server_url: "https://whereever.io", } provisioner.create_sandbox file.must_include %{node_name "eagles"} file.must_include %{user_path "/a/b/c/u"} file.must_include %{chef_server_url "https://whereever.io"} end it " supports adding new configuration" do config[:generic_rb] = { dark_secret: "golang", } provisioner.create_sandbox file.must_include %{dark_secret "golang"} end end def create_cookbook(path) %w{metadata.rb attributes/all.rb recipes/default.rb}.each do |file| create_file(File.join(path, file)) end end def full_cookbook_files %w{ README.org metadata.rb attributes/all.rb definitions/def.rb files/default/config.conf libraries/one.rb libraries/two.rb providers/sweet.rb recipes/default.rb resources/sweet.rb templates/ubuntu/12.04/nginx.conf.erb } end def create_full_cookbook(path) full_cookbook_files.each { |file| create_file(File.join(path, file)) } end def create_file(path) FileUtils.mkdir_p(File.dirname(path)) File.open(path, "wb") { |f| f.write(path) } end end def sandbox_path(path) Pathname.new(provisioner.sandbox_path).join(path) end def create_files_under(path) FileUtils.mkdir_p(File.join(path, "sub")) File.open(File.join(path, "alpha.txt"), "wb") do |file| file.write("stuff") end File.open(File.join(path, "sub", "bravo.txt"), "wb") do |file| file.write("junk") end end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def debug_line(msg) /^D, .* : #{Regexp.escape(msg)}$/ end end def regexify(str, line = :whole_line) r = Regexp.escape(str) r = "^\s*#{r}$" if line == :whole_line Regexp.new(r) end end test-kitchen-1.23.2/spec/kitchen/provisioner/chef_apply_spec.rb0000644000004100000410000000725713377651062024665 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: SAWANOBORI Yukihiko ) # # Copyright (C) 2015, HiganWorks LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/provisioner/chef_apply" describe Kitchen::Provisioner::ChefApply do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil) } let(:suite) { stub(name: "fries") } let(:config) do { test_base_path: "/b", kitchen_root: "/r" } end let(:instance) do stub( name: "coolbeans", logger: logger, suite: suite, platform: platform ) end let(:provisioner) do Kitchen::Provisioner::ChefApply.new(config).finalize_config!(instance) end it "provisioner api_version is 2" do provisioner.diagnose_plugin[:api_version].must_equal 2 end it "plugin_version is set to Kitchen::VERSION" do provisioner.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "default config" do it "sets :chef_apply_path to a path using :chef_omnibus_root" do config[:chef_omnibus_root] = "/nice/place" provisioner[:chef_apply_path].must_equal "/nice/place/bin/chef-apply" end end describe "#create_sandbox" do before do @root = Dir.mktmpdir config[:kitchen_root] = @root end after do FileUtils.remove_entry(@root) begin provisioner.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end end describe "#run_command" do before do config[:run_list] = %w{appry_recipe1 appry_recipe2} end let(:cmd) { provisioner.run_command } describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "uses sudo for chef-apply when configured" do config[:chef_omnibus_root] = "/c" config[:sudo] = true cmd.must_match regexify("sudo -E /c/bin/chef-apply apply/appry_recipe1.rb ", :partial_line) cmd.must_match regexify("sudo -E /c/bin/chef-apply apply/appry_recipe2.rb ", :partial_line) end it "does not use sudo for chef-apply when configured" do config[:chef_omnibus_root] = "/c" config[:sudo] = false cmd.must_match regexify("chef-apply apply/appry_recipe1.rb ", :partial_line) cmd.must_match regexify("chef-apply apply/appry_recipe2.rb ", :partial_line) cmd.wont_match regexify("sudo -E /c/bin/chef-apply ") end it "sets log level flag on chef-apply to auto by default" do cmd.must_match regexify(" --log_level auto", :partial_line) end it "set log level flag for custom level" do config[:log_level] = :extreme cmd.must_match regexify(" --log_level extreme", :partial_line) end it "sets no color flag on chef-apply" do cmd.must_match regexify(" --no-color", :partial_line) end end end def regexify(str, line = :whole_line) r = Regexp.escape(str) r = "^\s*#{r}$" if line == :whole_line Regexp.new(r) end end test-kitchen-1.23.2/spec/kitchen/provisioner/chef/0000755000004100000410000000000013377651062022106 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/provisioner/chef/policyfile_spec.rb0000644000004100000410000001104613377651062025606 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Noah Kantrowitz # # Copyright (C) 2016, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../../spec_helper" require "kitchen/provisioner/chef/policyfile" describe Kitchen::Provisioner::Chef::Policyfile do let(:policyfile) { "" } let(:path) { "" } let(:null_logger) do stub(fatal: nil, error: nil, warn: nil, info: nil, debug: nil, banner: nil) end let(:described_object) do Kitchen::Provisioner::Chef::Policyfile.new(policyfile, path, logger: null_logger) end let(:os) { "" } before do @original_rbconfig = RbConfig::CONFIG verbose = $VERBOSE $VERBOSE = nil RbConfig.const_set(:CONFIG, "host_os" => os) $VERBOSE = verbose end after do verbose = $VERBOSE $VERBOSE = nil RbConfig.const_set(:CONFIG, @original_rbconfig) $VERBOSE = verbose end # rubocop:disable Metrics/LineLength describe "#resolve" do subject { described_object.resolve } describe "on Unix" do let(:os) { "linux-gnu" } describe "with simple paths" do let(:policyfile) { "/home/user/cookbook/Policyfile.rb" } let(:path) { "/tmp/kitchen/cookbooks" } it do described_object.expects(:run_command).with("chef export /home/user/cookbook/Policyfile.rb /tmp/kitchen/cookbooks --force") subject end end describe "with Jenkins-y paths" do let(:policyfile) { "/home/jenkins/My Chef Cookbook/workspace/current/Policyfile.rb" } let(:path) { "/tmp/kitchen/cookbooks" } it do described_object.expects(:run_command).with('chef export /home/jenkins/My\\ Chef\\ Cookbook/workspace/current/Policyfile.rb /tmp/kitchen/cookbooks --force') subject end end end describe "on Windows" do let(:os) { "mswin" } describe "with simple paths" do let(:policyfile) { 'C:\\cookbook\\Policyfile.rb' } let(:path) { 'C:\\Temp\\kitchen\\cookbooks' } it do described_object.expects(:run_command).with('chef export C:\\cookbook\\Policyfile.rb C:\\Temp\\kitchen\\cookbooks --force') subject end end describe "with Jenkins-y paths" do let(:policyfile) { 'C:\\Program Files\\Jenkins\\My Chef Cookbook\\workspace\\current\\Policyfile.rb' } let(:path) { 'C:\\Temp\\kitchen\\cookbooks' } it do described_object.expects(:run_command).with('chef export "C:\\\\Program\\ Files\\\\Jenkins\\\\My\\ Chef\\ Cookbook\\\\workspace\\\\current\\\\Policyfile.rb" C:\\Temp\\kitchen\\cookbooks --force') subject end end end end describe "#compile" do subject { described_object.compile } describe "on Unix" do let(:os) { "linux-gnu" } describe "with simple paths" do let(:policyfile) { "/home/user/cookbook/Policyfile.rb" } it do described_object.expects(:run_command).with("chef install /home/user/cookbook/Policyfile.rb") subject end end describe "with Jenkins-y paths" do let(:policyfile) { "/home/jenkins/My Chef Cookbook/workspace/current/Policyfile.rb" } it do described_object.expects(:run_command).with('chef install /home/jenkins/My\\ Chef\\ Cookbook/workspace/current/Policyfile.rb') subject end end end describe "on Windows" do let(:os) { "mswin" } describe "with simple paths" do let(:policyfile) { 'C:\\cookbook\\Policyfile.rb' } it do described_object.expects(:run_command).with('chef install C:\\cookbook\\Policyfile.rb') subject end end describe "with Jenkins-y paths" do let(:policyfile) { 'C:\\Program Files\\Jenkins\\My Chef Cookbook\\workspace\\current\\Policyfile.rb' } it do described_object.expects(:run_command).with('chef install "C:\\\\Program\\ Files\\\\Jenkins\\\\My\\ Chef\\ Cookbook\\\\workspace\\\\current\\\\Policyfile.rb"') subject end end end end # rubocop:enable Metrics/LineLength end test-kitchen-1.23.2/spec/kitchen/provisioner/dummy_spec.rb0000644000004100000410000000512213377651062023673 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "logger" require "stringio" require "kitchen/provisioner/dummy" describe Kitchen::Provisioner::Dummy do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil, shell_type: nil) } let(:suite) { stub(name: "fries") } let(:state) { Hash.new } let(:config) do { test_base_path: "/basist", kitchen_root: "/rooty" } end let(:instance) do stub( name: "coolbeans", to_str: "instance", logger: logger, suite: suite, platform: platform ) end let(:provisioner) do Kitchen::Provisioner::Dummy.new(config).finalize_config!(instance) end it "provisioner api_version is 2" do provisioner.diagnose_plugin[:api_version].must_equal 2 end it "plugin_version is set to Kitchen::VERSION" do provisioner.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "configuration" do it "sets :sleep to 0 by default" do provisioner[:sleep].must_equal 0 end it "sets :random_failure to false by default" do provisioner[:random_failure].must_equal false end end describe "#call" do it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 12.5 provisioner.expects(:sleep).with(12.5).returns(true) provisioner.call(state) end it "raises ActionFailed if :fail is set" do config[:fail] = true proc { provisioner.call(state) }.must_raise Kitchen::ActionFailed end it "randomly raises ActionFailed if :random_failure is set" do config[:random_failure] = true provisioner.stubs(:randomly_fail?).returns(true) proc { provisioner.call(state) }.must_raise Kitchen::ActionFailed end it "logs a converge event to INFO" do provisioner.call(state) logged_output.string.must_match(/^.+ INFO .+ \[Dummy\] Converge on .+$/) end end end test-kitchen-1.23.2/spec/kitchen/provisioner/chef_zero_spec.rb0000644000004100000410000007614613377651062024522 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/provisioner/chef_zero" describe Kitchen::Provisioner::ChefZero do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil) } let(:suite) { stub(name: "fries") } let(:config) do { test_base_path: "/b", kitchen_root: "/r" } end let(:instance) do stub( name: "coolbeans", logger: logger, suite: suite, platform: platform ) end let(:provisioner) do Kitchen::Provisioner::ChefZero.new(config).finalize_config!(instance) end it "provisioner api_version is 2" do provisioner.diagnose_plugin[:api_version].must_equal 2 end it "plugin_version is set to Kitchen::VERSION" do provisioner.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "default config" do describe "for unix operating systems" do before { platform.stubs(:os_type).returns("unix") } it "sets :chef_client_path to a path using :chef_omnibus_root" do config[:chef_omnibus_root] = "/nice/place" provisioner[:chef_client_path] .must_equal "/nice/place/bin/chef-client" end it "sets :ruby_bindir to use an Omnibus Ruby" do config[:chef_omnibus_root] = "/nice" provisioner[:ruby_bindir].must_equal "/nice/embedded/bin" end end describe "for windows operating systems" do before { platform.stubs(:os_type).returns("windows") } it "sets :chef_client_path to a path using :chef_omnibus_root" do config[:chef_omnibus_root] = '$env:systemdrive\\nice\\place' provisioner[:chef_client_path] .must_equal '$env:systemdrive\\nice\\place\\bin\\chef-client.bat' end it "sets :ruby_bindir to use an Omnibus Ruby" do config[:chef_omnibus_root] = 'c:\\nice' provisioner[:ruby_bindir].must_equal 'c:\\nice\\embedded\\bin' end end it "sets :client_rb to an empty Hash" do provisioner[:client_rb].must_equal Hash.new end it "sets :json_attributes to true" do provisioner[:json_attributes].must_equal true end it "does not set :chef_zero_host" do provisioner[:chef_zero_host].must_be_nil end it "sets :chef_zero_port to 8889" do provisioner[:chef_zero_port].must_equal 8889 end end describe "#create_sandbox" do before do @root = Dir.mktmpdir config[:kitchen_root] = @root end after do FileUtils.remove_entry(@root) begin provisioner.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end describe "client.rb file" do let(:file) do IO.read(sandbox_path("client.rb")).lines.map(&:chomp) end let(:file_no_updated_resources) do IO.read(sandbox_path("client_no_updated_resources.rb")).lines.map(&:chomp) end it "creates a client.rb" do provisioner.create_sandbox sandbox_path("client.rb").file?.must_equal true end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing client.rb") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string .must_match debug_line_starting_with("Creating client.rb from {") end describe "defaults" do # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.common_client_rb_specs it "sets node_name to the instance name" do file.must_include %{node_name "#{instance.name}"} end it "sets checksum_path" do file.must_include %{checksum_path "#{base}checksums"} end it "sets file_backup_path" do file.must_include %{file_backup_path "#{base}backup"} end it "sets cookbook_path" do file.must_include %{cookbook_path } + %{["#{base}cookbooks", "#{base}site-cookbooks"]} end it "sets data_bag_path" do file.must_include %{data_bag_path "#{base}data_bags"} end it "sets environment_path" do file.must_include %{environment_path "#{base}environments"} end it "sets node_path" do file.must_include %{node_path "#{base}nodes"} end it "sets role_path" do file.must_include %{role_path "#{base}roles"} end it "sets client_path" do file.must_include %{client_path "#{base}clients"} end it "sets user_path" do file.must_include %{user_path "#{base}users"} end it "sets validation_key" do file.must_include %{validation_key "#{base}validation.pem"} end it "sets client_key" do file.must_include %{client_key "#{base}client.pem"} end it "sets chef_server_url" do file.must_include %{chef_server_url "http://127.0.0.1:8889"} end it "sets encrypted_data_bag_secret" do file.must_include %{encrypted_data_bag_secret } + %{"#{base}encrypted_data_bag_secret"} end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "for unix os types" do before do platform.stubs(:os_type).returns("unix") provisioner.create_sandbox end let(:base) { "/tmp/kitchen/" } common_client_rb_specs end describe "for windows os types with full path" do before do platform.stubs(:os_type).returns("windows") config[:root_path] = '\\a\\b' provisioner.create_sandbox end let(:base) { '\\\\a\\\\b\\\\' } common_client_rb_specs end describe "for windows os types with $env:TEMP prefixed paths" do before do platform.stubs(:os_type).returns("windows") config[:root_path] = '$env:TEMP\\a' provisioner.create_sandbox end let(:base) { "\#{ENV['TEMP']}\\\\a\\\\" } common_client_rb_specs end end it "supports overwriting defaults" do config[:client_rb] = { node_name: "eagles", user_path: "/a/b/c/u", client_key: "lol", } provisioner.create_sandbox file.must_include %{node_name "eagles"} file.must_include %{user_path "/a/b/c/u"} file.must_include %{client_key "lol"} end it " supports adding new configuration" do config[:client_rb] = { dark_secret: "golang", } provisioner.create_sandbox file.must_include %{dark_secret "golang"} end it "formats array values correctly" do config[:client_rb] = { foos: %w{foo1 foo2}, } provisioner.create_sandbox file.must_include %{foos ["foo1", "foo2"]} end it "formats integer values correctly" do config[:client_rb] = { foo: 7, } provisioner.create_sandbox file.must_include %{foo 7} end it "formats symbol-looking string values correctly" do config[:client_rb] = { foo: ":bar", } provisioner.create_sandbox file.must_include %{foo :bar} end it "formats boolean values correctly" do config[:client_rb] = { foo: false, bar: true, } provisioner.create_sandbox file.must_include %{foo false} file.must_include %{bar true} end it "supports idempotency check " do config[:multiple_converge] = 2 config[:enforce_idempotency] = true provisioner.create_sandbox file_no_updated_resources.join.must_match(/handler_file =.*chef-client-fail-if-update-handler.rb/) end end describe "validation.pem file" do it "creates file" do provisioner.create_sandbox sandbox_path("validation.pem").file?.must_equal true end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing validation.pem") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string .must_match debug_line_starting_with("Using a dummy validation.pem") end end describe "chef-client-zero.rb file" do describe "for modern Chef versions" do before { config[:require_chef_omnibus] = "11.10" } it "does not create the file" do provisioner.create_sandbox sandbox_path("chef-client-zero.rb").file?.must_equal false end it "a version of '11' is still considered modern" do config[:require_chef_omnibus] = "11" provisioner.create_sandbox sandbox_path("chef-client-zero.rb").file?.must_equal false end it "a version of 11 is still considered modern" do config[:require_chef_omnibus] = 11 provisioner.create_sandbox sandbox_path("chef-client-zero.rb").file?.must_equal false end end describe "for old Chef versions" do before { config[:require_chef_omnibus] = "10.20" } it "creates the file when using an old Chef version" do provisioner.create_sandbox sandbox_path("chef-client-zero.rb").file?.must_equal true end it "logs a message on info" do provisioner.create_sandbox logged_output.string .must_match info_line("Preparing chef-client-zero.rb") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string.must_match debug_line_starting_with( "Using a vendored chef-client-zero.rb") end end end def sandbox_path(path) Pathname.new(provisioner.sandbox_path).join(path) end end describe "#prepare_command" do let(:cmd) { provisioner.prepare_command } describe "for modern Chef versions" do before { config[:require_chef_omnibus] = "11.10" } it "returns nil" do cmd.must_be_nil end end describe "for old Chef versions" do before { config[:require_chef_omnibus] = "10.20" } describe "for bourne shells" do before do platform.stubs(:shell_type).returns("bourne") config[:ruby_bindir] = "/rbd" end it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[1..2].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..2].must_equal([ %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..4].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "sets the CHEF_REPO_PATH environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{CHEF_REPO_PATH="/r"; export CHEF_REPO_PATH}) end it "sets the GEM_HOME environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{GEM_HOME="/r/chef-client-zero-gems"; export GEM_HOME}) end it "sets the GEM_PATH environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{GEM_PATH="/r/chef-client-zero-gems"; export GEM_PATH}) end it "sets the GEM_CACHE environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{GEM_CACHE="/r/chef-client-zero-gems/cache"; export GEM_CACHE}) end it "prepends sudo for gem command when :sudo is set" do config[:sudo] = true cmd.must_match regexify(%{gem="sudo -E /rbd/gem"}) end it "does not sudo for gem commands when :sudo is falsey" do config[:sudo] = false cmd.must_match regexify(%{gem="/rbd/gem"}) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") config[:root_path] = '\\r' config[:ruby_bindir] = '\\rbd' end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..3].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "sets the CHEF_REPO_PATH environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:CHEF_REPO_PATH = "\\r"}) end it "sets the GEM_HOME environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:GEM_HOME = "\\r\\chef-client-zero-gems"}) end it "sets the GEM_PATH environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:GEM_PATH = "\\r\\chef-client-zero-gems"}) end it "sets the GEM_CACHE environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:GEM_CACHE = "\\r\\chef-client-zero-gems\\cache"}) end it "sets the path to the gem command" do cmd.must_match regexify(%{$gem = "\\rbd\\gem.bat"}) end end end end describe "#run_command" do let(:cmd) { provisioner.run_command } # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.common_shell_specs it "sets config flag on chef-client" do cmd.must_match regexify( " --config #{base}client.rb", :partial_line) end it "sets config flag for custom root_path" do config[:root_path] = custom_root cmd.must_match regexify( " --config #{custom_base}client.rb", :partial_line) end it "sets log level flag on chef-client to auto by default" do cmd.must_match regexify(" --log_level auto", :partial_line) end it "set log level flag for custom level" do config[:log_level] = :extreme cmd.must_match regexify(" --log_level extreme", :partial_line) end it "sets force formatter flag on chef-solo" do cmd.must_match regexify(" --force-formatter", :partial_line) end it "sets no color flag on chef-solo" do cmd.must_match regexify(" --no-color", :partial_line) end it "sets json attributes flag on chef-client" do cmd.must_match regexify( " --json-attributes #{base}dna.json", :partial_line) end it "sets json attribtes flag for custom root_path" do config[:root_path] = custom_root cmd.must_match regexify( " --json-attributes #{custom_base}dna.json", :partial_line) end it "does not set json attributes flag if config is falsey" do config[:json_attributes] = false cmd.wont_match regexify(" --json-attributes ", :partial_line) end it "sets logfile flag for custom value" do config[:log_file] = "#{custom_base}out.log" cmd.must_match regexify( " --logfile #{custom_base}out.log", :partial_line) end it "does not set logfile flag by default" do cmd.wont_match regexify(" --logfile ", :partial_line) end it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "for modern Chef versions" do before { config[:require_chef_omnibus] = "11.10" } # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.common_modern_shell_specs it "sets local mode flag on chef-client" do cmd.must_match regexify(" --local-mode", :partial_line) end it "sets chef zero port flag on chef-client" do cmd.must_match regexify(" --chef-zero-port 8889", :partial_line) end it "sets chef zero host flag for custom host" do config[:chef_zero_host] = "192.168.0.1" cmd.must_match regexify(" --chef-zero-host 192.168.0.1", :partial_line) end it "sets chef zero port flag for custom port" do config[:chef_zero_port] = 123 cmd.must_match regexify(" --chef-zero-port 123", :partial_line) end it "does not set chef zero host flag when value is falsey" do config[:chef_zero_host] = nil cmd.wont_match regexify(" --chef-zero-host ", :partial_line) end it "does not set chef zero port flag when value is falsey" do config[:chef_zero_port] = nil cmd.wont_match regexify(" --chef-zero-port ", :partial_line) end it "sets profile-ruby flag when config element is set" do config[:profile_ruby] = true cmd.must_match regexify( " --profile-ruby", :partial_line) end it "does not set profile-ruby flag when config element is falsey" do config[:profile_ruby] = false cmd.wont_match regexify(" --profile-ruby", :partial_line) end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } let(:base) { "/tmp/kitchen/" } let(:custom_base) { "/a/b/" } let(:custom_root) { "/a/b" } common_shell_specs common_modern_shell_specs it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "contains a standard second run if multiple_converge is set to 2" do config[:multiple_converge] = 2 cmd.must_match(/chef-client.*&&.*chef-client.*/m) cmd.wont_match(/chef-client.*&&.*chef-client.*client_no_updated_resources.rb/m) end it "contains a specific second run if enforce_idempotency is set" do config[:multiple_converge] = 2 config[:enforce_idempotency] = true cmd.must_match(/chef-client.*&&.*chef-client.*client_no_updated_resources.rb/m) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[1..2].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..2].must_equal([ %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..4].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "does no powershell PATH reloading for older chef packages" do cmd.wont_match regexify(%{[System.Environment]::}) end it "uses sudo for chef-client when configured" do config[:chef_omnibus_root] = "/c" config[:sudo] = true cmd.must_match regexify("sudo -E /c/bin/chef-client ", :partial_line) end it "does not use sudo for chef-client when configured" do config[:chef_omnibus_root] = "/c" config[:sudo] = false cmd.must_match regexify("/c/bin/chef-client ", :partial_line) cmd.wont_match regexify("sudo -E /c/bin/chef-client ", :partial_line) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end let(:base) { '$env:TEMP\\kitchen\\' } let(:custom_base) { '\\a\\b\\' } let(:custom_root) { '\\a\\b' } common_shell_specs common_modern_shell_specs it "contains a standard second run if multiple_converge is set to 2" do config[:multiple_converge] = 2 cmd.must_match(/chef-client.*;.*chef-client.*/m) cmd.wont_match(/chef-client.*;.*chef-client.*client_no_updated_resources.rb/m) end it "contains a specific second run if enforce_idempotency is set" do config[:multiple_converge] = 2 config[:enforce_idempotency] = true cmd.must_match(/chef-client.*;.*chef-client.*client_no_updated_resources.rb/m) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..3].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "reloads PATH for older chef packages" do cmd.must_match regexify("$env:PATH = try {\n" \ "[System.Environment]::GetEnvironmentVariable('PATH','Machine')\n" \ "} catch { $env:PATH }") end it "calls the chef-client command from :chef_client_path" do config[:chef_client_path] = '\\r\\chef-client.bat' cmd.must_match regexify('& \\r\\chef-client.bat ', :partial_line) end end end describe "for old Chef versions" do before do config[:require_chef_omnibus] = "10.20" end def self.common_old_shell_specs it "does not set local mode flag" do cmd.wont_match regexify(" --local-mode", :partial_line) end it "does not set chef zero host flag for custom host" do config[:chef_zero_host] = "192.168.0.1" cmd.wont_match regexify(" --chef-zero-host 192.168.0.1", :partial_line) end it "does not set chef zero port flag for custom port" do config[:chef_zero_port] = 123 cmd.wont_match regexify(" --chef-zero-port 123", :partial_line) end end describe "for bourne shells" do before do platform.stubs(:shell_type).returns("bourne") config[:ruby_bindir] = "/r/bin" end let(:base) { "/tmp/kitchen/" } let(:custom_base) { "/a/b/" } let(:custom_root) { "/a/b" } common_shell_specs common_old_shell_specs it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "uses sudo for ruby when configured" do config[:root_path] = "/x" config[:sudo] = true cmd.must_match regexify( "sudo -E /r/bin/ruby /x/chef-client-zero.rb ", :partial_line) end it "does not use sudo for ruby when configured" do config[:root_path] = "/x" config[:sudo] = false cmd.must_match regexify( "/r/bin/ruby /x/chef-client-zero.rb ", :partial_line) cmd.wont_match regexify( "sudo -E /r/bin/ruby /x/chef-client-zero.rb ", :partial_line) end it "sets the CHEF_REPO_PATH environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{CHEF_REPO_PATH="/r"; export CHEF_REPO_PATH}) end it "sets the GEM_HOME environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{GEM_HOME="/r/chef-client-zero-gems"; export GEM_HOME}) end it "sets the GEM_PATH environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{GEM_PATH="/r/chef-client-zero-gems"; export GEM_PATH}) end it "sets the GEM_CACHE environment variable" do config[:root_path] = "/r" cmd.must_match regexify( %{GEM_CACHE="/r/chef-client-zero-gems/cache"; export GEM_CACHE}) end it "does no powershell PATH reloading for older chef packages" do cmd.wont_match regexify(%{[System.Environment]::}) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") config[:ruby_bindir] = '\\r\\bin' end let(:base) { '$env:TEMP\\kitchen\\' } let(:custom_base) { '\\a\\b\\' } let(:custom_root) { '\\a\\b' } common_shell_specs common_old_shell_specs it "calls ruby from :ruby_bindir" do config[:root_path] = '\\x' cmd.must_match regexify( '\\r\\bin\\ruby.exe \\x\\chef-client-zero.rb ', :partial_line) end it "sets the CHEF_REPO_PATH environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:CHEF_REPO_PATH = "\\r"}) end it "sets the GEM_HOME environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:GEM_HOME = "\\r\\chef-client-zero-gems"}) end it "sets the GEM_PATH environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:GEM_PATH = "\\r\\chef-client-zero-gems"}) end it "sets the GEM_CACHE environment variable" do config[:root_path] = '\\r' cmd.must_match regexify( %{$env:GEM_CACHE = "\\r\\chef-client-zero-gems\\cache"}) end it "reloads PATH for older chef packages" do cmd.must_match regexify("$env:PATH = try {\n" \ "[System.Environment]::GetEnvironmentVariable('PATH','Machine')\n" \ "} catch { $env:PATH }") end end end end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def debug_line_starting_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end def regexify(str, line = :whole_line) r = Regexp.escape(str) r = "^\s*#{r}$" if line == :whole_line Regexp.new(r) end end test-kitchen-1.23.2/spec/kitchen/provisioner/chef_solo_spec.rb0000644000004100000410000004427713377651062024517 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/provisioner/chef_solo" describe Kitchen::Provisioner::ChefSolo do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil) } let(:suite) { stub(name: "fries") } let(:config) do { test_base_path: "/b", kitchen_root: "/r" } end let(:instance) do stub( name: "coolbeans", logger: logger, suite: suite, platform: platform ) end let(:provisioner) do Kitchen::Provisioner::ChefSolo.new(config).finalize_config!(instance) end it "provisioner api_version is 2" do provisioner.diagnose_plugin[:api_version].must_equal 2 end it "plugin_version is set to Kitchen::VERSION" do provisioner.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "default config" do describe "for unix operating systems" do before { platform.stubs(:os_type).returns("unix") } it "sets :chef_solo_path to a path using :chef_omnibus_root" do config[:chef_omnibus_root] = "/nice/place" provisioner[:chef_solo_path].must_equal "/nice/place/bin/chef-solo" end end describe "for windows operating systems" do before { platform.stubs(:os_type).returns("windows") } it "sets :chef_solo_path to a path using :chef_omnibus_root" do config[:chef_omnibus_root] = '$env:systemdrive\\nice\\place' provisioner[:chef_solo_path] .must_equal '$env:systemdrive\\nice\\place\\bin\\chef-solo.bat' end end it "sets :solo_rb to an empty Hash" do provisioner[:solo_rb].must_equal Hash.new end end describe "#create_sandbox" do before do @root = Dir.mktmpdir config[:kitchen_root] = @root end after do FileUtils.remove_entry(@root) begin provisioner.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end describe "solo.rb file" do let(:file) do IO.read(sandbox_path("solo.rb")).lines.map(&:chomp) end let(:file_no_updated_resources) do IO.read(sandbox_path("client_no_updated_resources.rb")).lines.map(&:chomp) end it "creates a solo.rb" do provisioner.create_sandbox sandbox_path("solo.rb").file?.must_equal true end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing solo.rb") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string .must_match debug_line_starting_with("Creating solo.rb from {") end describe "defaults" do # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.common_solo_rb_specs it "sets node_name to the instance name" do file.must_include %{node_name "#{instance.name}"} end it "sets checksum_path" do file.must_include %{checksum_path "#{base}checksums"} end it "sets file_backup_path" do file.must_include %{file_backup_path "#{base}backup"} end it "sets cookbook_path" do file.must_include %{cookbook_path } + %{["#{base}cookbooks", "#{base}site-cookbooks"]} end it "sets data_bag_path" do file.must_include %{data_bag_path "#{base}data_bags"} end it "sets environment_path" do file.must_include %{environment_path "#{base}environments"} end it "sets node_path" do file.must_include %{node_path "#{base}nodes"} end it "sets role_path" do file.must_include %{role_path "#{base}roles"} end it "sets client_path" do file.must_include %{client_path "#{base}clients"} end it "sets user_path" do file.must_include %{user_path "#{base}users"} end it "sets validation_key" do file.must_include %{validation_key "#{base}validation.pem"} end it "sets client_key" do file.must_include %{client_key "#{base}client.pem"} end it "sets chef_server_url" do file.must_include %{chef_server_url "http://127.0.0.1:8889"} end it "sets encrypted_data_bag_secret" do file.must_include %{encrypted_data_bag_secret } + %{"#{base}encrypted_data_bag_secret"} end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "for unix os types" do before do platform.stubs(:os_type).returns("unix") provisioner.create_sandbox end let(:base) { "/tmp/kitchen/" } common_solo_rb_specs end describe "for windows os types with full path" do before do platform.stubs(:os_type).returns("windows") config[:root_path] = '\\a\\b' provisioner.create_sandbox end let(:base) { '\\\\a\\\\b\\\\' } common_solo_rb_specs end describe "for windows os types with $env:TEMP prefixed paths" do before do platform.stubs(:os_type).returns("windows") config[:root_path] = '$env:TEMP\\a' provisioner.create_sandbox end let(:base) { "\#{ENV['TEMP']}\\\\a\\\\" } common_solo_rb_specs end end it "supports overwriting defaults" do config[:solo_rb] = { node_name: "eagles", user_path: "/a/b/c/u", client_key: "lol", } provisioner.create_sandbox file.must_include %{node_name "eagles"} file.must_include %{user_path "/a/b/c/u"} file.must_include %{client_key "lol"} end it "supports adding new configuration" do config[:solo_rb] = { dark_secret: "golang", } provisioner.create_sandbox file.must_include %{dark_secret "golang"} end it "formats array values correctly" do config[:solo_rb] = { foos: %w{foo1 foo2}, } provisioner.create_sandbox file.must_include %{foos ["foo1", "foo2"]} end it "formats integer values correctly" do config[:solo_rb] = { foo: 7, } provisioner.create_sandbox file.must_include %{foo 7} end it "formats symbol-looking string values correctly" do config[:solo_rb] = { foo: ":bar", } provisioner.create_sandbox file.must_include %{foo :bar} end it "formats boolean values correctly" do config[:solo_rb] = { foo: false, bar: true, } provisioner.create_sandbox file.must_include %{foo false} file.must_include %{bar true} end it "supports idempotency check " do config[:multiple_converge] = 2 config[:enforce_idempotency] = true provisioner.create_sandbox file_no_updated_resources.join.must_match(/handler_file =.*chef-client-fail-if-update-handler.rb/) end end def sandbox_path(path) Pathname.new(provisioner.sandbox_path).join(path) end end describe "#run_command" do let(:cmd) { provisioner.run_command } describe "common behavior" do before { platform.stubs(:shell_type).returns("fake") } it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end end describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "contains a standard second run if multiple_converge is set to 2" do config[:multiple_converge] = 2 cmd.must_match(/chef-solo.*&&.*chef-solo.*/m) cmd.wont_match(/chef-solo.*&&.*chef-solo.*client_no_updated_resources.rb/m) end it "contains a specific second run if enforce_idempotency is set" do config[:multiple_converge] = 2 config[:enforce_idempotency] = true cmd.must_match(/chef-solo.*&&.*chef-solo.*client_no_updated_resources.rb/m) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[1..2].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..2].must_equal([ %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..4].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "does no powershell PATH reloading for older chef packages" do cmd.wont_match regexify(%{[System.Environment]::}) end it "uses sudo for chef-solo when configured" do config[:chef_omnibus_root] = "/c" config[:sudo] = true cmd.must_match regexify("sudo -E /c/bin/chef-solo ", :partial_line) end it "does not use sudo for chef-solo when configured" do config[:chef_omnibus_root] = "/c" config[:sudo] = false cmd.must_match regexify("chef-solo ", :partial_line) cmd.wont_match regexify("sudo -E /c/bin/chef-solo ", :partial_line) end it "sets config flag on chef-solo" do cmd.must_match regexify(" --config /tmp/kitchen/solo.rb", :partial_line) end it "sets config flag for custom root_path" do config[:root_path] = "/a/b" cmd.must_match regexify(" --config /a/b/solo.rb", :partial_line) end it "sets json attributes flag on chef-solo" do cmd.must_match regexify( " --json-attributes /tmp/kitchen/dna.json", :partial_line) end it "sets json attribtes flag for custom root_path" do config[:root_path] = "/booyah" cmd.must_match regexify( " --json-attributes /booyah/dna.json", :partial_line) end it "sets log level flag on chef-solo to auto by default" do cmd.must_match regexify(" --log_level auto", :partial_line) end it "sets log level flag on chef-solo to info by default if running Chef < 11" do config[:require_chef_omnibus] = "10.34.6" cmd.must_match regexify(" --log_level info", :partial_line) end it "set log level flag for custom level" do config[:log_level] = :extreme cmd.must_match regexify(" --log_level extreme", :partial_line) end it "sets force formatter flag on chef-solo" do cmd.must_match regexify(" --force-formatter", :partial_line) end it "does not set force formatter flag on chef-solo if running Chef < 11" do config[:require_chef_omnibus] = "10.34.6" cmd.wont_match regexify(" --force-formatter", :partial_line) end it "sets no color flag on chef-solo" do cmd.must_match regexify(" --no-color", :partial_line) end it "does not set logfile flag by default" do cmd.wont_match regexify(" --logfile ", :partial_line) end it "sets logfile flag for custom value" do config[:log_file] = "/a/out.log" cmd.must_match regexify(" --logfile /a/out.log", :partial_line) end it "sets profile-ruby flag when config element is set" do config[:profile_ruby] = true cmd.must_match regexify( " --profile-ruby", :partial_line) end it "does not set profile-ruby flag when config element is falsey" do config[:profile_ruby] = false cmd.wont_match regexify(" --profile-ruby", :partial_line) end it "sets legacy-mode flag when config element is set" do config[:legacy_mode] = true cmd.must_match regexify(" --legacy-mode", :partial_line) end it "does not set legacy-mode flag when config element is falsey" do config[:legacy_mode] = false cmd.wont_match regexify(" --legacy-mode", :partial_line) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "contains a standard second run if multiple_converge is set to 2" do config[:multiple_converge] = 2 cmd.must_match(/chef-solo.*;.*chef-solo.*/m) cmd.wont_match(/chef-solo.*;.*chef-solo.*client_no_updated_resources.rb/m) end it "contains a specific second run if enforce_idempotency is set" do config[:multiple_converge] = 2 config[:enforce_idempotency] = true cmd.must_match(/chef-solo.*;.*chef-solo.*client_no_updated_resources.rb/m) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "exports all http proxy variables when both are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..3].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "reloads PATH for older chef packages" do cmd.must_match regexify("$env:PATH = try {\n" \ "[System.Environment]::GetEnvironmentVariable('PATH','Machine')\n" \ "} catch { $env:PATH }") end it "calls the chef-solo command from :chef_solo_path" do config[:chef_solo_path] = '\\r\\chef-solo.bat' cmd.must_match regexify('& \\r\\chef-solo.bat ', :partial_line) end it "sets config flag on chef-solo" do cmd.must_match regexify( ' --config $env:TEMP\\kitchen\\solo.rb', :partial_line) end it "sets config flag for custom root_path" do config[:root_path] = '\\a\\b' cmd.must_match regexify( ' --config \\a\\b\\solo.rb', :partial_line) end it "sets json attributes flag on chef-solo" do cmd.must_match regexify( ' --json-attributes $env:TEMP\\kitchen\\dna.json', :partial_line) end it "sets json attribtes flag for custom root_path" do config[:root_path] = '\\booyah' cmd.must_match regexify( ' --json-attributes \\booyah\\dna.json', :partial_line) end it "sets log level flag on chef-solo to auto by default" do cmd.must_match regexify(" --log_level auto", :partial_line) end it "set log level flag for custom level" do config[:log_level] = :extreme cmd.must_match regexify(" --log_level extreme", :partial_line) end it "sets force formatter flag on chef-solo" do cmd.must_match regexify(" --force-formatter", :partial_line) end it "sets no color flag on chef-solo" do cmd.must_match regexify(" --no-color", :partial_line) end it "does not set logfile flag by default" do cmd.wont_match regexify(" --logfile ", :partial_line) end it "sets logfile flag for custom value" do config[:log_file] = '\\a\\out.log' cmd.must_match regexify(' --logfile \\a\\out.log', :partial_line) end end end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def debug_line_starting_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end def regexify(str, line = :whole_line) r = Regexp.escape(str) r = "^\s*#{r}$" if line == :whole_line Regexp.new(r) end end test-kitchen-1.23.2/spec/kitchen/provisioner/shell_spec.rb0000644000004100000410000005015713377651062023657 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/provisioner/shell" describe Kitchen::Provisioner::Shell do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil, shell_type: nil) } let(:suite) { stub(name: "fries") } let(:config) do { test_base_path: "/basist", kitchen_root: "/rooty" } end let(:instance) do stub( name: "coolbeans", logger: logger, suite: suite, platform: platform ) end let(:provisioner) do Class.new(Kitchen::Provisioner::Shell) do def calculate_path(path, _opts = {}) "/#{path}" end end.new(config).finalize_config!(instance) end it "provisioner api_version is 2" do provisioner = Kitchen::Provisioner::Shell.new(config).finalize_config!(instance) provisioner.diagnose_plugin[:api_version].must_equal 2 end it "plugin_version is set to Kitchen::VERSION" do provisioner = Kitchen::Provisioner::Shell.new(config).finalize_config!(instance) provisioner.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "configuration" do describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it ":script uses calculate_path and is expanded" do provisioner[:script].must_equal os_safe_root_path("/rooty//bootstrap.sh") end end describe "for powershell shells" do before { platform.stubs(:shell_type).returns("powershell") } it ":script uses calculate_path and is expanded" do provisioner[:script].must_equal os_safe_root_path("/rooty//bootstrap.ps1") end end it ":data_path uses calculate_path and is expanded" do provisioner[:data_path].must_equal os_safe_root_path("/rooty//data") end end describe "#init_command" do let(:cmd) { provisioner.init_command } describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[1..2].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..2].must_equal([ %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "exports ftp_proxy & FTP_PROXY when :ftp_proxy is set" do config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[1..2].must_equal([ %{ftp_proxy="ftp://proxy"; export ftp_proxy\n}, %{FTP_PROXY="ftp://proxy"; export FTP_PROXY\n}, ]) end it "exports all proxy variables when all are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[1..6].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, %{ftp_proxy="ftp://proxy"; export ftp_proxy\n}, %{FTP_PROXY="ftp://proxy"; export FTP_PROXY\n}, ]) end it "uses sudo for rm when configured" do config[:sudo] = true cmd.must_match regexify("sudo -E rm -rf ", :partial_line) end it "does not use sudo for rm when configured" do config[:sudo] = false provisioner.init_command .must_match regexify("rm -rf ", :partial_line) provisioner.init_command .wont_match regexify("sudo -E rm -rf ", :partial_line) end it "removes the data directory" do config[:root_path] = "/route" cmd.must_match %r{rm -rf\b.*\s+/route/data\s+} end it "creates :root_path directory" do config[:root_path] = "/root/path" cmd.must_match regexify("mkdir -p /root/path", :partial_line) end it "respects command" do config[:command] = "asdf" cmd.must_be_nil end end describe "for powershell shells on windows os types" do before do platform.stubs(:os_type).returns("windows") platform.stubs(:shell_type).returns("powershell") end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "exports ftp_proxy & FTP_PROXY when :ftp_proxy is set" do config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:ftp_proxy = "ftp://proxy"\n}, %{$env:FTP_PROXY = "ftp://proxy"\n}, ]) end it "exports all proxy variables when all are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[0..5].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, %{$env:ftp_proxy = "ftp://proxy"\n}, %{$env:FTP_PROXY = "ftp://proxy"\n}, ]) end it "removes the data directory" do config[:root_path] = '\\route' cmd.must_match regexify(Kitchen::Util.outdent!(<<-POWERSHELL).chomp) if (Test-Path "\\route\\data") { Remove-Item "\\route\\data" -Recurse -Force } POWERSHELL end it "creates the :root_path directory" do config[:root_path] = '\\route' cmd.must_match regexify(Kitchen::Util.outdent!(<<-POWERSHELL).chomp) if (-Not (Test-Path "\\route")) { New-Item "\\route" -ItemType directory | Out-Null } POWERSHELL end end end describe "#prepare_command" do let(:cmd) { provisioner.prepare_command } describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "uses sudo for script when configured" do config[:root_path] = "/r" config[:sudo] = true cmd.must_match regexify("sudo -E chmod +x /r/bootstrap.sh", :partial_line) end it "does not use sudo for script when configured" do config[:root_path] = "/r" config[:sudo] = false cmd.must_match regexify("chmod +x /r/bootstrap.sh", :partial_line) cmd.wont_match regexify("sudo -E chmod +x /r/bootstrap.sh", :partial_line) end it "uses command_prefix for script when configured" do config[:command_prefix] = "TEST=yes" config[:root_path] = "/r" config[:sudo] = false cmd.must_match(/^TEST=yes/) end it "respects command" do config[:command] = "dothingy.rb" cmd.must_be_nil end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "does nothing when given a script" do cmd.must_be_nil end it "respects command" do config[:command] = "dothingy.rb" cmd.must_be_nil end end end describe "#run_command" do let(:cmd) { provisioner.run_command } describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[1..2].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[1..2].must_equal([ %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, ]) end it "exports ftp_proxy & FTP_PROXY when :ftp_proxy is set" do config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[1..2].must_equal([ %{ftp_proxy="ftp://proxy"; export ftp_proxy\n}, %{FTP_PROXY="ftp://proxy"; export FTP_PROXY\n}, ]) end it "exports all proxy variables when all are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[1..6].must_equal([ %{http_proxy="http://proxy"; export http_proxy\n}, %{HTTP_PROXY="http://proxy"; export HTTP_PROXY\n}, %{https_proxy="https://proxy"; export https_proxy\n}, %{HTTPS_PROXY="https://proxy"; export HTTPS_PROXY\n}, %{ftp_proxy="ftp://proxy"; export ftp_proxy\n}, %{FTP_PROXY="ftp://proxy"; export FTP_PROXY\n}, ]) end it "uses sudo for script when configured" do config[:root_path] = "/r" config[:sudo] = true cmd.must_match regexify("sudo -E /r/bootstrap.sh", :partial_line) end it "does not use sudo for script when configured" do config[:root_path] = "/r" config[:sudo] = false cmd.must_match regexify("/r/bootstrap.sh", :partial_line) cmd.wont_match regexify("sudo -E /r/bootstrap.sh", :partial_line) end it "uses command_prefix for script when configured" do config[:command_prefix] = "TEST=yes" config[:root_path] = "/r" config[:sudo] = false cmd.must_match(/^TEST=yes/) end it "accepts arguments when configured" do config[:arguments] = "--php 70 --mysql 57" cmd.must_match(/--php 70 --mysql 57/) end it "respects command" do config[:command] = "dothingy.rb" cmd.must_match regexify("dothingy.rb", :partial_line) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, ]) end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, ]) end it "exports ftp_proxy & FTP_PROXY when :ftp_proxy is set" do config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[0..1].must_equal([ %{$env:ftp_proxy = "ftp://proxy"\n}, %{$env:FTP_PROXY = "ftp://proxy"\n}, ]) end it "exports all proxy variables when all are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" cmd.lines.to_a[0..5].must_equal([ %{$env:http_proxy = "http://proxy"\n}, %{$env:HTTP_PROXY = "http://proxy"\n}, %{$env:https_proxy = "https://proxy"\n}, %{$env:HTTPS_PROXY = "https://proxy"\n}, %{$env:ftp_proxy = "ftp://proxy"\n}, %{$env:FTP_PROXY = "ftp://proxy"\n}, ]) end it "invokes the bootstrap.ps1 script" do config[:root_path] = '\\r' cmd.must_match regexify(%{& "\\r\\bootstrap.ps1"}) end end end describe "#create_sandbox" do before do @root = Dir.mktmpdir config[:kitchen_root] = @root end after do FileUtils.remove_entry(@root) begin provisioner.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end let(:provisioner) do Kitchen::Provisioner::Shell.new(config).finalize_config!(instance) end describe "data files" do before do create_files_under("#{config[:kitchen_root]}/my_data") config[:data_path] = "#{config[:kitchen_root]}/my_data" end it "skips directory creation if :data_path is not set" do config[:data_path] = nil provisioner.create_sandbox sandbox_path("data").directory?.must_equal false end it "copies tree from :data_path into sandbox" do provisioner.create_sandbox sandbox_path("data/alpha.txt").file?.must_equal true IO.read(sandbox_path("data/alpha.txt")).must_equal "stuff" sandbox_path("data/sub").directory?.must_equal true sandbox_path("data/sub/bravo.txt").file?.must_equal true IO.read(sandbox_path("data/sub/bravo.txt")).must_equal "junk" end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing data") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string.must_match debug_line( "Using data from #{config[:kitchen_root]}/my_data") end end describe "script file" do describe "with a valid :script file" do before do File.open("#{config[:kitchen_root]}/my_script", "wb") do |file| file.write("gonuts") end config[:script] = "#{config[:kitchen_root]}/my_script" end it "creates a file in the sandbox directory" do provisioner.create_sandbox sandbox_path("my_script").file?.must_equal true IO.read(sandbox_path("my_script")).must_equal "gonuts" end it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing script") end it "logs a message on debug" do provisioner.create_sandbox logged_output.string.must_match debug_line( "Using script from #{config[:kitchen_root]}/my_script") end end describe "with no :script file" do before { config[:script] = nil } it "has no run command" do provisioner.run_command.must_be_nil end describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing script") end it "logs a warning on info" do provisioner.create_sandbox logged_output.string.must_match info_line( "No provisioner script file specified, skipping") end it "does not create a file in the sandbox directory" do provisioner.create_sandbox sandbox_path("bootstrap.sh").file?.must_equal false end end describe "for powershell shells" do before { platform.stubs(:shell_type).returns("powershell") } it "logs a message on info" do provisioner.create_sandbox logged_output.string.must_match info_line("Preparing script") end it "logs a warning on info" do provisioner.create_sandbox logged_output.string.must_match info_line( "No provisioner script file specified, skipping") end it "does not create a file in the sandbox directory" do provisioner.create_sandbox sandbox_path("bootstrap.ps1").file?.must_equal false end end end end def sandbox_path(path) Pathname.new(provisioner.sandbox_path).join(path) end def create_files_under(path) FileUtils.mkdir_p(File.join(path, "sub")) File.open(File.join(path, "alpha.txt"), "wb") do |file| file.write("stuff") end File.open(File.join(path, "sub", "bravo.txt"), "wb") do |file| file.write("junk") end end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def debug_line(msg) /^D, .* : #{Regexp.escape(msg)}$/ end end def regexify(str, line = :whole_line) r = Regexp.escape(str) r = "^\s*#{r}$" if line == :whole_line Regexp.new(r) end end test-kitchen-1.23.2/spec/kitchen/login_command_spec.rb0000644000004100000410000000360013377651062022766 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/login_command" describe Kitchen::LoginCommand do let(:cmd) { "" } let(:argv) { [] } let(:opts) { Hash.new } let(:login_command) { Kitchen::LoginCommand.new(cmd, argv, opts) } it "#command returns the command" do cmd << "one" login_command.command.must_equal "one" end it "#arguments defaults to an empty array" do Kitchen::LoginCommand.new("echo", nil).arguments.must_equal [] end it "#arguments returns the command arguments" do argv.concat(["-o", "two"]) login_command.arguments.must_equal ["-o", "two"] end it "#options defaults to an empty hash" do Kitchen::LoginCommand.new(cmd, argv).options.must_equal Hash.new end it "#options returns the options hash from the constructor" do opts[:cake] = "yummy" login_command.options.must_equal(cake: "yummy") end it "#exec_args returns an array of arguments for Kernel.exec" do cmd << "alpha" login_command.exec_args.must_equal ["alpha", {}] argv.concat(["-o", "beta"]) login_command.exec_args.must_equal ["alpha", "-o", "beta", {}] opts[:charlie] = "delta" login_command.exec_args.must_equal [ "alpha", "-o", "beta", { charlie: "delta" }] end end test-kitchen-1.23.2/spec/kitchen/platform_spec.rb0000644000004100000410000000634713377651062022017 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/errors" require "kitchen/platform" describe Kitchen::Platform do let(:opts) { ; { name: "plata" }; } let(:klass) { Kitchen::Platform } it "raises an ArgumentError if name is missing" do opts.delete(:name) proc { klass.new(opts) }.must_raise Kitchen::ClientError end it "#os_type returns value passed into constructor with :os_type" do klass.new(name: "p", os_type: "unix").os_type.must_equal "unix" klass.new(name: "p", os_type: "windows").os_type.must_equal "windows" klass.new(name: "p", os_type: "unicorn").os_type.must_equal "unicorn" klass.new(name: "p", os_type: nil).os_type.must_be_nil end it "#os_type defaults to `unix` when not provided" do klass.new(name: "p").os_type.must_equal "unix" end it "#os_type defaults to `windows` if the name starts with 'win'" do klass.new(name: "win").os_type.must_equal "windows" klass.new(name: "Win").os_type.must_equal "windows" klass.new(name: "win7").os_type.must_equal "windows" klass.new(name: "windows").os_type.must_equal "windows" klass.new(name: "Windows").os_type.must_equal "windows" klass.new(name: "windows81").os_type.must_equal "windows" klass.new(name: "windows-2012").os_type.must_equal "windows" end it "#shell_type returns value passed into constructor with :shell_type" do klass.new(name: "p", shell_type: "bourne") .shell_type.must_equal "bourne" klass.new(name: "p", shell_type: "powershell") .shell_type.must_equal "powershell" klass.new(name: "p", shell_type: "unicorn") .shell_type.must_equal "unicorn" klass.new(name: "p", shell_type: nil) .shell_type.must_be_nil end it "#shell_type defaults to `bourne` when not provided" do klass.new(name: "p").shell_type.must_equal "bourne" end it "#shell_type defaults to `powershell` if the name starts with 'windows'" do klass.new(name: "win").shell_type.must_equal "powershell" klass.new(name: "Win").shell_type.must_equal "powershell" klass.new(name: "win7").shell_type.must_equal "powershell" klass.new(name: "windows").shell_type.must_equal "powershell" klass.new(name: "Windows").shell_type.must_equal "powershell" klass.new(name: "windows81").shell_type.must_equal "powershell" klass.new(name: "windows-2012").shell_type.must_equal "powershell" end it "#diagnose returns a hash with sorted keys" do opts[:os_type] = "unikitty" opts[:shell_type] = "wundershell" klass.new(opts).diagnose.must_equal( os_type: "unikitty", shell_type: "wundershell" ) end end test-kitchen-1.23.2/spec/kitchen/transport_spec.rb0000644000004100000410000000606313377651062022222 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Matt Wrock () # # Copyright (C) 2014, Matt Wrock # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/configurable" require "kitchen/errors" require "kitchen/logging" require "kitchen/transport" require "kitchen/transport/base" module Kitchen module Transport class Coolbeans < Kitchen::Transport::Base end class ItDepends < Kitchen::Transport::Base attr_reader :verify_call_count def initialize(config = {}) @verify_call_count = 0 super end def verify_dependencies @verify_call_count += 1 end end class UnstableDepends < Kitchen::Transport::Base def verify_dependencies raise UserError, "Oh noes, you don't have software!" end end end end describe Kitchen::Transport do describe ".for_plugin" do before do Kitchen::Transport.stubs(:require).returns(true) end it "returns a transport object of the correct class" do transport = Kitchen::Transport.for_plugin("coolbeans", {}) transport.must_be_kind_of Kitchen::Transport::Coolbeans end it "returns a transport initialized with its config" do transport = Kitchen::Transport.for_plugin("coolbeans", foo: "bar") transport[:foo].must_equal "bar" end it "calls #verify_dependencies on the transport object" do transport = Kitchen::Transport.for_plugin("it_depends", {}) transport.verify_call_count.must_equal 1 end it "calls #verify_dependencies once per transport require" do Kitchen::Transport.stubs(:require).returns(true, false) transport1 = Kitchen::Transport.for_plugin("it_depends", {}) transport1.verify_call_count.must_equal 1 transport2 = Kitchen::Transport.for_plugin("it_depends", {}) transport2.verify_call_count.must_equal 0 end it "raises ClientError if the transport could not be required" do Kitchen::Transport.stubs(:require).raises(LoadError) proc { Kitchen::Transport.for_plugin("coolbeans", {}) } .must_raise Kitchen::ClientError end it "raises ClientError if the transport's class constant was not found" do # pretend require worked Kitchen::Transport.stubs(:require).returns(true) proc { Kitchen::Transport.for_plugin("nope", {}) } .must_raise Kitchen::ClientError end it "raises UserError if #verify_dependencies failes" do proc { Kitchen::Transport.for_plugin("unstable_depends", {}) } .must_raise Kitchen::UserError end end end test-kitchen-1.23.2/spec/kitchen/instance_spec.rb0000644000004100000410000011557213377651062022000 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "stringio" require "kitchen/logging" require "kitchen/instance" require "kitchen/driver" require "kitchen/driver/dummy" require "kitchen/platform" require "kitchen/provisioner" require "kitchen/provisioner/dummy" require "kitchen/suite" require "kitchen/transport/dummy" require "kitchen/verifier/dummy" class DummyStateFile def initialize(*) @_state = {} end def read @_state.dup end def write(state) @_state = state.dup end def destroy @_state = {} end def diagnose {} end end class SerialDummyDriver < Kitchen::Driver::Dummy no_parallel_for :create, :destroy attr_reader :action_in_mutex def initialize(config = {}) super(config) @action_in_mutex = {} end def track_locked(action) @action_in_mutex ||= {} @action_in_mutex[action] = Kitchen::Instance.mutexes[self.class].locked? end def create(state) track_locked(:create) super end def destroy(state) track_locked(:destroy) super end end class LegacyDriver < Kitchen::Driver::SSHBase attr_reader :called_converge, :called_setup, :called_verify def converge(_) @called_converge end def setup(_) @called_setup end def verify(_) @called_verify end end describe Kitchen::Instance do let(:driver) { Kitchen::Driver::Dummy.new({}) } let(:logger_io) { StringIO.new } let(:logger) { Kitchen::Logger.new(logdev: logger_io) } let(:instance) { Kitchen::Instance.new(opts) } let(:lifecycle_hooks) { Kitchen::LifecycleHooks.new({}) } let(:provisioner) { Kitchen::Provisioner::Dummy.new({}) } let(:state_file) { DummyStateFile.new } let(:transport) { Kitchen::Transport::Dummy.new({}) } let(:verifier) { Kitchen::Verifier::Dummy.new({}) } let(:opts) do { suite: suite, platform: platform, driver: driver, lifecycle_hooks: lifecycle_hooks, provisioner: provisioner, verifier: verifier, logger: logger, state_file: state_file, transport: transport } end def suite(name = "suite") @suite ||= Kitchen::Suite.new(name: name) end def platform(name = "platform") @platform ||= Kitchen::Platform.new(name: name) end describe ".name_for" do it "combines the suite and platform names with a dash" do Kitchen::Instance.name_for(suite("suite"), platform("platform")) .must_equal "suite-platform" end it "squashes periods in suite name" do Kitchen::Instance.name_for(suite("suite.ness"), platform("platform")) .must_equal "suiteness-platform" end it "squashes periods in platform name" do Kitchen::Instance.name_for(suite("suite"), platform("platform.s")) .must_equal "suite-platforms" end it "squashes periods in suite and platform names" do Kitchen::Instance.name_for(suite("s.s"), platform("p.p")) .must_equal "ss-pp" end it "transforms underscores to dashes in suite name" do Kitchen::Instance.name_for(suite("suite_ness"), platform("platform")) .must_equal "suite-ness-platform" end it "transforms underscores to dashes in platform name" do Kitchen::Instance.name_for(suite("suite"), platform("platform_s")) .must_equal "suite-platform-s" end it "transforms underscores to dashes in suite and platform names" do Kitchen::Instance.name_for(suite("_s__s_"), platform("pp_")) .must_equal "-s--s--pp-" end it "transforms forward slashes to dashes in suite name" do Kitchen::Instance.name_for(suite("suite/ness"), platform("platform")) .must_equal "suite-ness-platform" end it "transforms forward slashes to dashes in platform name" do Kitchen::Instance.name_for(suite("suite"), platform("platform/s")) .must_equal "suite-platform-s" end it "transforms forward slashes to dashes in suite and platform names" do Kitchen::Instance.name_for(suite("/s//s/"), platform("pp/")) .must_equal "-s--s--pp-" end end describe "#suite" do it "returns its suite" do instance.suite.must_equal suite end it "raises an ArgumentError if missing" do opts.delete(:suite) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end end describe "#platform" do it "returns its platform" do instance.platform.must_equal platform end it "raises an ArgumentError if missing" do opts.delete(:platform) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end end describe "#driver" do it "returns its driver" do instance.driver.must_equal driver end it "raises an ArgumentError if missing" do opts.delete(:driver) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end it "sets Driver#instance to itself" do # it's mind-bottling instance.driver.instance.must_equal instance end end describe "#logger" do it "returns its logger" do instance.logger.must_equal logger end it "uses Kitchen.logger by default" do opts.delete(:logger) instance.logger.must_equal Kitchen.logger end end describe "#provisioner" do it "returns its provisioner" do instance.provisioner.must_equal provisioner end it "raises an ArgumentError if missing" do opts.delete(:provisioner) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end it "sets Provisioner#instance to itself" do # it's mind-bottling instance.provisioner.instance.must_equal instance end end describe "#transport" do it "returns its transport" do instance.transport.must_equal transport end it "raises an ArgumentError if missing" do opts.delete(:transport) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end it "sets Transport#instance to itself" do # it's mind-bottling instance.transport.instance.must_equal instance end end describe "#verifier" do it "returns its verifier" do instance.verifier.must_equal verifier end it "raises and ArgumentError if missing" do opts.delete(:verifier) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end it "sets Verifier#instance to itself" do # it's mind-bottling instance.verifier.instance.must_equal instance end end describe "#state_file" do it "raises an ArgumentError if missing" do opts.delete(:state_file) proc { Kitchen::Instance.new(opts) }.must_raise Kitchen::ClientError end end it "#name returns it name" do instance.name.must_equal "suite-platform" end it "#to_str returns a string representation with its name" do instance.to_str.must_equal "" end it "#login executes the transport's login_command" do conn = stub("connection") state_file.write(last_action: "create") transport.stubs(:connection).with(last_action: "create") .returns(conn) conn.stubs(:login_command) .returns(Kitchen::LoginCommand.new("echo", ["hello"], purple: true)) Kernel.expects(:exec).with("echo", "hello", purple: true) instance.login end it "#login raises a UserError if the instance is not created" do state_file.write({}) proc { instance.login }.must_raise Kitchen::UserError end describe "#diagnose" do it "returns a hash" do instance.diagnose.must_be_instance_of Hash end it "sets :platform key to platform's diagnose info" do platform.stubs(:diagnose).returns(a: "b") instance.diagnose[:platform].must_equal(a: "b") end it "sets :platform key to :unknown if obj can't respond to #diagnose" do opts[:platform] = Class.new(platform.class) do undef_method :diagnose end.new(name: "whoop") instance.diagnose[:platform].must_equal :unknown end it "sets :state_file key to state_file's diganose info" do state_file.stubs(:diagnose).returns(a: "b") instance.diagnose[:state_file].must_equal(a: "b") end it "sets :state_file key to :unknown if obj can't respond to #diagnose" do opts[:state_file] = Class.new(state_file.class) do undef_method :diagnose end.new instance.diagnose[:state_file].must_equal :unknown end it "sets :provisioner key to provisioner's diganose info" do provisioner.stubs(:diagnose).returns(a: "b") instance.diagnose[:provisioner].must_equal(a: "b") end it "sets :provisioner key to :unknown if obj can't respond to #diagnose" do opts[:provisioner] = Class.new(provisioner.class) do undef_method :diagnose end.new instance.diagnose[:provisioner].must_equal :unknown end it "sets :verifier key to verifier's diganose info" do verifier.stubs(:diagnose).returns(a: "b") instance.diagnose[:verifier].must_equal(a: "b") end it "sets :verifier key to :unknown if obj can't respond to #diagnose" do opts[:verifier] = Class.new(verifier.class) do undef_method :diagnose end.new({}) instance.diagnose[:verifier].must_equal :unknown end it "sets :transport key to transport's diganose info" do transport.stubs(:diagnose).returns(a: "b") instance.diagnose[:transport].must_equal(a: "b") end it "sets :transport key to :unknown if obj can't respond to #diagnose" do opts[:transport] = Class.new(transport.class) do undef_method :diagnose end.new instance.diagnose[:transport].must_equal :unknown end end describe "#diagnose_plugins" do it "returns a hash" do instance.diagnose_plugins.must_be_instance_of Hash end it "sets :driver key to driver's plugin_diagnose info" do driver.class.stubs(:diagnose).returns(a: "b") instance.diagnose_plugins[:driver].must_equal( name: "Dummy", a: "b" ) end it "sets :driver key to :unknown if class doesn't have #diagnose" do opts[:driver] = Class.new(driver.class) do undef_method :diagnose_plugin end.new({}) instance.diagnose_plugins[:driver].must_equal(:unknown) end it "sets :provisioner key to provisioner's plugin_diagnose info" do provisioner.class.stubs(:diagnose).returns(a: "b") instance.diagnose_plugins[:provisioner].must_equal( name: "Dummy", a: "b" ) end it "sets :provisioner key to :unknown if class doesn't have #diagnose" do opts[:provisioner] = Class.new(driver.class) do undef_method :diagnose_plugin end.new({}) instance.diagnose_plugins[:provisioner].must_equal(:unknown) end it "sets :verifier key to verifier's plugin_diagnose info" do verifier.class.stubs(:diagnose).returns(a: "b") instance.diagnose_plugins[:verifier].must_equal( name: "Dummy", a: "b" ) end it "sets :verifier key to :unknown if class doesn't have #diagnose" do opts[:verifier] = Class.new(verifier.class) do undef_method :diagnose_plugin end.new({}) instance.diagnose_plugins[:verifier].must_equal(:unknown) end it "sets :transport key to transport's plugin_diagnose info" do transport.class.stubs(:diagnose).returns(a: "b") instance.diagnose_plugins[:transport].must_equal( name: "Dummy", a: "b" ) end it "sets :transport key to :unknown if class doesn't have #diagnose" do opts[:transport] = Class.new(transport.class) do undef_method :diagnose_plugin end.new({}) instance.diagnose_plugins[:transport].must_equal(:unknown) end end describe "performing actions" do describe "#create" do describe "with no state" do it "calls Driver#create with empty state hash" do driver.expects(:create).with({}) instance.create end it "writes the state file with last_action" do instance.create state_file.read[:last_action].must_equal "create" end it "logs the action start" do instance.create logger_io.string.must_match regex_for("Creating #{instance.to_str}") end it "logs the action finish" do instance.create logger_io.string .must_match regex_for("Finished creating #{instance.to_str}") end it "calls lifecycle hooks" do lifecycle_hooks.expects(:run).with(instance, :create, state_file, :pre) lifecycle_hooks.expects(:run).with(instance, :create, state_file, :post) instance.create end end describe "with last_action of create" do before { state_file.write(last_action: "create") } it "calls Driver#create with state hash" do driver.expects(:create) .with { |state| state[:last_action] == "create" } instance.create end it "writes the state file with last_action" do instance.create state_file.read[:last_action].must_equal "create" end end end describe "#converge" do describe "with no state" do it "calls Driver#create and Provisioner#call with empty state hash" do driver.expects(:create).with({}) provisioner.expects(:call) .with { |state| state[:last_action] == "create" } instance.converge end it "writes the state file with last_action" do instance.converge state_file.read[:last_action].must_equal "converge" end it "logs the action start" do instance.converge logger_io.string.must_match regex_for("Converging #{instance.to_str}") end it "logs the action finish" do instance.converge logger_io.string .must_match regex_for("Finished converging #{instance.to_str}") end it "calls lifecycle hooks" do lifecycle_hooks.expects(:run).with(instance, :create, state_file, :pre) lifecycle_hooks.expects(:run).with(instance, :create, state_file, :post) lifecycle_hooks.expects(:run).with(instance, :converge, state_file, :pre) lifecycle_hooks.expects(:run).with(instance, :converge, state_file, :post) instance.converge end end describe "with last action of create" do before { state_file.write(last_action: "create") } it "calls Provisioner#call with state hash" do provisioner.expects(:call) .with { |state| state[:last_action] == "create" } instance.converge end it "writes the state file with last_action" do instance.converge state_file.read[:last_action].must_equal "converge" end it "calls lifecycle hooks" do lifecycle_hooks.expects(:run).with(instance, :converge, state_file, :pre) lifecycle_hooks.expects(:run).with(instance, :converge, state_file, :post) instance.converge end end describe "with last action of converge" do before { state_file.write(last_action: "converge") } it "calls Provisioner#call with state hash" do provisioner.expects(:call) .with { |state| state[:last_action] == "converge" } instance.converge end it "writes the state file with last_action" do instance.converge state_file.read[:last_action].must_equal "converge" end end end describe "#setup" do describe "with no state" do it "calls create and converge with empty state hash" do driver.expects(:create).with({}) provisioner.expects(:call) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } .never instance.setup end it "writes the state file with last_action" do instance.setup state_file.read[:last_action].must_equal "setup" end it "logs the action start" do instance.setup logger_io.string.must_match regex_for("Setting up #{instance.to_str}") end it "logs the action finish" do instance.setup logger_io.string .must_match regex_for("Finished setting up #{instance.to_str}") end end describe "with last action of create" do before { state_file.write(last_action: "create") } it "calls Provisioner#call with state hash" do provisioner.expects(:call) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } .never instance.setup end it "writes the state file with last_action" do instance.setup state_file.read[:last_action].must_equal "setup" end end describe "with last action of converge" do before { state_file.write(last_action: "converge") } it "calls nothing with state hash" do driver.expects(:setup) .with { |state| state[:last_action] == "converge" } .never instance.setup end it "writes the state file with last_action" do instance.setup state_file.read[:last_action].must_equal "setup" end end describe "with last action of setup" do before { state_file.write(last_action: "setup") } it "calls nothing with state hash" do driver.expects(:setup) .with { |state| state[:last_action] == "setup" } .never instance.setup end it "writes the state file with last_action" do instance.setup state_file.read[:last_action].must_equal "setup" end end end describe "#verify" do describe "with no state" do it "calls create, converge, and verify with empty state hash" do driver.expects(:create).with({}) provisioner.expects(:call) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } .never verifier.expects(:call) .with { |state| state[:last_action] == "setup" } instance.verify end it "writes the state file with last_action" do instance.verify state_file.read[:last_action].must_equal "verify" end it "logs the action start" do instance.verify logger_io.string.must_match regex_for("Verifying #{instance.to_str}") end it "logs the action finish" do instance.verify logger_io.string .must_match regex_for("Finished verifying #{instance.to_str}") end end describe "with last of create" do before { state_file.write(last_action: "create") } it "calls converge, and verify with state hash" do provisioner.expects(:call) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } .never verifier.expects(:call) .with { |state| state[:last_action] == "setup" } instance.verify end it "writes the state file with last_action" do instance.verify state_file.read[:last_action].must_equal "verify" end end describe "with last of converge" do before { state_file.write(last_action: "converge") } it "calls Verifier#call with state hash" do driver.expects(:setup) .with { |state| state[:last_action] == "converge" } .never verifier.expects(:call) .with { |state| state[:last_action] == "setup" } instance.verify end it "writes the state file with last_action" do instance.verify state_file.read[:last_action].must_equal "verify" end end describe "with last of setup" do before { state_file.write(last_action: "setup") } it "calls Verifier#call with state hash" do verifier.expects(:call) .with { |state| state[:last_action] == "setup" } instance.verify end it "writes the state file with last_action" do instance.verify state_file.read[:last_action].must_equal "verify" end end describe "with last of verify" do before { state_file.write(last_action: "verify") } it "calls Verifier#call with state hash" do verifier.expects(:call) .with { |state| state[:last_action] == "verify" } instance.verify end it "writes the state file with last_action" do instance.verify state_file.read[:last_action].must_equal "verify" end end end describe "#destroy" do describe "with no state" do it "calls Driver#destroy with empty state hash" do driver.expects(:destroy).with({}) instance.destroy end it "destroys the state file" do state_file.expects(:destroy) instance.destroy end it "logs the action start" do instance.destroy logger_io.string .must_match regex_for("Destroying #{instance.to_str}") end it "logs the create finish" do instance.destroy logger_io.string .must_match regex_for("Finished destroying #{instance.to_str}") end end [:create, :converge, :setup, :verify].each do |action| describe "with last_action of #{action}" do before { state_file.write(last_action: action) } it "calls Driver#create with state hash" do driver.expects(:destroy) .with { |state| state[:last_action] == action } instance.destroy end it "destroys the state file" do state_file.expects(:destroy) instance.destroy end end end end describe "#test" do describe "with no state" do it "calls destroy, create, converge, setup, verify, destroy" do driver.expects(:destroy) driver.expects(:create) provisioner.expects(:call) verifier.expects(:call) driver.expects(:destroy) instance.test end it "logs the action start" do instance.test logger_io.string.must_match regex_for("Testing #{instance.to_str}") end it "logs the action finish" do instance.test logger_io.string .must_match regex_for("Finished testing #{instance.to_str}") end end [:create, :converge, :setup, :verify].each do |action| describe "with last action of #{action}" do before { state_file.write(last_action: action) } it "calls destroy, create, converge, setup, verify, destroy" do driver.expects(:destroy) driver.expects(:create) provisioner.expects(:call) verifier.expects(:call) driver.expects(:destroy) instance.test end end end describe "with destroy mode of never" do it "calls destroy, create, converge, setup, verify" do driver.expects(:destroy).once driver.expects(:create) provisioner.expects(:call) verifier.expects(:call) instance.test(:never) end end describe "with destroy mode of always" do it "calls destroy at even when action fails" do driver.expects(:destroy) driver.expects(:create) provisioner.expects(:call).raises(Kitchen::ActionFailed) driver.expects(:destroy) begin instance.test(:always) rescue # rubocop:disable Lint/HandleExceptions end end end describe "with destroy mode of passing" do it "doesn't call Driver#destroy at when action fails" do driver.stubs(:create).raises(Kitchen::ActionFailed) driver.expects(:destroy).once begin instance.test(:passing) rescue # rubocop:disable Lint/HandleExceptions end end end end describe "#remote_exec" do before { state_file.write(last_action: "create") } it "calls Transport#execute with command" do connection = mock("connection") connection.expects(:execute).with("uptime") transport.stubs(:connection).yields(connection) instance.remote_exec("uptime") end end [:create, :converge, :setup, :verify, :test].each do |action| describe "#{action} on driver crash with ActionFailed" do before do driver.stubs(:create).raises(Kitchen::ActionFailed, "death") end it "write the state file with last action" do begin instance.public_send(action) rescue Kitchen::Error true # no need to act here end state_file.read[:last_action].must_be_nil end it "raises an InstanceFailure" do proc { instance.public_send(action) } .must_raise Kitchen::InstanceFailure end it "populates the InstanceFailure message" do begin instance.public_send(action) rescue Kitchen::Error => e e.message.must_match regex_for( "Create failed on instance #{instance.to_str}") end end it "logs the failure" do begin instance.public_send(action) rescue Kitchen::Error true # no need to act here end logger_io.string.must_match regex_for( "Create failed on instance #{instance.to_str}") end end describe "on driver crash with unexpected exception class" do before do driver.stubs(:create).raises(RuntimeError, "watwat") end it "write the state file with last action" do begin instance.public_send(action) rescue Kitchen::Error true # no need to act here end state_file.read[:last_action].must_be_nil end it "raises an ActionFailed" do proc { instance.public_send(action) } .must_raise Kitchen::ActionFailed end it "populates the ActionFailed message" do begin instance.public_send(action) rescue Kitchen::Error => e e.message.must_match regex_for( "Failed to complete #create action: [watwat]") end end it "logs the failure" do begin instance.public_send(action) rescue Kitchen::Error true # no need to act here end logger_io.string.must_match regex_for( "Create failed on instance #{instance.to_str}") end end end describe "crashes preserve last action for desired verify action" do before do verifier.stubs(:call).raises(Kitchen::ActionFailed, "death") end [:create, :converge, :setup].each do |action| it "for last state #{action}" do state_file.write(last_action: action.to_s) begin instance.verify rescue Kitchen::Error true # no need to act here end state_file.read[:last_action].must_equal "setup" end end it "for last state verify" do state_file.write(last_action: "verify") begin instance.verify rescue Kitchen::Error true # no need to act here end state_file.read[:last_action].must_equal "verify" end end describe "on drivers with serial actions" do let(:driver) { SerialDummyDriver.new({}) } it "runs in a synchronized block for serial actions" do instance.test driver.action_in_mutex[:create].must_equal true driver.action_in_mutex[:destroy].must_equal true end end describe "with legacy Driver::SSHBase subclasses" do let(:driver) { LegacyDriver.new({}) } describe "#converge" do describe "with no state" do it "calls Driver#create and Driver#converge with empty state hash" do driver.expects(:create).with({}) driver.expects(:converge) .with { |state| state[:last_action] == "create" } instance.converge end end describe "with last action of create" do before { state_file.write(last_action: "create") } it "calls Driver#converge with state hash" do driver.expects(:converge) .with { |state| state[:last_action] == "create" } instance.converge end end describe "with last action of converge" do before { state_file.write(last_action: "converge") } it "calls Driver#converge with state hash" do driver.expects(:converge) .with { |state| state[:last_action] == "converge" } instance.converge end end end describe "#setup" do describe "with no state" do it "calls create, converge, and setup with empty state hash" do driver.expects(:create).with({}) driver.expects(:converge) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } instance.setup end end describe "with last action of create" do before { state_file.write(last_action: "create") } it "calls Provisioner#call and setup with state hash" do driver.expects(:converge) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } instance.setup end end describe "with last action of converge" do before { state_file.write(last_action: "converge") } it "calls Driver#setup with state hash" do driver.expects(:setup) .with { |state| state[:last_action] == "converge" } instance.setup end end describe "with last action of setup" do before { state_file.write(last_action: "setup") } it "calls Driver#setup with state hash" do driver.expects(:setup) .with { |state| state[:last_action] == "setup" } instance.setup end end end describe "#verify" do describe "with no state" do it "calls create, converge, setup, and verify with empty state hash" do driver.expects(:create).with({}) driver.expects(:converge) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } driver.expects(:verify) .with { |state| state[:last_action] == "setup" } instance.verify end end describe "with last of create" do before { state_file.write(last_action: "create") } it "calls converge, setup, and verify with state hash" do driver.expects(:converge) .with { |state| state[:last_action] == "create" } driver.expects(:setup) .with { |state| state[:last_action] == "converge" } driver.expects(:verify) .with { |state| state[:last_action] == "setup" } instance.verify end end describe "with last of converge" do before { state_file.write(last_action: "converge") } it "calls Driver#setup, and verify with state hash" do driver.expects(:setup) .with { |state| state[:last_action] == "converge" } driver.expects(:verify) .with { |state| state[:last_action] == "setup" } instance.verify end end describe "with last of setup" do before { state_file.write(last_action: "setup") } it "calls Driver#verify with state hash" do driver.expects(:verify) .with { |state| state[:last_action] == "setup" } instance.verify end end describe "with last of verify" do before { state_file.write(last_action: "verify") } it "calls Driver#verify with state hash" do driver.expects(:verify) .with { |state| state[:last_action] == "verify" } instance.verify end end end describe "#test" do describe "with no state" do it "calls destroy, create, converge, setup, verify, destroy" do driver.expects(:destroy) driver.expects(:create) driver.expects(:converge) driver.expects(:setup) driver.expects(:verify) driver.expects(:destroy) instance.test end end [:create, :converge, :setup, :verify].each do |action| describe "with last action of #{action}" do before { state_file.write(last_action: action) } it "calls destroy, create, converge, setup, verify, destroy" do driver.expects(:destroy) driver.expects(:create) driver.expects(:converge) driver.expects(:setup) driver.expects(:verify) driver.expects(:destroy) instance.test end end end describe "with destroy mode of never" do it "calls destroy, create, converge, setup, verify" do driver.expects(:destroy).once driver.expects(:create) driver.expects(:converge) driver.expects(:setup) driver.expects(:verify) instance.test(:never) end end describe "with destroy mode of always" do it "calls destroy at even when action fails" do driver.expects(:destroy) driver.expects(:create) driver.expects(:converge).raises(Kitchen::ActionFailed) driver.expects(:destroy) begin instance.test(:always) rescue # rubocop:disable Lint/HandleExceptions end end end end it "#login executes the driver's login_command" do state_file.write(last_action: "create") driver.stubs(:login_command).with(last_action: "create") .returns(Kitchen::LoginCommand.new("echo", ["hello"], purple: true)) Kernel.expects(:exec).with("echo", "hello", purple: true) instance.login end end end describe Kitchen::Instance::FSM do let(:fsm) { Kitchen::Instance::FSM } describe ".actions" do it "passing nils returns destroy" do fsm.actions(nil, nil).must_equal [:destroy] end it "accepts a string for desired argument" do fsm.actions(nil, "create").must_equal [:create] end it "accepts a symbol for desired argument" do fsm.actions(nil, :create).must_equal [:create] end it "starting from no state to create returns create" do fsm.actions(nil, :create).must_equal [:create] end it "starting from :create to create returns create" do fsm.actions(:create, :create).must_equal [:create] end it "starting from no state to converge returns create, converge" do fsm.actions(nil, :converge).must_equal [:create, :converge] end it "starting from create to converge returns converge" do fsm.actions(:create, :converge).must_equal [:converge] end it "starting from converge to converge returns converge" do fsm.actions(:converge, :converge).must_equal [:converge] end it "starting from no state to setup returns create, converge, setup" do fsm.actions(nil, :setup).must_equal [:create, :converge, :setup] end it "starting from create to setup returns converge, setup" do fsm.actions(:create, :setup).must_equal [:converge, :setup] end it "starting from converge to setup returns setup" do fsm.actions(:converge, :setup).must_equal [:setup] end it "starting from setup to setup return setup" do fsm.actions(:setup, :setup).must_equal [:setup] end it "starting from no state to verify returns create, converge, setup, verify" do fsm.actions(nil, :verify).must_equal [:create, :converge, :setup, :verify] end it "starting from create to verify returns converge, setup, verify" do fsm.actions(:create, :verify).must_equal [:converge, :setup, :verify] end it "starting from converge to verify returns setup, verify" do fsm.actions(:converge, :verify).must_equal [:setup, :verify] end it "starting from setup to verify returns verify" do fsm.actions(:setup, :verify).must_equal [:verify] end it "starting from verify to verify returns verify" do fsm.actions(:verify, :verify).must_equal [:verify] end [:verify, :setup, :converge].each do |s| it "starting from #{s} to create returns create" do fsm.actions(s, :create).must_equal [:create] end end [:verify, :setup].each do |s| it "starting from #{s} to converge returns converge" do fsm.actions(s, :converge).must_equal [:converge] end end it "starting from verify to setup returns setup" do fsm.actions(:verify, :setup).must_equal [:setup] end end end def regex_for(string) Regexp.new(Regexp.escape(string)) end end test-kitchen-1.23.2/spec/kitchen/diagnostic_spec.rb0000644000004100000410000000770713377651062022320 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/diagnostic" describe Kitchen::Diagnostic do let(:loader) do stub(diagnose: { who: "loader" }) end let(:instances) do [ stub( name: "i1", diagnose: { stuff: "sup" }, diagnose_plugins: { driver: { name: "driva", a: "b" }, provisioner: { name: "prov", c: "d" }, transport: { name: "transa", e: "f" }, verifier: { name: "verve", g: "h" }, } ), stub( name: "i2", diagnose: { stuff: "yo" }, diagnose_plugins: { driver: { name: "driva", a: "b" }, provisioner: { name: "presto", i: "j" }, transport: { name: "tressa", k: "l" }, verifier: { name: "verve", g: "h" }, } ), ] end it "#read returns a Hash" do Kitchen::Diagnostic.new.read.must_be_instance_of Hash end it "#read returns the version of Test Kitchen" do Kitchen::Diagnostic.new.read["kitchen_version"].must_equal Kitchen::VERSION end it "#read returns a timestamp in UTC" do Time.stubs(:now).returns(Time.at(0)) Kitchen::Diagnostic.new.read["timestamp"] .must_equal "1970-01-01 00:00:00 UTC" end it "#read doesn't return a loader hash if not given one" do Kitchen::Diagnostic.new.read.key?("loader").must_equal false end it "#read returns the loader's diganose hash if a loader is present" do Kitchen::Diagnostic.new(loader: loader) .read["loader"].must_equal("who" => "loader") end it "#read returns an error hash for loader if error hash is passed in" do Kitchen::Diagnostic.new(loader: { error: "damn" }) .read["loader"].must_equal("error" => "damn") end it "#read returns the unique set of plugins' diagnose hash if :plugins is set" do Kitchen::Diagnostic.new(instances: instances, plugins: true) .read["plugins"] .must_equal( "driver" => { "driva" => { "a" => "b" }, }, "provisioner" => { "prov" => { "c" => "d" }, "presto" => { "i" => "j" }, }, "transport" => { "transa" => { "e" => "f" }, "tressa" => { "k" => "l" }, }, "verifier" => { "verve" => { "g" => "h" }, } ) end it "#read returns an empty plugins hash if no instances were given" do Kitchen::Diagnostic.new(plugins: true) .read["plugins"].must_equal Hash.new end it "#read returns an empty instances hash if no instances were given" do Kitchen::Diagnostic.new.read["instances"].must_equal Hash.new end it "#read returns an error hash for plugins if error hash is passed in" do Kitchen::Diagnostic.new( instances: { error: "shoot" }, plugins: true ).read["plugins"].must_equal("error" => "shoot") end it "#read returns the instances' diganose hashes if instances are present" do Kitchen::Diagnostic.new(instances: instances) .read["instances"] .must_equal("i1" => { "stuff" => "sup" }, "i2" => { "stuff" => "yo" }) end it "#read returns an error hash for instances if error hash is passed in" do Kitchen::Diagnostic.new(instances: { error: "shoot" }) .read["instances"].must_equal("error" => "shoot") end end test-kitchen-1.23.2/spec/kitchen/util_spec.rb0000644000004100000410000001621613377651062021144 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "logger" require "kitchen/util" describe Kitchen::Util do describe ".to_logger_level" do it "returns nil for invalid symbols" do Kitchen::Util.to_logger_level(:nope).must_be_nil end %w{debug info warn error fatal}.each do |level| it "returns Logger::#{level.upcase} for :#{level} input" do Kitchen::Util.to_logger_level(level.to_sym) .must_equal Logger.const_get(level.upcase) end end end describe ".from_logger_level" do it "returns :fatal for invalid symbols" do Kitchen::Util.from_logger_level("nope").must_equal :fatal end %w{debug info warn error fatal}.each do |level| it "returns :#{level} for Logger::#{level.upcase} input" do Kitchen::Util.from_logger_level(Logger.const_get(level.upcase)) .must_equal(level.to_sym) end end end describe ".symbolized_hash" do it "returns itself if not a hash" do obj = Object.new Kitchen::Util.symbolized_hash(obj).must_equal obj end it "preserves a symbolized hash" do hash = { one: [{ two: "three" }] } Kitchen::Util.symbolized_hash(hash).must_equal hash end it "converts string keys into symbols" do Kitchen::Util .symbolized_hash("one" => [{ "two" => :three, :four => "five" }]) .must_equal(one: [{ two: :three, four: "five" }]) end end describe ".stringified_hash" do it "returns itself if not a hash" do obj = Object.new Kitchen::Util.stringified_hash(obj).must_equal obj end it "preserves a stringified hash" do hash = { "one" => [{ "two" => "three" }] } Kitchen::Util.stringified_hash(hash).must_equal hash end it "converts symbol keys into strings" do Kitchen::Util .stringified_hash(one: [{ :two => :three, "four" => "five" }]) .must_equal("one" => [{ "two" => :three, "four" => "five" }]) end end describe ".duration" do it "turns nil into a zero" do Kitchen::Util.duration(nil).must_equal "(0m0.00s)" end it "formats seconds to 2 digits" do Kitchen::Util.duration(60).must_equal "(1m0.00s)" end it "formats large values into minutes and seconds" do Kitchen::Util.duration(48_033).must_equal "(800m33.00s)" end end describe ".wrap_unix_command" do it "returns the wrapped command" do end it "returns a false if command is nil" do Kitchen::Util.wrap_command(nil).must_equal("sh -c '\nfalse\n'") end it "returns a true if command string is empty" do Kitchen::Util.wrap_command("yoyo").must_equal("sh -c '\nyoyo\n'") end it "handles a command string with a trailing newline" do Kitchen::Util.wrap_command("yep\n").must_equal("sh -c '\nyep\n'") end end describe ".outdent!" do it "modifies the argument string in place, destructively" do string = "yep" Kitchen::Util.outdent!(string).object_id.must_equal string.object_id end it "returns the same string if no leading whitespace exists" do string = "one\ntwo\nthree" Kitchen::Util.outdent!(string).must_equal "one\ntwo\nthree" end it "strips same amount of leading whitespace as found on first line" do string = " one\n two\n three\nfour" Kitchen::Util.outdent!(string).must_equal "one\n two\n three\nfour" end end describe ".shell_helpers" do %w{ exists do_wget do_curl do_fetch do_perl do_python do_download }.each do |func| it "contains a #{func} shell function" do Kitchen::Util.shell_helpers.must_match "#{func}() {" end end it "does not contain bare single quotes" do Kitchen::Util.shell_helpers.wont_match "'" end def regexify(str) Regexp.new("^ +" + Regexp.escape(str) + "$") end end describe ".list_directory" do before do @root = Dir.mktmpdir FileUtils.touch(File.join(@root, "foo")) Dir.mkdir(File.join(@root, "bar")) FileUtils.touch(File.join(@root, ".foo")) FileUtils.touch(File.join(@root, "bar", "baz")) FileUtils.touch(File.join(@root, "bar", ".baz")) end after do FileUtils.remove_entry(@root) end it "returns [] when the directory does not exist" do Kitchen::Util.list_directory(File.join(@root, "notexist")).must_equal [] end it "lists one level with no dot files by default" do listed = Kitchen::Util.list_directory(@root) expected = %w{ foo bar }.map { |f| File.join(@root, f) } (listed - expected).must_equal [] (expected - listed).must_equal [] end it "matches dot files only when include_dot" do listed = Kitchen::Util.list_directory(@root, include_dot: true) expected = [ "foo", ".foo", "bar", ].map { |f| File.join(@root, f) } (listed - expected).must_equal [] (expected - listed).must_equal [] end it "recusivly lists only when recurse" do listed = Kitchen::Util.list_directory(@root, recurse: true) expected = [ "foo", "bar", "bar/baz", ].map { |f| File.join(@root, f) } (listed - expected).must_equal [] (expected - listed).must_equal [] end it "recusivly lists and provides dots when recurse and include_dot" do listed = Kitchen::Util.list_directory(@root, recurse: true, include_dot: true) expected = [ "foo", ".foo", "bar", "bar/baz", "bar/.", "bar/.baz", ].map { |f| File.join(@root, f) } (listed - expected).must_equal [] (expected - listed).must_equal [] end end describe ".safe_glob" do before do @root = Dir.mktmpdir FileUtils.touch(File.join(@root, "foo")) Dir.mkdir(File.join(@root, "bar")) FileUtils.touch(File.join(@root, "foo")) FileUtils.touch(File.join(@root, "foo.rb")) FileUtils.touch(File.join(@root, "bar", "baz.rb")) end after do FileUtils.remove_entry(@root) end it "globs without parameters" do Kitchen::Util.safe_glob(@root, "**/*").must_equal Dir.glob(File.join(@root, "**/*")) end it "globs with parameters" do Kitchen::Util.safe_glob(@root, "**/*", File::FNM_DOTMATCH).must_equal( Dir.glob(File.join(@root, "**/*"), File::FNM_DOTMATCH)) end it "globs a folder that does not exist" do dne_dir = File.join(@root, "notexist") Kitchen::Util.safe_glob(dne_dir, "**/*").must_equal Dir.glob(File.join(dne_dir, "**/*")) end end end test-kitchen-1.23.2/spec/kitchen/cli_spec.rb0000644000004100000410000000273613377651062020740 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Tyler Ball () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/cli" require "kitchen" module Kitchen describe CLI do let(:cli) { CLI.new } before do @orig_env = ENV.to_hash end after do ENV.clear @orig_env.each do |k, v| ENV[k] = v end end describe "#initialize" do it "does not set logging config when environment variables are missing" do assert_equal Kitchen::DEFAULT_LOG_LEVEL, cli.config.log_level assert_equal Kitchen::DEFAULT_LOG_OVERWRITE, cli.config.log_overwrite end it "does set logging config when environment variables are present" do ENV["KITCHEN_LOG"] = "warn" ENV["KITCHEN_LOG_OVERWRITE"] = "false" assert_equal :warn, cli.config.log_level assert_equal false, cli.config.log_overwrite end end end end test-kitchen-1.23.2/spec/kitchen/config_spec.rb0000644000004100000410000002664413377651062021442 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen" require "kitchen/logging" require "kitchen/collection" require "kitchen/config" require "kitchen/driver" require "kitchen/instance" require "kitchen/platform" require "kitchen/provisioner" require "kitchen/suite" require "kitchen/transport" require "kitchen/util" require "kitchen/verifier" module Kitchen class DummyLoader attr_writer :data def initialize @data = {} end def read @data end end end describe Kitchen::Config do # Explicitly enable tty to test colorize default option later before do Kitchen.stubs(:tty?).returns(true) end let(:loader) { Kitchen::DummyLoader.new } let(:config) { Kitchen::Config.new(opts) } let(:opts) do { loader: loader, kitchen_root: "/tmp/that/place", log_root: "/tmp/logs", test_base_path: "/testing/yo", log_level: :debug, log_overwrite: false, colorize: false, debug: true, } end let(:default_kitchen_config) do { defaults: { driver: "dummy", provisioner: "chef_solo", transport: "ssh", verifier: "busser", }, kitchen_root: "/tmp/that/place", test_base_path: "/testing/yo", log_level: :debug, log_overwrite: false, colorize: false, } end describe "#loader" do it "returns its loader" do config.loader.must_equal loader end it "creates a Kitchen::Loader::YAML loader by default" do opts.delete(:loader) config.loader.must_be_kind_of Kitchen::Loader::YAML end end describe "#kitchen_root" do it "returns its kitchen root" do config.kitchen_root.must_equal "/tmp/that/place" end it "uses Dir.pwd by default" do opts.delete(:kitchen_root) config.kitchen_root.must_equal Dir.pwd end end describe "#log_root" do it "returns its log root" do config.log_root.must_equal "/tmp/logs" end it "calculates a default log root using kitchen_root" do opts.delete(:log_root) config.log_root.must_equal "/tmp/that/place/.kitchen/logs" end end describe "#test_base_path" do it "returns its base test path" do config.test_base_path.must_equal "/testing/yo" end it "calculates a default base using kitchen_root" do opts.delete(:test_base_path) config.test_base_path.must_equal "/tmp/that/place/test/integration" end end describe "#log_level" do it "returns its log level" do config.log_level.must_equal :debug end it "uses :info by default" do opts.delete(:log_level) config.log_level.must_equal :info end end describe "#log_overwrite" do it "returns its log level" do config.log_overwrite.must_equal false end it "uses :info by default" do opts.delete(:log_overwrite) config.log_overwrite.must_equal true end end describe "#colorize" do it "returns its colorize" do config.colorize.must_equal false end it "uses true by default" do opts.delete(:colorize) config.colorize.must_equal true end end describe "#debug" do it "returns its debug" do config.debug.must_equal true end it "uses false by default" do opts.delete(:debug) config.debug.must_equal false end end describe "#platforms" do before do Kitchen::DataMunger.stubs(:new).returns(munger) Kitchen::Platform.stubs(:new).returns("platform") end let(:munger) do stub( platform_data: [{ one: "a" }, { two: "b" }] ) end it "loader loads data" do loader.expects(:read).returns({}) config.platforms end it "constructs a munger with loader data and defaults" do loader.stubs(:read).returns("datum") Kitchen::DataMunger.expects(:new).with do |data, kitchen_config| data.must_equal "datum" kitchen_config.is_a?(Hash).must_equal true end.returns(munger) config.platforms end it "platform_data is called on munger" do munger.expects(:platform_data).returns([]) config.platforms end it "contructs Platform objects" do Kitchen::Platform.expects(:new).with(one: "a") Kitchen::Platform.expects(:new).with(two: "b") config.platforms end it "returns a Collection of platforms" do Kitchen::Platform.stubs(:new) .with(one: "a").returns(stub(name: "one")) Kitchen::Platform.stubs(:new) .with(two: "b").returns(stub(name: "two")) config.platforms.as_names.must_equal %w{one two} end end describe "#suites" do before do Kitchen::DataMunger.stubs(:new).returns(munger) Kitchen::Suite.stubs(:new).returns("suite") end let(:munger) do stub( suite_data: [{ one: "a" }, { two: "b" }] ) end it "loader loads data" do loader.expects(:read).returns({}) config.suites end it "constucts a munger with loader data and defaults" do loader.stubs(:read).returns("datum") Kitchen::DataMunger.expects(:new).with do |data, kitchen_config| data.must_equal "datum" kitchen_config.is_a?(Hash).must_equal true end.returns(munger) config.suites end it "platform_data is called on munger" do munger.expects(:suite_data).returns([]) config.suites end it "contructs Suite objects" do Kitchen::Suite.expects(:new).with(one: "a") Kitchen::Suite.expects(:new).with(two: "b") config.suites end it "returns a Collection of suites" do Kitchen::Suite.stubs(:new) .with(one: "a").returns(stub(name: "one")) Kitchen::Suite.stubs(:new) .with(two: "b").returns(stub(name: "two")) config.suites.as_names.must_equal %w{one two} end end describe "#instances" do let(:platforms) do [stub(name: "unax")] end let(:suites) do [stub(name: "tiny", includes: [], excludes: [])] end let(:munger) do stub( driver_data_for: { "junk" => true }, lifecycle_hooks_data_for: { "junk" => true }, provisioner_data_for: { "junk" => true }, transport_data_for: { "junk" => true }, verifier_data_for: { "junk" => true } ) end before do Kitchen::Instance.stubs(:new).returns("instance") Kitchen::Driver.stubs(:for_plugin).returns("driver") Kitchen::Provisioner.stubs(:for_plugin).returns("provisioner") Kitchen::Transport.stubs(:for_plugin).returns("transport") Kitchen::Verifier.stubs(:for_plugin).returns("verifier") Kitchen::Logger.stubs(:new).returns("logger") Kitchen::StateFile.stubs(:new).returns("state_file") Kitchen::LifecycleHooks.stubs(:new).returns("lifecycle_hooks") Kitchen::DataMunger.stubs(:new).returns(munger) config.stubs(:platforms).returns(platforms) config.stubs(:suites).returns(suites) end it "constructs a Driver object" do munger.expects(:driver_data_for).with("tiny", "unax") .returns(name: "drivey", datum: "lots") Kitchen::Driver.unstub(:for_plugin) Kitchen::Driver.expects(:for_plugin) .with("drivey", name: "drivey", datum: "lots") config.instances end it "constructs a Provisioner object" do munger.expects(:provisioner_data_for).with("tiny", "unax") .returns(name: "provey", datum: "lots") Kitchen::Provisioner.unstub(:for_plugin) Kitchen::Provisioner.expects(:for_plugin) .with("provey", name: "provey", datum: "lots") config.instances end it "constructs a Transport object" do munger.expects(:transport_data_for).with("tiny", "unax") .returns(name: "transey", datum: "lots") Kitchen::Transport.unstub(:for_plugin) Kitchen::Transport.expects(:for_plugin) .with("transey", name: "transey", datum: "lots") config.instances end it "constructs a Verifier object" do munger.expects(:verifier_data_for).with("tiny", "unax") .returns(name: "vervey", datum: "lots") Kitchen::Verifier.unstub(:for_plugin) Kitchen::Verifier.expects(:for_plugin) .with("vervey", name: "vervey", datum: "lots") config.instances end it "constructs a Logger object" do Kitchen::Logger.unstub(:new) Kitchen::Logger.expects(:new).with( stdout: STDOUT, color: :cyan, logdev: "/tmp/logs/tiny-unax.log", log_overwrite: false, level: 0, progname: "tiny-unax", colorize: false ) config.instances end it "constructs a StateFile object" do Kitchen::StateFile.unstub(:new) Kitchen::StateFile.expects(:new).with("/tmp/that/place", "tiny-unax") config.instances end it "constructs an Instance object from all built objects" do Kitchen::Instance.unstub(:new) Kitchen::Instance.expects(:new).with( driver: "driver", logger: "logger", suite: suites.first, lifecycle_hooks: "lifecycle_hooks", platform: platforms.first, provisioner: "provisioner", transport: "transport", verifier: "verifier", state_file: "state_file" ) config.instances end end describe "using Suite#includes" do it "selects only platforms in a suite's includes array" do config.stubs(:platforms).returns([ stub(name: "good"), stub(name: "nope"), stub(name: "one"), ]) config.stubs(:suites).returns([ stub(name: "selecta", includes: %w{good one}, excludes: []), stub(name: "allem", includes: [], excludes: []), ]) config.instances.as_names.must_equal [ "selecta-good", "selecta-one", "allem-good", "allem-nope", "allem-one"] end end describe "using Suite#excludes" do it "selects only platforms in a suite's includes array" do config.stubs(:platforms).returns([ stub(name: "good"), stub(name: "nope"), stub(name: "one"), ]) config.stubs(:suites).returns([ stub(name: "selecta", includes: [], excludes: ["nope"]), stub(name: "allem", includes: [], excludes: []), ]) config.instances.as_names.must_equal [ "selecta-good", "selecta-one", "allem-good", "allem-nope", "allem-one"] end end end test-kitchen-1.23.2/spec/kitchen/errors_spec.rb0000644000004100000410000002511013377651062021474 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen" require "kitchen/errors" describe Kitchen::Error do let(:exception) { Kitchen::StandardError.new("shoot") } describe ".formatted_exception" do it "returns an array of a formatted message" do Kitchen::Error.formatted_exception(exception).must_equal([ "------Exception-------", "Class: Kitchen::StandardError", "Message: shoot", "----------------------", ]) end it "takes a customized title" do Kitchen::Error.formatted_exception(exception, "Trouble").first .must_equal("-------Trouble--------") end end describe ".formatted_exception" do it "returns an array of a formatted message with a nil backtrace" do Kitchen::Error.formatted_trace(exception).must_equal([ "------Exception-------", "Class: Kitchen::StandardError", "Message: shoot", "----------------------", ]) end it "returns an array containing the exception's backtrace" do begin raise Kitchen::StandardError, "shoot" rescue => e Kitchen::Error.formatted_trace(e)[5...-1].must_equal e.backtrace end end it "returns an array containing a nested exception, if given" do begin raise IOError, "no disk, yo" rescue e = Kitchen::StandardError.new("shoot") Kitchen::Error.formatted_trace(e).must_equal([ "------Exception-------", "Class: Kitchen::StandardError", "Message: shoot", "----------------------", "---Nested Exception---", "Class: IOError", "Message: no disk, yo", "----------------------", ]) end end it "returns an array when an error has more than one error in original" do error_array = [] error_array << Kitchen::StandardError.new("one") error_array << Kitchen::StandardError.new("two") composite_error = Kitchen::StandardError.new("array", error_array) Kitchen::Error.formatted_trace(composite_error).must_equal([ "------Exception-------", "Class: Kitchen::StandardError", "Message: array", "----------------------", "-Composite Exception--", "Class: Kitchen::StandardError", "Message: one", "----------------------", "-Composite Exception--", "Class: Kitchen::StandardError", "Message: two", "----------------------" ]) end end end describe Kitchen::StandardError do it "is a kind of Kitchen::Error" do Kitchen::StandardError.new("oops").must_be_kind_of Kitchen::Error end it "by default, sets original exception to the last raised exception" do begin raise IOError, "crap" rescue original = Kitchen::StandardError.new("oops").original original.must_be_kind_of IOError original.message.must_equal "crap" end end it "can embed an exception when constructing" do original = Kitchen::StandardError.new("durn", IOError.new("ack")).original original.must_be_kind_of IOError original.message.must_equal "ack" end end [ Kitchen::UserError, Kitchen::ClientError, Kitchen::TransientFailure ].each do |klass| describe klass do it "is a kind of Kitchen::StandardError" do klass.new("oops").must_be_kind_of Kitchen::StandardError end end end [ Kitchen::ActionFailed, Kitchen::InstanceFailure ].each do |klass| describe klass do it "is a kind of Kitchen::TransientFailure" do klass.new("oops").must_be_kind_of Kitchen::TransientFailure end end end describe Kitchen do describe ".with_friendly_errors" do let(:logger_io) { StringIO.new } let(:logger) { Kitchen::Logger.new(logdev: logger_io) } before do Kitchen.stubs(:tty?).returns(true) @orig_stderr = $stderr $stderr = StringIO.new @orig_logger = Kitchen.logger Kitchen.logger = logger end after do $stderr = @orig_stderr Kitchen.logger = @orig_logger end describe "for instance failures" do def go_boom Kitchen.with_friendly_errors do begin raise IOError, "no stuff" rescue raise Kitchen::InstanceFailure, "cannot do that" end end end it "exits with 10" do begin go_boom rescue SystemExit => e e.status.must_equal 10 end end it "prints a message on STDERR" do output = [ ">>>>>> cannot do that", ">>>>>> ------Exception-------", ">>>>>> Class: IOError", ">>>>>> Message: no stuff", ">>>>>> ----------------------", ].map { |l| Kitchen::Color.colorize(l, :red) }.join("\n").concat("\n") begin go_boom rescue SystemExit $stderr.string.must_equal output end end it "prints a message on STDERR without color" do Kitchen.stubs(:tty?).returns(false) output = [ ">>>>>> cannot do that", ">>>>>> ------Exception-------", ">>>>>> Class: IOError", ">>>>>> Message: no stuff", ">>>>>> ----------------------", ].join("\n").concat("\n") begin go_boom rescue SystemExit $stderr.string.must_equal output end end it "logs the exception message on the common logger's error severity" do begin go_boom rescue SystemExit logger_io.string.must_match(/ERROR -- Kitchen: cannot do that$/) end end it "logs the exception message on debug, if set" do logger.level = ::Logger::DEBUG begin go_boom rescue SystemExit logger_io.string.must_match(/DEBUG -- Kitchen: cannot do that$/) end end end describe "for unexpected failures" do def go_boom Kitchen.with_friendly_errors do begin raise IOError, "wtf?" rescue raise Kitchen::StandardError, "ah crap" end end end it "exits with 20" do begin go_boom rescue SystemExit => e e.status.must_equal 20 end end it "prints a message on STDERR" do output = [ ">>>>>> ------Exception-------", ">>>>>> Class: Kitchen::StandardError", ">>>>>> Message: ah crap", ">>>>>> ----------------------", ">>>>>> Please see .kitchen/logs/kitchen.log for more details", ">>>>>> Also try running `kitchen diagnose --all` for configuration\n", ].map { |l| Kitchen::Color.colorize(l, :red) }.join("\n").concat("\n") begin go_boom rescue SystemExit $stderr.string.must_equal output end end it "prints a message on STDERR without color" do Kitchen.stubs(:tty?).returns(false) output = [ ">>>>>> ------Exception-------", ">>>>>> Class: Kitchen::StandardError", ">>>>>> Message: ah crap", ">>>>>> ----------------------", ">>>>>> Please see .kitchen/logs/kitchen.log for more details", ">>>>>> Also try running `kitchen diagnose --all` for configuration", ].join("\n").concat("\n") begin go_boom rescue SystemExit $stderr.string.must_equal output end end it "logs the exception message on the common logger's error severity" do begin go_boom rescue SystemExit logger_io.string .must_match(/ERROR -- Kitchen: ------Exception-------$/) logger_io.string .must_match(/ERROR -- Kitchen: Class: Kitchen::StandardError$/) logger_io.string .must_match(/ERROR -- Kitchen: ------Backtrace-------$/) end end it "logs the exception message on debug, if set" do logger.level = ::Logger::DEBUG begin go_boom rescue SystemExit logger_io.string .must_match(/DEBUG -- Kitchen: ------Exception-------$/) logger_io.string .must_match(/DEBUG -- Kitchen: Class: Kitchen::StandardError$/) logger_io.string .must_match(/DEBUG -- Kitchen: ------Backtrace-------$/) end end end end end test-kitchen-1.23.2/spec/kitchen/transport/0000755000004100000410000000000013377651062020656 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/transport/base_spec.rb0000644000004100000410000001073013377651062023130 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" describe Kitchen::Transport::Base do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:instance) do stub(name: "coolbeans", logger: logger) end let(:transport) do Kitchen::Transport::Base.new(config).finalize_config!(instance) end it "has an #connection method which raises a ClientError" do proc { transport.connection({}) }.must_raise Kitchen::ClientError end describe "#logger" do before { @klog = Kitchen.logger } after { Kitchen.logger = @klog } it "returns the instance's logger" do transport.send(:logger).must_equal logger end it "returns the default logger if instance's logger is not set" do transport = Kitchen::Transport::Base.new(config) Kitchen.logger = "yep" transport.send(:logger).must_equal Kitchen.logger end end describe Kitchen::Transport::TransportFailed do let(:failure_with_no_exit_code) { Kitchen::Transport::TransportFailed.new("Boom") } let(:failure_with_exit_code) { Kitchen::Transport::TransportFailed.new("Boom", 123) } describe "when no exit code is provided" do it "#exit_code is nil" do failure_with_no_exit_code.exit_code.must_be_nil end end describe "when an exit code is provided" do it "#exit_code returns the supplied exit code" do failure_with_exit_code.exit_code.must_equal 123 end end end end describe Kitchen::Transport::Base::Connection do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:options) { { logger: logger } } let(:connection) do Kitchen::Transport::Base::Connection.new(options) end it "has a #close method that does nothing" do connection.close.must_be_nil end it "has an #execute method which raises a ClientError" do proc { connection.execute("boo") }.must_raise Kitchen::ClientError end it "has a #login_command method which raises an ActionFailed" do proc { connection.login_command }.must_raise Kitchen::ActionFailed end it "has an #upload method which raises a ClientError" do proc { connection.upload(["file"], "/path/to") } .must_raise Kitchen::ClientError end it "has an #download method which raises a ClientError" do proc { connection.download(["remote"], "local") } .must_raise Kitchen::ClientError end it "has a #wait_until_ready method that does nothing" do connection.wait_until_ready.must_be_nil end describe "#execute_with_retry" do let(:failure_with_exit_code) { Kitchen::Transport::TransportFailed.new("Boom", 123) } it "raises ClientError with no retries" do proc { connection.execute_with_retry("hi", [], nil, nil) } .must_raise Kitchen::ClientError end it "retries three times" do connection.expects(:execute).with("Hi").returns("Hello") connection.expects(:debug).with("Attempting to execute command - try 3 of 3.") connection.expects(:execute).with("Hi").raises(failure_with_exit_code) connection.expects(:debug).with("Attempting to execute command - try 2 of 3.") connection.expects(:execute).with("Hi").raises(failure_with_exit_code) connection.expects(:debug).with("Attempting to execute command - try 1 of 3.") connection.execute_with_retry("Hi", [123], 3, 1).must_equal "Hello" end end describe "#retry?" do it "raises an exception when multiple retryable exit codes are passed as a String" do proc { connection.retry?(2, 2, "35 1", 35) } .must_raise("undefined method `flatten' for \"35 1\":String") end it "returns true when the retryable exit codes are formatted in a nested array" do connection.retry?(1, 2, [[35, 1]], 35).must_equal true end end end test-kitchen-1.23.2/spec/kitchen/transport/winrm_spec.rb0000644000004100000410000011023613377651062023354 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Matt Wrock () # # Copyright (C) 2014, Matt Wrock # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen/transport/winrm" require "winrm" require "winrm-fs" require "winrm-elevated" module Kitchen module Transport class WinRMConnectionDummy < Kitchen::Transport::Winrm::Connection attr_reader :saved_command, :remote_path, :local_path def upload(locals, remote) @saved_command = IO.read(locals) @local_path = locals @remote_path = remote end end end end describe Kitchen::Transport::Winrm do before do RbConfig::CONFIG.stubs(:[]).with("host_os").returns("blah") end let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:state) { Hash.new } let(:instance) do stub(name: "coolbeans", logger: logger, to_str: "instance") end let(:transport) do t = Kitchen::Transport::Winrm.new(config) # :load_winrm_s! is not cross-platform safe # and gets initialized too early in the pipeline t.stubs(:load_winrm_s!) t.finalize_config!(instance) end it "provisioner api_version is 1" do transport.diagnose_plugin[:api_version].must_equal 1 end it "plugin_version is set to Kitchen::VERSION" do transport.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "default_config" do it "sets :scheme to http by default" do transport[:scheme].must_equal "http" end it "sets :port to 5985 by default" do transport[:port].must_equal 5985 end it "sets :username to administrator by default" do transport[:username].must_equal "administrator" end it "sets :password to nil by default" do transport[:password].must_be_nil end it "sets :rdp_port to 3389 by default" do transport[:rdp_port].must_equal 3389 end it "sets :connection_retries to 5 by default" do transport[:connection_retries].must_equal 5 end it "sets :connection_retry_sleep to 1 by default" do transport[:connection_retry_sleep].must_equal 1 end it "sets :max_wait_until_ready to 600 by default" do transport[:max_wait_until_ready].must_equal 600 end it "sets :winrm_transport to :negotiate" do transport[:winrm_transport].must_equal :negotiate end it "sets :elevated to false" do transport[:elevated].must_equal false end end describe "#connection" do let(:klass) { Kitchen::Transport::Winrm::Connection } # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.common_connection_specs before do config[:hostname] = "here" config[:kitchen_root] = "/i/am/root" config[:password] = "password" end it "returns a Kitchen::Transport::Winrm::Connection object" do transport.connection(state).must_be_kind_of klass end it "sets :instance_name to the instance's name" do klass.expects(:new).with do |hash| hash[:instance_name] == "coolbeans" end make_connection end it "sets :kitchen_root to the transport's kitchen_root" do klass.expects(:new).with do |hash| hash[:kitchen_root] == "/i/am/root" end make_connection end it "sets the :logger to the transport's logger" do klass.expects(:new).with do |hash| hash[:logger] == logger end make_connection end it "sets the :winrm_transport to :negotiate" do klass.expects(:new).with do |hash| hash[:transport] == :negotiate end make_connection end it "sets the :disable_sspi to false" do klass.expects(:new).with do |hash| hash[:disable_sspi] == false end make_connection end it "sets :endpoint when hostname is an IPv6 address" do config[:hostname] = "caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78" klass.expects(:new).with do |hash| hash[:endpoint] == "http://[caec:cec6:c4ef:bb7b:1a78:d055:216d:3a78]:5985/wsman" end make_connection end it "sets :endpoint from data in config" do config[:hostname] = "host-from-config" config[:port] = "42" config[:winrm_transport] = "ssl" klass.expects(:new).with do |hash| hash[:endpoint] == "https://host-from-config:42/wsman" end make_connection end it "sets :endpoint from data in state over config data" do state[:hostname] = "host-from-state" config[:hostname] = "host-from-config" state[:port] = "42" config[:port] = "43" config[:winrm_transport] = "ssl" klass.expects(:new).with do |hash| hash[:endpoint] == "https://host-from-state:42/wsman" end make_connection end it "sets :user from :username in config" do config[:username] = "user_from_config" klass.expects(:new).with do |hash| hash[:user] == "user_from_config" end make_connection end it "sets :user from :username in state over config data" do state[:username] = "user_from_state" config[:username] = "user_from_config" klass.expects(:new).with do |hash| hash[:user] == "user_from_state" end make_connection end it "sets :pass from :password in config" do config[:password] = "pass_from_config" klass.expects(:new).with do |hash| hash[:password] == "pass_from_config" end make_connection end it "sets :pass from :password in state over config data" do state[:password] = "pass_from_state" config[:password] = "pass_from_config" klass.expects(:new).with do |hash| hash[:password] == "pass_from_state" end make_connection end it "sets :rdp_port from config" do config[:rdp_port] = "rdp_from_config" klass.expects(:new).with do |hash| hash[:rdp_port] == "rdp_from_config" end make_connection end it "sets :rdp_port from state over config data" do state[:rdp_port] = "rdp_from_state" config[:rdp_port] = "rdp_from_config" klass.expects(:new).with do |hash| hash[:rdp_port] == "rdp_from_state" end make_connection end it "sets :connection_retries from config" do config[:connection_retries] = "retries_from_config" klass.expects(:new).with do |hash| hash[:connection_retries] == "retries_from_config" end make_connection end it "sets :connection_retries from state over config data" do state[:connection_retries] = "retries_from_state" config[:connection_retries] = "retries_from_config" klass.expects(:new).with do |hash| hash[:connection_retries] == "retries_from_state" end make_connection end it "sets :connection_retry_sleep from config" do config[:connection_retry_sleep] = "sleep_from_config" klass.expects(:new).with do |hash| hash[:connection_retry_sleep] == "sleep_from_config" end make_connection end it "sets :connection_retry_sleep from state over config data" do state[:connection_retry_sleep] = "sleep_from_state" config[:connection_retry_sleep] = "sleep_from_config" klass.expects(:new).with do |hash| hash[:connection_retry_sleep] == "sleep_from_state" end make_connection end it "sets :max_wait_until_ready from config" do config[:max_wait_until_ready] = "max_from_config" klass.expects(:new).with do |hash| hash[:max_wait_until_ready] == "max_from_config" end make_connection end it "sets :max_wait_until_ready from state over config data" do state[:max_wait_until_ready] = "max_from_state" config[:max_wait_until_ready] = "max_from_config" klass.expects(:new).with do |hash| hash[:max_wait_until_ready] == "max_from_state" end make_connection end it "sets :winrm_transport from config data" do config[:winrm_transport] = "ssl" klass.expects(:new).with do |hash| hash[:transport] == :ssl end make_connection end it "sets elevated_username from user by default" do config[:username] = "user" klass.expects(:new).with do |hash| hash[:elevated_username] == "user" end make_connection end it "sets elevated_username from overriden elevated_username" do config[:username] = "user" config[:elevated_username] = "elevated_user" klass.expects(:new).with do |hash| hash[:elevated_username] == "elevated_user" end make_connection end it "sets elevated_password from user by default" do config[:password] = "pass" klass.expects(:new).with do |hash| hash[:elevated_password] == "pass" end make_connection end it "sets elevated_password from overriden elevated_password" do config[:password] = "pass" config[:elevated_password] = "elevated_pass" klass.expects(:new).with do |hash| hash[:elevated_password] == "elevated_pass" end make_connection end it "sets elevated_password to nil if overriden elevated_password is nil" do config[:password] = "pass" config[:elevated_password] = nil klass.expects(:new).with do |hash| hash[:elevated_password].nil? end make_connection end describe "when negotiate is set in config" do before do config[:winrm_transport] = "negotiate" end it "sets :winrm_transport to negotiate" do klass.expects(:new).with do |hash| hash[:transport] == :negotiate && hash[:disable_sspi] == false && hash[:basic_auth_only] == false end make_connection end end it "returns the same connection when called again with same state" do first_connection = make_connection(state) second_connection = make_connection(state) first_connection.object_id.must_equal second_connection.object_id end it "logs a debug message when the connection is reused" do make_connection(state) make_connection(state) logged_output.string.lines.count do |l| l =~ debug_line_with("[WinRM] reusing existing connection ") end.must_equal 1 end it "returns a new connection when called again if state differs" do first_connection = make_connection(state) second_connection = make_connection(state.merge(port: 9000)) first_connection.object_id.wont_equal second_connection.object_id end it "closes first connection when a second is created" do first_connection = make_connection(state) first_connection.expects(:close) make_connection(state.merge(port: 9000)) end it "logs a debug message a second connection is created" do make_connection(state) make_connection(state.merge(port: 9000)) logged_output.string.lines.count do |l| l =~ debug_line_with("[WinRM] shutting previous connection ") end.must_equal 1 end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "called without a block" do def make_connection(s = state) transport.connection(s) end common_connection_specs end describe "called with a block" do def make_connection(s = state) transport.connection(s) do |conn| conn end end common_connection_specs end end describe "#load_needed_dependencies" do describe "winrm-elevated" do let(:transport) { Kitchen::Transport::Winrm.new(config) } before do transport.stubs(:require).with("winrm") transport.stubs(:require).with("winrm-fs") end describe "elevated is false" do it "does not require winrm-elevated" do transport.expects(:require).with("winrm-elevated").never transport.finalize_config!(instance) end end describe "elevated is true" do before { config[:elevated] = true } it "does requires winrm-elevated" do transport.expects(:require).with("winrm-elevated") transport.finalize_config!(instance) end end end describe "winrm-fs" do before do # force loading of winrm-fs to get the version constant require "winrm-fs" end it "logs a message to debug that code will be loaded" do transport logged_output.string.must_match debug_line_with( "winrm-fs requested, loading winrm-fs gem") end it "logs a message to debug when library is initially loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm", anything) transport.stubs(:require).with("winrm-fs").returns(true) transport.finalize_config!(instance) logged_output.string.must_match( /winrm-fs is loaded/ ) end it "logs a message to debug when library is previously loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm", anything) transport.stubs(:require).with("winrm-fs").returns(false) transport.finalize_config!(instance) logged_output.string.must_match( /winrm-fs was already loaded/ ) end it "logs a message to fatal when libraries cannot be loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm", anything) transport.stubs(:require).with("winrm-fs") .raises(LoadError, "uh oh") begin transport.finalize_config!(instance) rescue # rubocop:disable Lint/HandleExceptions # we are interested in the log output, not this exception end logged_output.string.must_match fatal_line_with( "The `winrm-fs` gem is missing and must be installed") end it "raises a UserError when libraries cannot be loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm", anything) transport.stubs(:require).with("winrm-fs") .raises(LoadError, "uh oh") err = proc do transport.finalize_config!(instance) end.must_raise Kitchen::UserError err.message.must_match(/^Could not load or activate winrm-fs\. /) end end describe "winrm" do it "logs a message to debug that code will be loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm-fs", anything) transport.stubs(:require) transport.finalize_config!(instance) logged_output.string.must_match debug_line_with( "winrm requested, loading winrm gem") end it "logs a message to debug when library is initially loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm-fs", anything) transport.stubs(:require).returns(true) transport.finalize_config!(instance) logged_output.string.must_match( /winrm is loaded/ ) end it "logs a message to debug when library is previously loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm-fs", anything) transport.stubs(:require).returns(false) transport.finalize_config!(instance) logged_output.string.must_match( /winrm was already loaded/ ) end it "logs a message to fatal when libraries cannot be loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm-fs", anything) transport.stubs(:require).raises(LoadError, "uh oh") begin transport.finalize_config!(instance) rescue # rubocop:disable Lint/HandleExceptions # we are interested in the log output, not this exception end logged_output.string.must_match fatal_line_with( "The `winrm` gem is missing and must be installed") end it "raises a UserError when libraries cannot be loaded" do transport = Kitchen::Transport::Winrm.new(config) transport.stubs(:require).with("winrm-fs", anything) transport.stubs(:require).raises(LoadError, "uh oh") err = proc do transport.finalize_config!(instance) end.must_raise Kitchen::UserError err.message.must_match(/^Could not load or activate winrm\. /) end end end def debug_line_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end def fatal_line_with(msg) /^F, .* : #{Regexp.escape(msg)}/ end end describe Kitchen::Transport::Winrm::Connection do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:options) do { logger: logger, user: "me", password: "haha", endpoint: "http://foo:5985/wsman", winrm_transport: :plaintext, kitchen_root: "/i/am/root", instance_name: "coolbeans", rdp_port: "rdpyeah" } end let(:info) do copts = { user: "me", password: "haha", endpoint: "http://foo:5985/wsman", winrm_transport: :plaintext, } "<#{copts}>" end let(:winrm_session) do s = mock("winrm_session") s.responds_like_instance_of(::WinRM::Connection) s end let(:executor) do s = mock("command_executor") s.responds_like_instance_of(WinRM::Shells::Powershell) s end let(:transporter) do t = mock("file_transporter") t.responds_like_instance_of(WinRM::FS::Core::FileTransporter) t end let(:elevated_runner) do r = mock("elevated_runner") r.responds_like_instance_of(WinRM::Shells::Elevated) r end let(:connection) do Kitchen::Transport::Winrm::Connection.new(options) end before do WinRM::Connection.stubs(:new).returns(winrm_session) winrm_session.stubs(:logger=) logger.level = Logger::DEBUG end describe "#close" do let(:response) do o = WinRM::Output.new o.exitcode = 0 o << { stdout: "ok\r\n" } o end before do transporter.stubs(:upload) elevated_runner.stubs(:run).returns(response) winrm_session.stubs(:shell).with(:powershell).returns(executor) executor.stubs(:close) elevated_runner.stubs(:close) executor.stubs(:run) .with("doit").yields("ok\n", nil).returns(response) executor.stubs(:run) .with("$env:temp").yields("ok\n", nil).returns(response) end it "only closes the shell once for multiple calls" do executor.expects(:close).once connection.execute("doit") connection.close connection.close connection.close end it "clears the file_transporter executor" do WinRM::FS::Core::FileTransporter.expects(:new).returns(transporter).twice connection.upload("local", "remote") connection.close connection.upload("local", "remote") end it "clears the elevated_runner executor" do options[:elevated] = true elevated_runner.stubs(:username=) elevated_runner.stubs(:password=) elevated_runner.expects(:close).once winrm_session.expects(:shell).with(:elevated).returns(elevated_runner).twice connection.execute("doit") connection.close connection.execute("doit") end end describe "#execute" do before do winrm_session.stubs(:shell).with(:powershell).returns(executor) end describe "for a successful command" do let(:response) do o = WinRM::Output.new o.exitcode = 0 o << { stdout: "ok\r\n" } o << { stderr: "congrats\r\n" } o end before do executor.expects(:run) .with("doit").yields("ok\n", nil).returns(response) end it "logger displays command on debug" do connection.execute("doit") logged_output.string.must_match debug_line( "[WinRM] #{info} (doit)") end it "logger captures stdout" do connection.execute("doit") logged_output.string.must_match(/^ok$/) end it "logger captures stderr on warn if logger is at debug level" do logger.level = Logger::DEBUG connection.execute("doit") logged_output.string.must_match warn_line("congrats") end it "logger does not log stderr on warn if logger is below debug level" do logger.level = Logger::INFO connection.execute("doit") logged_output.string.wont_match warn_line("congrats") end end describe "elevated command" do let(:response) do o = WinRM::Output.new o.exitcode = 0 o << { stdout: "ok\r\n" } o << { stderr: "congrats\r\n" } o end let(:env_temp_response) do o = WinRM::Output.new o.exitcode = 0 o << { stdout: "temp_dir" } o end let(:elevated_runner) do r = mock("elevated_runner") r.responds_like_instance_of(WinRM::Shells::Elevated) r end before do options[:elevated] = true winrm_session.stubs(:shell).with(:elevated).returns(elevated_runner) end describe "elevated user is not login user" do before do options[:elevated_username] = "username" options[:elevated_password] = "password" executor.expects(:run) .with("$env:temp").returns(env_temp_response) elevated_runner.expects(:run) .with( "$env:temp='temp_dir';doit" ).yields("ok\n", nil).returns(response) elevated_runner.expects(:username=).with("username") elevated_runner.expects(:password=).with("password") end it "logger captures stdout" do connection.execute("doit") logged_output.string.must_match(/^ok$/) end end describe "elevated user is login user" do before do options[:elevated_username] = options[:user] options[:elevated_password] = options[:password] executor.expects(:run) .with("$env:temp").returns(env_temp_response) elevated_runner.expects(:run) .with( "$env:temp='temp_dir';doit" ).yields("ok\n", nil).returns(response) elevated_runner.expects(:username=).with(options[:user]) elevated_runner.expects(:password=).with(options[:password]) end it "logger captures stdout" do connection.execute("doit") logged_output.string.must_match(/^ok$/) end end end describe "for a failed command" do let(:response) do o = WinRM::Output.new o.exitcode = 1 o << { stderr: "#< CLIXML\r\n" } o << { stderr: '' } o << { stderr: "doit : The term 'doit' is not recognized as the " } o << { stderr: "name of a cmdlet, function, _x000D__x000A_" } o << { stderr: 'script file, or operable program. ' } o << { stderr: "Check the spelling of" } o << { stderr: 'the name, or if a path _x000D__x000A_was included, verify that the path is corr' } o << { stderr: 'ect and try again._x000D__x000A_At line:1 char:1_x000D__x000A_+ doit_x000D__x000A_+ ~~~~_' } o << { stderr: 'x000D__x000A_ + CategoryInf' } o << { stderr: "o : ObjectNotFound: (doit:String) [], Co" } o << { stderr: 'mmandNotFoun _x000D__x000A_ ' } o << { stderr: 'dException_x000D__x000A_ + ' } o << { stderr: "FullyQualifiedErrorId : CommandNotFoundException_" } o << { stderr: 'x000D__x000A_ _x000D__x000A_" } o end before do executor.expects(:run) .with("doit").yields("nope\n", nil).returns(response) end # rubocop:disable Metrics/MethodLength def self.common_failed_command_specs it "logger displays command on debug" do begin connection.execute("doit") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match debug_line( "[WinRM] #{info} (doit)" ) end it "logger captures stdout" do begin connection.execute("doit") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match(/^nope$/) end it "stderr is printed on logger warn level" do begin connection.execute("doit") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end message = <<'MSG'.chomp! doit : The term 'doit' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling ofthe name, or if a path was included, verify that the path is correct and try again. At line:1 char:1 + doit + ~~~~ + CategoryInfo : ObjectNotFound: (doit:String) [], CommandNotFoun dException + FullyQualifiedErrorId : CommandNotFoundException MSG message.lines.each do |line| logged_output.string.must_match warn_line(line.chomp) end end end # rubocop:enable Metrics/MethodLength describe "when a non-zero exit code is returned" do common_failed_command_specs it "raises a WinrmFailed exception" do err = proc do connection.execute("doit") end.must_raise Kitchen::Transport::WinrmFailed err.message.must_equal "WinRM exited (1) for command: [doit]" end it "raises WinrmFailed exception with the exit code of the failure" do begin connection.execute("doit") rescue Kitchen::Transport::WinrmFailed => e e.exit_code.must_equal 1 end end end end describe "for a nil command" do it "does not log on debug" do executor.expects(:open).never connection.execute(nil) logged_output.string.must_equal "" end end [ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, ::WinRM::WinRMHTTPTransportError, ::WinRM::WinRMAuthorizationError, HTTPClient::KeepAliveDisconnected, HTTPClient::ConnectTimeoutError ].each do |klass| describe "raising #{klass}" do before do k = if klass == ::WinRM::WinRMHTTPTransportError # this exception takes 2 args in its constructor, which is not stock klass.new("dang", 200) else klass end options[:connection_retries] = 3 options[:connection_retry_sleep] = 7 winrm_session.stubs(:shell).with(:powershell).raises(k) end it "reraises the #{klass} exception" do proc { connection.execute("nope") }.must_raise klass end end end end describe "#login_command" do let(:login_command) { connection.login_command } let(:args) { login_command.arguments.join(" ") } let(:exec_args) { login_command.exec_args } let(:rdp_doc) do File.join(File.join(options[:kitchen_root], ".kitchen", "coolbeans.rdp")) end describe "for Mac-based workstations" do before do RbConfig::CONFIG.stubs(:[]).with("host_os").returns("darwin14") end it "returns a LoginCommand" do with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) login_command.must_be_instance_of Kitchen::LoginCommand end end it "creates an rdp document" do actual = nil with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) login_command actual = IO.read(rdp_doc) end actual.must_equal Kitchen::Util.outdent!(<<-RDP) drivestoredirect:s:* full address:s:foo:rdpyeah prompt for credentials:i:1 username:s:me RDP end it "prints the rdp document on debug" do with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) login_command end expected = Kitchen::Util.outdent!(<<-OUTPUT) Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp) ------------ drivestoredirect:s:* full address:s:foo:rdpyeah prompt for credentials:i:1 username:s:me ------------ OUTPUT debug_output(logged_output.string).must_match expected end it "returns a LoginCommand which calls open on the rdp document" do actual = nil with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) actual = login_command end actual.exec_args.must_equal ["open", rdp_doc, {}] end end describe "for Windows-based workstations" do before do RbConfig::CONFIG.stubs(:[]).with("host_os").returns("mingw32") end it "returns a LoginCommand" do with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) login_command.must_be_instance_of Kitchen::LoginCommand end end it "creates an rdp document" do actual = nil with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) login_command actual = IO.read(rdp_doc) end actual.must_equal Kitchen::Util.outdent!(<<-RDP) full address:s:foo:rdpyeah prompt for credentials:i:1 username:s:me RDP end it "prints the rdp document on debug" do with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) login_command end expected = Kitchen::Util.outdent!(<<-OUTPUT) Creating RDP document for coolbeans (/i/am/root/.kitchen/coolbeans.rdp) ------------ full address:s:foo:rdpyeah prompt for credentials:i:1 username:s:me ------------ OUTPUT debug_output(logged_output.string).must_match expected end it "returns a LoginCommand which calls mstsc on the rdp document" do actual = nil with_fake_fs do FileUtils.mkdir_p(File.dirname(rdp_doc)) actual = login_command end actual.exec_args.must_equal ["mstsc", rdp_doc, {}] end end describe "for Linux-based workstations" do before do RbConfig::CONFIG.stubs(:[]).with("host_os").returns("linux-gnu") end it "returns a LoginCommand" do login_command.must_be_instance_of Kitchen::LoginCommand end it "is an rdesktop command" do login_command.command.must_equal "rdesktop" args.must_match %r{ foo:rdpyeah$} end it "sets the user" do args.must_match regexify("-u me ") end it "sets the pass if given" do args.must_match regexify(" -p haha ") end it "won't set the pass if not given" do options.delete(:password) args.wont_match regexify(" -p haha ") end end describe "for unknown workstation platforms" do before do RbConfig::CONFIG.stubs(:[]).with("host_os").returns("cray") end it "raises an ActionFailed error" do err = proc { login_command }.must_raise Kitchen::ActionFailed err.message.must_equal "Remote login not supported in " \ "Kitchen::Transport::Winrm::Connection from host OS 'cray'." end end end describe "#upload" do before do winrm_session.stubs(:shell).with(:powershell).returns(executor) WinRM::FS::Core::FileTransporter.stubs(:new) .with(executor).returns(transporter) transporter.stubs(:upload) end def self.common_specs_for_upload it "builds a Winrm::FileTransporter" do WinRM::FS::Core::FileTransporter.unstub(:new) WinRM::FS::Core::FileTransporter.expects(:new) .with(executor).returns(transporter) upload end it "reuses the Winrm::FileTransporter" do WinRM::FS::Core::FileTransporter.unstub(:new) WinRM::FS::Core::FileTransporter.expects(:new) .with(executor).returns(transporter).once upload upload upload end end describe "for a file" do def upload # execute every time, not lazily once connection.upload("/tmp/file.txt", 'C:\\dest') end common_specs_for_upload end describe "for a collection of files" do def upload # execute every time, not lazily once connection.upload(%w{/tmp/file1.txt /tmp/file2.txt}, 'C:\\dest') end common_specs_for_upload end end describe "#wait_until_ready" do before do winrm_session.stubs(:shell).with(:powershell).returns(executor) options[:max_wait_until_ready] = 300 end describe "when connection is successful" do let(:response) do o = WinRM::Output.new o.exitcode = 0 o << { stdout: "[WinRM] Established\r\n" } o end before do executor.expects(:run) .with("Write-Host '[WinRM] Established\n'").returns(response) end it "executes an empty command string to ensure working" do connection.wait_until_ready end end describe "when connection suceeds but command fails, sad panda" do let(:response) do o = WinRM::Output.new o.exitcode = 42 o << { stderr: "Ah crap.\r\n" } o end before do executor.expects(:run) .with("Write-Host '[WinRM] Established\n'").returns(response) end it "executes an empty command string to ensure working" do err = proc do connection.wait_until_ready end.must_raise Kitchen::Transport::WinrmFailed err.message.must_equal "WinRM exited (42) for command: " \ "[Write-Host '[WinRM] Established\n']" end it "stderr is printed on logger warn level" do begin connection.wait_until_ready rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match warn_line("Ah crap.\n") end end end def debug_output(output) regexp = /^D, .* DEBUG -- : / output.lines.grep(/^D, .* DEBUG -- : /).map { |l| l.sub(regexp, "") }.join end def debug_line(msg) /^D, .* : #{Regexp.escape(msg)}$/ end def debug_line_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def info_line_with(msg) /^I, .* : #{Regexp.escape(msg)}/ end def regexify(string) Regexp.new(Regexp.escape(string)) end def warn_line(msg) /^W, .* : #{Regexp.escape(msg)}$/ end def warn_line_with(msg) /^W, .* : #{Regexp.escape(msg)}/ end end test-kitchen-1.23.2/spec/kitchen/transport/ssh_spec.rb0000644000004100000410000011147513377651062023023 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen/transport/ssh" require "net/ssh/test" describe Kitchen::Transport::Ssh do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:state) { Hash.new } let(:instance) do stub(name: "coolbeans", logger: logger, to_str: "instance") end let(:transport) do Net::SSH::Test::Extensions::IO.with_test_extension { Kitchen::Transport::Ssh.new(config).finalize_config!(instance) } end it "provisioner api_version is 1" do transport.diagnose_plugin[:api_version].must_equal 1 end it "plugin_version is set to Kitchen::VERSION" do transport.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "default_config" do it "sets :port to 22 by default" do transport[:port].must_equal 22 end it "sets :username to root by default" do transport[:username].must_equal "root" end it "sets :compression to true by default" do transport[:compression].must_equal false end it "sets :compression to false if set to none" do config[:compression] = "none" transport[:compression].must_equal false end it "sets :compression to zlib@openssh.com if set to zlib" do config[:compression] = "zlib" transport[:compression].must_equal "zlib@openssh.com" end it "sets :compression_level to 6 by default" do transport[:compression_level].must_equal 0 end it "sets :compression_level to 6 if :compression is set to true" do config[:compression] = true transport[:compression_level].must_equal 6 end it "sets :keepalive to true by default" do transport[:keepalive].must_equal true end it "sets :keepalive_interval to 60 by default" do transport[:keepalive_interval].must_equal 60 end it "sets :connection_timeout to 15 by default" do transport[:connection_timeout].must_equal 15 end it "sets :connection_retries to 5 by default" do transport[:connection_retries].must_equal 5 end it "sets :connection_retry_sleep to 1 by default" do transport[:connection_retry_sleep].must_equal 1 end it "sets :max_wait_until_ready to 600 by default" do transport[:max_wait_until_ready].must_equal 600 end it "sets :ssh_key to nil by default" do transport[:ssh_key].must_be_nil end it "sets :ssh_key_only to nil by default" do transport[:ssh_key_only].must_be_nil end it "expands :ssh_path path if set" do config[:kitchen_root] = "/rooty" config[:ssh_key] = "my_key" transport[:ssh_key].must_equal os_safe_root_path("/rooty/my_key") end it "sets :max_ssh_sessions to 9 by default" do transport[:max_ssh_sessions].must_equal 9 end it "sets :ssh_http_proxy to nil by default" do transport[:ssh_http_proxy].must_be_nil end it "sets :ssh_http_proxy_port to nil by default" do transport[:ssh_http_proxy_port].must_be_nil end it "sets :ssh_http_proxy_user to nil by default" do transport[:ssh_http_proxy_user].must_be_nil end it "sets :ssh_http_proxy_password to nil by default" do transport[:ssh_http_proxy_password].must_be_nil end it "sets :ssh_gateway to nil by default" do transport[:ssh_gateway].must_be_nil end it "sets :ssh_gateway_username to nil by default" do transport[:ssh_gateway_username].must_be_nil end it "sets :ssh_gateway_port to 22 by default" do transport[:ssh_gateway_port].must_equal 22 end end describe "#connection" do let(:klass) { Kitchen::Transport::Ssh::Connection } let(:options_http_proxy) { Hash.new } let(:proxy_conn) do state[:ssh_http_proxy] = "ssh_http_proxy_from_state" state[:ssh_http_proxy_port] = "ssh_http_proxy_port_from_state" options_http_proxy[:user] = state[:ssh_http_proxy_user] options_http_proxy[:password] = state[:ssh_http_proxy_password] Net::SSH::Proxy::HTTP.new(state[:ssh_http_proxy], state[:ssh_http_proxy_port], options_http_proxy) end # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def self.common_connection_specs before do Net::SSH::Proxy::HTTP.stubs(:new).returns(proxy_conn) end it "returns a Kitchen::Transport::Ssh::Connection object" do transport.connection(state).must_be_kind_of klass end it "sets the :logger to the transport's logger" do klass.expects(:new).with do |hash| hash[:logger] == logger end make_connection end it "sets the :user_known_hosts_file to /dev/null" do klass.expects(:new).with do |hash| hash[:user_known_hosts_file] == "/dev/null" end make_connection end it "sets the :verify_host_key flag to false" do klass.expects(:new).with do |hash| hash[:verify_host_key] == false end make_connection end it "sets :hostname from config" do config[:hostname] = "host_from_config" klass.expects(:new).with do |hash| hash[:hostname] == "host_from_config" end make_connection end it "sets :hostname from state over config data" do state[:hostname] = "host_from_state" config[:hostname] = "host_from_config" klass.expects(:new).with do |hash| hash[:hostname] == "host_from_state" end make_connection end it "sets :port from config" do config[:port] = "port_from_config" klass.expects(:new).with do |hash| hash[:port] == "port_from_config" end make_connection end it "sets :port from state over config data" do state[:port] = "port_from_state" config[:port] = "port_from_config" klass.expects(:new).with do |hash| hash[:port] == "port_from_state" end make_connection end it "sets :username from config" do config[:username] = "user_from_config" klass.expects(:new).with do |hash| hash[:username] == "user_from_config" end make_connection end it "sets :username from state over config data" do state[:username] = "user_from_state" config[:username] = "user_from_config" klass.expects(:new).with do |hash| hash[:username] == "user_from_state" end make_connection end it "sets :compression from config" do config[:compression] = "none" klass.expects(:new).with do |hash| hash[:compression] == false end make_connection end it "sets :compression from state over config data" do state[:compression] = "none" config[:compression] = "zlib" klass.expects(:new).with do |hash| hash[:compression] == "none" end make_connection end it "sets :compression_level from config" do config[:compression_level] = 9999 klass.expects(:new).with do |hash| hash[:compression_level] == 9999 end make_connection end it "sets :compression_level from state over config data" do state[:compression_level] = 9999 config[:compression_level] = 1111 klass.expects(:new).with do |hash| hash[:compression_level] == 9999 end make_connection end it "sets :timeout from :connection_timeout in config" do config[:connection_timeout] = "timeout_from_config" klass.expects(:new).with do |hash| hash[:timeout] == "timeout_from_config" end make_connection end it "sets :timeout from :connection_timeout in state over config data" do state[:connection_timeout] = "timeout_from_state" config[:connection_timeout] = "timeout_from_config" klass.expects(:new).with do |hash| hash[:timeout] == "timeout_from_state" end make_connection end it "sets :keepalive from config" do config[:keepalive] = "keepalive_from_config" klass.expects(:new).with do |hash| hash[:keepalive] == "keepalive_from_config" end make_connection end it "sets :keepalive from state over config data" do state[:keepalive] = "keepalive_from_state" config[:keepalive] = "keepalive_from_config" klass.expects(:new).with do |hash| hash[:keepalive] == "keepalive_from_state" end make_connection end it "sets :keepalive_interval from config" do config[:keepalive_interval] = "interval_from_config" klass.expects(:new).with do |hash| hash[:keepalive_interval] == "interval_from_config" end make_connection end it "sets :keepalive_interval from state over config data" do state[:keepalive_interval] = "interval_from_state" config[:keepalive_interval] = "interval_from_config" klass.expects(:new).with do |hash| hash[:keepalive_interval] == "interval_from_state" end make_connection end it "sets :connection_retries from config" do config[:connection_retries] = "retries_from_config" klass.expects(:new).with do |hash| hash[:connection_retries] == "retries_from_config" end make_connection end it "sets :connection_retries from state over config data" do state[:connection_retries] = "retries_from_state" config[:connection_retries] = "retries_from_config" klass.expects(:new).with do |hash| hash[:connection_retries] == "retries_from_state" end make_connection end it "sets :connection_retry_sleep from config" do config[:connection_retry_sleep] = "sleep_from_config" klass.expects(:new).with do |hash| hash[:connection_retry_sleep] == "sleep_from_config" end make_connection end it "sets :connection_retry_sleep from state over config data" do state[:connection_retry_sleep] = "sleep_from_state" config[:connection_retry_sleep] = "sleep_from_config" klass.expects(:new).with do |hash| hash[:connection_retry_sleep] == "sleep_from_state" end make_connection end it "sets :max_wait_until_ready from config" do config[:max_wait_until_ready] = "max_from_config" klass.expects(:new).with do |hash| hash[:max_wait_until_ready] == "max_from_config" end make_connection end it "sets :max_wait_until_ready from state over config data" do state[:max_wait_until_ready] = "max_from_state" config[:max_wait_until_ready] = "max_from_config" klass.expects(:new).with do |hash| hash[:max_wait_until_ready] == "max_from_state" end make_connection end it "sets :keys_only to true if :ssh_key is set in config" do config[:ssh_key] = "ssh_key_from_config" klass.expects(:new).with do |hash| hash[:keys_only] == true end make_connection end it "sets :auth_methods to only publickey if :ssh_key is set in config" do config[:ssh_key] = "ssh_key_from_config" klass.expects(:new).with do |hash| hash[:auth_methods] == ["publickey"] end make_connection end it "sets :keys_only to true if :ssh_key is set in state" do state[:ssh_key] = "ssh_key_from_config" config[:ssh_key] = false klass.expects(:new).with do |hash| hash[:keys_only] == true end make_connection end it "sets :proxy to proxy if :ssh_http_proxy is set in state" do config[:ssh_http_proxy] = true klass.expects(:new).with do |hash| hash[:proxy] == proxy_conn end make_connection end it "sets :keys to an array if :ssh_key is set in config" do config[:kitchen_root] = "/r" config[:ssh_key] = "ssh_key_from_config" klass.expects(:new).with do |hash| hash[:keys] == [os_safe_root_path("/r/ssh_key_from_config")] end make_connection end it "sets :keys to an array if :ssh_key is set in state" do state[:ssh_key] = "ssh_key_from_state" config[:ssh_key] = "ssh_key_from_config" klass.expects(:new).with do |hash| hash[:keys] == ["ssh_key_from_state"] end make_connection end it "does not set :keys_only if :ssh_key is set in config but password is set" do config[:ssh_key] = "ssh_key_from_config" config[:password] = "password" klass.expects(:new).with do |hash| hash[:keys_only].nil? end make_connection end it "does not set :auth_methods if :ssh_key is set in config but password is set" do config[:ssh_key] = "ssh_key_from_config" config[:password] = "password" klass.expects(:new).with do |hash| hash[:auth_methods].nil? end make_connection end it "does not set :keys_only if :ssh_key is set in state but password is set" do state[:ssh_key] = "ssh_key_from_config" config[:ssh_key] = false config[:password] = "password" klass.expects(:new).with do |hash| hash[:keys_only].nil? end make_connection end it "does not set :keys to an array if :ssh_key is set in config but password is set" do config[:kitchen_root] = "/r" config[:ssh_key] = "ssh_key_from_config" config[:password] = "password" klass.expects(:new).with do |hash| hash[:keys].nil? end make_connection end it "does not set :keys to an array if :ssh_key is set in state but password is set" do state[:ssh_key] = "ssh_key_from_state" config[:ssh_key] = "ssh_key_from_config" config[:password] = "password" klass.expects(:new).with do |hash| hash[:keys].nil? end make_connection end it "sets :auth_methods to only publickey if :ssh_key_only is set in config" do config[:ssh_key_only] = true klass.expects(:new).with do |hash| hash[:auth_methods] == ["publickey"] end make_connection end it "passes in :password if set in config" do config[:password] = "password_from_config" klass.expects(:new).with do |hash| hash[:password] == "password_from_config" end make_connection end it "passes in :password from state over config data" do state[:password] = "password_from_state" config[:password] = "password_from_config" klass.expects(:new).with do |hash| hash[:password] == "password_from_state" end make_connection end it "passes in :forward_agent if set in config" do config[:forward_agent] = "forward_agent_from_config" klass.expects(:new).with do |hash| hash[:forward_agent] == "forward_agent_from_config" end make_connection end it "passes in :forward_agent from state over config data" do state[:forward_agent] = "forward_agent_from_state" config[:forward_agent] = "forward_agent_from_config" klass.expects(:new).with do |hash| hash[:forward_agent] == "forward_agent_from_state" end make_connection end it "returns the same connection when called again with same state" do first_connection = make_connection(state) second_connection = make_connection(state) first_connection.object_id.must_equal second_connection.object_id end it "logs a debug message when the connection is reused" do make_connection(state) make_connection(state) logged_output.string.lines.count do |l| l =~ debug_line_with("[SSH] reusing existing connection ") end.must_equal 1 end it "returns a new connection when called again if state differs" do first_connection = make_connection(state) second_connection = make_connection(state.merge(port: 9000)) first_connection.object_id.wont_equal second_connection.object_id end it "closes first connection when a second is created" do first_connection = make_connection(state) first_connection.expects(:close) make_connection(state.merge(port: 9000)) end it "logs a debug message a second connection is created" do make_connection(state) make_connection(state.merge(port: 9000)) logged_output.string.lines.count do |l| l =~ debug_line_with("[SSH] shutting previous connection ") end.must_equal 1 end end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize describe "called without a block" do def make_connection(s = state) transport.connection(s) end common_connection_specs end describe "called with a block" do def make_connection(s = state) transport.connection(s) do |conn| conn end end common_connection_specs end end def debug_line_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end end describe Kitchen::Transport::Ssh::Connection do include Net::SSH::Test # sadly, Net:SSH::Test includes a #connection method so we'll alias this one # before redefining it alias_method :net_ssh_connection, :connection let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:conn) { Net::SSH::Test::Extensions::IO.with_test_extension { net_ssh_connection } } let(:options) do { logger: logger, username: "me", hostname: "foo", port: 22, max_ssh_sessions: 9, } end let(:connection) do Kitchen::Transport::Ssh::Connection.new(options) end before do logger.level = Logger::DEBUG Net::SSH.stubs(:start).returns(conn) end describe "establishing a connection" do [ Errno::EACCES, Errno::EALREADY, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EPIPE, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout, Timeout::Error ].each do |klass| describe "raising #{klass}" do before do Net::SSH.stubs(:start).raises(klass) options[:connection_retries] = 3 options[:connection_retry_sleep] = 7 connection.stubs(:sleep) end it "raises an SshFailed exception" do e = proc do connection.execute("nope") end.must_raise Kitchen::Transport::SshFailed e.message.must_match regexify("SSH session could not be established") end it "attempts to connect :connection_retries times" do begin connection.execute("nope") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.lines.count do |l| l =~ debug_line("[SSH] opening connection to me@foo<{:port=>22}>") end.must_equal 3 end it "sleeps for :connection_retry_sleep seconds between retries" do connection.unstub(:sleep) connection.expects(:sleep).with(7).twice begin connection.execute("nope") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end end it "logs the first 2 retry failures on info" do begin connection.execute("nope") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.lines.count do |l| l =~ info_line_with( "[SSH] connection failed, retrying in 7 seconds") end.must_equal 2 end it "logs the last retry failures on warn" do begin connection.execute("nope") rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.lines.count do |l| l =~ warn_line_with("[SSH] connection failed, terminating ") end.must_equal 1 end end end end describe "#close" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("ok\n") channel.gets_exit_status(0) channel.gets_close channel.sends_close end end it "logger displays closing connection on debug" do conn.expects(:close) assert_scripted do connection.execute("doit") connection.close end logged_output.string.must_match debug_line( "[SSH] closing connection to me@foo<{:port=>22}>" ) end it "only closes the connection once for multiple calls" do conn.expects(:close).once assert_scripted do connection.execute("doit") connection.close connection.close connection.close end end end describe "#execute" do describe "for a successful command" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("ok\n") channel.gets_extended_data("some stderr stuffs\n") channel.gets_exit_status(0) channel.gets_close channel.sends_close end end it "logger displays command on debug" do assert_scripted { connection.execute("doit") } logged_output.string.must_match debug_line( "[SSH] me@foo<{:port=>22}> (doit)" ) end it "logger displays establishing connection on debug" do assert_scripted { connection.execute("doit") } logged_output.string.must_match debug_line( "[SSH] opening connection to me@foo<{:port=>22}>" ) end it "logger captures stdout" do assert_scripted { connection.execute("doit") } logged_output.string.must_match(/^ok$/) end it "logger captures stderr" do assert_scripted { connection.execute("doit") } logged_output.string.must_match(/^some stderr stuffs$/) end end describe "for a failed command" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("nope\n") channel.gets_extended_data("youdead\n") channel.gets_exit_status(42) channel.gets_close channel.sends_close end end it "logger displays command on debug" do begin assert_scripted { connection.execute("doit") } rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match debug_line( "[SSH] me@foo<{:port=>22}> (doit)" ) end it "logger displays establishing connection on debug" do begin assert_scripted { connection.execute("doit") } rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match debug_line( "[SSH] opening connection to me@foo<{:port=>22}>" ) end it "logger captures stdout" do begin assert_scripted { connection.execute("doit") } rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match(/^nope$/) end it "logger captures stderr" do begin assert_scripted { connection.execute("doit") } rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.must_match(/^youdead$/) end it "raises an SshFailed exception" do err = proc do assert_scripted { connection.execute("doit") } end.must_raise Kitchen::Transport::SshFailed err.message.must_equal "SSH exited (42) for command: [doit]" end it "returns the exit code with an SshFailed exception" do begin assert_scripted { connection.execute("doit") } rescue Kitchen::Transport::SshFailed => e e.exit_code.must_equal 42 end end end describe "for an interrupted command" do let(:conn) { mock("session") } before do Net::SSH.stubs(:start).returns(conn) end it "raises SshFailed when an SSH exception is raised" do conn.stubs(:open_channel).raises(Net::SSH::Exception) e = proc do connection.execute("nope") end.must_raise Kitchen::Transport::SshFailed e.message.must_match regexify("SSH command failed") end end describe "for a nil command" do it "does not log on debug" do connection.execute(nil) logged_output.string.must_equal "" end end end describe "#login_command" do let(:login_command) { connection.login_command } let(:args) { login_command.arguments.join(" ") } it "returns a LoginCommand" do login_command.must_be_instance_of Kitchen::LoginCommand end it "is an SSH command" do login_command.command.must_equal "ssh" args.must_match %r{ me@foo$} end it "sets the UserKnownHostsFile option" do args.must_match regexify("-o UserKnownHostsFile=/dev/null ") end it "sets the StrictHostKeyChecking option" do args.must_match regexify(" -o StrictHostKeyChecking=no ") end it "won't set IdentitiesOnly option by default" do args.wont_match regexify(" -o IdentitiesOnly=") end it "sets the IdentiesOnly option if :keys option is given" do options[:keys] = ["yep"] args.must_match regexify(" -o IdentitiesOnly=yes ") end it "sets the LogLevel option to VERBOSE if logger is set to debug" do logger.level = ::Logger::DEBUG options[:logger] = logger args.must_match regexify(" -o LogLevel=VERBOSE ") end it "sets the LogLevel option to ERROR if logger is not set to debug" do logger.level = ::Logger::INFO options[:logger] = logger args.must_match regexify(" -o LogLevel=ERROR ") end it "won't set the ForwardAgent option by default" do args.wont_match regexify(" -o ForwardAgent=") end it "sets the ForwardAgent option to yes if truthy" do options[:forward_agent] = "yep" args.must_match regexify(" -o ForwardAgent=yes") end it "sets the ForwardAgent option to no if falsey" do options[:forward_agent] = false args.must_match regexify(" -o ForwardAgent=no") end it "won't add any SSH keys by default" do args.wont_match regexify(" -i ") end it "sets SSH keys options if given" do options[:keys] = %w{one two} args.must_match regexify(" -i one ") args.must_match regexify(" -i two ") end it "sets the port option to 22 by default" do args.must_match regexify(" -p 22 ") end it "sets the port option" do options[:port] = 1234 args.must_match regexify(" -p 1234 ") end end describe "#upload" do describe "for a file" do let(:content) { "a" * 1234 } let(:src) do file = Tempfile.new("file") file.write("a" * 1234) file.close FileUtils.chmod(0755, file.path) file end before do expect_scp_session("-t /tmp/remote") do |channel| file_mode = running_tests_on_windows? ? 0644 : 0755 channel.gets_data("\0") channel.sends_data("C#{padded_octal_string(file_mode)} 1234 #{File.basename(src.path)}\n") channel.gets_data("\0") channel.sends_data("a" * 1234) channel.sends_data("\0") channel.gets_data("\0") end end after do src.unlink end it "uploads a file to remote over scp" do assert_scripted do connection.upload(src.path, "/tmp/remote") end end end describe "for a path" do before do @dir = Dir.mktmpdir("local") # Since File.chmod is a NOOP on Windows @tmp_dir_mode = running_tests_on_windows? ? 0755 : 0700 @alpha_file_mode = running_tests_on_windows? ? 0644 : 0644 @beta_file_mode = running_tests_on_windows? ? 0444 : 0555 FileUtils.chmod(0700, @dir) File.open("#{@dir}/alpha", "wb") { |f| f.write("alpha-contents\n") } FileUtils.chmod(0644, "#{@dir}/alpha") FileUtils.mkdir_p("#{@dir}/subdir") FileUtils.chmod(0755, "#{@dir}/subdir") File.open("#{@dir}/subdir/beta", "wb") { |f| f.write("beta-contents\n") } FileUtils.chmod(0555, "#{@dir}/subdir/beta") File.open("#{@dir}/zulu", "wb") { |f| f.write("zulu-contents\n") } FileUtils.chmod(0444, "#{@dir}/zulu") expect_scp_session("-t -r /tmp/remote") do |channel| channel.gets_data("\0") channel.sends_data("D#{padded_octal_string(@tmp_dir_mode)} 0 #{File.basename(@dir)}\n") channel.gets_data("\0") channel.sends_data("C#{padded_octal_string(@alpha_file_mode)} 15 alpha\n") channel.gets_data("\0") channel.sends_data("alpha-contents\n") channel.sends_data("\0") channel.gets_data("\0") channel.sends_data("D0755 0 subdir\n") channel.gets_data("\0") channel.sends_data("C#{padded_octal_string(@beta_file_mode)} 14 beta\n") channel.gets_data("\0") channel.sends_data("beta-contents\n") channel.sends_data("\0") channel.gets_data("\0") channel.sends_data("E\n") channel.gets_data("\0") channel.sends_data("C0444 14 zulu\n") channel.gets_data("\0") channel.sends_data("zulu-contents\n") channel.sends_data("\0") channel.gets_data("\0") channel.sends_data("E\n") channel.gets_data("\0") end end after do FileUtils.remove_entry_secure(@dir) end it "uploads a file to remote over scp" do with_sorted_dir_entries do assert_scripted { connection.upload(@dir, "/tmp/remote") } end end end describe "for a failed upload" do let(:conn) { mock("session") } before do Net::SSH.stubs(:start).returns(conn) end it "raises SshFailed when an SSH exception is raised" do conn.stubs(:scp).raises(Net::SSH::Exception) e = proc do connection.upload("nope", "fail") end.must_raise Kitchen::Transport::SshFailed e.message.must_match regexify("SCP upload failed") end end end describe "#download" do let(:conn) { mock("session") } let(:scp) { mock("scp") } before do Net::SSH.stubs(:start).returns(conn) conn.stubs(:scp).returns(scp) @local_parent = Dir.mktmpdir("dir") @local = File.join(@local_parent, "local") end after do FileUtils.remove_entry_secure(@local_parent) end describe "for a file" do it "downloads a file from a remote over scp" do FileUtils.expects(:mkdir_p).with(@local_parent) scp.expects(:download!).with("/remote", @local) connection.download("/remote", @local) end end describe "for a list of files" do it "downloads the files from a remote over scp" do FileUtils.expects(:mkdir_p).with(@local_parent) scp.expects(:download!).with("/remote-1", @local) scp.expects(:download!).with("/remote-2", @local) connection.download(["/remote-1", "/remote-2"], @local) end end describe "for a directory" do it "downloads the directory from a remote over scp" do FileUtils.expects(:mkdir_p).with(@local_parent) scp.expects(:download!).with("/remote-dir", @local).raises(Net::SCP::Error) scp.expects(:download!).with("/remote-dir", @local, recursive: true) connection.download("/remote-dir", @local) end end describe "for a file that does not exist" do it "logs a warning" do FileUtils.expects(:mkdir_p).with(@local_parent) scp.expects(:download!).with("/remote", @local).raises(Net::SCP::Error) scp.expects(:download!).with("/remote", @local, recursive: true) .raises(Net::SCP::Error) connection.download("/remote", @local) logged_output.string.lines.count do |l| l =~ warn_line_with( "SCP download failed for file or directory '/remote', perhaps it does not exist?" ) end.must_equal 1 end end describe "for a failed download" do it "raises SshFailed when an SSH exception is raised" do conn.stubs(:scp).raises(Net::SSH::Exception) e = proc do connection.download("nope", "fail") end.must_raise Kitchen::Transport::SshFailed e.message.must_match regexify("SCP download failed") end end end describe "#wait_until_ready" do before do options[:max_wait_until_ready] = 300 connection.stubs(:sleep) end describe "when failing to connect" do before do Net::SSH.stubs(:start).raises(Errno::ECONNREFUSED) end it "attempts to connect :max_wait_until_ready / 3 times if failing" do begin connection.wait_until_ready rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end logged_output.string.lines.count do |l| l =~ info_line_with( "Waiting for SSH service on foo:22, retrying in 3 seconds") end.must_equal((300 / 3) - 1) logged_output.string.lines.count do |l| l =~ debug_line_with("[SSH] connection failed ") end.must_equal((300 / 3) - 1) logged_output.string.lines.count do |l| l =~ warn_line_with("[SSH] connection failed, terminating ") end.must_equal 1 end it "sleeps for 3 seconds between retries" do connection.unstub(:sleep) connection.expects(:sleep).with(3).times((300 / 3) - 1) begin connection.wait_until_ready rescue # rubocop:disable Lint/HandleExceptions # the raise is not what is being tested here, rather its side-effect end end end describe "when connection is successful" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("echo '[SSH] Established'") channel.gets_data("[SSH] Established\n") channel.gets_exit_status(0) channel.gets_close channel.sends_close end end it "executes an ping command string to ensure working" do assert_scripted { connection.wait_until_ready } end it "logger captures stdout" do assert_scripted { connection.wait_until_ready } logged_output.string.must_match(/^\[SSH\] Established$/) end end end def expect_scp_session(args) story do |script| channel = script.opens_channel channel.sends_exec("scp #{args}") yield channel if block_given? channel.sends_eof channel.gets_exit_status(0) channel.gets_eof channel.gets_close channel.sends_close end end def debug_line(msg) /^D, .* : #{Regexp.escape(msg)}$/ end def debug_line_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end def info_line_with(msg) /^I, .* : #{Regexp.escape(msg)}/ end def regexify(string) Regexp.new(Regexp.escape(string)) end def warn_line_with(msg) /^W, .* : #{Regexp.escape(msg)}/ end end test-kitchen-1.23.2/spec/kitchen/transport/exec_spec.rb0000644000004100000410000000432213377651062023142 0ustar www-datawww-data# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require_relative "../../spec_helper" require "kitchen/transport/exec" describe Kitchen::Transport::Ssh do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:state) { Hash.new } let(:instance) do stub(name: "coolbeans", logger: logger, to_str: "instance") end let(:transport) do Kitchen::Transport::Exec.new(config).finalize_config!(instance) end it "provisioner api_version is 1" do transport.diagnose_plugin[:api_version].must_equal 1 end it "plugin_version is set to Kitchen::VERSION" do transport.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "#connection" do it "returns a Kitchen::Transport::Exec::Connection object" do transport.connection(state).must_be_kind_of Kitchen::Transport::Exec::Connection end end end describe Kitchen::Transport::Exec::Connection do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:options) do { logger: logger } end let(:connection) do Kitchen::Transport::Exec::Connection.new(options) end describe "#execute" do it "runs the command" do connection.expects(:run_command).with("do the thing") connection.execute("do the thing") end it "ignores nil" do connection.expects(:run_command).never connection.execute(nil) end end describe "#upload" do it "copies files" do FileUtils.expects(:mkdir_p).with("/tmp/kitchen") FileUtils.expects(:cp_r).with("/tmp/sandbox/cookbooks", "/tmp/kitchen") connection.upload(%w{/tmp/sandbox/cookbooks}, "/tmp/kitchen") end end end test-kitchen-1.23.2/spec/kitchen/provisioner_spec.rb0000644000004100000410000000572113377651062022545 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/configurable" require "kitchen/errors" require "kitchen/logging" require "kitchen/provisioner" require "kitchen/provisioner/base" module Kitchen module Provisioner class Coolbeans < Kitchen::Provisioner::Base end class ItDepends < Kitchen::Provisioner::Base attr_reader :verify_call_count def initialize(config = {}) @verify_call_count = 0 super end def verify_dependencies @verify_call_count += 1 end end class UnstableDepends < Kitchen::Provisioner::Base def verify_dependencies raise UserError, "Oh noes, you don't have software!" end end end end describe Kitchen::Provisioner do describe ".for_plugin" do before do Kitchen::Provisioner.stubs(:require).returns(true) end it "returns a provisioner object of the correct class" do provisioner = Kitchen::Provisioner.for_plugin("coolbeans", {}) provisioner.must_be_kind_of Kitchen::Provisioner::Coolbeans end it "returns a provisioner initialized with its config" do provisioner = Kitchen::Provisioner.for_plugin("coolbeans", foo: "bar") provisioner[:foo].must_equal "bar" end it "calls #verify_dependencies on the provisioner object" do provisioner = Kitchen::Provisioner.for_plugin("it_depends", {}) provisioner.verify_call_count.must_equal 1 end it "calls #verify_dependencies once per provisioner require" do Kitchen::Provisioner.stubs(:require).returns(true, false) provisioner1 = Kitchen::Provisioner.for_plugin("it_depends", {}) provisioner1.verify_call_count.must_equal 1 provisioner2 = Kitchen::Provisioner.for_plugin("it_depends", {}) provisioner2.verify_call_count.must_equal 0 end it "raises ClientError if the provisioner could not be required" do Kitchen::Provisioner.stubs(:require).raises(LoadError) proc { Kitchen::Provisioner.for_plugin("coolbeans", {}) } .must_raise Kitchen::ClientError end it "raises ClientError if the provisioner's class constant was not found" do # pretend require worked Kitchen::Provisioner.stubs(:require).returns(true) proc { Kitchen::Provisioner.for_plugin("nope", {}) } .must_raise Kitchen::ClientError end end end test-kitchen-1.23.2/spec/kitchen/collection_spec.rb0000644000004100000410000000416713377651062022324 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "ostruct" require "kitchen/collection" describe Kitchen::Collection do let(:collection) do Kitchen::Collection.new([ obj("one"), obj("two", "a"), obj("two", "b"), obj("three") ]) end it "transparently wraps an Array" do collection.must_be_instance_of Array end describe "#get" do it "returns a single object by its name" do collection.get("three").must_equal obj("three") end it "returns the first occurance of an object by its name" do collection.get("two").must_equal obj("two", "a") end it "returns nil if an object cannot be found by its name" do collection.get("nope").must_be_nil end end describe "#get_all" do it "returns a Collection of objects whose name matches the regex" do result = collection.get_all(/(one|three)/) result.size.must_equal 2 result[0].must_equal obj("one") result[1].must_equal obj("three") result.get_all(/one/).size.must_equal 1 end it "returns an empty Collection if no matches are found" do result = collection.get_all(/noppa/) result.must_equal [] result.get("nahuh").must_be_nil end end describe "#as_name" do it "returns an Array of names as strings" do collection.as_names.must_equal %w{one two two three} end end private def obj(name, extra = nil) OpenStruct.new(name: name, extra: extra) end end test-kitchen-1.23.2/spec/kitchen/shell_out_spec.rb0000644000004100000410000000771113377651062022165 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/errors" require "kitchen/shell_out" require "kitchen/util" module Kitchen class Shelly include Kitchen::ShellOut attr_reader :logs def debug(msg) @logs ||= [] @logs << msg end def logger "alogger" end end end describe Kitchen::ShellOut do let(:command) do stub( run_command: true, error!: true, stdout: "", execution_time: 123 ) end let(:subject) { Kitchen::Shelly.new } describe "#run_command" do let(:opts) do { live_stream: "alogger", timeout: 60_000 } end before do Mixlib::ShellOut.stubs(:new).returns(command) end it "builds a Mixlib::ShellOut object with default options" do Mixlib::ShellOut.unstub(:new) Mixlib::ShellOut.expects(:new).with("yoyo", opts).returns(command) subject.run_command("yoyo") end [:timeout, :cwd, :environment].each do |attr| it "builds a Mixlib::ShellOut object with a custom #{attr}" do opts[attr] = "custom" Mixlib::ShellOut.unstub(:new) Mixlib::ShellOut.expects(:new).with("yoyo", opts).returns(command) subject.run_command("yoyo", attr => "custom") end end it "returns the command's standard out" do command.stubs(:stdout).returns("sweetness") subject.run_command("icecream").must_equal "sweetness" end it "raises a ShellCommandFailed if the command does not cleanly exit" do command.stubs(:error!) .raises(Mixlib::ShellOut::ShellCommandFailed, "boom bad") err = proc { subject.run_command("boom") } .must_raise Kitchen::ShellOut::ShellCommandFailed err.message.must_equal "boom bad" end it "raises a Kitchen::Errror tagged exception for unknown exceptions" do command.stubs(:error!).raises(IOError, "boom bad") err = proc { subject.run_command("boom") }.must_raise IOError err.must_be_kind_of Kitchen::Error err.message.must_equal "boom bad" end it "prepends with sudo if :use_sudo is truthy" do Mixlib::ShellOut.unstub(:new) Mixlib::ShellOut.expects(:new).with("sudo -E yo", opts).returns(command) subject.run_command("yo", use_sudo: true) end it "prepends with custom :sudo_command if :use_sudo is truthy" do Mixlib::ShellOut.unstub(:new) Mixlib::ShellOut.expects(:new).with("wat yo", opts).returns(command) subject.run_command("yo", use_sudo: true, sudo_command: "wat") end it "logs a debug BEGIN message" do subject.run_command("echo whoopa\ndoopa\ndo") subject.logs.first .must_equal "[local command] BEGIN (echo whoopa\ndoopa\ndo)" end it "logs a debug BEGIN message with custom log subject" do subject.run_command("tenac", log_subject: "thed") subject.logs.first.must_equal "[thed command] BEGIN (tenac)" end it "truncates the debug BEGIN command if it spans multiple lines" do end it "logs a debug END message" do subject.run_command("echo whoopa doopa") subject.logs.last.must_equal "[local command] END (2m3.00s)" end it "logs a debug END message with custom log subject" do subject.run_command("tenac", log_subject: "thed") subject.logs.last.must_equal "[thed command] END (2m3.00s)" end end end test-kitchen-1.23.2/spec/kitchen/color_spec.rb0000644000004100000410000000307213377651062021301 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/color" describe Kitchen::Color do describe ".escape" do it "returns an empty string if name is nil" do Kitchen::Color.escape(nil).must_equal "" end it "returns an empty string if name is not in the ANSI hash" do Kitchen::Color.escape(:puce).must_equal "" end it "returns an ansi escape sequence string for cyan" do Kitchen::Color.escape(:cyan).must_equal "\e[36m" end it "returns an ansi escape sequence string for reset" do Kitchen::Color.escape(:reset).must_equal "\e[0m" end end describe ".colorize" do it "returns an ansi escaped string colored yellow" do Kitchen::Color.colorize("hello", :yellow).must_equal "\e[33mhello\e[0m" end it "returns an unescaped string if color is not in the ANSI hash" do Kitchen::Color.colorize("double", :rainbow).must_equal "double" end end end test-kitchen-1.23.2/spec/kitchen/verifier_spec.rb0000644000004100000410000000615413377651062022002 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/verifier" require "kitchen/configurable" module Kitchen module Verifier class Base include Configurable def initialize(config = {}) init_config(config) end end end end module Kitchen module Verifier class Coolbeans < Kitchen::Verifier::Base end class ItDepends < Kitchen::Verifier::Base attr_reader :verify_call_count def initialize(config = {}) @verify_call_count = 0 super end def verify_dependencies @verify_call_count += 1 end end class UnstableDepends < Kitchen::Verifier::Base def verify_dependencies raise UserError, "Oh noes, you don't have software!" end end end end describe Kitchen::Verifier do describe ".for_plugin" do before do Kitchen::Verifier.stubs(:require).returns(true) end it "returns a verifier object of the correct class" do verifier = Kitchen::Verifier.for_plugin("coolbeans", {}) verifier.must_be_kind_of Kitchen::Verifier::Coolbeans end it "returns a verifier initialized with its config" do verifier = Kitchen::Verifier.for_plugin("coolbeans", foo: "bar") verifier[:foo].must_equal "bar" end it "calls #verify_dependencies on the transport object" do verifier = Kitchen::Verifier.for_plugin("it_depends", {}) verifier.verify_call_count.must_equal 1 end it "calls #verify_dependencies once per verifier require" do Kitchen::Verifier.stubs(:require).returns(true, false) verifier1 = Kitchen::Verifier.for_plugin("it_depends", {}) verifier1.verify_call_count.must_equal 1 verifier2 = Kitchen::Verifier.for_plugin("it_depends", {}) verifier2.verify_call_count.must_equal 0 end it "raises ClientError if the verifier could not be required" do Kitchen::Verifier.stubs(:require).raises(LoadError) proc { Kitchen::Verifier.for_plugin("coolbeans", {}) } .must_raise Kitchen::ClientError end it "raises ClientError if the verifier's class constant was not found" do # pretend require worked Kitchen::Verifier.stubs(:require).returns(true) proc { Kitchen::Verifier.for_plugin("nope", {}) } .must_raise Kitchen::ClientError end it "raises UserError if #verify_dependencies failes" do proc { Kitchen::Verifier.for_plugin("unstable_depends", {}) } .must_raise Kitchen::UserError end end end test-kitchen-1.23.2/spec/kitchen/ssh_spec.rb0000644000004100000410000003733313377651062020767 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/ssh" require "tmpdir" require "net/ssh/test" describe Kitchen::SSH do include Net::SSH::Test let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:opts) { Hash.new } let(:ssh) { Kitchen::SSH.new("foo", "me", opts) } let(:conn) { Net::SSH::Test::Extensions::IO.with_test_extension { connection } } before do logger.level = Logger::DEBUG opts[:logger] = logger Net::SSH.stubs(:start).returns(conn) end describe "establishing a connection" do [ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout ].each do |klass| describe "raising #{klass}" do before do Net::SSH.stubs(:start).raises(klass) opts[:ssh_retries] = 3 ssh.stubs(:sleep) end it "reraises the #{klass} exception" do proc { ssh.exec("nope") }.must_raise klass end it "attempts to connect ':ssh_retries' times" do begin ssh.exec("nope") rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.lines.count do |l| l =~ debug_line("[SSH] opening connection to me@foo:22<{:ssh_retries=>3}>") end.must_equal opts[:ssh_retries] end it "sleeps for 1 second between retries" do ssh.unstub(:sleep) ssh.expects(:sleep).with(1).twice begin ssh.exec("nope") rescue # rubocop:disable Lint/HandleExceptions end end it "logs the first 2 retry failures on info" do begin ssh.exec("nope") rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.lines.count do |l| l =~ info_line_with("[SSH] connection failed, retrying ") end.must_equal 2 end it "logs the last retry failures on warn" do begin ssh.exec("nope") rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.lines.count do |l| l =~ warn_line_with("[SSH] connection failed, terminating ") end.must_equal 1 end end end end describe "#exec" do describe "for a successful command" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("ok\n") channel.gets_extended_data("some stderr stuffs\n") channel.gets_exit_status(0) channel.gets_close channel.sends_close end end it "logger displays command on debug" do assert_scripted { ssh.exec("doit") } logged_output.string.must_match debug_line( "[SSH] me@foo:22<{}> (doit)" ) end it "logger displays establishing connection on debug" do assert_scripted { ssh.exec("doit") } logged_output.string.must_match debug_line( "[SSH] opening connection to me@foo:22<{}>" ) end it "logger captures stdout" do assert_scripted { ssh.exec("doit") } logged_output.string.must_match(/^ok$/) end it "logger captures stderr" do assert_scripted { ssh.exec("doit") } logged_output.string.must_match(/^some stderr stuffs$/) end end describe "for a failed command" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("nope\n") channel.gets_extended_data("youdead\n") channel.gets_exit_status(42) channel.gets_close channel.sends_close end end it "logger displays command on debug" do begin assert_scripted { ssh.exec("doit") } rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.must_match debug_line( "[SSH] me@foo:22<{}> (doit)" ) end it "logger displays establishing connection on debug" do begin assert_scripted { ssh.exec("doit") } rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.must_match debug_line( "[SSH] opening connection to me@foo:22<{}>" ) end it "logger captures stdout" do begin assert_scripted { ssh.exec("doit") } rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.must_match(/^nope$/) end it "logger captures stderr" do begin assert_scripted { ssh.exec("doit") } rescue # rubocop:disable Lint/HandleExceptions end logged_output.string.must_match(/^youdead$/) end it "raises an SSHFailed exception" do err = proc { assert_scripted { ssh.exec("doit") } }.must_raise Kitchen::SSHFailed err.message.must_equal "SSH exited (42) for command: [doit]" end end end describe "#upload!" do let(:content) { "a" * 1234 } let(:src) do file = Tempfile.new("file") file.write("a" * 1234) file.close FileUtils.chmod(0755, file.path) file end before do expect_scp_session("-t /tmp/remote") do |channel| file_mode = running_tests_on_windows? ? 0644 : 0755 channel.gets_data("\0") channel.sends_data("C#{padded_octal_string(file_mode)} 1234 #{File.basename(src.path)}\n") channel.gets_data("\0") channel.sends_data("a" * 1234) channel.sends_data("\0") channel.gets_data("\0") end end after do src.unlink end it "uploads a file to remote over scp" do assert_scripted do ssh.upload!(src.path, "/tmp/remote") end end it "logs upload progress to debug" do assert_scripted do ssh.upload!(src.path, "/tmp/remote") end logged_output.string.must_match debug_line( "[SSH] opening connection to me@foo:22<{}>" ) logged_output.string.must_match debug_line( "Uploaded #{src.path} (1234 bytes)" ) end end describe "#upload_path!" do before do @dir = Dir.mktmpdir("local") # Since File.chmod is a NOOP on Windows @tmp_dir_mode = running_tests_on_windows? ? 0755 : 0700 @alpha_file_mode = running_tests_on_windows? ? 0644 : 0644 @beta_file_mode = running_tests_on_windows? ? 0444 : 0555 FileUtils.chmod(0700, @dir) File.open("#{@dir}/alpha", "wb") { |f| f.write("alpha-contents\n") } FileUtils.chmod(0644, "#{@dir}/alpha") FileUtils.mkdir_p("#{@dir}/subdir") FileUtils.chmod(0755, "#{@dir}/subdir") File.open("#{@dir}/subdir/beta", "wb") { |f| f.write("beta-contents\n") } FileUtils.chmod(0555, "#{@dir}/subdir/beta") File.open("#{@dir}/zulu", "wb") { |f| f.write("zulu-contents\n") } FileUtils.chmod(0444, "#{@dir}/zulu") expect_scp_session("-t -r /tmp/remote") do |channel| channel.gets_data("\0") channel.sends_data("D#{padded_octal_string(@tmp_dir_mode)} 0 #{File.basename(@dir)}\n") channel.gets_data("\0") channel.sends_data("C#{padded_octal_string(@alpha_file_mode)} 15 alpha\n") channel.gets_data("\0") channel.sends_data("alpha-contents\n") channel.sends_data("\0") channel.gets_data("\0") channel.sends_data("D0755 0 subdir\n") channel.gets_data("\0") channel.sends_data("C#{padded_octal_string(@beta_file_mode)} 14 beta\n") channel.gets_data("\0") channel.sends_data("beta-contents\n") channel.sends_data("\0") channel.gets_data("\0") channel.sends_data("E\n") channel.gets_data("\0") channel.sends_data("C0444 14 zulu\n") channel.gets_data("\0") channel.sends_data("zulu-contents\n") channel.sends_data("\0") channel.gets_data("\0") channel.sends_data("E\n") channel.gets_data("\0") end end after do FileUtils.remove_entry_secure(@dir) end it "uploads a file to remote over scp" do with_sorted_dir_entries do assert_scripted { ssh.upload_path!(@dir, "/tmp/remote") } end end it "logs upload progress to debug" do remote_base = "#{Dir.tmpdir}/#{File.basename(@dir)}" with_sorted_dir_entries do assert_scripted { ssh.upload_path!(@dir, "/tmp/remote") } end logged_output.string.must_match debug_line( "[SSH] opening connection to me@foo:22<{}>" ) logged_output.string.must_match debug_line( "Uploaded #{remote_base}/alpha (15 bytes)" ) logged_output.string.must_match debug_line( "Uploaded #{remote_base}/subdir/beta (14 bytes)" ) logged_output.string.must_match debug_line( "Uploaded #{remote_base}/zulu (14 bytes)" ) end end describe "#shutdown" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("ok\n") channel.gets_exit_status(0) channel.gets_close channel.sends_close end end it "logger displays closing connection on debug" do conn.expects(:shutdown!) assert_scripted do ssh.exec("doit") ssh.shutdown end logged_output.string.must_match debug_line( "[SSH] closing connection to me@foo:22<{}>" ) end it "only closes the connection once for multiple calls" do conn.expects(:shutdown!).once assert_scripted do ssh.exec("doit") ssh.shutdown ssh.shutdown ssh.shutdown end end end describe "block form" do before do story do |script| channel = script.opens_channel channel.sends_request_pty channel.sends_exec("doit") channel.gets_data("ok\n") channel.gets_exit_status(0) channel.gets_close channel.sends_close end end it "shuts down the connection when block closes" do conn.expects(:shutdown!) Net::SSH::Test::Extensions::IO.with_test_extension do Kitchen::SSH.new("foo", "me", opts) do |ssh| ssh.exec("doit") end end end end describe "#login_command" do let(:login_command) { ssh.login_command } let(:args) { login_command.arguments.join(" ") } it "returns a LoginCommand" do login_command.must_be_instance_of Kitchen::LoginCommand end it "is an SSH command" do login_command.command.must_equal "ssh" args.must_match %r{ me@foo$} end it "sets the UserKnownHostsFile option" do args.must_match regexify("-o UserKnownHostsFile=/dev/null ") end it "sets the StrictHostKeyChecking option" do args.must_match regexify(" -o StrictHostKeyChecking=no ") end it "won't set IdentitiesOnly option by default" do args.wont_match regexify(" -o IdentitiesOnly=") end it "sets the IdentiesOnly option if :keys option is given" do opts[:keys] = ["yep"] args.must_match regexify(" -o IdentitiesOnly=yes ") end it "sets the LogLevel option to VERBOSE if logger is set to debug" do logger.level = ::Logger::DEBUG opts[:logger] = logger args.must_match regexify(" -o LogLevel=VERBOSE ") end it "sets the LogLevel option to ERROR if logger is not set to debug" do logger.level = ::Logger::INFO opts[:logger] = logger args.must_match regexify(" -o LogLevel=ERROR ") end it "won't set the ForwardAgent option by default" do args.wont_match regexify(" -o ForwardAgent=") end it "sets the ForwardAgent option to yes if truthy" do opts[:forward_agent] = "yep" args.must_match regexify(" -o ForwardAgent=yes") end it "sets the ForwardAgent option to no if falsey" do opts[:forward_agent] = false args.must_match regexify(" -o ForwardAgent=no") end it "won't add any SSH keys by default" do args.wont_match regexify(" -i ") end it "sets SSH keys options if given" do opts[:keys] = %w{one two} args.must_match regexify(" -i one ") args.must_match regexify(" -i two ") end it "sets the port option to 22 by default" do args.must_match regexify(" -p 22 ") end it "sets the port option" do opts[:port] = 1234 args.must_match regexify(" -p 1234 ") end end describe "#test_ssh" do let(:tcp_socket) { stub(select_for_read?: true, close: true) } before { ssh.stubs(:sleep) } it "returns a truthy value" do TCPSocket.stubs(:new).returns(tcp_socket) Net::SSH::Test::Extensions::IO.with_test_extension do result = ssh.send(:test_ssh) result.wont_equal nil result.wont_equal false end end it "closes socket when finished" do TCPSocket.stubs(:new).returns(tcp_socket) tcp_socket.expects(:close) Net::SSH::Test::Extensions::IO.with_test_extension { ssh.send(:test_ssh) } end [ SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError ].each do |klass| describe "when #{klass} is raised" do before { TCPSocket.stubs(:new).raises(klass) } it "returns false" do ssh.send(:test_ssh).must_equal false end it "sleeps for 2 seconds" do ssh.expects(:sleep).with(2) ssh.send(:test_ssh) end end end [ Errno::EPERM, Errno::ETIMEDOUT ].each do |klass| describe "when #{klass} is raised" do it "returns false when #{klass} is raised" do TCPSocket.stubs(:new).raises(klass) ssh.send(:test_ssh).must_equal false end end end end describe "#wait" do let(:not_ready) do stub(select_for_read?: false, idle!: true, close: true) end let(:ready) do stub(select_for_read?: true, close: true) end it "logs to info for each retry" do TCPSocket.stubs(:new).returns(not_ready, not_ready, ready) Net::SSH::Test::Extensions::IO.with_test_extension { ssh.wait } logged_output.string.lines.count do |l| l =~ info_line_with("Waiting for foo:22...") end.must_equal 2 end end def expect_scp_session(args) story do |script| channel = script.opens_channel channel.sends_exec("scp #{args}") yield channel if block_given? channel.sends_eof channel.gets_exit_status(0) channel.gets_eof channel.gets_close channel.sends_close end end def regexify(string) Regexp.new(Regexp.escape(string)) end def debug_line(msg) /^D, .* : #{Regexp.escape(msg)}$/ end def debug_line_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end def info_line_with(msg) /^I, .* : #{Regexp.escape(msg)}/ end def warn_line_with(msg) /^W, .* : #{Regexp.escape(msg)}/ end end test-kitchen-1.23.2/spec/kitchen/data_munger_spec.rb0000644000004100000410000023114213377651062022452 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/data_munger" module Kitchen # rubocop:disable Metrics/ModuleLength describe DataMunger do describe "#platform_data" do it "returns an array of platform data" do DataMunger.new( platforms: [ { name: "one", stuff: "junk", }, { name: "two", misc: "things", }, ] ).platform_data.must_equal([ { name: "one", stuff: "junk", }, { name: "two", misc: "things", }, ]) end it "returns an empty array if platforms is not defined" do DataMunger.new({}).platform_data.must_equal([]) end end describe "#suite_data" do it "returns an array of suite data" do DataMunger.new( suites: [ { name: "one", stuff: "junk", }, { name: "two", misc: "things", }, ] ).suite_data.must_equal([ { name: "one", stuff: "junk", }, { name: "two", misc: "things", }, ]) end it "returns an empty array if suites is not defined" do DataMunger.new({}).suite_data.must_equal([]) end end DATA_KEYS = { driver: :name, provisioner: :name, transport: :name, verifier: :name, }.freeze DATA_KEYS.each_pair do |key, default_key| describe "##{key}" do describe "from single source" do it "returns empty hash if no common #{key} hash is provided" do DataMunger.new( {}, {} ).public_send("#{key}_data_for", "suite", "platform").must_equal({}) end it "drops common #{key} if hash is nil" do DataMunger.new( { key => nil, }, {} ).public_send("#{key}_data_for", "suite", "plat").must_equal({}) end it "returns kitchen config #{key} name" do DataMunger.new( {}, defaults: { key => "thenoseknows", } ).public_send("#{key}_data_for", "suite", "platform").must_equal( default_key => "thenoseknows" ) end it "returns kitchen config #{key} name from callable" do DataMunger.new( {}, defaults: { key => ->(suite, platform) { "#{suite}++#{platform}" }, } ).public_send("#{key}_data_for", "suite", "platform").must_equal( default_key => "suite++platform" ) end it "returns common #{key} name" do DataMunger.new( { key => "starship", }, {} ).public_send("#{key}_data_for", "suite", "platform").must_equal( default_key => "starship" ) end it "returns common #{key} config" do DataMunger.new( { key => { default_key => "starship", :speed => 42, }, }, {} ).public_send("#{key}_data_for", "suite", "platform").must_equal( default_key => "starship", :speed => 42 ) end it "returns empty hash if platform config doesn't have #{key} hash" do DataMunger.new( { platforms: [ { name: "plat" }, ], }, {} ).public_send("#{key}_data_for", "suite", "plat").must_equal({}) end it "drops platform #{key} if hash is nil" do DataMunger.new( { platforms: [ { :name => "plat", key => nil, }, ], }, {} ).public_send("#{key}_data_for", "suite", "plat").must_equal({}) end it "returns platform #{key} name" do DataMunger.new( { platforms: [ { :name => "plat", key => "flip", }, ], }, {} ).public_send("#{key}_data_for", "suite", "plat").must_equal( default_key => "flip" ) end it "returns platform config containing #{key} hash" do DataMunger.new( { platforms: [ { :name => "plat", key => { default_key => "flip", :flop => "yep", }, }, ], }, {} ).public_send("#{key}_data_for", "suite", "plat").must_equal( default_key => "flip", :flop => "yep" ) end it "returns empty hash if suite config doesn't have #{key} hash" do DataMunger.new( { suites: [ { name: "sweet" }, ], }, {} ).public_send("#{key}_data_for", "sweet", "platform").must_equal({}) end it "drops suite #{key} hash is nil" do DataMunger.new( { suites: [ { :name => "suite", key => nil, }, ], }, {} ).public_send("#{key}_data_for", "suite", "plat").must_equal({}) end it "returns suite #{key} name" do DataMunger.new( { suites: [ { :name => "sweet", key => "waz", }, ], }, {} ).public_send("#{key}_data_for", "sweet", "platform").must_equal( default_key => "waz" ) end it "returns suite config containing #{key} hash" do DataMunger.new( { suites: [ { :name => "sweet", key => { default_key => "waz", :up => "nope", }, }, ], }, {} ).public_send("#{key}_data_for", "sweet", "platform").must_equal( default_key => "waz", :up => "nope" ) end end describe "from multiple sources merging" do it "suite into platform into common" do DataMunger.new( { key => { default_key => "commony", :color => "purple", :fruit => %w{apple pear}, :deep => { common: "junk" }, }, :platforms => [ { :name => "plat", key => { default_key => "platformy", :fruit => ["banana"], :deep => { platform: "stuff" }, }, }, ], :suites => [ { :name => "sweet", key => { default_key => "suitey", :vehicle => "car", :deep => { suite: "things" }, }, }, ], }, {} ).public_send("#{key}_data_for", "sweet", "plat").must_equal( default_key => "suitey", :color => "purple", :fruit => ["banana"], :vehicle => "car", :deep => { common: "junk", platform: "stuff", suite: "things", } ) end it "platform into common" do DataMunger.new( { key => { default_key => "commony", :color => "purple", :fruit => %w{apple pear}, :deep => { common: "junk" }, }, :platforms => [ { :name => "plat", key => { default_key => "platformy", :fruit => ["banana"], :deep => { platform: "stuff" }, }, }, ], }, {} ).public_send("#{key}_data_for", "sweet", "plat").must_equal( default_key => "platformy", :color => "purple", :fruit => ["banana"], :deep => { common: "junk", platform: "stuff", } ) end it "suite into common" do DataMunger.new( { key => { default_key => "commony", :color => "purple", :fruit => %w{apple pear}, :deep => { common: "junk" }, }, :suites => [ { :name => "sweet", key => { default_key => "suitey", :vehicle => "car", :deep => { suite: "things" }, }, }, ], }, {} ).public_send("#{key}_data_for", "sweet", "plat").must_equal( default_key => "suitey", :color => "purple", :fruit => %w{apple pear}, :vehicle => "car", :deep => { common: "junk", suite: "things", } ) end it "suite into platform" do DataMunger.new( { platforms: [ { :name => "plat", key => { default_key => "platformy", :fruit => ["banana"], :deep => { platform: "stuff" }, }, }, ], suites: [ { :name => "sweet", key => { default_key => "suitey", :vehicle => "car", :deep => { suite: "things" }, }, }, ], }, {} ).public_send("#{key}_data_for", "sweet", "plat").must_equal( default_key => "suitey", :fruit => ["banana"], :vehicle => "car", :deep => { platform: "stuff", suite: "things", } ) end end end end describe "primary Chef data" do describe "in a suite" do it "moves attributes into provisioner" do DataMunger.new( { provisioner: "chefy", suites: [ { name: "sweet", attributes: { one: "two" }, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", attributes: { one: "two" } ) end it "moves run_list into provisioner" do DataMunger.new( { provisioner: "chefy", suites: [ { name: "sweet", run_list: %w{one two}, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two} ) end it "moves named_run_list into provisioner" do DataMunger.new( { provisioner: "chefy", suites: [ { name: "sweet", named_run_list: "other_run_list", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", named_run_list: "other_run_list" ) end it "maintains run_list in provisioner" do DataMunger.new( { provisioner: "chefy", suites: [ { name: "sweet", provisioner: { run_list: %w{one two}, }, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two} ) end it "merge provisioner into attributes if provisioner exists" do DataMunger.new( { suites: [ { name: "sweet", attributes: { one: "two" }, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", attributes: { one: "two" } ) end it "merge provisioner into run_list if provisioner exists" do DataMunger.new( { suites: [ { name: "sweet", run_list: %w{one two}, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two} ) end it "merge provisioner into named_run_list if provisioner exists" do DataMunger.new( { suites: [ { name: "sweet", named_run_list: "other_run_list", provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", named_run_list: "other_run_list" ) end it "drops nil run_list" do DataMunger.new( { suites: [ { name: "sweet", run_list: nil, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy" ) end it "drops nil attributes" do DataMunger.new( { suites: [ { name: "sweet", attributes: nil, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy" ) end end describe "in a platform" do it "moves attributes into provisioner" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", attributes: { one: "two" }, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", attributes: { one: "two" } ) end it "moves run_list into provisioner" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", run_list: %w{one two}, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two} ) end it "moves named_run_list into provisioner" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", named_run_list: "other_run_list", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", named_run_list: "other_run_list" ) end it "maintains run_list in provisioner" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", provisioner: { run_list: %w{one two}, }, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two} ) end it "merge provisioner into attributes if provisioner exists" do DataMunger.new( { platforms: [ { name: "plat", attributes: { one: "two" }, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", attributes: { one: "two" } ) end it "merge provisioner into run_list if provisioner exists" do DataMunger.new( { platforms: [ { name: "plat", run_list: %w{one two}, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two} ) end it "merge provisioner into named_run_list if provisioner exists" do DataMunger.new( { platforms: [ { name: "plat", named_run_list: "other_run_list", provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", named_run_list: "other_run_list" ) end it "drops nil run_list" do DataMunger.new( { platforms: [ { name: "plat", run_list: nil, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy" ) end it "drops nil attributes" do DataMunger.new( { platforms: [ { name: "plat", attributes: nil, provisioner: "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy" ) end end describe "in a suite and platform" do it "merges suite attributes into platform attributes" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", attributes: { color: "blue", deep: { platform: "much" }, }, }, ], suites: [ { name: "sweet", attributes: { color: "pink", deep: { suite: "wow" }, }, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", attributes: { color: "pink", deep: { suite: "wow", platform: "much", }, } ) end it "concats suite run_list to platform run_list" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", run_list: %w{one two}, }, ], suites: [ { name: "sweet", run_list: %w{three four}, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two three four} ) end it "concats suite run_list in provisioner to platform run_list" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", run_list: %w{one two}, }, ], suites: [ { name: "sweet", provisioner: { run_list: %w{three four}, }, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two three four} ) end it "concats suite run_list to platform run_list in provisioner" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", provisioner: { run_list: %w{one two}, }, }, ], suites: [ { name: "sweet", run_list: %w{three four}, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: %w{one two three four} ) end it "concats to nil run_lists into an empty Array" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat", provisioner: { run_list: nil, }, }, ], suites: [ { name: "sweet", run_list: nil, }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy", run_list: [] ) end it "does not corrupt run_list data for multiple suite/platform pairs" do munger = DataMunger.new( { provisioner: "chefy", platforms: [ { name: "p1", }, { name: "p2", run_list: %w{one two}, }, ], suites: [ { name: "s1", run_list: %w{alpha beta}, }, { name: "s2", provisioner: { run_list: %w{three four}, }, }, ], }, {} ) # call munger for other data to cause any necessary internal # data mutation munger.provisioner_data_for("s1", "p1") munger.provisioner_data_for("s1", "p2") munger.provisioner_data_for("s2", "p1") munger.provisioner_data_for("s2", "p2").must_equal( name: "chefy", run_list: %w{one two three four} ) end end end describe "kitchen config" do [:kitchen_root, :test_base_path].each do |key| describe "for #{key}" do describe "for #driver_data_for" do it "is returned when provided" do DataMunger.new( { driver: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).driver_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, driver: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, driver: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).driver_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "rejects any value in driver data" do DataMunger.new( { driver: { :name => "chefy", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( name: "chefy" ) end end describe "for #provisioner_data_for" do it "is returned when provided" do DataMunger.new( { provisioner: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).provisioner_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, provisioner: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, provisioner: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).provisioner_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "rejects any value in provisioner data" do DataMunger.new( { provisioner: { :name => "chefy", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy" ) end end describe "for #verifier_data_for" do it "is returned when provided" do DataMunger.new( { verifier: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).verifier_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, verifier: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, verifier: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).verifier_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "rejects any value in verifier data" do DataMunger.new( { verifier: { :version => "chefy", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( version: "chefy" ) end end describe "for #transport_data_for" do it "is returned when provided" do DataMunger.new( { transport: "pipes", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).transport_data_for("sweet", "plat").must_equal( :name => "pipes", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, transport: "pipes", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).transport_data_for("sweet", "plat").must_equal( :name => "pipes", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, transport: "pipes", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).transport_data_for("sweet", "plat").must_equal( :name => "pipes", key => "datvalue" ) end it "rejects any value in transport data" do DataMunger.new( { transport: { :name => "pipes", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).transport_data_for("sweet", "plat").must_equal( name: "pipes" ) end end end end [:log_level].each do |key| describe "for #{key}" do describe "for #driver_data_for" do it "is returned when provided" do DataMunger.new( { driver: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).driver_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, driver: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, driver: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).driver_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "rejects any value in driver data" do DataMunger.new( { driver: { :name => "chefy", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( name: "chefy" ) end end describe "for #provisioner_data_for" do it "uses value in provisioner data" do DataMunger.new( { provisioner: { :name => "chefy", key => "datvalue", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "rejects any value in user data" do DataMunger.new( { kitchen: { key => "imevil", }, provisioner: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( name: "chefy" ) end end describe "for #verifier_data_for" do it "is returned when provided" do DataMunger.new( { verifier: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).verifier_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, verifier: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, verifier: "chefy", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).verifier_data_for("sweet", "plat").must_equal( :name => "chefy", key => "datvalue" ) end it "rejects any value in verifier data" do DataMunger.new( { verifier: { :version => "chefy", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( version: "chefy" ) end end describe "for #transport_data_for" do it "is returned when provided" do DataMunger.new( { transport: "pipes", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "datvalue" ).transport_data_for("sweet", "plat").must_equal( :name => "pipes", key => "datvalue" ) end it "is returned when provided in user data" do DataMunger.new( { kitchen: { key => "datvalue", }, transport: "pipes", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).transport_data_for("sweet", "plat").must_equal( :name => "pipes", key => "datvalue" ) end it "user data value beats provided value" do DataMunger.new( { kitchen: { key => "datvalue", }, transport: "pipes", platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, key => "ilose" ).transport_data_for("sweet", "plat").must_equal( :name => "pipes", key => "datvalue" ) end it "rejects any value in transport data" do DataMunger.new( { transport: { :name => "pipes", key => "imevil", }, platforms: [ { name: "plat" }, ], suites: [ { name: "sweet" }, ], }, {} ).transport_data_for("sweet", "plat").must_equal( name: "pipes" ) end end end end end describe "legacy driver_config and driver_plugin" do describe "from a single source" do it "returns common driver name" do DataMunger.new( { driver_plugin: "starship", }, {} ).driver_data_for("suite", "platform").must_equal( name: "starship" ) end it "merges driver into driver_plugin if driver exists" do DataMunger.new( { driver_plugin: "starship", driver: "zappa", }, {} ).driver_data_for("suite", "platform").must_equal( name: "zappa" ) end it "returns common driver config" do DataMunger.new( { driver_plugin: "starship", driver_config: { speed: 42, }, }, {} ).driver_data_for("suite", "platform").must_equal( name: "starship", speed: 42 ) end it "merges driver into driver_config if driver with name exists" do DataMunger.new( { driver_config: { eh: "yep", }, driver: "zappa", }, {} ).driver_data_for("suite", "platform").must_equal( name: "zappa", eh: "yep" ) end it "merges driver into driver_config if driver exists" do DataMunger.new( { driver_plugin: "imold", driver_config: { eh: "yep", color: "pink", }, driver: { name: "zappa", color: "black", }, }, {} ).driver_data_for("suite", "platform").must_equal( name: "zappa", eh: "yep", color: "black" ) end it "returns platform driver name" do DataMunger.new( { platforms: [ { name: "plat", driver_plugin: "flip", }, ], }, {} ).driver_data_for("suite", "plat").must_equal( name: "flip" ) end it "returns platform config containing driver hash" do DataMunger.new( { platforms: [ { name: "plat", driver_plugin: "flip", driver_config: { flop: "yep", }, }, ], }, {} ).driver_data_for("suite", "plat").must_equal( name: "flip", flop: "yep" ) end it "returns suite driver name" do DataMunger.new( { suites: [ { name: "sweet", driver_plugin: "waz", }, ], }, {} ).driver_data_for("sweet", "platform").must_equal( name: "waz" ) end it "returns suite config containing driver hash" do DataMunger.new( { suites: [ { name: "sweet", driver_plugin: "waz", driver_config: { up: "nope", }, }, ], }, {} ).driver_data_for("sweet", "platform").must_equal( name: "waz", up: "nope" ) end end describe "from multiple sources" do it "suite into platform into common" do DataMunger.new( { driver_plugin: "commony", driver_config: { color: "purple", fruit: %w{apple pear}, deep: { common: "junk" }, }, platforms: [ { name: "plat", driver_plugin: "platformy", driver_config: { fruit: ["banana"], deep: { platform: "stuff" }, }, }, ], suites: [ { name: "sweet", driver_plugin: "suitey", driver_config: { vehicle: "car", deep: { suite: "things" }, }, }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( name: "suitey", color: "purple", fruit: ["banana"], vehicle: "car", deep: { common: "junk", platform: "stuff", suite: "things", } ) end it "platform into common" do DataMunger.new( { driver_plugin: "commony", driver_config: { color: "purple", fruit: %w{apple pear}, deep: { common: "junk" }, }, platforms: [ { name: "plat", driver_plugin: "platformy", driver_config: { fruit: ["banana"], deep: { platform: "stuff" }, }, }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( name: "platformy", color: "purple", fruit: ["banana"], deep: { common: "junk", platform: "stuff", } ) end it "suite into common" do DataMunger.new( { driver_plugin: "commony", driver_config: { color: "purple", fruit: %w{apple pear}, deep: { common: "junk" }, }, suites: [ { name: "sweet", driver_plugin: "suitey", driver_config: { vehicle: "car", deep: { suite: "things" }, }, }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( name: "suitey", color: "purple", fruit: %w{apple pear}, vehicle: "car", deep: { common: "junk", suite: "things", } ) end it "suite into platform" do DataMunger.new( { platforms: [ { name: "plat", driver_plugin: "platformy", driver_config: { fruit: ["banana"], deep: { platform: "stuff" }, }, }, ], suites: [ { name: "sweet", driver_plugin: "suitey", driver_config: { vehicle: "car", deep: { suite: "things" }, }, }, ], }, {} ).driver_data_for("sweet", "plat").must_equal( name: "suitey", fruit: ["banana"], vehicle: "car", deep: { platform: "stuff", suite: "things", } ) end end end describe "legacy chef paths from suite" do LEGACY_CHEF_PATHS = [ :data_path, :data_bags_path, :environments_path, :nodes_path, :roles_path, :encrypted_data_bag_secret_key_path ].freeze LEGACY_CHEF_PATHS.each do |key| it "moves #{key} into provisioner" do DataMunger.new( { provisioner: "chefy", suites: [ { :name => "sweet", key => "mypath", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( :name => "chefy", key => "mypath" ) end it "merges provisioner into data_path if provisioner exists" do DataMunger.new( { suites: [ { :name => "sweet", key => "mypath", :provisioner => "chefy", }, ], }, {} ).provisioner_data_for("sweet", "plat").must_equal( :name => "chefy", key => "mypath" ) end end end describe "legacy require_chef_omnibus from driver" do describe "from a single source" do it "common driver value moves into provisioner" do DataMunger.new( { provisioner: "chefy", driver: { name: "starship", require_chef_omnibus: "it's probably fine", }, }, {} ).provisioner_data_for("suite", "platform").must_equal( name: "chefy", require_chef_omnibus: "it's probably fine" ) end it "common driver value loses to existing provisioner value" do DataMunger.new( { provisioner: { name: "chefy", require_chef_omnibus: "it's probably fine", }, driver: { name: "starship", require_chef_omnibus: "dragons", }, }, {} ).provisioner_data_for("suite", "platform").must_equal( name: "chefy", require_chef_omnibus: "it's probably fine" ) end it "suite driver value moves into provisioner" do DataMunger.new( { suites: [ { name: "sweet", provisioner: "chefy", driver: { name: "starship", require_chef_omnibus: "it's probably fine", }, }, ], }, {} ).provisioner_data_for("sweet", "platform").must_equal( name: "chefy", require_chef_omnibus: "it's probably fine" ) end it "suite driver value loses to existing provisioner value" do DataMunger.new( { suites: [ { name: "sweet", provisioner: { name: "chefy", require_chef_omnibus: "it's probably fine", }, driver: { name: "starship", require_chef_omnibus: "dragons", }, }, ], }, {} ).provisioner_data_for("sweet", "platform").must_equal( name: "chefy", require_chef_omnibus: "it's probably fine" ) end it "platform driver value moves into provisioner" do DataMunger.new( { platforms: [ { name: "plat", provisioner: "chefy", driver: { name: "starship", require_chef_omnibus: "it's probably fine", }, }, ], }, {} ).provisioner_data_for("suite", "plat").must_equal( name: "chefy", require_chef_omnibus: "it's probably fine" ) end it "platform driver value loses to existing provisioner value" do DataMunger.new( { platforms: [ { name: "plat", provisioner: { name: "chefy", require_chef_omnibus: "it's probably fine", }, driver: { name: "starship", require_chef_omnibus: "dragons", }, }, ], }, {} ).provisioner_data_for("suite", "plat").must_equal( name: "chefy", require_chef_omnibus: "it's probably fine" ) end end end describe "legacy http_proxy & https_proxy from driver" do describe "from a single source" do it "common driver value remains in driver" do DataMunger.new( { provisioner: "chefy", driver: { name: "starship", http_proxy: "http://proxy", https_proxy: "https://proxy", }, }, {} ).driver_data_for("suite", "platform").must_equal( name: "starship", http_proxy: "http://proxy", https_proxy: "https://proxy" ) end it "common driver value copies into provisioner" do DataMunger.new( { provisioner: "chefy", driver: { name: "starship", http_proxy: "http://proxy", https_proxy: "https://proxy", }, }, {} ).provisioner_data_for("suite", "platform").must_equal( name: "chefy", http_proxy: "http://proxy", https_proxy: "https://proxy" ) end it "common driver value copies into verifier" do DataMunger.new( { verifier: "bussy", driver: { name: "starship", http_proxy: "http://proxy", https_proxy: "https://proxy", }, }, {} ).verifier_data_for("suite", "platform").must_equal( name: "bussy", http_proxy: "http://proxy", https_proxy: "https://proxy" ) end it "common driver value loses to existing provisioner value" do DataMunger.new( { provisioner: { name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta", }, driver: { name: "starship", http_proxy: "dragons", https_proxy: "cats", }, }, {} ).provisioner_data_for("suite", "platform").must_equal( name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "common driver value loses to existing verifier value" do DataMunger.new( { verifier: { name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta", }, driver: { name: "starship", http_proxy: "dragons", https_proxy: "cats", }, }, {} ).verifier_data_for("suite", "platform").must_equal( name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "suite driver value remains in driver" do DataMunger.new( { suites: [ { name: "sweet", provisioner: "chefy", driver: { name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta", }, }, ], }, {} ).driver_data_for("sweet", "platform").must_equal( name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "suite driver value copies into provisioner" do DataMunger.new( { suites: [ { name: "sweet", provisioner: "chefy", driver: { name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta", }, }, ], }, {} ).provisioner_data_for("sweet", "platform").must_equal( name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "suite driver value copies into verifier" do DataMunger.new( { suites: [ { name: "sweet", verifier: "bussy", driver: { name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta", }, }, ], }, {} ).verifier_data_for("sweet", "platform").must_equal( name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "suite driver value loses to existing provisioner value" do DataMunger.new( { suites: [ { name: "sweet", provisioner: { name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta", }, driver: { name: "starship", http_proxy: "dragons", https_proxy: "cats", }, }, ], }, {} ).provisioner_data_for("sweet", "platform").must_equal( name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "suite driver value loses to existing verifier value" do DataMunger.new( { suites: [ { name: "sweet", verifier: { name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta", }, driver: { name: "starship", http_proxy: "dragons", https_proxy: "cats", }, }, ], }, {} ).verifier_data_for("sweet", "platform").must_equal( name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "platform driver value remains in driver" do DataMunger.new( { platforms: [ { name: "plat", provisioner: "chefy", driver: { name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta", }, }, ], }, {} ).driver_data_for("suite", "plat").must_equal( name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "platform driver value copies into provisioner" do DataMunger.new( { platforms: [ { name: "plat", provisioner: "chefy", driver: { name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta", }, }, ], }, {} ).provisioner_data_for("suite", "plat").must_equal( name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "platform driver value copies into verifier" do DataMunger.new( { platforms: [ { name: "plat", verifier: "bussy", driver: { name: "starship", http_proxy: "it's probably fine", https_proxy: "la quinta", }, }, ], }, {} ).verifier_data_for("suite", "plat").must_equal( name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "platform driver value loses to existing provisioner value" do DataMunger.new( { platforms: [ { name: "plat", provisioner: { name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta", }, driver: { name: "starship", http_proxy: "dragons", https_proxy: "cats", }, }, ], }, {} ).provisioner_data_for("suite", "plat").must_equal( name: "chefy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end it "platform driver value loses to existing verifier value" do DataMunger.new( { platforms: [ { name: "plat", verifier: { name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta", }, driver: { name: "starship", http_proxy: "dragons", https_proxy: "cats", }, }, ], }, {} ).verifier_data_for("suite", "plat").must_equal( name: "bussy", http_proxy: "it's probably fine", https_proxy: "la quinta" ) end end end describe "legacy busser blocks to verifier" do describe "from a single source" do it "merges old common busser name to version into verifier" do DataMunger.new( { busser: "starship", }, {} ).verifier_data_for("suite", "platform").must_equal( name: "busser", version: "starship" ) end it "merges old common busser name to version with exising verifier" do DataMunger.new( { busser: "starship", verifier: { a: "b", }, }, {} ).verifier_data_for("suite", "platform").must_equal( name: "busser", version: "starship", a: "b" ) end it "merges old common busser name to version into verifier with name" do DataMunger.new( { busser: "starship", verifier: "stellar", }, {} ).verifier_data_for("suite", "platform").must_equal( name: "stellar", version: "starship" ) end it "merges old busser data into verifier with name" do DataMunger.new( { busser: { a: "b", }, verifier: "stellar", }, {} ).verifier_data_for("suite", "platform").must_equal( name: "stellar", a: "b" ) end it "merges old busser data into verifier data" do DataMunger.new( { busser: { a: "b", both: "legacy", }, verifier: { name: "stellar", c: "d", both: "modern", }, }, {} ).verifier_data_for("suite", "platform").must_equal( name: "stellar", a: "b", c: "d", both: "modern" ) end it "returns platform verifier name" do DataMunger.new( { platforms: [ { name: "plat", busser: "flip", }, ], }, {} ).verifier_data_for("suite", "plat").must_equal( name: "busser", version: "flip" ) end it "return platform config containing verifier hash" do DataMunger.new( { platforms: [ { name: "plat", busser: "flip", verifier: { flop: "yep", }, }, ], }, {} ).verifier_data_for("suite", "plat").must_equal( name: "busser", version: "flip", flop: "yep" ) end it "returns suite driver name" do DataMunger.new( { suites: [ { name: "sweet", busser: "waz", }, ], }, {} ).verifier_data_for("sweet", "platform").must_equal( name: "busser", version: "waz" ) end it "returns suite config containing verifier hash" do DataMunger.new( { suites: [ { name: "sweet", busser: "waz", verifier: { up: "nope", }, }, ], }, {} ).verifier_data_for("sweet", "platform").must_equal( name: "busser", version: "waz", up: "nope" ) end end describe "from multiple sources" do it "suite into platform into common" do DataMunger.new( { busser: { version: "commony", color: "purple", fruit: %w{apple pear}, deep: { common: "junk" }, }, platforms: [ { name: "plat", busser: { version: "platformy", fruit: ["banana"], deep: { platform: "stuff" }, }, }, ], suites: [ { name: "sweet", busser: { version: "suitey", vehicle: "car", deep: { suite: "things" }, }, }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( name: "busser", version: "suitey", color: "purple", fruit: ["banana"], vehicle: "car", deep: { common: "junk", platform: "stuff", suite: "things", } ) end it "platform into common" do DataMunger.new( { busser: { version: "commony", color: "purple", fruit: %w{apple pear}, deep: { common: "junk" }, }, platforms: [ { name: "plat", busser: { version: "platformy", fruit: ["banana"], deep: { platform: "stuff" }, }, }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( name: "busser", version: "platformy", color: "purple", fruit: ["banana"], deep: { common: "junk", platform: "stuff", } ) end it "suite into common" do DataMunger.new( { busser: { version: "commony", color: "purple", fruit: %w{apple pear}, deep: { common: "junk" }, }, suites: [ { name: "sweet", busser: { version: "suitey", vehicle: "car", deep: { suite: "things" }, }, }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( name: "busser", version: "suitey", color: "purple", fruit: %w{apple pear}, vehicle: "car", deep: { common: "junk", suite: "things", } ) end it "suite into platform" do DataMunger.new( { platforms: [ { name: "plat", busser: { version: "platformy", fruit: ["banana"], deep: { platform: "stuff" }, }, }, ], suites: [ { name: "sweet", busser: { version: "suitey", vehicle: "car", deep: { suite: "things" }, }, }, ], }, {} ).verifier_data_for("sweet", "plat").must_equal( name: "busser", version: "suitey", fruit: ["banana"], vehicle: "car", deep: { platform: "stuff", suite: "things", } ) end end end describe "lifecycle_hooks stuff" do it "handles a single global hook" do DataMunger.new( { lifecycle: { pre_create: "echo foo" }, platforms: [{ name: "plat" }], suites: [{ name: "sweet" }], }, {} ).lifecycle_hooks_data_for("sweet", "plat").must_equal( pre_create: ["echo foo"] ) end it "handles multiple global hooks" do DataMunger.new( { lifecycle: { pre_create: "echo foo", post_converge: ["echo bar", { local: "echo baz" }], pre_verify: [{ remote: "echo other" }], }, platforms: [{ name: "plat" }], suites: [{ name: "sweet" }], }, {} ).lifecycle_hooks_data_for("sweet", "plat").must_equal( pre_create: ["echo foo"], post_converge: ["echo bar", { local: "echo baz" }], pre_verify: [{ remote: "echo other" }] ) end it "handles hooks in platforms and suites" do DataMunger.new( { lifecycle: { pre_create: "echo foo", post_create: "echo post" }, platforms: [{ name: "plat", lifecycle: { pre_create: "echo bar" } }], suites: [{ name: "sweet", lifecycle: { pre_create: "echo baz" } }], }, {} ).lifecycle_hooks_data_for("sweet", "plat").must_equal( pre_create: ["echo foo", "echo bar", "echo baz"], post_create: ["echo post"] ) end it "munges a global legacy pre_create_command" do DataMunger.new( { driver: { pre_create_command: "echo bar" }, lifecycle: { pre_create: "echo foo" }, platforms: [{ name: "plat" }], suites: [{ name: "sweet" }], }, {} ).lifecycle_hooks_data_for("sweet", "plat").must_equal( pre_create: ["echo foo", { local: "echo bar" }] ) end it "munges a platform/suite legacy pre_create_commands" do DataMunger.new( { lifecycle: { pre_create: "echo foo" }, platforms: [{ name: "plat", driver: { pre_create_command: "echo bar" } }], suites: [{ name: "sweet", driver: { pre_create_command: "echo baz" } }], }, {} ).lifecycle_hooks_data_for("sweet", "plat").must_equal( pre_create: ["echo foo", { local: "echo bar" }, { local: "echo baz" }] ) end end end end test-kitchen-1.23.2/spec/kitchen/logging_spec.rb0000644000004100000410000000267713377651062021623 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/logging" class LoggingDummy include Kitchen::Logging attr_reader :logger def initialize(logger) @logger = logger end class Logger METHODS = [:banner, :debug, :info, :warn, :error, :fatal].freeze attr_reader(*(METHODS.map { |m| "#{m}_msg".to_sym })) METHODS.each do |meth| define_method(meth) do |*args| instance_variable_set("@#{meth}_msg", args.first) end end end end describe Kitchen::Logging do let(:logger) { LoggingDummy::Logger.new } let(:subject) { LoggingDummy.new(logger) } LoggingDummy::Logger::METHODS.each do |meth| it "##{meth} calls method on logger" do subject.public_send(meth, "ping") logger.public_send("#{meth}_msg").must_equal "ping" end end end test-kitchen-1.23.2/spec/kitchen/metadata_chopper_spec.rb0000644000004100000410000000425513377651062023467 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/metadata_chopper" describe Kitchen::MetadataChopper do before do FakeFS.activate! FileUtils.mkdir_p("/tmp") end after do FakeFS.deactivate! FakeFS::FileSystem.clear end let(:described_class) { Kitchen::MetadataChopper } describe ".new" do it "contains a :name attribute" do stub_metadata!("banzai") described_class.new("/tmp/metadata.rb")[:name].must_equal "banzai" end it "contains a :version attribute" do stub_metadata!("foobar", "1.2.3") described_class.new("/tmp/metadata.rb")[:version].must_equal "1.2.3" end end describe ".extract" do it "returns a tuple" do stub_metadata!("foo", "1.2.3") described_class.extract("/tmp/metadata.rb").must_equal ["foo", "1.2.3"] end it "returns nils for a name or version that isn't present" do File.open("/tmp/metadata.rb", "wb") do |f| f.write %{maintainer "Michael Bluth"\n} end described_class.extract("/tmp/metadata.rb").must_equal [nil, nil] end end def stub_metadata!(name = "foobar", version = "5.2.1") File.open("/tmp/metadata.rb", "wb") do |f| f.write <<-METADATA_RB.gsub(/^ {8}/, "") name "#{name}" maintainer "Michael Bluth" maintainer_email "michael@bluth.com" license "Apache 2.0" description "Doing stuff!" long_description "Doing stuff!" version "#{version}" METADATA_RB end end end test-kitchen-1.23.2/spec/kitchen/suite_spec.rb0000644000004100000410000000307313377651062021315 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/errors" require "kitchen/suite" describe Kitchen::Suite do let(:opts) do { name: "suitezy", includes: %w{testbuntu testcent}, excludes: %w{prodbuntu}, } end let(:suite) { Kitchen::Suite.new(opts) } it "returns the name" do suite.name.must_equal "suitezy" end it "raises an ArgumentError if name is missing" do opts.delete(:name) proc { Kitchen::Suite.new(opts) }.must_raise Kitchen::ClientError end it "returns the includes" do suite.includes.must_equal %w{testbuntu testcent} end it "returns an empty Array when includes not given" do opts.delete(:includes) suite.includes.must_equal [] end it "returns the excludes" do suite.excludes.must_equal %w{prodbuntu} end it "returns an empty Array when excludes not given" do opts.delete(:excludes) suite.excludes.must_equal [] end end test-kitchen-1.23.2/spec/kitchen/loader/0000755000004100000410000000000013377651062020070 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/loader/yaml_spec.rb0000644000004100000410000005547513377651062022411 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen/errors" require "kitchen/util" require "kitchen/loader/yaml" class Yamled attr_accessor :foo end describe Kitchen::Loader::YAML do let(:loader) do Kitchen::Loader::YAML.new(project_config: "/tmp/.kitchen.yml") end before do FakeFS.activate! FileUtils.mkdir_p("/tmp") end after do FakeFS.deactivate! FakeFS::FileSystem.clear end describe ".initialize" do it "sets project_config based on Dir.pwd by default" do stub_file(File.join(Dir.pwd, "kitchen.yml"), {}) loader = Kitchen::Loader::YAML.new loader.diagnose[:project_config][:filename] .must_equal File.expand_path(File.join(Dir.pwd, "kitchen.yml")) end it "when kitchen.yml not present, falls back to .kitchen.yml" do stub_file(File.join(Dir.pwd, ".kitchen.yml"), {}) loader = Kitchen::Loader::YAML.new loader.diagnose[:project_config][:filename] .must_equal File.expand_path(File.join(Dir.pwd, ".kitchen.yml")) end it "prefers kitchen.yml to .kitchen.yml" do stub_file(File.join(Dir.pwd, "kitchen.yml"), {}) loader = Kitchen::Loader::YAML.new loader.diagnose[:project_config][:filename] .must_equal File.expand_path(File.join(Dir.pwd, "kitchen.yml")) end it "errors when kitchen.yml and .kitchen.yml are both present" do stub_file(File.join(Dir.pwd, "kitchen.yml"), {}) stub_file(File.join(Dir.pwd, ".kitchen.yml"), {}) proc { Kitchen::Loader::YAML.new }.must_raise Kitchen::UserError end it "sets project_config from parameter, if given" do stub_file("/tmp/crazyfunkytown.file", {}) loader = Kitchen::Loader::YAML.new( project_config: "/tmp/crazyfunkytown.file") loader.diagnose[:project_config][:filename] .must_match %r{/tmp/crazyfunkytown.file$} end it "sets local_config based on Dir.pwd by default" do stub_file(File.join(Dir.pwd, ".kitchen.local.yml"), {}) loader = Kitchen::Loader::YAML.new loader.diagnose[:local_config][:filename] .must_equal File.expand_path(File.join(Dir.pwd, ".kitchen.local.yml")) end it "sets local_config based on location of project_config by default" do stub_file("/tmp/.kitchen.local.yml", {}) loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml") loader.diagnose[:local_config][:filename] .must_match %r{/tmp/.kitchen.local.yml$} end it "errors if both visible and hidden copies of default local_config exist" do stub_file("/tmp/kitchen.local.yml", {}) stub_file("/tmp/.kitchen.local.yml", {}) proc { Kitchen::Loader::YAML.new(project_config: "/tmp/.kitchen.yml") } .must_raise Kitchen::UserError end it "sets local_config from parameter, if given" do stub_file("/tmp/crazyfunkytown.file", {}) loader = Kitchen::Loader::YAML.new( local_config: "/tmp/crazyfunkytown.file") loader.diagnose[:local_config][:filename] .must_match %r{/tmp/crazyfunkytown.file$} end it "sets global_config based on ENV['HOME'] by default" do stub_file(File.join(ENV["HOME"], ".kitchen/config.yml"), {}) loader = Kitchen::Loader::YAML.new loader.diagnose[:global_config][:filename].must_equal File.expand_path( File.join(ENV["HOME"], ".kitchen/config.yml")) end it "sets global_config from parameter, if given" do stub_file("/tmp/crazyfunkytown.file", {}) loader = Kitchen::Loader::YAML.new( global_config: "/tmp/crazyfunkytown.file") loader.diagnose[:global_config][:filename] .must_match %r{/tmp/crazyfunkytown.file$} end end describe "#read" do it "returns a hash of kitchen.yml with symbolized keys" do stub_yaml!( "foo" => "bar" ) loader.read.must_equal(foo: "bar") end it "deep merges in kitchen.local.yml configuration with kitchen.yml" do stub_yaml!("common" => { "xx" => 1 }, "a" => "b" ) stub_yaml!( { "common" => { "yy" => 2 }, "c" => "d", }, ".kitchen.local.yml" ) loader.read.must_equal( a: "b", c: "d", common: { xx: 1, yy: 2 } ) end it "deep merges in a global config file with all other configs" do stub_yaml!("common" => { "xx" => 1 }, "a" => "b" ) stub_yaml!( { "common" => { "yy" => 2 }, "c" => "d", }, ".kitchen.local.yml" ) stub_global!( "common" => { "zz" => 3 }, "e" => "f" ) loader.read.must_equal( a: "b", c: "d", e: "f", common: { xx: 1, yy: 2, zz: 3 } ) end it "merges kitchen.yml over configuration in global config" do stub_global!( "common" => { "thekey" => "nope" } ) stub_yaml!("common" => { "thekey" => "yep" }) loader.read.must_equal(common: { thekey: "yep" }) end it "merges kitchen.local.yml over configuration in kitchen.yml" do stub_yaml!("common" => { "thekey" => "nope" }) stub_yaml!( { "common" => { "thekey" => "yep" } }, ".kitchen.local.yml" ) loader.read.must_equal(common: { thekey: "yep" }) end it "merges kitchen.local.yml over both kitchen.yml and global config" do stub_yaml!("common" => { "thekey" => "nope" }) stub_yaml!( { "common" => { "thekey" => "yep" } }, ".kitchen.local.yml" ) stub_global!( "common" => { "thekey" => "kinda" } ) loader.read.must_equal(common: { thekey: "yep" }) end NORMALIZED_KEYS = { "driver" => "name", "provisioner" => "name", "busser" => "version", }.freeze NORMALIZED_KEYS.each do |key, default_key| describe "normalizing #{key} config hashes" do it "merges local with #{key} string value over yaml with hash value" do stub_yaml!(key => { "dakey" => "ya" }) stub_yaml!( { key => "namey" }, ".kitchen.local.yml" ) loader.read.must_equal( key.to_sym => { default_key.to_sym => "namey", :dakey => "ya" } ) end it "merges local with #{key} hash value over yaml with string value" do stub_yaml!(key => "namey") stub_yaml!( { key => { "dakey" => "ya" } }, ".kitchen.local.yml" ) loader.read.must_equal( key.to_sym => { default_key.to_sym => "namey", :dakey => "ya" } ) end it "merges local with #{key} nil value over yaml with hash value" do stub_yaml!(key => { "dakey" => "ya" }) stub_yaml!( { key => nil }, ".kitchen.local.yml" ) loader.read.must_equal( key.to_sym => { dakey: "ya" } ) end it "merges local with #{key} hash value over yaml with nil value" do stub_yaml!(key => "namey") stub_yaml!( { key => nil }, ".kitchen.local.yml" ) loader.read.must_equal( key.to_sym => { default_key.to_sym => "namey" } ) end it "merges global with #{key} string value over yaml with hash value" do stub_yaml!(key => { "dakey" => "ya" }) stub_global!( key => "namey" ) loader.read.must_equal( key.to_sym => { default_key.to_sym => "namey", :dakey => "ya" } ) end it "merges global with #{key} hash value over yaml with string value" do stub_yaml!(key => "namey") stub_global!( key => { "dakey" => "ya" } ) loader.read.must_equal( key.to_sym => { default_key.to_sym => "namey", :dakey => "ya" } ) end it "merges global with #{key} nil value over yaml with hash value" do stub_yaml!(key => { "dakey" => "ya" }) stub_global!( key => nil ) loader.read.must_equal( key.to_sym => { dakey: "ya" } ) end it "merges global with #{key} hash value over yaml with nil value" do stub_yaml!(key => nil) stub_global!( key => { "dakey" => "ya" } ) loader.read.must_equal( key.to_sym => { dakey: "ya" } ) end it "merges global, local, over yaml with mixed hash, string, nil values" do stub_yaml!(key => nil) stub_yaml!( { key => "namey" }, ".kitchen.local.yml" ) stub_global!( key => { "dakey" => "ya" } ) loader.read.must_equal( key.to_sym => { default_key.to_sym => "namey", :dakey => "ya" } ) end end end it "handles a kitchen.local.yml with no yaml elements" do stub_yaml!("a" => "b") stub_yaml!({}, ".kitchen.local.yml") loader.read.must_equal(a: "b") end it "handles a kitchen.yml with no yaml elements" do stub_yaml!({}) stub_yaml!( { "a" => "b" }, ".kitchen.local.yml" ) loader.read.must_equal(a: "b") end it "handles a kitchen.yml with yaml elements that parse as nil" do stub_yaml!(nil) stub_yaml!( { "a" => "b" }, ".kitchen.local.yml" ) loader.read.must_equal(a: "b") end it "raises an UserError if the config_file does not exist" do proc { loader.read }.must_raise Kitchen::UserError end it "arbitrary objects aren't deserialized in kitchen.yml" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") do |f| f.write <<-YAML.gsub(/^ {10}/, "") --- !ruby/object:Yamled foo: bar YAML end proc { loader.read }.must_raise Kitchen::UserError end it "arbitrary objects aren't deserialized in kitchen.local.yml" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.local.yml", "wb") do |f| f.write <<-YAML.gsub(/^ {10}/, "") --- !ruby/object:Yamled wakka: boop YAML end stub_yaml!({}) proc { loader.read }.must_raise Kitchen::UserError end it "raises a UserError if kitchen.yml cannot be parsed" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") { |f| f.write "&*%^*" } err = proc { loader.read }.must_raise Kitchen::UserError err.message.must_match Regexp.new( "Error parsing ([a-zA-Z]:)?/tmp/.kitchen.yml") end it "raises a UserError if kitchen.yml cannot be parsed" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") { |f| f.write "uhoh" } err = proc { loader.read }.must_raise Kitchen::UserError err.message.must_match Regexp.new( "Error parsing ([a-zA-Z]:)?/tmp/.kitchen.yml") end it "handles a kitchen.yml if it is a commented out YAML document" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") { |f| f.write '#---\n' } loader.read.must_equal({}) end it "raises a UserError if kitchen.local.yml cannot be parsed" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.local.yml", "wb") { |f| f.write "&*%^*" } stub_yaml!({}) proc { loader.read }.must_raise Kitchen::UserError end it "evaluates kitchen.yml through erb before loading by default" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") do |f| f.write <<-'YAML'.gsub(/^ {10}/, "") --- name: <%= "AHH".downcase + "choo" %> YAML end loader.read.must_equal(name: "ahhchoo") end it "accepts kitchen.yml with alias" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") do |f| f.write <<-'YAML'.gsub(/^ {10}/, "") --- xxx: &k foo: bar yyy: *k YAML end loader.read[:yyy].must_equal(foo: "bar") end it "raises a UserError if there is an ERB processing error" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") do |f| f.write <<-'YAML'.gsub(/^ {10}/, "") --- <%= poop %>: yep YAML end err = proc { loader.read }.must_raise Kitchen::UserError err.message.must_match Regexp.new( "Error parsing ERB content in ([a-zA-Z]:)?/tmp/.kitchen.yml") end it "evaluates kitchen.local.yml through erb before loading by default" do FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.local.yml", "wb") do |f| f.write <<-'YAML'.gsub(/^ {10}/, "") --- <% %w{noodle mushroom}.each do |kind| %> <%= kind %>: soup <% end %> YAML end stub_yaml!("spinach" => "salad") loader.read.must_equal( spinach: "salad", noodle: "soup", mushroom: "soup" ) end it "skips evaluating kitchen.yml through erb if disabled" do loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_erb: false) FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.yml", "wb") do |f| f.write <<-'YAML'.gsub(/^ {10}/, "") --- name: <%= "AHH".downcase %> YAML end loader.read.must_equal(name: '<%= "AHH".downcase %>') end it "skips evaluating kitchen.local.yml through erb if disabled" do loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_erb: false) FileUtils.mkdir_p "/tmp" File.open("/tmp/.kitchen.local.yml", "wb") do |f| f.write <<-'YAML'.gsub(/^ {10}/, "") --- name: <%= "AHH".downcase %> YAML end stub_yaml!({}) loader.read.must_equal(name: '<%= "AHH".downcase %>') end it "skips kitchen.local.yml if disabled" do loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_local: false) stub_yaml!("a" => "b") stub_yaml!( { "superawesomesauceadditions" => "enabled, yo" }, ".kitchen.local.yml" ) loader.read.must_equal(a: "b") end it "skips the global config if disabled" do loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_global: false) stub_yaml!("a" => "b") stub_global!( "superawesomesauceadditions" => "enabled, yo" ) loader.read.must_equal(a: "b") end end describe "#diagnose" do it "returns a Hash" do stub_yaml!({}) loader.diagnose.must_be_kind_of(Hash) end it "contains erb processing information when true" do stub_yaml!({}) loader.diagnose[:process_erb].must_equal true end it "contains erb processing information when false" do stub_yaml!({}) loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_erb: false) loader.diagnose[:process_erb].must_equal false end it "contains local processing information when true" do stub_yaml!({}) loader.diagnose[:process_local].must_equal true end it "contains local processing information when false" do stub_yaml!({}) loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_local: false) loader.diagnose[:process_local].must_equal false end it "contains global processing information when true" do stub_yaml!({}) loader.diagnose[:process_global].must_equal true end it "contains global processing information when false" do stub_yaml!({}) loader = Kitchen::Loader::YAML.new( project_config: "/tmp/.kitchen.yml", process_global: false) loader.diagnose[:process_global].must_equal false end describe "for yaml files" do before do stub_yaml!("from_project" => "project", "common" => { "p" => "pretty" } ) stub_yaml!({ "from_local" => "local", "common" => { "l" => "looky" }, }, ".kitchen.local.yml" ) stub_global!( "from_global" => "global", "common" => { "g" => "goody" } ) end it "global config contains a filename" do loader.diagnose[:global_config][:filename] .must_equal File.join(ENV["HOME"].tr('\\', "/"), ".kitchen/config.yml") end it "global config contains raw data" do loader.diagnose[:global_config][:raw_data].must_equal( "from_global" => "global", "common" => { "g" => "goody" } ) end it "project config contains a filename" do loader.diagnose[:project_config][:filename] .must_match %r{/tmp/.kitchen.yml$} end it "project config contains raw data" do loader.diagnose[:project_config][:raw_data].must_equal( "from_project" => "project", "common" => { "p" => "pretty" } ) end it "local config contains a filename" do loader.diagnose[:local_config][:filename] .must_match %r{/tmp/.kitchen.local.yml$} end it "local config contains raw data" do loader.diagnose[:local_config][:raw_data].must_equal( "from_local" => "local", "common" => { "l" => "looky" } ) end it "combined config contains a nil filename" do loader.diagnose[:combined_config][:filename] .must_be_nil end it "combined config contains raw data" do loader.diagnose[:combined_config][:raw_data].must_equal( "from_global" => "global", "from_project" => "project", "from_local" => "local", "common" => { "g" => "goody", "p" => "pretty", "l" => "looky", } ) end describe "for global on error" do before do FileUtils.mkdir_p(File.join(ENV["HOME"], ".kitchen")) File.open(File.join(ENV["HOME"], ".kitchen/config.yml"), "wb") do |f| f.write "&*%^*" end end it "uses an error hash with the raw file contents" do loader.diagnose[:global_config][:raw_data][:error][:raw_file] .must_equal "&*%^*" end it "uses an error hash with the exception" do loader.diagnose[:global_config][:raw_data][:error][:exception] .must_match(/Kitchen::UserError/) end it "uses an error hash with the exception message" do loader.diagnose[:global_config][:raw_data][:error][:message] .must_match(/Error parsing/) end it "uses an error hash with the exception backtrace" do loader.diagnose[:global_config][:raw_data][:error][:backtrace] .must_be_kind_of Array end end describe "for project on error" do before do File.open("/tmp/.kitchen.yml", "wb") do |f| f.write "&*%^*" end end it "uses an error hash with the raw file contents" do loader.diagnose[:project_config][:raw_data][:error][:raw_file] .must_equal "&*%^*" end it "uses an error hash with the exception" do loader.diagnose[:project_config][:raw_data][:error][:exception] .must_match(/Kitchen::UserError/) end it "uses an error hash with the exception message" do loader.diagnose[:project_config][:raw_data][:error][:message] .must_match(/Error parsing/) end it "uses an error hash with the exception backtrace" do loader.diagnose[:project_config][:raw_data][:error][:backtrace] .must_be_kind_of Array end end describe "for local on error" do before do File.open("/tmp/.kitchen.local.yml", "wb") do |f| f.write "&*%^*" end end it "uses an error hash with the raw file contents" do loader.diagnose[:local_config][:raw_data][:error][:raw_file] .must_equal "&*%^*" end it "uses an error hash with the exception" do loader.diagnose[:local_config][:raw_data][:error][:exception] .must_match(/Kitchen::UserError/) end it "uses an error hash with the exception message" do loader.diagnose[:local_config][:raw_data][:error][:message] .must_match(/Error parsing/) end it "uses an error hash with the exception backtrace" do loader.diagnose[:local_config][:raw_data][:error][:backtrace] .must_be_kind_of Array end end describe "for combined on error" do before do File.open("/tmp/.kitchen.yml", "wb") do |f| f.write "&*%^*" end end it "uses an error hash with nil raw file contents" do loader.diagnose[:combined_config][:raw_data][:error][:raw_file] .must_be_nil end it "uses an error hash with the exception" do loader.diagnose[:combined_config][:raw_data][:error][:exception] .must_match(/Kitchen::UserError/) end it "uses an error hash with the exception message" do loader.diagnose[:combined_config][:raw_data][:error][:message] .must_match(/Error parsing/) end it "uses an error hash with the exception backtrace" do loader.diagnose[:combined_config][:raw_data][:error][:backtrace] .must_be_kind_of Array end end end end private def stub_file(path, hash) FileUtils.mkdir_p(File.dirname(path)) File.open(path, "wb") { |f| f.write(hash.to_yaml) } end def stub_yaml!(hash, name = ".kitchen.yml") stub_file(File.join("/tmp", name), hash) end def stub_global!(hash) stub_file(File.join(File.expand_path(ENV["HOME"]), ".kitchen", "config.yml"), hash) end end test-kitchen-1.23.2/spec/kitchen/verifier/0000755000004100000410000000000013377651062020435 5ustar www-datawww-datatest-kitchen-1.23.2/spec/kitchen/verifier/base_spec.rb0000644000004100000410000002120413377651062022705 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require_relative "../ssh_spec" require "logger" require "stringio" require "kitchen/verifier/base" require "kitchen/transport/base" module Kitchen module Verifier class TestingDummy < Kitchen::Verifier::Base attr_reader :called_create_sandbox, :called_cleanup_sandbox def install_command "install" end def init_command "init" end def prepare_command "prepare" end def run_command "run" end def create_sandbox @called_create_sandbox = true end def cleanup_sandbox @called_cleanup_sandbox = true end def sandbox_path "/tmp/sandbox" end end end end describe Kitchen::Verifier::Base do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil, shell_type: nil) } let(:suite) { stub(name: "germany") } let(:config) { Hash.new } let(:transport) do t = mock("transport") t.responds_like_instance_of(Kitchen::Transport::Base) t end let(:instance) do stub( name: "coolbeans", to_str: "instance", logger: logger, platform: platform, suite: suite, transport: transport ) end let(:verifier) do Kitchen::Verifier::Base.new(config).finalize_config!(instance) end describe "configuration" do describe "for unix operating systems" do before { platform.stubs(:os_type).returns("unix") } it ":sudo defaults to true" do verifier[:sudo].must_equal true end it ":sudo_command defaults to sudo -E" do verifier[:sudo_command].must_equal "sudo -E" end it ":root_path defaults to '/tmp/verifier'" do verifier[:root_path].must_equal "/tmp/verifier" end end describe "for windows operating systems" do before { platform.stubs(:os_type).returns("windows") } it ":sudo defaults to nil" do verifier[:sudo].must_be_nil end it ":sudo_command defaults to nil" do verifier[:sudo_command].must_be_nil end it ':root_path defaults to $env:TEMP\\verifier' do verifier[:root_path].must_equal '$env:TEMP\\verifier' end end it ":suite_name defaults to the passed in suite name" do verifier[:suite_name].must_equal "germany" end it ":http_proxy defaults to nil" do verifier[:http_proxy].must_be_nil end it ":http_proxys defaults to nil" do verifier[:https_proxy].must_be_nil end it ":ftp_proxy defaults to nil" do verifier[:ftp_proxy].must_be_nil end end describe "#call" do let(:state) { Hash.new } let(:cmd) { verifier.call(state) } let(:connection) do c = mock("transport_connection") c.responds_like_instance_of(Kitchen::Transport::Base::Connection) c end let(:verifier) do Kitchen::Verifier::TestingDummy.new(config).finalize_config!(instance) end before do FakeFS.activate! FileUtils.mkdir_p(File.join(verifier.sandbox_path, "stuff")) transport.stubs(:connection).yields(connection) connection.stubs(:execute) connection.stubs(:upload) end after do FakeFS.deactivate! FakeFS::FileSystem.clear end it "creates the sandbox" do verifier.expects(:create_sandbox) cmd end it "ensures that the sandbox is cleanup up" do transport.stubs(:connection).raises verifier.expects(:cleanup_sandbox) begin cmd rescue # rubocop:disable Lint/HandleExceptions end end it "yields a connection given the state" do state[:a] = "b" transport.expects(:connection).with(state).yields(connection) cmd end it "invokes the verifier commands over the transport" do order = sequence("order") connection.expects(:execute).with("install").in_sequence(order) connection.expects(:execute).with("init").in_sequence(order) connection.expects(:execute).with("prepare").in_sequence(order) connection.expects(:execute).with("run").in_sequence(order) cmd end it "logs to info" do cmd logged_output.string .must_match(/INFO -- : Transferring files to instance$/) end it "uploads sandbox files" do connection.expects(:upload).with(["/tmp/sandbox/stuff"], "/tmp/verifier") cmd end it "logs to debug" do cmd logged_output.string.must_match(/DEBUG -- : Transfer complete$/) end it "raises an ActionFailed on transfer when TransportFailed is raised" do connection.stubs(:upload) .raises(Kitchen::Transport::TransportFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end it "raises an ActionFailed on execute when TransportFailed is raised" do connection.stubs(:execute) .raises(Kitchen::Transport::TransportFailed.new("dang")) proc { cmd }.must_raise Kitchen::ActionFailed end end [:init_command, :install_command, :prepare_command, :run_command].each do |cmd| it "has a #{cmd} method" do verifier.public_send(cmd).must_be_nil end end describe "sandbox" do after do begin verifier.cleanup_sandbox rescue # rubocop:disable Lint/HandleExceptions end end it "raises ClientError if #sandbox_path is called before #create_sandbox" do proc { verifier.sandbox_path }.must_raise Kitchen::ClientError end it "#create_sandbox creates a temporary directory" do verifier.create_sandbox File.directory?(verifier.sandbox_path).must_equal true format("%o", File.stat(verifier.sandbox_path).mode)[1, 4] .must_equal "0755" end it "#create_sandbox logs an info message" do verifier.create_sandbox logged_output.string.must_match info_line("Preparing files for transfer") end it "#create_sandbox logs a debug message" do verifier.create_sandbox logged_output.string .must_match debug_line_starting_with("Creating local sandbox in ") end it "#cleanup_sandbox deletes the sandbox directory" do verifier.create_sandbox verifier.cleanup_sandbox File.directory?(verifier.sandbox_path).must_equal false end it "#cleanup_sandbox logs a debug message" do verifier.create_sandbox verifier.cleanup_sandbox logged_output.string .must_match debug_line_starting_with("Cleaning up local sandbox in ") end def info_line(msg) /^I, .* : #{Regexp.escape(msg)}$/ end def debug_line_starting_with(msg) /^D, .* : #{Regexp.escape(msg)}/ end end describe "#sudo" do describe "with :sudo set" do before { config[:sudo] = true } it "prepends sudo command" do verifier.send(:sudo, "wakka").must_equal("sudo -E wakka") end it "customizes sudo when :sudo_command is set" do config[:sudo_command] = "blueto -Ohai" verifier.send(:sudo, "wakka").must_equal("blueto -Ohai wakka") end end describe "with :sudo falsey" do before { config[:sudo] = false } it "does not include sudo command" do verifier.send(:sudo, "wakka").must_equal("wakka") end it "does not include sudo command, even when :sudo_command is set" do config[:sudo_command] = "blueto -Ohai" verifier.send(:sudo, "wakka").must_equal("wakka") end end end describe "#prefix_command" do describe "with :command_prefix set" do before { config[:command_prefix] = "my_prefix" } it "prepends the command with the prefix" do verifier.send(:prefix_command, "my_command").must_equal("my_prefix my_command") end end describe "with :command_prefix unset" do before { config[:command_prefix] = nil } it "returns an unaltered command" do verifier.send(:prefix_command, "my_command").must_equal("my_command") end end end end test-kitchen-1.23.2/spec/kitchen/verifier/dummy_spec.rb0000644000004100000410000000504313377651062023131 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "logger" require "stringio" require "kitchen/verifier/dummy" describe Kitchen::Verifier::Dummy do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil, shell_type: nil) } let(:suite) { stub(name: "fries") } let(:state) { Hash.new } let(:config) do { test_base_path: "/basist", kitchen_root: "/rooty" } end let(:instance) do stub( name: "coolbeans", to_str: "instance", logger: logger, suite: suite, platform: platform ) end let(:verifier) do Kitchen::Verifier::Dummy.new(config).finalize_config!(instance) end it "verifier api_version is 1" do verifier.diagnose_plugin[:api_version].must_equal 1 end it "plugin_version is set to Kitchen::VERSION" do verifier.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "configuration" do it "sets :sleep to 0 by default" do verifier[:sleep].must_equal 0 end it "sets :random_failure to false by default" do verifier[:random_failure].must_equal false end end describe "#call" do it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 12.5 verifier.expects(:sleep).with(12.5).returns(true) verifier.call(state) end it "raises ActionFailed if :fail is set" do config[:fail] = true proc { verifier.call(state) }.must_raise Kitchen::ActionFailed end it "randomly raises ActionFailed if :random_failure is set" do config[:random_failure] = true verifier.stubs(:randomly_fail?).returns(true) proc { verifier.call(state) }.must_raise Kitchen::ActionFailed end it "logs a converge event to INFO" do verifier.call(state) logged_output.string.must_match(/^.+ INFO .+ \[Dummy\] Verify on .+$/) end end end test-kitchen-1.23.2/spec/kitchen/verifier/busser_spec.rb0000644000004100000410000003670713377651062023314 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "kitchen" require "kitchen/verifier/busser" describe Kitchen::Verifier::Busser do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:config) { Hash.new } let(:platform) { stub(os_type: nil, shell_type: nil) } let(:suite) { stub(name: "germany") } let(:instance) do stub( name: "coolbeans", logger: logger, platform: platform, suite: suite, to_str: "instance" ) end let(:verifier) do Kitchen::Verifier::Busser.new(config).finalize_config!(instance) end let(:files) do { "mondospec/charlie" => { content: "charlie", perms: (running_tests_on_windows? ? "0644" : "0764"), }, "minispec/beta" => { content: "beta", perms: "0644", }, "abba/alpha" => { content: "alpha", perms: (running_tests_on_windows? ? "0444" : "0440"), }, } end let(:helper_files) do { "minispec/spec_helper" => { content: "helping", perms: "0644", }, "abba/common" => { content: "yeppers", perms: (running_tests_on_windows? ? "0644" : "0664"), }, } end before do @root = Dir.mktmpdir config[:test_base_path] = @root end after do FileUtils.remove_entry(@root) end # TODO: deal with this: # it "raises a UserError if the suite name is 'helper'" do # proc { # Kitchen::Busser.new("helper", config) # }.must_raise Kitchen::UserError # end it "verifier api_version is 1" do verifier.diagnose_plugin[:api_version].must_equal 1 end it "plugin_version is set to Kitchen::VERSION" do verifier.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "configuration" do describe "for unix operating systems" do before do platform.stubs(:os_type).returns("unix") end it ":ruby_bindir defaults the an Omnibus Chef installation" do verifier[:ruby_bindir].must_equal "/opt/chef/embedded/bin" end it ":busser_bin defaults to a binstub under :root_path" do config[:root_path] = "/beep" verifier[:busser_bin].must_equal "/beep/bin/busser" end end describe "for windows operating systems" do before { platform.stubs(:os_type).returns("windows") } it ":ruby_bindir defaults the an Omnibus Chef installation" do verifier[:ruby_bindir] .must_equal '$env:systemdrive\\opscode\\chef\\embedded\\bin' end it ":busser_bin defaults to a binstub under :root_path" do config[:root_path] = '\\beep' verifier[:busser_bin].must_equal '\\beep\\bin\\busser.bat' end end it ":version defaults to 'busser'" do verifier[:version].must_equal "busser" end end def self.common_bourne_variable_specs it "uses bourne shell" do cmd.must_match(/\Ash -c '$/) cmd.must_match(/'\Z/) end it "ends with a single quote" do cmd.must_match(/'\Z/) end it "sets the BUSSER_ROOT environment variable" do cmd.must_match regexify(%{BUSSER_ROOT="/r"; export BUSSER_ROOT}) end it "sets the GEM_HOME environment variable" do cmd.must_match regexify(%{GEM_HOME="/r/gems"; export GEM_HOME}) end it "sets the GEM_PATH environment variable" do cmd.must_match regexify(%{GEM_PATH="/r/gems"; export GEM_PATH}) end it "sets the GEM_CACHE environment variable" do cmd.must_match regexify(%{GEM_CACHE="/r/gems/cache"; export GEM_CACHE}) end end def self.common_powershell_variable_specs it "sets the BUSSER_ROOT environment variable" do cmd.must_match regexify(%{$env:BUSSER_ROOT = "\\r"}) end it "sets the GEM_HOME environment variable" do cmd.must_match regexify(%{$env:GEM_HOME = "\\r\\gems"}) end it "sets the GEM_PATH environment variable" do cmd.must_match regexify(%{$env:GEM_PATH = "\\r\\gems"}) end it "sets the GEM_CACHE environment variable" do cmd.must_match regexify(%{$env:GEM_CACHE = "\\r\\gems\\cache"}) end end describe "#install_command" do let(:cmd) { verifier.install_command } describe "with no suite test files" do describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "returns nil" do cmd.must_be_nil end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "returns nil" do cmd.must_be_nil end end end describe "with suite test files" do describe "common behavior" do before do platform.stubs(:shell_type).returns("fake") create_test_files end it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end end describe "for bourne shells" do before do platform.stubs(:shell_type).returns("bourne") create_test_files config[:ruby_bindir] = "/rbd" config[:root_path] = "/r" end common_bourne_variable_specs it "sets path to ruby command" do cmd.must_match regexify(%{ruby="/rbd/ruby"}) end it "sets path to gem command" do cmd.must_match regexify(%{gem="/rbd/gem"}) end it "sets version for busser" do config[:version] = "the_best" cmd.must_match regexify(%{version="the_best"}) end it "sets gem install arguments" do cmd.must_match regexify( 'gem_install_args="busser --no-rdoc --no-ri --no-format-executable' \ ' -n /r/bin --no-user-install"' ) end it "prepends sudo for busser binstub command when :sudo is set" do cmd.must_match regexify(%{busser="sudo -E /r/bin/busser"}) end it "does not sudo for busser binstub command when :sudo is falsey" do config[:sudo] = false cmd.must_match regexify(%{busser="/r/bin/busser"}) end it "sets the busser plugins list" do cmd.must_match regexify( %{plugins="busser-abba busser-minispec busser-mondospec"}) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") create_test_files config[:ruby_bindir] = '\\rbd' config[:root_path] = '\\r' end common_powershell_variable_specs it "sets path to ruby command" do cmd.must_match regexify(%{$ruby = "\\rbd\\ruby.exe"}) end it "sets path to gem command" do cmd.must_match regexify(%{$gem = "\\rbd\\gem"}) end it "sets version for busser" do config[:version] = "the_best" cmd.must_match regexify(%{$version = "the_best"}) end it "sets gem install arguments" do cmd.must_match regexify( '$gem_install_args = "busser --no-rdoc --no-ri --no-format-executable' \ ' -n \\r\\bin --no-user-install"' ) end it "sets path to busser binstub command" do cmd.must_match regexify(%{$busser = "\\r\\bin\\busser.bat"}) end it "sets the busser plugins list" do cmd.must_match regexify( %{$plugins = "busser-abba busser-minispec busser-mondospec"}) end end end end describe "#init_command" do let(:cmd) { verifier.init_command } describe "with no suite test files" do describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "returns nil" do cmd.must_be_nil end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "returns nil" do cmd.must_be_nil end end end describe "with suite test files" do describe "common behavior" do before do platform.stubs(:shell_type).returns("fake") create_test_files end it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end end describe "for bourne shells" do before do platform.stubs(:shell_type).returns("bourne") create_test_files config[:ruby_bindir] = "/rbd" config[:root_path] = "/r" end common_bourne_variable_specs it "runs busser's suite cleanup with sudo, if set" do config[:root_path] = "/b" config[:sudo] = true cmd.must_match regexify(%{sudo -E /b/bin/busser suite cleanup}) end it "runs busser's suite cleanup without sudo, if falsey" do config[:root_path] = "/b" config[:sudo] = false cmd.wont_match regexify(%{sudo -E /b/bin/busser suite cleanup}) cmd.must_match regexify(%{/b/bin/busser suite cleanup}) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") create_test_files config[:ruby_bindir] = '\\rbd' config[:root_path] = '\\r' end common_powershell_variable_specs it "runs busser's suite cleanup" do config[:root_path] = '\\b' cmd.must_match regexify(%{& \\b\\bin\\busser.bat suite cleanup}) end end end end describe "#run_command" do let(:cmd) { verifier.run_command } describe "with no suite test files" do describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "returns nil" do cmd.must_be_nil end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") end it "returns nil" do cmd.must_be_nil end end end describe "with suite test files" do describe "common behavior" do before do platform.stubs(:shell_type).returns("fake") create_test_files end it "prefixs the whole command with the command_prefix if set" do config[:command_prefix] = "my_prefix" cmd.must_match(/\Amy_prefix /) end it "does not prefix the command if command_prefix is not set" do config[:command_prefix] = nil cmd.wont_match(/\Amy_prefix /) end end describe "for bourne shells" do before do platform.stubs(:shell_type).returns("bourne") create_test_files config[:ruby_bindir] = "/rbd" config[:root_path] = "/r" end common_bourne_variable_specs it "uses sudo for busser test when configured" do config[:sudo] = true config[:busser_bin] = "/p/b" cmd.must_match regexify("sudo -E /p/b test", :partial_line) end it "does not use sudo for busser test when configured" do config[:sudo] = false config[:busser_bin] = "/p/b" cmd.must_match regexify("/p/b test", :partial_line) cmd.wont_match regexify("sudo -E /p/b test", :partial_line) end end describe "for powershell shells on windows os types" do before do platform.stubs(:shell_type).returns("powershell") platform.stubs(:os_type).returns("windows") create_test_files config[:ruby_bindir] = '\\rbd' config[:root_path] = '\\r' end common_powershell_variable_specs it "runs busser's test" do config[:root_path] = '\\b' cmd.must_match regexify(%{& \\b\\bin\\busser.bat test}, :partial_line) end end end end describe "#create_sandbox" do before do create_test_files end it "copies each suite file into the suites directory in sandbox" do verifier.create_sandbox files.each do |f, md| file = sandbox_path("suites/#{f}") file.file?.must_equal true file.stat.mode.to_s(8)[2, 4].must_equal md[:perms] IO.read(file).must_equal md[:content] end end it "copies each helper file into the suites directory in sandbox" do verifier.create_sandbox helper_files.each do |f, md| file = sandbox_path("suites/#{f}") file.file?.must_equal true file.stat.mode.to_s(8)[2, 4].must_equal md[:perms] IO.read(file).must_equal md[:content] end end def sandbox_path(path) Pathname.new(verifier.sandbox_path).join(path) end end describe "Busser legacy behavior for code calling old method names" do let(:busser) { verifier } it "responds to #setup_cmd which calls #install_command" do busser.stubs(:install_command).returns("install") busser.setup_cmd.must_equal "install" end it "responds to #run_cmd which calls #run_command" do busser.stubs(:run_command).returns("run") busser.run_cmd.must_equal "run" end it "responds to #sync_cmd which logs a warning" do busser.sync_cmd logged_output.string.must_match warn_line_with( "Legacy call to #sync_cmd cannot be preserved") end end def create_file(file, content, perms) FileUtils.mkdir_p(File.dirname(file)) File.open(file, "wb") { |f| f.write(content) } FileUtils.chmod(perms.to_i(8), file) end def create_test_files base = "#{config[:test_base_path]}/germany" hbase = "#{config[:test_base_path]}/helpers" files.map { |f, md| [File.join(base, f), md] }.each do |f, md| create_file(f, md[:content], md[:perms]) end helper_files.map { |f, md| [File.join(hbase, f), md] }.each do |f, md| create_file(f, md[:content], md[:perms]) end end def regexify(str, line = :whole_line) r = Regexp.escape(str) r = "^\s*#{r}$" if line == :whole_line Regexp.new(r) end def warn_line_with(msg) /^W, .* : #{Regexp.escape(msg)}/ end end test-kitchen-1.23.2/spec/kitchen/verifier/shell_spec.rb0000644000004100000410000001054513377651062023110 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: SAWANOBORI Yukihiko () # # Copyright (C) 2015, HiganWorks LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../spec_helper" require "logger" require "stringio" require "kitchen/verifier/shell" require "kitchen/transport/ssh" describe Kitchen::Verifier::Shell do let(:logged_output) { StringIO.new } let(:logger) { Logger.new(logged_output) } let(:platform) { stub(os_type: nil, shell_type: nil, name: "coolbeans") } let(:suite) { stub(name: "fries") } let(:state) { Hash.new } let(:config) do { test_base_path: "/basist", kitchen_root: "/rooty" } end let(:instance) do stub( name: [platform.name, suite.name].join("-"), to_str: "instance", logger: logger, suite: suite, platform: platform ) end let(:verifier) do Kitchen::Verifier::Shell.new(config).finalize_config!(instance) end it "verifier api_version is 1" do verifier.diagnose_plugin[:api_version].must_equal 1 end it "plugin_version is set to Kitchen::VERSION" do verifier.diagnose_plugin[:version].must_equal Kitchen::VERSION end describe "configuration" do it "sets :sleep to 0 by default" do verifier[:sleep].must_equal 0 end it "sets :command to 'true' by default" do verifier[:command].must_equal "true" end it "sets :live_stream to stdout by default" do verifier[:live_stream].must_equal $stdout end end describe "#call" do describe "#shell_out" do it "calls sleep if :sleep value is greater than 0" do config[:sleep] = 3 verifier.expects(:sleep).with(1).returns(true).at_least(3) verifier.call(state) end it "states are set to environment" do state[:hostname] = "testhost" state[:server_id] = "i-xxxxxx" state[:port] = 22 verifier.call(state) config[:shellout_opts][:environment]["KITCHEN_HOSTNAME"].must_equal "testhost" config[:shellout_opts][:environment]["KITCHEN_SERVER_ID"].must_equal "i-xxxxxx" config[:shellout_opts][:environment]["KITCHEN_PORT"].must_equal "22" config[:shellout_opts][:environment]["KITCHEN_INSTANCE"].must_equal "coolbeans-fries" config[:shellout_opts][:environment]["KITCHEN_PLATFORM"].must_equal "coolbeans" config[:shellout_opts][:environment]["KITCHEN_SUITE"].must_equal "fries" end it "raises ActionFailed if set false to :command" do config[:command] = "false" proc { verifier.call(state) }.must_raise Kitchen::ActionFailed end it "logs a converge event to INFO" do verifier.call(state) logged_output.string.must_match(/^.+ INFO .+ \[Shell\] Verify on .+$/) end end describe "remote_exec" do let(:transport) do t = mock("transport") t.responds_like_instance_of(Kitchen::Transport::Ssh) t end let(:connection) do c = mock("transport_connection") c.responds_like_instance_of(Kitchen::Transport::Ssh::Connection) c end let(:instance) do stub( name: "coolbeans", to_str: "instance", logger: logger, platform: platform, suite: suite, transport: transport ) end before do transport.stubs(:connection).yields(connection) connection.stubs(:execute) end it "execute command onto instance." do config[:remote_exec] = true transport.expects(:connection).with(state).yields(connection) verifier.call(state) end end end describe "#run_command" do it "execute localy and returns nil" do verifier.run_command end it "returns string when remote_exec" do config[:remote_exec] = true verifier.run_command.must_equal "true" end end end test-kitchen-1.23.2/spec/kitchen/lifecycle_hooks_spec.rb0000644000004100000410000001434513377651062023332 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Noah Kantrowitz # # Copyright (C) 2018, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/errors" require "kitchen/lifecycle_hooks" describe Kitchen::LifecycleHooks do let(:suite) { mock("suite").tap { |i| i.stubs(name: "default") } } let(:platform) { mock("platform").tap { |i| i.stubs(name: "toaster-1.0") } } let(:state_file) { mock("state_file").tap { |s| s.stubs(read: { hostname: "localhost" }) } } let(:connection) { mock("connection") } let(:transport) { mock("transport").tap { |t| t.stubs(:connection).with({ hostname: "localhost" }).returns(connection) } } let(:last_action) { :create } let(:instance) { mock("instance").tap { |i| i.stubs(name: "default-toaster-10", transport: transport, last_action: last_action, suite: suite, platform: platform) } } let(:config) { { kitchen_root: "/kitchen" } } let(:lifecycle_hooks) { Kitchen::LifecycleHooks.new(config).tap { |lh| lh.finalize_config!(instance) } } def run_lifecycle_hooks lifecycle_hooks.run_with_hooks(:create, state_file) {} end # Pull this out because it's used in a bunch of tests. STANDARD_LOCAL_OPTIONS = { cwd: "/kitchen", environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } } it "runs a single local command" do config.update(post_create: ["echo foo"]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "runs multiple local commands" do config.update(post_create: ["echo foo", { local: "echo bar" }]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) lifecycle_hooks.expects(:run_command).with("echo bar", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "runs multiple local hooks" do config.update(pre_create: ["echo foo"], post_create: ["echo bar"]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) lifecycle_hooks.expects(:run_command).with("echo bar", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "runs a local command with a user option" do config.update(post_create: [{ local: "echo foo", user: "bar" }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: "/kitchen", user: "bar", environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a local command with environment options" do config.update(post_create: [{ local: "echo foo", environment: { FOO: "one", BAR: "two" } }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: "/kitchen", environment: { "FOO" => "one", "BAR" => "two", "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a local command with a relative cwd option" do config.update(post_create: [{ local: "echo foo", cwd: "test" }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: os_safe_root_path("/kitchen/test"), environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a local command with an absolute cwd option" do config.update(post_create: [{ local: "echo foo", cwd: "/test" }]) lifecycle_hooks.expects(:run_command).with("echo foo", { cwd: os_safe_root_path("/test"), environment: { "KITCHEN_INSTANCE_NAME" => "default-toaster-10", "KITCHEN_SUITE_NAME" => "default", "KITCHEN_PLATFORM_NAME" => "toaster-1.0", "KITCHEN_INSTANCE_HOSTNAME" => "localhost", } }) run_lifecycle_hooks end it "runs a single remote command" do config.update(post_create: [{ remote: "echo foo" }]) lifecycle_hooks.expects(:run_command).never connection.expects(:execute).with("echo foo") run_lifecycle_hooks end it "rejects unknown hook targets" do config.update(post_create: [{ banana: "echo foo" }]) lifecycle_hooks.expects(:run_command).never proc { run_lifecycle_hooks }.must_raise Kitchen::UserError end it "runs mixed local and remote commands" do config.update(post_create: ["echo foo", { local: "echo bar" }, { remote: "echo baz" }]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) lifecycle_hooks.expects(:run_command).with("echo bar", STANDARD_LOCAL_OPTIONS) connection.expects(:execute).with("echo baz") run_lifecycle_hooks end describe "with no last_action" do let(:last_action) { nil } it "runs local commands" do config.update(post_create: [{ local: "echo foo" }]) lifecycle_hooks.expects(:run_command).with("echo foo", STANDARD_LOCAL_OPTIONS) run_lifecycle_hooks end it "fails on remote commands" do config.update(post_create: [{ remote: "echo foo" }]) lifecycle_hooks.expects(:run_command).never proc { run_lifecycle_hooks }.must_raise Kitchen::UserError end it "ignores skippable remote commands" do config.update(post_create: [{ remote: "echo foo", skippable: true }]) lifecycle_hooks.expects(:run_command).never run_lifecycle_hooks end end end test-kitchen-1.23.2/spec/kitchen/base64_stream_spec.rb0000644000004100000410000000446013377651062022624 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "base64" require "stringio" require "securerandom" require "kitchen/base64_stream" describe Kitchen::Base64Stream do SHORT_BODIES = %w{you test wakkawakkawakka}.freeze describe ".strict_encode" do SHORT_BODIES.each do |body| it "encodes short payload ('#{body}') from input IO to output IO" do output = StringIO.new("", "wb") StringIO.open(body) do |input| Kitchen::Base64Stream.strict_encode(input, output) end output.string.must_equal Base64.strict_encode64(body) end end it "encodes a large payload from input IO to output IO" do body = SecureRandom.random_bytes(1_048_576 * 8) output = StringIO.new("", "wb") StringIO.open(body) do |input| Kitchen::Base64Stream.strict_encode(input, output) end output.string.must_equal Base64.strict_encode64(body) end end describe ".strict_decode" do SHORT_BODIES.map { |b| Base64.strict_encode64(b) }.each do |body| it "decodes short payload ('#{body}') from input IO to output IO" do output = StringIO.new("", "wb") StringIO.open(body) do |input| Kitchen::Base64Stream.strict_decode(input, output) end output.string.must_equal Base64.strict_decode64(body) end end it "decodes a large payload from input IO to output IO" do body = Base64.strict_encode64(SecureRandom.hex(1_048_576 * 8)) output = StringIO.new("", "wb") StringIO.open(body) do |input| Kitchen::Base64Stream.strict_decode(input, output) end output.string.must_equal Base64.strict_decode64(body) end end end test-kitchen-1.23.2/spec/kitchen/lazy_hash_spec.rb0000644000004100000410000000676613377651062022162 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/lazy_hash" describe Kitchen::LazyHash do let(:context) do stub(color: "blue", metal: "heavy") end let(:hash_obj) do { shed_color: ->(c) { c.color }, barn: "locked", genre: proc { |c| "#{c.metal} metal" }, } end describe "#[]" do it "returns regular values for keys" do Kitchen::LazyHash.new(hash_obj, context)[:barn].must_equal "locked" end it "invokes call on values that are lambdas" do Kitchen::LazyHash.new(hash_obj, context)[:shed_color].must_equal "blue" end it "invokes call on values that are Procs" do Kitchen::LazyHash.new(hash_obj, context)[:genre].must_equal "heavy metal" end end describe "#fetch" do it "returns regular hash values for keys" do Kitchen::LazyHash.new(hash_obj, context).fetch(:barn).must_equal "locked" end it "invokes call on values that are lambdas" do Kitchen::LazyHash.new(hash_obj, context) .fetch(:shed_color).must_equal "blue" end it "invokes call on values that are Procs" do Kitchen::LazyHash.new(hash_obj, context) .fetch(:genre).must_equal "heavy metal" end it "uses a default value for unset values" do Kitchen::LazyHash.new(hash_obj, context) .fetch(:nope, "candy").must_equal "candy" end it "uses a block for unset values" do Kitchen::LazyHash.new(hash_obj, context) .fetch(:nope) { |key| "#{key} is costly" }.must_equal "nope is costly" end end describe "#to_hash" do it "invokes any callable values and returns a Hash object" do converted = Kitchen::LazyHash.new(hash_obj, context).to_hash converted.must_be_instance_of Hash converted.fetch(:shed_color).must_equal "blue" converted.fetch(:barn).must_equal "locked" converted.fetch(:genre).must_equal "heavy metal" end end describe "select" do it "calls Procs when appropriate" do Kitchen::LazyHash.new(hash_obj, context).select { |_, _| true } .must_equal shed_color: "blue", barn: "locked", genre: "heavy metal" end end describe "enumerable" do it "is an Enumerable" do assert Kitchen::LazyHash.new(hash_obj, context).is_a? Enumerable end it "returns an Enumerator from each() if no block given" do e = Kitchen::LazyHash.new(hash_obj, context).each e.is_a? Enumerator e.next.must_equal [:shed_color, "blue"] e.next.must_equal [:barn, "locked"] e.next.must_equal [:genre, "heavy metal"] end it "yields each item to the block if a block is given to each()" do items = [] Kitchen::LazyHash.new(hash_obj, context).each { |i| items << i } items.must_equal [[:shed_color, "blue"], [:barn, "locked"], [:genre, "heavy metal"]] end end end test-kitchen-1.23.2/spec/kitchen/logger_spec.rb0000644000004100000410000002454413377651062021451 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen" describe Kitchen::Logger do before do @orig_stdout = $stdout $stdout = StringIO.new end after do $stdout = @orig_stdout end def colorize(*args) Kitchen::Color.colorize(*args) end def log_tmpname t = Time.now.strftime("%Y%m%d") "kitchen-#{t}-#{$$}-#{rand(0x100000000).to_s(36)}.log" end let(:opts) do { color: :red, colorize: true } end let(:logger) do Kitchen::Logger.new(opts) end it "sets the log level to :info by default" do logger.level.must_equal Kitchen::Util.to_logger_level(:info) logger.debug?.must_equal false logger.info?.must_equal true logger.error?.must_equal true logger.warn?.must_equal true logger.fatal?.must_equal true end it "sets a level at creation" do opts[:level] = Kitchen::Util.to_logger_level(:warn) logger.level.must_equal Kitchen::Util.to_logger_level(:warn) logger.info?.must_equal false logger.warn?.must_equal true logger.fatal?.must_equal true end it "sets a level after creation" do logger.level = Kitchen::Util.to_logger_level(:fatal) logger.level.must_equal Kitchen::Util.to_logger_level(:fatal) logger.warn?.must_equal false logger.fatal?.must_equal true end it "datetime_format is nil by default" do logger.datetime_format.must_be_nil end it "sets datetime_format after creation" do logger.datetime_format = "smart?" logger.datetime_format.must_equal "smart?" end it "sets progname to Kitchen by default" do logger.progname.must_equal "Kitchen" end it "sets progname at creation" do opts[:progname] = "Dream Theater" logger.progname.must_equal "Dream Theater" end it "sets progname after creation" do logger.progname = "MASTA" logger.progname.must_equal "MASTA" end describe "stdout-based logger" do let(:stdout) { StringIO.new } before { opts[:stdout] = stdout } it "sets up a simple STDOUT logger by default" do opts.delete(:stdout) logger.info("hello") $stdout.string.must_equal colorize(" hello", opts[:color]) + "\n" end it "sets up a simple STDOUT logger by default with no color" do opts[:colorize] = false opts.delete(:stdout) logger.info("hello") $stdout.string.must_equal " hello\n" end it "accepts a :stdout option to redirect output" do logger.info("hello") stdout.string.must_equal colorize(" hello", opts[:color]) + "\n" end it "accepts a :stdout option to redirect output with no color" do opts[:colorize] = false logger.info("hello") stdout.string.must_equal " hello\n" end describe "for severity" do before { opts[:level] = Kitchen::Util.to_logger_level(:debug) } it "logs to banner" do logger.banner("yo") stdout.string.must_equal colorize("-----> yo", opts[:color]) + "\n" end it "logs to banner with no color" do opts[:colorize] = false logger.banner("yo") stdout.string.must_equal "-----> yo\n" end it "logs to debug" do logger.debug("yo") stdout.string.must_equal colorize("D yo", opts[:color]) + "\n" end it "logs to debug with no color" do opts[:colorize] = false logger.debug("yo") stdout.string.must_equal "D yo\n" end it "logs to info" do logger.info("yo") stdout.string.must_equal colorize(" yo", opts[:color]) + "\n" end it "logs to info with no color" do opts[:colorize] = false logger.info("yo") stdout.string.must_equal " yo\n" end it "logs to error" do logger.error("yo") stdout.string.must_equal colorize(">>>>>> yo", opts[:color]) + "\n" end it "logs to error with no color" do opts[:colorize] = false logger.error("yo") stdout.string.must_equal ">>>>>> yo\n" end it "logs to warn" do logger.warn("yo") stdout.string.must_equal colorize("$$$$$$ yo", opts[:color]) + "\n" end it "logs to warn with no color" do opts[:colorize] = false logger.warn("yo") stdout.string.must_equal "$$$$$$ yo\n" end it "logs to fatal" do logger.fatal("yo") stdout.string.must_equal colorize("!!!!!! yo", opts[:color]) + "\n" end it "logs to fatal with no color" do opts[:colorize] = false logger.fatal("yo") stdout.string.must_equal "!!!!!! yo\n" end it "logs to unknown" do logger.unknown("yo") stdout.string.must_equal colorize("?????? yo", opts[:color]) + "\n" end it "logs to unknown with no color" do opts[:colorize] = false logger.unknown("yo") stdout.string.must_equal "?????? yo\n" end end describe "#<<" do it "message with a newline are logged on info" do logger << "yo\n" stdout.string.must_equal colorize(" yo", opts[:color]) + "\n" end it "message with multiple newlines are separately logged on info" do logger << "yo\nheya\n" stdout.string.must_equal( colorize(" yo", opts[:color]) + "\n" + colorize(" heya", opts[:color]) + "\n" ) end it "message with info, error, and banner lines will be preserved" do logger << [ "-----> banner", " info", ">>>>>> error", "vanilla", ].join("\n").concat("\n") stdout.string.must_equal( colorize("-----> banner", opts[:color]) + "\n" + colorize(" info", opts[:color]) + "\n" + colorize(">>>>>> error", opts[:color]) + "\n" + colorize(" vanilla", opts[:color]) + "\n" ) end it "message with line that is not newline terminated will be buffered" do logger << [ "-----> banner", " info", "partial", ].join("\n") stdout.string.must_equal( colorize("-----> banner", opts[:color]) + "\n" + colorize(" info", opts[:color]) + "\n" ) end it "logger with buffered data will flush on next message with newline" do logger << "partial" logger << "ly\nokay\n" stdout.string.must_equal( colorize(" partially", opts[:color]) + "\n" + colorize(" okay", opts[:color]) + "\n" ) end it "logger that receives mixed first chunk will flush next message with newline" do logger << "partially\no" logger << "kay\n" stdout.string.must_equal( colorize(" partially", opts[:color]) + "\n" + colorize(" okay", opts[:color]) + "\n" ) end it "logger chomps carriage return characters" do logger << [ "-----> banner\r", "vanilla\r", ].join("\n").concat("\n") stdout.string.must_equal( colorize("-----> banner", opts[:color]) + "\n" + colorize(" vanilla", opts[:color]) + "\n" ) end end end describe "opened IO logdev-based logger" do let(:logdev) { StringIO.new } before { opts[:logdev] = logdev } describe "for severity" do before { opts[:level] = Kitchen::Util.to_logger_level(:debug) } let(:ts) { '\\[[^\\]]+\\]' } it "logs to banner" do logger.banner("yo") logdev.string.must_match(/^I, #{ts} INFO -- Kitchen: -----> yo$/) end it "logs to debug" do logger.debug("yo") logdev.string.must_match(/^D, #{ts} DEBUG -- Kitchen: yo$/) end it "logs to info" do logger.info("yo") logdev.string.must_match(/^I, #{ts} INFO -- Kitchen: yo$/) end it "logs to error" do logger.error("yo") logdev.string.must_match(/^E, #{ts} ERROR -- Kitchen: yo$/) end it "logs to warn" do logger.warn("yo") logdev.string.must_match(/^W, #{ts} WARN -- Kitchen: yo$/) end it "logs to fatal" do logger.fatal("yo") logdev.string.must_match(/^F, #{ts} FATAL -- Kitchen: yo$/) end it "logs to unknown" do logger.unknown("yo") logdev.string.must_match(/^A, #{ts} ANY -- Kitchen: yo$/) end end end describe "file IO logdev-based logger" do let(:logfile) { File.join Dir.tmpdir, log_tmpname } before do opts[:logdev] = logfile FakeFS.activate! FileUtils.mkdir_p("/tmp") end after do FakeFS.deactivate! FakeFS::FileSystem.clear end describe "for severity" do before { opts[:level] = Kitchen::Util.to_logger_level(:debug) } let(:ts) { '\\[[^\\]]+\\]' } it "logs to banner" do logger.banner("yo") IO.read(logfile).must_match(/^I, #{ts} INFO -- Kitchen: -----> yo$/) end it "logs to debug" do logger.debug("yo") IO.read(logfile).must_match(/^D, #{ts} DEBUG -- Kitchen: yo$/) end it "logs to info" do logger.info("yo") IO.read(logfile).must_match(/^I, #{ts} INFO -- Kitchen: yo$/) end it "logs to error" do logger.error("yo") IO.read(logfile).must_match(/^E, #{ts} ERROR -- Kitchen: yo$/) end it "logs to warn" do logger.warn("yo") IO.read(logfile).must_match(/^W, #{ts} WARN -- Kitchen: yo$/) end it "logs to fatal" do logger.fatal("yo") IO.read(logfile).must_match(/^F, #{ts} FATAL -- Kitchen: yo$/) end it "logs to unknown" do logger.unknown("yo") IO.read(logfile).must_match(/^A, #{ts} ANY -- Kitchen: yo$/) end end end end test-kitchen-1.23.2/spec/kitchen/configurable_spec.rb0000644000004100000410000010070713377651062022626 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "stringio" require "kitchen" require "kitchen/errors" require "kitchen/configurable" module Kitchen module Thing class Tiny include Kitchen::Configurable attr_reader :instance def initialize(config = {}) init_config(config) @instance = config[:instance] end end class Versioned < Tiny plugin_version "1.8.17" end class StaticDefaults include Kitchen::Configurable default_config :beans, "kidney" default_config :tunables, "flimflam" => "positate" default_config :edible, true default_config :fetch_command, "curl" default_config :success_path, "./success" default_config :bunch_of_paths, %w{./a ./b ./c} default_config :beans_url do |subject| "http://gim.me/#{subject[:beans]}" end default_config :command do |subject| "#{subject[:fetch_command]} #{subject[:beans_url]}" end default_config :fetch_url do |subject| "http://gim.me/beans-for/#{subject.instance.name}" end required_config :need_it required_config :a_default required_config :no_nuts do |attr, value, _subject| raise UserError, "NO NUTS FOR #{attr}!" if value == "nuts" end expand_path_for :success_path expand_path_for :bunch_of_paths expand_path_for :relative_path, false expand_path_for :another_path expand_path_for :complex_path do |subject| subject[:something_else] == "is_set" end def initialize(config = {}) init_config(config) end end class SubclassDefaults < StaticDefaults default_config :yea, "ya" default_config :fetch_command, "wget" default_config :fetch_url, "http://no.beans" required_config :a_default do |_attr, value, _subject| raise UserError, "Overriding a_default is fun" unless value == "please" end expand_path_for :another_path, false end end end describe Kitchen::Configurable do let(:config) { Hash.new } let(:platform) { stub } let(:instance) do stub(name: "coolbeans", to_str: "", platform: platform) end let(:subject) do Kitchen::Thing::Tiny.new(config).finalize_config!(instance) end describe "creation and setup" do it "#instance returns its instance" do subject.instance.must_equal instance end it "#finalize_config! raises ClientError if instance is nil" do proc { Kitchen::Thing::Tiny.new({}).finalize_config!(nil) } .must_raise(Kitchen::ClientError) end it "#finalize_config! returns self for chaining" do t = Kitchen::Thing::Tiny.new({}) t.finalize_config!(instance).must_equal t end end describe "configuration" do describe "provided from the outside" do it "returns provided config" do config[:fruit] = %w{apples oranges} config[:cool_enough] = true subject[:fruit].must_equal %w{apples oranges} subject[:cool_enough].must_equal true end end describe "using static default_config statements" do let(:config) do { need_it: true, a_default: true } end let(:subject) do Kitchen::Thing::StaticDefaults.new(config).finalize_config!(instance) end it "uses defaults" do subject[:beans].must_equal "kidney" subject[:tunables]["flimflam"].must_equal "positate" subject[:edible].must_equal true end it "uses provided config over default_config" do config[:beans] = "pinto" config[:edible] = false subject[:beans].must_equal "pinto" subject[:edible].must_equal false end it "uses other config values to compute values" do subject[:beans_url].must_equal "http://gim.me/kidney" subject[:command].must_equal "curl http://gim.me/kidney" end it "computed value blocks have access to instance object" do subject[:fetch_url].must_equal "http://gim.me/beans-for/coolbeans" end it "uses provided config over default_config for computed values" do config[:command] = "echo listentome" config[:beans] = "pinto" subject[:command].must_equal "echo listentome" subject[:beans_url].must_equal "http://gim.me/pinto" end end describe "using inherited static default_config statements" do let(:config) do { need_it: true, a_default: "please" } end let(:subject) do Kitchen::Thing::SubclassDefaults.new(config).finalize_config!(instance) end it "contains defaults from superclass" do subject[:beans].must_equal "kidney" subject[:tunables]["flimflam"].must_equal "positate" subject[:edible].must_equal true subject[:yea].must_equal "ya" end it "uses provided config over default config" do config[:beans] = "pinto" config[:edible] = false subject[:beans].must_equal "pinto" subject[:edible].must_equal false subject[:yea].must_equal "ya" subject[:beans_url].must_equal "http://gim.me/pinto" end it "uses its own default_config over inherited default_config" do subject[:fetch_url].must_equal "http://no.beans" subject[:command].must_equal "wget http://gim.me/kidney" end end describe "using static required_config statements" do let(:config) do { a_default: true } end let(:subject) do Kitchen::Thing::StaticDefaults.new(config).finalize_config!(instance) end it "uses a value when provided" do config[:need_it] = "okay" subject[:need_it].must_equal "okay" end it "without a block, raises a UserError if attr is nil" do config[:need_it] = nil begin subject flunk "UserError must be raised" rescue Kitchen::UserError => e attr = "Kitchen::Thing::StaticDefaults#config[:need_it]" e.message.must_equal "#{attr} cannot be blank" end end it "without a block, raises a UserError if attr is an empty string" do config[:need_it] = "" begin subject flunk "UserError must be raised" rescue Kitchen::UserError => e attr = "Kitchen::Thing::StaticDefaults#config[:need_it]" e.message.must_equal "#{attr} cannot be blank" end end it "with a block, it is saved and invoked" do config[:need_it] = "okay" config[:no_nuts] = "nuts" begin subject flunk "UserError must be raised" rescue Kitchen::UserError => e e.message.must_equal "NO NUTS FOR no_nuts!" end end end describe "using inherited static require_config statements" do let(:subject) do Kitchen::Thing::SubclassDefaults.new(config).finalize_config!(instance) end it "contains required config from superclass" do config[:a_default] = nil config[:need_it] = nil begin subject flunk "UserError must be raised" rescue Kitchen::UserError => e attr = "Kitchen::Thing::StaticDefaults#config[:need_it]" e.message.must_equal "#{attr} cannot be blank" end end it "uses its own require_config over inherited require_config" do config[:need_it] = true config[:a_default] = nil begin subject flunk "UserError must be raised" rescue Kitchen::UserError => e e.message.must_equal "Overriding a_default is fun" end end end describe "using static expand_path_for statements" do let(:config) do { need_it: "a", a_default: "b", kitchen_root: "/tmp/yo/self" } end let(:subject) do Kitchen::Thing::StaticDefaults.new(config).finalize_config!(instance) end it "expands a default value" do subject[:success_path].must_equal os_safe_root_path("/tmp/yo/self/success") end it "uses provided config over default_config" do config[:success_path] = "mine" subject[:success_path].must_equal os_safe_root_path("/tmp/yo/self/mine") end it "leaves a full path expanded" do config[:success_path] = "/the/other/one" subject[:success_path].must_equal os_safe_root_path("/the/other/one") end it "expands all items if path is an array" do paths = %w{ /tmp/yo/self/a /tmp/yo/self/b /tmp/yo/self/c } os_safe_paths = paths.collect { |path| os_safe_root_path(path) } subject[:bunch_of_paths].must_equal os_safe_paths end it "doesn't expand path with a falsy expand_path_for value" do config[:relative_path] = "./rel" subject[:relative_path].must_equal "./rel" end it "expands a path if a lambda returns truthy" do config[:something_else] = "is_set" config[:complex_path] = "./complex" subject[:complex_path].must_equal os_safe_root_path("/tmp/yo/self/complex") end it "leaves a nil config value as nil" do config[:success_path] = nil subject[:success_path].must_be_nil end it "leaves a false config value as false" do config[:success_path] = false subject[:success_path].must_equal false end end describe "using inherited static expand_path_for statements" do let(:config) do { need_it: "a", a_default: "please", kitchen_root: "/rooty" } end let(:subject) do Kitchen::Thing::SubclassDefaults.new(config).finalize_config!(instance) end it "contains expand_path_for from superclass" do subject[:success_path].must_equal os_safe_root_path("/rooty/success") end it "uses its own expand_path_for over inherited expand_path_for" do config[:another_path] = "./pp" subject[:another_path].must_equal "./pp" end end it "#config_keys returns an array of config key names" do subject = Kitchen::Thing::Tiny.new(ice_cream: "dragon") subject.config_keys.sort.must_equal [:ice_cream] end end it "#name returns the name of the plugin" do subject.name.must_equal "Tiny" end describe "#diagnose" do it "returns an empty hash for no config" do subject.diagnose.must_equal Hash.new end it "returns a hash of config" do config[:alpha] = "beta" subject.diagnose.must_equal(alpha: "beta") end it "returns a hash with sorted keys" do config[:zebra] = true config[:elephant] = true subject.diagnose.keys.must_equal [:elephant, :zebra] end end describe "#diagnose_plugin" do it "returns a plugin hash for a plugin without version" do subject.diagnose_plugin.must_equal( name: "Tiny", class: "Kitchen::Thing::Tiny", version: nil, api_version: nil ) end it "returns a plugin hash for a plugin with version" do subject = Kitchen::Thing::Versioned.new(config).finalize_config!(instance) subject.diagnose_plugin.must_equal( name: "Versioned", class: "Kitchen::Thing::Versioned", version: "1.8.17", api_version: nil ) end end describe "#calculate_path" do let(:config) do { test_base_path: "/the/basest" } end let(:suite) do stub(name: "ultimate") end let(:instance) do stub(name: "coolbeans", to_str: "", suite: suite) end let(:subject) do Kitchen::Thing::Tiny.new(config).finalize_config!(instance) end before do FakeFS.activate! end after do FakeFS.deactivate! FakeFS::FileSystem.clear end describe "for directories" do before do FileUtils.mkdir_p(File.join(Dir.pwd, "winner")) FileUtils.mkdir_p("/the/basest/winner") FileUtils.mkdir_p("/the/basest/ultimate/winner") end it "prefers a path containing base path and suite name if it exists" do subject.calculate_path("winner") .must_equal "/the/basest/ultimate/winner" end it "prefers a path containing base path if it exists" do FileUtils.rm_rf("/the/basest/ultimate/winner") subject.calculate_path("winner").must_equal "/the/basest/winner" end it "prefers a path in the current working directory if it exists" do FileUtils.rm_rf("/the/basest/ultimate/winner") FileUtils.rm_rf("/the/basest/winner") pwd_dir = File.join(Dir.pwd, "winner") subject.calculate_path("winner").must_equal pwd_dir end it "raises a UserError if test_base_path key is not set" do config.delete(:test_base_path) proc { subject.calculate_path("winner") }.must_raise Kitchen::UserError end it "uses a custom base path" do FileUtils.mkdir_p("/custom/ultimate/winner") subject.calculate_path("winner", base_path: "/custom") .must_equal "/custom/ultimate/winner" end end describe "for files" do before do FileUtils.mkdir_p(Dir.pwd) FileUtils.touch(File.join(Dir.pwd, "winner")) FileUtils.mkdir_p("/the/basest") FileUtils.touch(File.join("/the/basest", "winner")) FileUtils.mkdir_p("/the/basest/ultimate") FileUtils.touch(File.join("/the/basest/ultimate", "winner")) end it "prefers a path containing base path and suite name if it exists" do subject.calculate_path("winner", type: :file) .must_equal "/the/basest/ultimate/winner" end it "prefers a path containing base path if it exists" do FileUtils.rm_rf("/the/basest/ultimate/winner") subject.calculate_path("winner", type: :file) .must_equal "/the/basest/winner" end it "prefers a path in the current working directory if it exists" do FileUtils.rm_rf("/the/basest/ultimate/winner") FileUtils.rm_rf("/the/basest/winner") pwd_dir = File.join(Dir.pwd, "winner") subject.calculate_path("winner", type: :file).must_equal pwd_dir end it "raises a UserError if test_base_path key is not set" do config.delete(:test_base_path) proc { subject.calculate_path("winner") }.must_raise Kitchen::UserError end it "uses a custom base path" do FileUtils.mkdir_p("/custom/ultimate") FileUtils.touch(File.join("/custom/ultimate", "winner")) subject.calculate_path("winner", type: :file, base_path: "/custom") .must_equal "/custom/ultimate/winner" end end end describe "#remote_path_join" do it "returns unix style path separators for unix os_type" do platform.stubs(:os_type).returns("unix") subject.remote_path_join("a", "b", "c").must_equal "a/b/c" end it "returns windows style path separators for windows os_type" do platform.stubs(:os_type).returns("windows") subject.remote_path_join("a", "b", "c").must_equal 'a\\b\\c' end it "accepts combinations of strings and arrays" do platform.stubs(:os_type).returns("unix") subject.remote_path_join(%w{a b}, "c", %w{d e}).must_equal "a/b/c/d/e" end it "accepts a single array" do platform.stubs(:os_type).returns("windows") subject.remote_path_join(%w{a b}).must_equal 'a\\b' end it "converts all windows path separators to unix for unix os_type" do platform.stubs(:os_type).returns("unix") subject.remote_path_join('\\a\\b', "c/d").must_equal "/a/b/c/d" end it "converts all unix path separators to windows for windows os_type" do platform.stubs(:os_type).returns("windows") subject.remote_path_join("/a/b", 'c\\d').must_equal '\\a\\b\\c\\d' end end describe "#windows_os?" do it "for windows type platform returns true" do platform.stubs(:os_type).returns("windows") subject.windows_os?.must_equal true end it "for unix type platform returns false" do platform.stubs(:os_type).returns("unix") subject.windows_os?.must_equal false end it "for newfangled type platform return false" do platform.stubs(:os_type).returns("internet_cat") subject.windows_os?.must_equal false end it "for unset type platform returns false" do platform.stubs(:os_type).returns(nil) subject.windows_os?.must_equal false end end describe "#unix_os?" do it "for windows type platform returns false" do platform.stubs(:os_type).returns("windows") subject.unix_os?.must_equal false end it "for unix type platform returns true" do platform.stubs(:os_type).returns("unix") subject.unix_os?.must_equal true end it "for newfangled type platform return false" do platform.stubs(:os_type).returns("internet_cat") subject.unix_os?.must_equal false end it "for unset type platform returns true" do platform.stubs(:os_type).returns(nil) subject.unix_os?.must_equal true end end describe "#powershell_shell?" do it "for powershell type shell returns true" do platform.stubs(:shell_type).returns("powershell") subject.powershell_shell?.must_equal true end it "for bourne type shell returns false" do platform.stubs(:shell_type).returns("bourne") subject.powershell_shell?.must_equal false end it "for newfangled type shell return false" do platform.stubs(:shell_type).returns("internet_cat") subject.powershell_shell?.must_equal false end it "for unset type shell returns false" do platform.stubs(:shell_type).returns(nil) subject.powershell_shell?.must_equal false end end describe "#bourne_shell?" do it "for powershell type shell returns false" do platform.stubs(:shell_type).returns("powershell") subject.bourne_shell?.must_equal false end it "for bourne type shell returns true" do platform.stubs(:shell_type).returns("bourne") subject.bourne_shell?.must_equal true end it "for newfangled type shell return false" do platform.stubs(:shell_type).returns("internet_cat") subject.bourne_shell?.must_equal false end it "for unset type shell returns true" do platform.stubs(:shell_type).returns(nil) subject.bourne_shell?.must_equal true end end describe "#shell_env_var" do it "for powershell type shells returns a powershell environment variable" do platform.stubs(:shell_type).returns("powershell") subject.send(:shell_env_var, "foo", "bar") .must_equal %{$env:foo = "bar"} end it "for bourne type shells returns a bourne environment variable" do platform.stubs(:shell_type).returns("bourne") subject.send(:shell_env_var, "foo", "bar") .must_equal %{foo="bar"; export foo} end end describe "#shell_var" do it "for powershell type shells returns a powershell variable" do platform.stubs(:shell_type).returns("powershell") subject.send(:shell_var, "foo", "bar").must_equal %{$foo = "bar"} end it "for bourne type shells returns a bourne variable" do platform.stubs(:shell_type).returns("bourne") subject.send(:shell_var, "foo", "bar").must_equal %{foo="bar"} end end describe "#wrap_shell_code" do let(:cmd) { subject.send(:wrap_shell_code, "mkdir foo") } before do @original_env = ENV.to_hash ENV.replace("http_proxy" => nil, "HTTP_PROXY" => nil, "https_proxy" => nil, "HTTPS_PROXY" => nil, "ftp_proxy" => nil, "FTP_PROXY" => nil, "no_proxy" => nil, "NO_PROXY" => nil, "CI" => nil) end after do ENV.clear ENV.replace(@original_env) end describe "for bourne shells" do before { platform.stubs(:shell_type).returns("bourne") } it "uses bourne shell (sh)" do cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' http_proxy="http://proxy"; export http_proxy HTTP_PROXY="http://proxy"; export HTTP_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' https_proxy="https://proxy"; export https_proxy HTTPS_PROXY="https://proxy"; export HTTPS_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports ftp_proxy & FTP_PROXY when :ftp_proxy is set" do config[:ftp_proxy] = "ftp://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' ftp_proxy="ftp://proxy"; export ftp_proxy FTP_PROXY="ftp://proxy"; export FTP_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports all http proxy variables when all are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" config[:no_proxy] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' http_proxy="http://proxy"; export http_proxy HTTP_PROXY="http://proxy"; export HTTP_PROXY https_proxy="https://proxy"; export https_proxy HTTPS_PROXY="https://proxy"; export HTTPS_PROXY ftp_proxy="ftp://proxy"; export ftp_proxy FTP_PROXY="ftp://proxy"; export FTP_PROXY no_proxy="http://no"; export no_proxy NO_PROXY="http://no"; export NO_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports http_proxy & HTTP_PROXY from workstation when :http_proxy isn't set" do ENV["http_proxy"] = "http://proxy" ENV["HTTP_PROXY"] = "http://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' http_proxy="http://proxy"; export http_proxy HTTP_PROXY="http://proxy"; export HTTP_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports https_proxy & HTTPS_PROXY from workstation when :https_proxy isn't set" do ENV["https_proxy"] = "https://proxy" ENV["HTTPS_PROXY"] = "https://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' https_proxy="https://proxy"; export https_proxy HTTPS_PROXY="https://proxy"; export HTTPS_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "does not export http_proxy or HTTP_PROXY when :http_proxy is empty" do ENV["http_proxy"] = "http://proxy" ENV["HTTP_PROXY"] = "http://proxy" config[:http_proxy] = "" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "does not export https_proxy or HTTPS_PROXY when :https_proxy is empty" do ENV["https_proxy"] = "https://proxy" ENV["HTTPS_PROXY"] = "https://proxy" config[:https_proxy] = "" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports ftp_proxy & FTP_PROXY from workstation when :ftp_proxy isn't set" do ENV["ftp_proxy"] = "ftp://proxy" ENV["FTP_PROXY"] = "ftp://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' ftp_proxy="ftp://proxy"; export ftp_proxy FTP_PROXY="ftp://proxy"; export FTP_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "does not export ftp_proxy or FTP_PROXY when :ftp_proxy is empty" do config[:ftp_proxy] = "" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports no_proxy & NO_PROXY from workstation when http_proxy is set from workstation" do ENV["http_proxy"] = "http://proxy" ENV["HTTP_PROXY"] = "http://proxy" ENV["no_proxy"] = "http://no" ENV["NO_PROXY"] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' http_proxy="http://proxy"; export http_proxy HTTP_PROXY="http://proxy"; export HTTP_PROXY no_proxy="http://no"; export no_proxy NO_PROXY="http://no"; export NO_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports no_proxy & NO_PROXY from workstation when https_proxy is set from workstation" do ENV["https_proxy"] = "https://proxy" ENV["HTTPS_PROXY"] = "https://proxy" ENV["no_proxy"] = "http://no" ENV["NO_PROXY"] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' https_proxy="https://proxy"; export https_proxy HTTPS_PROXY="https://proxy"; export HTTPS_PROXY no_proxy="http://no"; export no_proxy NO_PROXY="http://no"; export NO_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports no_proxy & NO_PROXY from workstation when ftp_proxy is set from workstation" do ENV["ftp_proxy"] = "ftp://proxy" ENV["FTP_PROXY"] = "ftp://proxy" ENV["no_proxy"] = "http://no" ENV["NO_PROXY"] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' ftp_proxy="ftp://proxy"; export ftp_proxy FTP_PROXY="ftp://proxy"; export FTP_PROXY no_proxy="http://no"; export no_proxy NO_PROXY="http://no"; export NO_PROXY TEST_KITCHEN="1"; export TEST_KITCHEN mkdir foo ' CODE end it "exports CI when CI is set" do ENV["CI"] = "1" cmd.must_equal(outdent!(<<-CODE.chomp)) sh -c ' TEST_KITCHEN="1"; export TEST_KITCHEN CI="1"; export CI mkdir foo ' CODE end end describe "for powershell shells" do before { platform.stubs(:shell_type).returns("powershell") } it "uses powershell shell" do cmd.must_equal("$env:TEST_KITCHEN = \"1\"\nmkdir foo") end it "exports http_proxy & HTTP_PROXY when :http_proxy is set" do config[:http_proxy] = "http://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:http_proxy = "http://proxy" $env:HTTP_PROXY = "http://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports https_proxy & HTTPS_PROXY when :https_proxy is set" do config[:https_proxy] = "https://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:https_proxy = "https://proxy" $env:HTTPS_PROXY = "https://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports ftp_proxy & FTP_PROXY when :ftp_proxy is set" do config[:ftp_proxy] = "ftp://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:ftp_proxy = "ftp://proxy" $env:FTP_PROXY = "ftp://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports all http proxy variables when all are set" do config[:http_proxy] = "http://proxy" config[:https_proxy] = "https://proxy" config[:ftp_proxy] = "ftp://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:http_proxy = "http://proxy" $env:HTTP_PROXY = "http://proxy" $env:https_proxy = "https://proxy" $env:HTTPS_PROXY = "https://proxy" $env:ftp_proxy = "ftp://proxy" $env:FTP_PROXY = "ftp://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports http_proxy & HTTP_PROXY from workstation when :http_proxy isn't set" do ENV["http_proxy"] = "http://proxy" ENV["HTTP_PROXY"] = "http://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:http_proxy = "http://proxy" $env:HTTP_PROXY = "http://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports https_proxy & HTTPS_PROXY from workstation when :https_proxy isn't set" do ENV["https_proxy"] = "https://proxy" ENV["HTTPS_PROXY"] = "https://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:https_proxy = "https://proxy" $env:HTTPS_PROXY = "https://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports ftp_proxy & FTP_PROXY from workstation when :ftp_proxy isn't set" do ENV["ftp_proxy"] = "ftp://proxy" ENV["FTP_PROXY"] = "ftp://proxy" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:ftp_proxy = "ftp://proxy" $env:FTP_PROXY = "ftp://proxy" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports no_proxy & NO_PROXY from workstation when http_proxy is set from workstation" do ENV["http_proxy"] = "http://proxy" ENV["HTTP_PROXY"] = "http://proxy" ENV["no_proxy"] = "http://no" ENV["NO_PROXY"] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:http_proxy = "http://proxy" $env:HTTP_PROXY = "http://proxy" $env:no_proxy = "http://no" $env:NO_PROXY = "http://no" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports no_proxy & NO_PROXY from workstation when https_proxy is set from workstation" do ENV["https_proxy"] = "https://proxy" ENV["HTTPS_PROXY"] = "https://proxy" ENV["no_proxy"] = "http://no" ENV["NO_PROXY"] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:https_proxy = "https://proxy" $env:HTTPS_PROXY = "https://proxy" $env:no_proxy = "http://no" $env:NO_PROXY = "http://no" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports no_proxy & NO_PROXY from workstation when ftp_proxy is set from workstation" do ENV["ftp_proxy"] = "ftp://proxy" ENV["FTP_PROXY"] = "ftp://proxy" ENV["no_proxy"] = "http://no" ENV["NO_PROXY"] = "http://no" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:ftp_proxy = "ftp://proxy" $env:FTP_PROXY = "ftp://proxy" $env:no_proxy = "http://no" $env:NO_PROXY = "http://no" $env:TEST_KITCHEN = "1" mkdir foo CODE end it "exports CI when CI is set" do ENV["CI"] = "1" cmd.must_equal(outdent!(<<-CODE.chomp)) $env:TEST_KITCHEN = "1" $env:CI = "1" mkdir foo CODE end end end it "has a default verify dependencies method" do subject.verify_dependencies.must_be_nil end describe "#logger" do before { @klog = Kitchen.logger } after { Kitchen.logger = @klog } it "returns the instance's logger" do logger = stub("logger") instance = stub(logger: logger) subject = Kitchen::Thing::Tiny.new(config.merge(instance: instance)) subject.send(:logger).must_equal logger end it "returns the default logger if instance's logger is not set" do subject = Kitchen::Thing::Tiny.new(config) Kitchen.logger = "yep" subject.send(:logger).must_equal Kitchen.logger end end def outdent!(*args) Kitchen::Util.outdent!(*args) end end test-kitchen-1.23.2/spec/kitchen/state_file_spec.rb0000644000004100000410000000604313377651062022303 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../spec_helper" require "kitchen/errors" require "kitchen/state_file" require "kitchen/util" class YamledState attr_accessor :yoinks end describe Kitchen::StateFile do let(:state_file) { Kitchen::StateFile.new("/tmp", "oftheunion") } let(:file_name) { "/tmp/.kitchen/oftheunion.yml" } before do FakeFS.activate! FileUtils.mkdir_p("/tmp") end after do FakeFS.deactivate! FakeFS::FileSystem.clear end describe "#read" do it "returns an empty hash if the file does not exist" do state_file.read.must_equal({}) end it "returns and empty hash if the file is zero length" do stub_state_file!("") state_file.read.must_equal({}) end it "returns a Hash with symbolized keys from the state file" do stub_state_file! state_file.read.must_equal( cloud_id: 42, flavor: "extra_crispy" ) end it "arbitrary objects aren't deserialized from state file" do stub_state_file! <<-'YAML'.gsub(/^ {8}/, "") --- !ruby/object:YamledState yoinks: zoinks YAML proc { state_file.read }.must_raise Kitchen::StateFileLoadError end it "raises a StateFileLoadError if the state file cannot be parsed" do stub_state_file!("&*%^*") proc { state_file.read }.must_raise Kitchen::StateFileLoadError end end describe "#write" do it "creates the directory path to the state file" do File.directory?("/tmp/.kitchen").must_equal false state_file.write({}) File.directory?("/tmp/.kitchen").must_equal true end it "writes a state file with stringified keys" do state_file.write(thekey: "thyself") IO.read(file_name).split("\n").must_include "thekey: thyself" end end describe "#destroy" do it "executes if no file exists" do File.exist?(file_name).must_equal false state_file.destroy File.exist?(file_name).must_equal false end it "deletes the state file" do stub_state_file! state_file.destroy File.exist?(file_name).must_equal false end end private def stub_state_file!(yaml_string = nil) if yaml_string.nil? yaml_string = <<-'YAML'.gsub(/^ {8}/, "") --- cloud_id: 42 flavor: extra_crispy YAML end FileUtils.mkdir_p(File.dirname(file_name)) File.open(file_name, "wb") { |f| f.write(yaml_string) } end end test-kitchen-1.23.2/CHANGELOG.md0000644000004100000410000036557613377651062016102 0ustar www-datawww-data# Change Log ## [v1.23.2](https://github.com/test-kitchen/test-kitchen/tree/v1.23.2) (2018-08-06) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.23.0...v1.23.2) **Closed issues:** - Race condition: conflicting chdir during another chdir block [\#1416](https://github.com/test-kitchen/test-kitchen/issues/1416) **Merged pull requests:** - Synchronize calls to chdir to be thread safe [\#1430](https://github.com/test-kitchen/test-kitchen/pull/1430) ([s-bernard](https://github.com/s-bernard)) - Catch 'Operation already in progress' as seen on Ubuntu on WSL [\#1435](https://github.com/test-kitchen/test-kitchen/pull/1435) ([bdwyertech](https://github.com/bdwyertech)) - Fix \#1104 add supplemental kitchen commands [\#1105](https://github.com/test-kitchen/test-kitchen/pull/1105) ([4-20ma](https://github.com/4-20ma)) ## [v1.23.0](https://github.com/test-kitchen/test-kitchen/tree/v1.23.0) (2018-07-30) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.22.0...v1.23.0) **Merged pull requests:** - Lifecycle hooks [\#1428](https://github.com/test-kitchen/test-kitchen/pull/1428) ([coderanger](https://github.com/coderanger)) - Minor technical cleanup and unify behavior for files and directories. [\#1401](https://github.com/test-kitchen/test-kitchen/pull/1401) ([coderanger](https://github.com/coderanger)) ## [v1.22.0](https://github.com/test-kitchen/test-kitchen/tree/v1.22.0) (2018-06-28) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.21.2...v1.22.0) - Add the ssh_gateway_port config in ssh transport [#1421](https://github.com/test-kitchen/test-kitchen/pull/1421) ([sjeandeaux](https://github.com/sjeandeaux)) - Shell Provisioner: make script executable [#1381](https://github.com/test-kitchen/test-kitchen/pull/1381) ([thewyzard44](https://github.com/thewyzard44)) - Stop calling the Chef packages omnibus packages [#1425](https://github.com/test-kitchen/test-kitchen/pull/1425) ([tas50](https://github.com/tas50)) ## [v1.21.2](https://github.com/test-kitchen/test-kitchen/tree/v1.21.2) (2018-05-07) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.21.1...v1.21.2) - Removing thor upper bound in step with berks [\#1410](https://github.com/test-kitchen/test-kitchen/pull/1410) ([cheeseplus](https://github.com/cheeseplus) ) ## [v1.21.1](https://github.com/test-kitchen/test-kitchen/tree/v1.21.1) (2018-04-18) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.21.0...v1.21.1) - Revert: honor root\_path for location of chef installer script [\#1369] ## [v1.21.0](https://github.com/test-kitchen/test-kitchen/tree/v1.21.0) (2018-04-16) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.20.0...v1.21.0) **Merged pull requests:** - Support `\*\_YML` for env vars too, for better UX [\#1398](https://github.com/test-kitchen/test-kitchen/pull/1398) ([coderanger](https://github.com/coderanger)) - allow winrm-fs 1.2.0 [\#1396](https://github.com/test-kitchen/test-kitchen/pull/1396) ([gtmanfred](https://github.com/gtmanfred)) - added KITCHEN\_YML [\#1392](https://github.com/test-kitchen/test-kitchen/pull/1392) ([jjasghar](https://github.com/jjasghar)) - Rubocop appeasement [\#1379](https://github.com/test-kitchen/test-kitchen/pull/1379) ([robbkidd](https://github.com/robbkidd)) - don't add drivers to a project's Gemfile during init [\#1378](https://github.com/test-kitchen/test-kitchen/pull/1378) ([robbkidd](https://github.com/robbkidd)) - halt if visible & hidden default configs are both present [\#1377](https://github.com/test-kitchen/test-kitchen/pull/1377) ([robbkidd](https://github.com/robbkidd)) - Update and fix appveyor [\#1373](https://github.com/test-kitchen/test-kitchen/pull/1373) ([cheeseplus](https://github.com/cheeseplus)) - Support IPv6 addresses for WinRM [\#1371](https://github.com/test-kitchen/test-kitchen/pull/1371) ([jzinn](https://github.com/jzinn)) - honor root\_path for location of chef installer script [\#1369](https://github.com/test-kitchen/test-kitchen/pull/1369) ([robbkidd](https://github.com/robbkidd)) - Prefer kitchen.yml to .kitchen.yml [\#1363](https://github.com/test-kitchen/test-kitchen/pull/1363) ([thommay](https://github.com/thommay)) - Support yaml alias [\#1359](https://github.com/test-kitchen/test-kitchen/pull/1359) ([limitusus](https://github.com/limitusus)) - Adding Ruby 2.5, updating other versions [\#1348](https://github.com/test-kitchen/test-kitchen/pull/1348) ([cheeseplus](https://github.com/cheeseplus)) - Update CentOS 7 / Ubuntu to the latest versions [\#1289](https://github.com/test-kitchen/test-kitchen/pull/1289) ([tas50](https://github.com/tas50)) ## [v1.20.0](https://github.com/test-kitchen/test-kitchen/tree/v1.20.0) (2018-01-19) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.19.2...v1.20.0) **Merged pull requests:** - Support multiple paths for data bags [\#1313](https://github.com/test-kitchen/test-kitchen/pull/1313) ([thomasdziedzic](https://github.com/thomasdziedzic)) - Support for configuration deprecation warnings [\#1303](https://github.com/test-kitchen/test-kitchen/pull/1303) ([wrightp](https://github.com/wrightp)) - Support for SSH via an HTTP Proxy [\#1329](https://github.com/test-kitchen/test-kitchen/pull/1329) ([NAshwini](https://github.com/NAshwini)) - Add download support to the base transport and provisioner [\#1306](https://github.com/test-kitchen/test-kitchen/pull/1306) ([atheiman](https://github.com/atheiman)) - Fix download support for WinRM [\#1338](https://github.com/test-kitchen/test-kitchen/pull/1338) ([gtmanfred](https://github.com/gtmanfred)) - Fix code to validate retry\_on\_exit\_code [\#1312](https://github.com/test-kitchen/test-kitchen/pull/1312) ([NAshwini](https://github.com/NAshwini)) - Remove safe\_yaml [\#1328](https://github.com/test-kitchen/test-kitchen/pull/1328) ([coderanger](https://github.com/coderanger)) - Pin minitest to 5.11 [\#1339](https://github.com/test-kitchen/test-kitchen/pull/1339) ([cheeseplus](https://github.com/cheeseplus)) ## [v1.19.2](https://github.com/test-kitchen/test-kitchen/tree/v1.19.2) (2017-11-28) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.19.1...v1.19.2) **Release Notes:** - Updated the winrm-fs depdency from ~> 1.0.2 to ~> 1.1.0 to allow using the newer version ## [v1.19.1](https://github.com/test-kitchen/test-kitchen/tree/v1.19.1) (2017-11-17) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.19.0...v1.19.1) **Release Notes:** Removed an extraneous bash shebang that caused the script generated to install chef-client to fail on certain platforms. **Merged pull requests** - Remove extraneous bash shebang. [\#1317](https://github.com/test-kitchen/test-kitchen/pull/1317) ([rhass](https://github.com/rhass)) ## [v1.19.0](https://github.com/test-kitchen/test-kitchen/tree/v1.19.0) (2017-11-1) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.18.0...v1.19.0) **Merged pull requests** - Basic framework for kitchen doctor [\#1301](https://github.com/test-kitchen/test-kitchen/pull/1301) ([coderanger](https://github.com/coderanger)) - add kitchen-sparkleformation driver to ECOSYSTEM.md [\#1300](https://github.com/test-kitchen/test-kitchen/pull/1300) ([pesimon](https://github.com/pesimon)) - Add a --debug command line option [\#1296](https://github.com/test-kitchen/test-kitchen/pull/1296) ([coderanger](https://github.com/coderanger)) - Exec driver [\#1295](https://github.com/test-kitchen/test-kitchen/pull/1295) ([coderanger](https://github.com/coderanger)) - Misc cleanups [\#1294](https://github.com/test-kitchen/test-kitchen/pull/1294) ([coderanger](https://github.com/coderanger)) - Upgrades to the shell provisioner [\#1293](https://github.com/test-kitchen/test-kitchen/pull/1293) ([coderanger](https://github.com/coderanger)) - Remove the `driver create` and `driver discover` commands [\#1290](https://github.com/test-kitchen/test-kitchen/pull/1290) ([coderanger](https://github.com/coderanger)) - Adds pre_create_command for running arbitrary commands [\#1243](https://github.com/test-kitchen/test-kitchen/pull/1243) ([sean797](https://github.com/sean797)) - Added better routine to install Busser+Plugins [\#1083](https://github.com/test-kitchen/test-kitchen/pull/1083) ([yeoldegrove](https://github.com/yeoldegrove)) ## [v1.18.0](https://github.com/test-kitchen/test-kitchen/tree/v1.18.0) (2017-09-28) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.17.0...v1.18.0) **Fixed bugs:** - Shell verifier: Print instance name instead of object [\#1263](https://github.com/test-kitchen/test-kitchen/pull/1263) ([rbngzlv](https://github.com/rbngzlv)) **Merged pull requests:** - reset\_command doesn't actually need to be required [\#1286](https://github.com/test-kitchen/test-kitchen/pull/1286) ([coderanger](https://github.com/coderanger)) - Continue to support older net-ssh while fixing 4.2 deprecation [\#1285](https://github.com/test-kitchen/test-kitchen/pull/1285) ([cheeseplus](https://github.com/cheeseplus)) - Update winrm-fs and make winrm\* gems proper deps [\#1284](https://github.com/test-kitchen/test-kitchen/pull/1284) ([cheeseplus](https://github.com/cheeseplus)) - Pin to net-ssh 4.1.0 for now [\#1283](https://github.com/test-kitchen/test-kitchen/pull/1283) ([cheeseplus](https://github.com/cheeseplus)) - idempotent\_check: Allow specificaton of enforce\_idempotency [\#1282](https://github.com/test-kitchen/test-kitchen/pull/1282) ([MarkGibbons](https://github.com/MarkGibbons)) - Support renamed net-ssh option `verify\_host\_key` [\#1281](https://github.com/test-kitchen/test-kitchen/pull/1281) ([cheeseplus](https://github.com/cheeseplus)) - Reorganized a section and added kitchen-vcenter [\#1279](https://github.com/test-kitchen/test-kitchen/pull/1279) ([jjasghar](https://github.com/jjasghar)) - Add proxy support when using product\_name [\#1276](https://github.com/test-kitchen/test-kitchen/pull/1276) ([wrightp](https://github.com/wrightp)) - Remove Ruby 1.8.7 compat code [\#1274](https://github.com/test-kitchen/test-kitchen/pull/1274) ([tas50](https://github.com/tas50)) - Move extra dev deps to the Gemfile [\#1273](https://github.com/test-kitchen/test-kitchen/pull/1273) ([tas50](https://github.com/tas50)) - Add tas50 as a maintainer [\#1270](https://github.com/test-kitchen/test-kitchen/pull/1270) ([tas50](https://github.com/tas50)) - Swap IRC for Slack in the readme [\#1269](https://github.com/test-kitchen/test-kitchen/pull/1269) ([tas50](https://github.com/tas50)) - Remove rack pin for Ruby 2.1 & move changelog gen to gemfile [\#1268](https://github.com/test-kitchen/test-kitchen/pull/1268) ([tas50](https://github.com/tas50)) - Add download\_url and checksum provisioner config options [\#1267](https://github.com/test-kitchen/test-kitchen/pull/1267) ([wrightp](https://github.com/wrightp)) - Add kitchen-terraform to the readme [\#1266](https://github.com/test-kitchen/test-kitchen/pull/1266) ([tas50](https://github.com/tas50)) - New install\_strategy option used in conjunction with product\_name [\#1262](https://github.com/test-kitchen/test-kitchen/pull/1262) ([wrightp](https://github.com/wrightp)) - Allow command line arguments config in shell provisioner [\#943](https://github.com/test-kitchen/test-kitchen/pull/943) ([mmckinst](https://github.com/mmckinst)) ## [v1.17.0](https://github.com/test-kitchen/test-kitchen/tree/v1.17.0) (2017-08-11) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.16.0...v1.17.0) **Fixed bugs:** - Fix Dir.glob usage [\#1258](https://github.com/test-kitchen/test-kitchen/pull/1258) ([jaym](https://github.com/jaym)) ## [v1.16.0](https://github.com/test-kitchen/test-kitchen/tree/v1.16.0) (2017-03-03) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.15.0...v1.16.0) **Implemented enhancements:** - Enforce suite idempotency [\#874](https://github.com/test-kitchen/test-kitchen/issues/874) - Export no\_proxy from kitchen config [\#1178](https://github.com/test-kitchen/test-kitchen/pull/1178) ([itmustbejj](https://github.com/itmustbejj)) - Adding transport option "ssh\_key\_only". [\#1141](https://github.com/test-kitchen/test-kitchen/pull/1141) ([cliles](https://github.com/cliles)) - Run chef-client twice in chef-zero provisioner [\#875](https://github.com/test-kitchen/test-kitchen/pull/875) ([kamaradclimber](https://github.com/kamaradclimber)) **Fixed bugs:** - Pinning thor to match berks [\#1189](https://github.com/test-kitchen/test-kitchen/pull/1189) ([cheeseplus](https://github.com/cheeseplus)) **Closed issues:** - Message: Could not load the 'ansible\_playbook' provisioner from the load path [\#1197](https://github.com/test-kitchen/test-kitchen/issues/1197) - pull or push in a docker registry with kitchen [\#1186](https://github.com/test-kitchen/test-kitchen/issues/1186) - Compat issues with net-ssh 4.x [\#1184](https://github.com/test-kitchen/test-kitchen/issues/1184) - Changelog was not updated for the 1.15.0 release [\#1183](https://github.com/test-kitchen/test-kitchen/issues/1183) - Could not load or activate Berkshelf [\#1172](https://github.com/test-kitchen/test-kitchen/issues/1172) - WinRm - I/O Operation Aborted [\#1142](https://github.com/test-kitchen/test-kitchen/issues/1142) - Guest hostname does not get set if converge times out during vagrant VM boot [\#1128](https://github.com/test-kitchen/test-kitchen/issues/1128) - I'm trying to run kitchen converge but getting the converge IO error [\#1075](https://github.com/test-kitchen/test-kitchen/issues/1075) - Documentation for support for Encrypted Data Bags [\#384](https://github.com/test-kitchen/test-kitchen/issues/384) ## [v1.15.0](https://github.com/test-kitchen/test-kitchen/tree/v1.15.0) (2017-01-12) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.14.2...v1.15.0) **Implemented enhancements:** - Display the last action's success [\#1124](https://github.com/test-kitchen/test-kitchen/issues/1124) - Relax dependencies to bring in newer gem versions [\#1176](https://github.com/test-kitchen/test-kitchen/pull/1176) ([lamont-granquist](https://github.com/lamont-granquist)) - Make RakeTask\#config public. [\#1069](https://github.com/test-kitchen/test-kitchen/pull/1069) ([gregsymons](https://github.com/gregsymons)) **Fixed bugs:** - Fix busser trying to run bats when bats tests don't exist [\#1133](https://github.com/test-kitchen/test-kitchen/pull/1133) ([amontalban](https://github.com/amontalban)) **Closed issues:** - "incompatible character encodings: UTF-8 and ASCII-8BIT" when using cyrillic letters in cookbook [\#1170](https://github.com/test-kitchen/test-kitchen/issues/1170) - ssh\_key is not read and sent to the args for ssh transport [\#1169](https://github.com/test-kitchen/test-kitchen/issues/1169) ## [v1.14.2](https://github.com/test-kitchen/test-kitchen/tree/v1.14.2) (2016-12-09) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.14.1...v1.14.2) **Implemented enhancements:** - Replace finstyle in favor of chefstyle [\#1166](https://github.com/test-kitchen/test-kitchen/pull/1166) ([afiune](https://github.com/afiune)) ## [v1.14.1](https://github.com/test-kitchen/test-kitchen/tree/v1.14.1) (2016-12-08) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.14.0...v1.14.1) **Closed issues:** - Getting message: "Expected array default value for '--driver'; got "kitchen-vagrant" \(string\)" with every operation [\#1163](https://github.com/test-kitchen/test-kitchen/issues/1163) - Possible to specify a custom bootstrap template? [\#1162](https://github.com/test-kitchen/test-kitchen/issues/1162) - Deployment of cookbooks do differ from berks package [\#1158](https://github.com/test-kitchen/test-kitchen/issues/1158) - Failed to complete \#create action: \[undefined method '\[\]' for nil:NilClass\] [\#1157](https://github.com/test-kitchen/test-kitchen/issues/1157) - inspec works, but kitchen verify fails [\#1154](https://github.com/test-kitchen/test-kitchen/issues/1154) **Merged pull requests:** - Fix typo in berkshelf chef provisioner. [\#1160](https://github.com/test-kitchen/test-kitchen/pull/1160) ([thommay](https://github.com/thommay)) - Update MAINTAINERS.md [\#1156](https://github.com/test-kitchen/test-kitchen/pull/1156) ([afiune](https://github.com/afiune)) - Fix to work with Thor 0.19.2 [\#1155](https://github.com/test-kitchen/test-kitchen/pull/1155) ([coderanger](https://github.com/coderanger)) ## [v1.14.0](https://github.com/test-kitchen/test-kitchen/tree/v1.14.0) (2016-11-22) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.13.2...v1.14.0) **Implemented enhancements:** - Test Kitchen should use omnitruck's -d option by default [\#809](https://github.com/test-kitchen/test-kitchen/issues/809) **Closed issues:** - Kitchen converge fails, doesn't install omnibus, \[\[WinRM::FS::Core::FileTransporter\] Upload failed [\#1150](https://github.com/test-kitchen/test-kitchen/issues/1150) - Re-Enable Code Climate [\#1146](https://github.com/test-kitchen/test-kitchen/issues/1146) - kitchen + berkshelf don't work together with the latest versions of gems [\#1144](https://github.com/test-kitchen/test-kitchen/issues/1144) - Vagrant drivers brings up virtualbox machine with 'cable connected' disabled option [\#1143](https://github.com/test-kitchen/test-kitchen/issues/1143) - kitchen converge throws Berkshelf::LockfileNotFound on Windows [\#1140](https://github.com/test-kitchen/test-kitchen/issues/1140) - Inspect tests is an empty value when using the kitchen\_ec2 driver [\#1136](https://github.com/test-kitchen/test-kitchen/issues/1136) - kitchen test or verify with --parallel option fails [\#1125](https://github.com/test-kitchen/test-kitchen/issues/1125) **Merged pull requests:** - Added `cache` interface for Drivers so that provisioners can leverage [\#1149](https://github.com/test-kitchen/test-kitchen/pull/1149) ([afiune](https://github.com/afiune)) - Ensure that we only berks update with a lockfile [\#1145](https://github.com/test-kitchen/test-kitchen/pull/1145) ([thommay](https://github.com/thommay)) - Added `last\_error` and `--json` to `kitchen list` [\#1135](https://github.com/test-kitchen/test-kitchen/pull/1135) ([BackSlasher](https://github.com/BackSlasher)) - Allow the user to make deprecations errors [\#1117](https://github.com/test-kitchen/test-kitchen/pull/1117) ([thommay](https://github.com/thommay)) ## [v1.13.2](https://github.com/test-kitchen/test-kitchen/tree/v1.13.2) (2016-09-26) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.13.1...v1.13.2) **Fixed bugs:** - fix broken path on nano so shell out works [\#1129](https://github.com/test-kitchen/test-kitchen/pull/1129) ([mwrock](https://github.com/mwrock)) ## [v1.13.1](https://github.com/test-kitchen/test-kitchen/tree/v1.13.1) (2016-09-22) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.13.0...v1.13.1) **Implemented enhancements:** - Allow mixlib-install 2.0 [\#1126](https://github.com/test-kitchen/test-kitchen/pull/1126) ([jkeis er](https://github.com/jkeiser)) ## [v1.13.0](https://github.com/test-kitchen/test-kitchen/tree/v1.13.0) (2016-09-16) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.12.0...v1.13.0) **Implemented enhancements:** - Add `kitchen status` command [\#87](https://github.com/test-kitchen/test-kitchen/issues/87) - Add support for Windows Nano installs via chef provisioners [\#1119](https://github.com/test-kitchen/test-kitchen/pull/1119) ([mwrock](https://github.com/mwrock)) - Add package driver command [\#1074](https://github.com/test-kitchen/test-kitchen/pull/1074) ([neillturner](https://github.com/neillturner)) **Fixed bugs:** - SSH Transport: Bastion proxy results in broken pipe error [\#1079](https://github.com/test-kitchen/test-kitchen/issues/1079) ## [v1.12.0](https://github.com/test-kitchen/test-kitchen/tree/v1.12.0) (2016-09-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.11.1...v1.12.0) **Implemented enhancements:** - Use winrm v2 release gems [\#1061](https://github.com/test-kitchen/test-kitchen/pull/1061) ([mwrock](https://github.com/mwrock)) - Add a new config option always\_update\_cookbooks [\#1107](https://github.com/test-kitchen/test-kitchen/pull/1107) ([coderanger](https://github.com/coderanger)) - Always run `chef install` even if the lock file exists. [\#1103](https://github.com/test-kitchen/test-kitchen/pull/1103) ([coderanger](https://github.com/coderanger)) - support passing Kitchen::Config Hash keys to Kitchen::RakeTasks.new [\#1102](https://github.com/test-kitchen/test-kitchen/pull/1102) ([theckman](https://github.com/theckman)) ## [v1.11.1](https://github.com/test-kitchen/test-kitchen/tree/v1.11.1) (2016-08-13) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.11.0...v1.11.1) **Fixed bugs:** - Check the actual value, because `password: nil` shouldn't disable sending the key [\#1098](https://github.com/test-kitchen/test-kitchen/pull/1098) ([coderanger](https://github.com/coderanger)) ## [v1.11.0](https://github.com/test-kitchen/test-kitchen/tree/v1.11.0) (2016-08-11) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.10.2...v1.11.0) **Implemented enhancements:** - Provide some way for Chef to know it's running under test [\#458](https://github.com/test-kitchen/test-kitchen/issues/458) - Dont set ssh key configuration if a password is specified [\#1095](https://github.com/test-kitchen/test-kitchen/pull/1095) ([mwrock](https://github.com/mwrock)) - Ability to work with Instances over SSH tunnel. [\#1091](https://github.com/test-kitchen/test-kitchen/pull/1091) ([EYurchenko](https://github.com/EYurchenko)) - Add environment variables $TEST\_KITCHEN and $CI [\#1081](https://github.com/test-kitchen/test-kitchen/pull/1081) ([coderanger](https://github.com/coderanger)) - Adding test\_base\_path CLI arg to the diagnose command [\#1076](https://github.com/test-kitchen/test-kitchen/pull/1076) ([tyler-ball](https://github.com/tyler-ball)) - Add legacy\_mode argument for chef\_solo provisioner [\#1073](https://github.com/test-kitchen/test-kitchen/pull/1073) ([SaltwaterC](https://github.com/SaltwaterC)) - Added support for Chef 10 [\#1072](https://github.com/test-kitchen/test-kitchen/pull/1072) ([acondrat](https://github.com/acondrat)) **Fixed bugs:** - Escape paths before running policyfile commands [\#1085](https://github.com/test-kitchen/test-kitchen/pull/1085) ([coderanger](https://github.com/coderanger)) ## [v1.10.2](https://github.com/test-kitchen/test-kitchen/tree/v1.10.2) (2016-06-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.10.1...v1.10.2) **Fixed bugs:** - Mainly just a gem repackage against a clean repo on a linux machine ## [v1.10.1](https://github.com/test-kitchen/test-kitchen/tree/v1.10.1) (2016-06-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.10.0...v1.10.1) **Fixed bugs:** - Reboot resource with new 'reboot and try again' feature [\#1062](https://github.com/test-kitchen/test-kitchen/issues/1062) - Fix WinRM Upload Failures After Reboot [\#1064](https://github.com/test-kitchen/test-kitchen/pull/1064) ([smurawski](https://github.com/smurawski)) ## [v1.10.0](https://github.com/test-kitchen/test-kitchen/tree/v1.10.0) (2016-06-16) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.9.2...v1.10.0) **Implemented enhancements:** - Retry `Kitchen::Provisioner\#run\_command` after allowed exit codes [\#1055](https://github.com/test-kitchen/test-kitchen/pull/1055) ([smurawski](https://github.com/smurawski)) - Add fallback support for `policyfile` for compat with the older policyfile\_zero [\#1053](https://github.com/test-kitchen/test-kitchen/pull/1053) ([coderanger](https://github.com/coderanger)) ## [v1.9.2](https://github.com/test-kitchen/test-kitchen/tree/v1.9.2) (2016-06-09) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.9.1...v1.9.2) **Implemented enhancements:** - add max scp session handling [\#1047](https://github.com/test-kitchen/test-kitchen/pull/1047) ([lamont-granquist](https://github.com/lamont-granquist)) **Fixed bugs:** - Message: SCP upload failed \(open failed \(1\)\) [\#1035](https://github.com/test-kitchen/test-kitchen/issues/1035) ## [v1.9.1](https://github.com/test-kitchen/test-kitchen/tree/v1.9.1) (2016-06-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.9.0...v1.9.1) **Implemented enhancements:** - Allow rake task to use env var [\#1046](https://github.com/test-kitchen/test-kitchen/pull/1046) ([smurawski](https://github.com/smurawski)) - Add color options [\#1032](https://github.com/test-kitchen/test-kitchen/pull/1032) ([jorhett](https://github.com/jorhett)) - Add support for SSH connection debugging. [\#990](https://github.com/test-kitchen/test-kitchen/pull/990) ([rhass](https://github.com/rhass)) ## [1.9.0](https://github.com/test-kitchen/test-kitchen/tree/v1.9.0) (2016-05-26) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.8.0...v1.9.0) **Implemented enhancements:** - Buffer errors until the end of an action [\#1034](https://github.com/test-kitchen/test-kitchen/pull/1034) ([smurawski](https://github.com/smurawski)) - Added ECOSYSTEM doc highlight all the core Test-Kitchen and community plugins. [\#1015](https://github.com/test-kitchen/test-kitchen/pull/1015) ([jjasghar](https://github.com/jjasghar)) - Add kitchen-azurerm to list of community-provided drivers [\#1024](https://github.com/test-kitchen/test-kitchen/pull/1024) ([stuartpreston](https://github.com/stuartpreston)) - uploads: reuse connections+disable compression [\#1023](https://github.com/test-kitchen/test-kitchen/pull/1023) ([lamont-granquist](https://github.com/lamont-granquist)) **Fixed bugs:** - Use command\_prefix provided by Kitchen::Provisioner::Base in shell provisioner [\#1033](https://github.com/test-kitchen/test-kitchen/pull/1033) ([pstengel](https://github.com/pstengel)) - Empty string for the config setting for proxies did not really work [\#1027](https://github.com/test-kitchen/test-kitchen/pull/1027) ([smurawski](https://github.com/smurawski)) - Update `chef\_omnbius\_url` default value [\#1028](https://github.com/test-kitchen/test-kitchen/pull/1028) ([schisamo](https://github.com/schisamo)) - Fix grammar in common\_sandbox warning message [\#1031](https://github.com/test-kitchen/test-kitchen/pull/1031) ([emachnic](https://github.com/emachnic)) ## [1.8.0](https://github.com/test-kitchen/test-kitchen/tree/v1.8.0) (2016-05-05) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.7.3...v1.8.0) **Implemented enhancements:** - Add native policyfile resolution support [\#1014](https://github.com/test-kitchen/test-kitchen/pull/1014) ([danielsdeleo](https://github.com/danielsdeleo)) - Provide the option to run all winrm commands through a scheduled task [\#1012](https://github.com/test-kitchen/test-kitchen/pull/1012) ([mwrock](https://github.com/mwrock)) ## [1.7.3](https://github.com/test-kitchen/test-kitchen/tree/v1.7.3) (2016-04-13) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.7.2...v1.7.3) **Fixed bugs:** - Test Kitchen on windows fails to upload data bags [\#1006](https://github.com/test-kitchen/test-kitchen/issues/1006) - Fixes busser install for older omnibus windows installs [\#1003](https://github.com/test-kitchen/test-kitchen/pull/1003) ([mwrock](https://github.com/mwrock)) ## [1.7.2](https://github.com/test-kitchen/test-kitchen/tree/v1.7.2) (2016-04-07) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.7.1...v1.7.2) **Merged pull requests:** - Don't require dev dependencies to build [\#1000](https://github.com/test-kitchen/test-kitchen/pull/1000) ([jkeiser](https://github.com/jkeiser)) - update to win2k8 friendly dependencies [\#999](https://github.com/test-kitchen/test-kitchen/pull/999) ([mwrock](https://github.com/mwrock)) - Fix Berkshelf load test [\#998](https://github.com/test-kitchen/test-kitchen/pull/998) ([chefsalim](https://github.com/chefsalim)) ## [v1.7.1](https://github.com/test-kitchen/test-kitchen/tree/v1.7.1) (2016-04-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.7.1.dev...v1.7.1) **Fixed bugs:** - Adding gitattributes file for managing line ending conversions [\#991](https://github.com/test-kitchen/test-kitchen/pull/991) ([mwrock](https://github.com/mwrock)) ## [v1.7.0](https://github.com/test-kitchen/test-kitchen/tree/v1.7.0) (2016-04-01) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.6.0...v1.7.0) **Implemented enhancements:** - Travis and Appveyor should do actual kitchen create/converge/verify against PRs [\#980](https://github.com/test-kitchen/test-kitchen/pull/980) ([mwrock](https://github.com/mwrock)) - Use latest mixlib-install 1.0.2 [\#976](https://github.com/test-kitchen/test-kitchen/pull/976) ([mwrock](https://github.com/mwrock)) - Nominate Seth Thomas as lieutenant of Test Kitchen [\#975](https://github.com/test-kitchen/test-kitchen/pull/975) ([tyler-ball](https://github.com/tyler-ball)) - Create template for github issues [\#963](https://github.com/test-kitchen/test-kitchen/pull/963) ([smurawski](https://github.com/smurawski)) - Stop log\_level being copied from base config into provisioner config [\#950](https://github.com/test-kitchen/test-kitchen/pull/950) ([drrk](https://github.com/drrk)) **Fixed bugs:** - Fix encrypted data bag uploads on windows [\#981](https://github.com/test-kitchen/test-kitchen/pull/981) ([mwrock](https://github.com/mwrock)) - Shell verifier should ensure env vars are strings [\#973](https://github.com/test-kitchen/test-kitchen/pull/973) ([jsok](https://github.com/jsok)) - Support Empty Proxy Settings [\#936](https://github.com/test-kitchen/test-kitchen/pull/936) ([tacchino](https://github.com/tacchino)) ## [v1.6.0](https://github.com/test-kitchen/test-kitchen/tree/v1.6.0) (2016-02-29) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.5.0...v1.6.0) **Implemented enhancements:** - Publicly expose winrm session [\#670](https://github.com/test-kitchen/test-kitchen/issues/670) - Support Chef-DK [\#443](https://github.com/test-kitchen/test-kitchen/issues/443) - allow non-busser verifier to work with legacy drivers [\#944](https://github.com/test-kitchen/test-kitchen/pull/944) ([chris-rock](https://github.com/chris-rock)) - use winrm transport as alternative detection method [\#928](https://github.com/test-kitchen/test-kitchen/pull/928) ([chris-rock](https://github.com/chris-rock)) - Make chef-config an optional dependency. [\#924](https://github.com/test-kitchen/test-kitchen/pull/924) ([coderanger](https://github.com/coderanger)) - Deprecating winrm-tansport and winrm-s gems [\#902](https://github.com/test-kitchen/test-kitchen/pull/902) ([mwrock](https://github.com/mwrock)) - Add Provisioner chef\_apply [\#623](https://github.com/test-kitchen/test-kitchen/pull/623) ([sawanoboly](https://github.com/sawanoboly)) **Fixed bugs:** - encrypted\_data\_bag\_secret\_key\_path does not fully work with Chef 12.x [\#751](https://github.com/test-kitchen/test-kitchen/issues/751) - Permission denied for Busser [\#749](https://github.com/test-kitchen/test-kitchen/issues/749) - --force-formatter is passed to a version of chef-client that does not support it. [\#593](https://github.com/test-kitchen/test-kitchen/issues/593) - http\(s\)\_proxy in test [\#533](https://github.com/test-kitchen/test-kitchen/issues/533) - make rubocop glücklich [\#956](https://github.com/test-kitchen/test-kitchen/pull/956) ([chris-rock](https://github.com/chris-rock)) - properly initialize attributes for new negotiate [\#937](https://github.com/test-kitchen/test-kitchen/pull/937) ([chris-rock](https://github.com/chris-rock)) - Fix sudo dependency [\#932](https://github.com/test-kitchen/test-kitchen/pull/932) ([alexpop](https://github.com/alexpop)) **Closed issues:** - key not found: "src\_md5" on kitchen converge [\#954](https://github.com/test-kitchen/test-kitchen/issues/954) - Kitchen Converge Argument Error [\#940](https://github.com/test-kitchen/test-kitchen/issues/940) - Intermittent key not found: "src\_md5" failures on windows nodes [\#926](https://github.com/test-kitchen/test-kitchen/issues/926) - Chef Omnibus Windows Issues \(mixlib-install \#22 related\) [\#847](https://github.com/test-kitchen/test-kitchen/issues/847) - Invoking Rake tasks with concurrency? [\#799](https://github.com/test-kitchen/test-kitchen/issues/799) - msiexec was not successful [\#742](https://github.com/test-kitchen/test-kitchen/issues/742) - not able to force chef-client in local model even my .kitchen.yml said so. [\#739](https://github.com/test-kitchen/test-kitchen/issues/739) - TK attempts to download install.sh every converge [\#714](https://github.com/test-kitchen/test-kitchen/issues/714) - kitchen not detecting vagrant plugin `kitchen-vagrant` [\#622](https://github.com/test-kitchen/test-kitchen/issues/622) - Not correct URL for opensuse-13.1 platform [\#599](https://github.com/test-kitchen/test-kitchen/issues/599) - Error 404 if if chef-solo-search is anywhere in the dep-tree [\#591](https://github.com/test-kitchen/test-kitchen/issues/591) - Difference in tty behaviour between verify and converge [\#563](https://github.com/test-kitchen/test-kitchen/issues/563) - recipe idempotence checking [\#561](https://github.com/test-kitchen/test-kitchen/issues/561) - chefzero integration test with several docker containers [\#560](https://github.com/test-kitchen/test-kitchen/issues/560) - AWS is not a class \(TypeError\) [\#552](https://github.com/test-kitchen/test-kitchen/issues/552) - Test Kitchen setup issue [\#546](https://github.com/test-kitchen/test-kitchen/issues/546) - Run serverspec tests in 'ssh mode' instead of 'inside the machine' [\#539](https://github.com/test-kitchen/test-kitchen/issues/539) - Auto creating nodes [\#528](https://github.com/test-kitchen/test-kitchen/issues/528) - enable multi YAML configuration support [\#514](https://github.com/test-kitchen/test-kitchen/issues/514) - Allow for site-cookbooks when using Librarian [\#511](https://github.com/test-kitchen/test-kitchen/issues/511) - Support for running \*\_spec.rb according to the hostname or private ipaddress of a node [\#494](https://github.com/test-kitchen/test-kitchen/issues/494) - Local platform exclusions [\#493](https://github.com/test-kitchen/test-kitchen/issues/493) - Don't reset locale in Kitchen::Driver::Base run\_command\(\) [\#485](https://github.com/test-kitchen/test-kitchen/issues/485) - Intermittent 'kitchen test' failures [\#449](https://github.com/test-kitchen/test-kitchen/issues/449) - shell-provisioner: lots of trouble with a noexec /tmp, failing workaround. [\#444](https://github.com/test-kitchen/test-kitchen/issues/444) - Message: Failed to complete \#converge action: \[Permission denied [\#441](https://github.com/test-kitchen/test-kitchen/issues/441) - Idea: enable chef-zero to run on another server than the converged node. [\#437](https://github.com/test-kitchen/test-kitchen/issues/437) - Test Artifact Fetch Feature [\#434](https://github.com/test-kitchen/test-kitchen/issues/434) - Loading installed gem dependencies with busser plugins [\#406](https://github.com/test-kitchen/test-kitchen/issues/406) - Wrap mkdir in sudo\(\) for init\_command of chef\_base provisioner? [\#382](https://github.com/test-kitchen/test-kitchen/issues/382) - Unable to override `test\_base\_path` in test-kitchen v1.2.1 [\#377](https://github.com/test-kitchen/test-kitchen/issues/377) - Busser depends on Ruby \(ChefDK\) being available on target VM [\#347](https://github.com/test-kitchen/test-kitchen/issues/347) - Option to turn off ssh forwarding x11? [\#338](https://github.com/test-kitchen/test-kitchen/issues/338) **Merged pull requests:** - Update release process to use github changelog generator [\#952](https://github.com/test-kitchen/test-kitchen/pull/952) ([jkeiser](https://github.com/jkeiser)) - The Net::SSH::Extensions were overwriting IO.select agressively, so we scaled this down some [\#935](https://github.com/test-kitchen/test-kitchen/pull/935) ([tyler-ball](https://github.com/tyler-ball)) - bypass execution policy when running powershell script files [\#925](https://github.com/test-kitchen/test-kitchen/pull/925) ([mwrock](https://github.com/mwrock)) ## [v1.5.0](https://github.com/test-kitchen/test-kitchen/tree/v1.5.0) (2016-01-21) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.5.0.rc.1...v1.5.0) **Implemented enhancements:** - Cluster support with Kitchen [\#905](https://github.com/test-kitchen/test-kitchen/issues/905) - toggling attributes in kitchen.yml [\#884](https://github.com/test-kitchen/test-kitchen/issues/884) - Allow for "double-converges" on specific test suites [\#162](https://github.com/test-kitchen/test-kitchen/issues/162) - Added try/catch around main and set error action to stop [\#872](https://github.com/test-kitchen/test-kitchen/pull/872) ([mcallb](https://github.com/mcallb)) - Add hooks for instance cleanup before exit. [\#825](https://github.com/test-kitchen/test-kitchen/pull/825) ([coderanger](https://github.com/coderanger)) - add tests for empty or missing files [\#753](https://github.com/test-kitchen/test-kitchen/pull/753) ([miketheman](https://github.com/miketheman)) **Fixed bugs:** - kitchen init will modify Rakefile and cause RuboCop issues [\#915](https://github.com/test-kitchen/test-kitchen/issues/915) - \(Win2012r2\) Chef-client version to install seems to be ignored [\#882](https://github.com/test-kitchen/test-kitchen/issues/882) - No Proxy Settings in Setup Phase [\#821](https://github.com/test-kitchen/test-kitchen/issues/821) - It seems dna.json is being repeated [\#606](https://github.com/test-kitchen/test-kitchen/issues/606) - The netssh 3.0 update returns a different error on connection timeout than 2.9.2 did, adding it to the retry list [\#912](https://github.com/test-kitchen/test-kitchen/pull/912) ([tyler-ball](https://github.com/tyler-ball)) - Fix handling of chunked ssh output. [\#824](https://github.com/test-kitchen/test-kitchen/pull/824) ([kingpong](https://github.com/kingpong)) - Set default log level even if you forget to add it to command line arg [\#697](https://github.com/test-kitchen/test-kitchen/pull/697) ([scotthain](https://github.com/scotthain)) - Use single quotes in Rake/Thorfile templates [\#499](https://github.com/test-kitchen/test-kitchen/pull/499) ([chr4](https://github.com/chr4)) **Closed issues:** - Kubernetes driver [\#920](https://github.com/test-kitchen/test-kitchen/issues/920) - Latest build in chef-dk failing in travis [\#918](https://github.com/test-kitchen/test-kitchen/issues/918) - Unable to test Chef11 due to net-ssh [\#914](https://github.com/test-kitchen/test-kitchen/issues/914) - kitchen driver help message incorrect [\#903](https://github.com/test-kitchen/test-kitchen/issues/903) - No arg for -v option \(install.sh missing version number\) [\#900](https://github.com/test-kitchen/test-kitchen/issues/900) - n help converge [\#890](https://github.com/test-kitchen/test-kitchen/issues/890) - Chef Zero should be the default provisioner with init [\#889](https://github.com/test-kitchen/test-kitchen/issues/889) - Windows tests broken - mkdir -p [\#886](https://github.com/test-kitchen/test-kitchen/issues/886) - Berkshelf not managing dependencies [\#869](https://github.com/test-kitchen/test-kitchen/issues/869) - Errno::ETIMEDOUT needed in winrm transport [\#855](https://github.com/test-kitchen/test-kitchen/issues/855) - Appears to freeze on second converge. [\#850](https://github.com/test-kitchen/test-kitchen/issues/850) - How to specify RubyGem source in .kitchen.yml for serverspec gems? [\#844](https://github.com/test-kitchen/test-kitchen/issues/844) - f using serch to find self node [\#842](https://github.com/test-kitchen/test-kitchen/issues/842) - Kitchen : reconverge with another user [\#840](https://github.com/test-kitchen/test-kitchen/issues/840) - Can't transfer cookbook to Windows node using Chef Kitchen [\#818](https://github.com/test-kitchen/test-kitchen/issues/818) - ability to change location of test/integration/default/ [\#814](https://github.com/test-kitchen/test-kitchen/issues/814) - Kitchen destroy fails if VM manually removed [\#796](https://github.com/test-kitchen/test-kitchen/issues/796) - reconverge with test-kitchen [\#780](https://github.com/test-kitchen/test-kitchen/issues/780) - ssh breaks if vm restarts [\#769](https://github.com/test-kitchen/test-kitchen/issues/769) - Transfer files more efficiently. [\#657](https://github.com/test-kitchen/test-kitchen/issues/657) - Possibility to lock down versions of gems [\#515](https://github.com/test-kitchen/test-kitchen/issues/515) - Missing vagrant-wrapper gem, update test-kitchen gem dependencies? [\#488](https://github.com/test-kitchen/test-kitchen/issues/488) - : Message: SSH exited \(1\) for command: \[sh -c 'BUSSER\_ROOT="/tmp/busser" GEM\_HOME="/tmp/busser/gems" GEM\_PATH="/tmp/busser/gems" GEM\_CACHE="/tmp/busser/gems/cache" ; export BUSSER\_ROOT GEM\_HOME GEM\_PATH GEM\_CACHE; sudo -E /tmp/busser/bin/busser test'\] [\#411](https://github.com/test-kitchen/test-kitchen/issues/411) - TestKitchen isn't using VAGRANT\_HOME path [\#398](https://github.com/test-kitchen/test-kitchen/issues/398) - deal with travis [\#369](https://github.com/test-kitchen/test-kitchen/issues/369) - use a default path rubygems, ruby and busser [\#362](https://github.com/test-kitchen/test-kitchen/issues/362) - Bats tests are being executed even missing specification [\#360](https://github.com/test-kitchen/test-kitchen/issues/360) - shell provisioner: Add a KITCHEN\_DIR environment variable [\#349](https://github.com/test-kitchen/test-kitchen/issues/349) - Don't use generic descriptions for create, converge, setup, verify, and destroy [\#344](https://github.com/test-kitchen/test-kitchen/issues/344) - Exception Handler does not always print out anything to stdout [\#281](https://github.com/test-kitchen/test-kitchen/issues/281) **Merged pull requests:** - 150 release prep [\#921](https://github.com/test-kitchen/test-kitchen/pull/921) ([tyler-ball](https://github.com/tyler-ball)) - Because net/ssh is no longer including timeout.rb, we need to so that Ruby doesn't think Timeout belongs to the TK class [\#919](https://github.com/test-kitchen/test-kitchen/pull/919) ([tyler-ball](https://github.com/tyler-ball)) - Diet travis [\#911](https://github.com/test-kitchen/test-kitchen/pull/911) ([cheeseplus](https://github.com/cheeseplus)) - Revert "fix driver help output" [\#910](https://github.com/test-kitchen/test-kitchen/pull/910) ([cheeseplus](https://github.com/cheeseplus)) - Updating to the latest release of net-ssh to consume https://github.com/net-ssh/net-ssh/pull/280 [\#908](https://github.com/test-kitchen/test-kitchen/pull/908) ([tyler-ball](https://github.com/tyler-ball)) - Set version to 1.5.0 [\#907](https://github.com/test-kitchen/test-kitchen/pull/907) ([jkeiser](https://github.com/jkeiser)) - Adding Maintainers file [\#906](https://github.com/test-kitchen/test-kitchen/pull/906) ([cheeseplus](https://github.com/cheeseplus)) - fix driver help output [\#904](https://github.com/test-kitchen/test-kitchen/pull/904) ([akissa](https://github.com/akissa)) - Add support for --profile-ruby [\#901](https://github.com/test-kitchen/test-kitchen/pull/901) ([martinb3](https://github.com/martinb3)) - fix chef install on non-windows [\#899](https://github.com/test-kitchen/test-kitchen/pull/899) ([mwrock](https://github.com/mwrock)) - typo: on != no [\#897](https://github.com/test-kitchen/test-kitchen/pull/897) ([miketheman](https://github.com/miketheman)) - Fix Windows Omnibus Install \#811 [\#864](https://github.com/test-kitchen/test-kitchen/pull/864) ([dissonanz](https://github.com/dissonanz)) - add cli option to set the test path [\#857](https://github.com/test-kitchen/test-kitchen/pull/857) ([chris-rock](https://github.com/chris-rock)) - WinRM connect \(with retry\) is failing on Windows [\#835](https://github.com/test-kitchen/test-kitchen/pull/835) ([Stift](https://github.com/Stift)) - update omnibus url to chef.io [\#827](https://github.com/test-kitchen/test-kitchen/pull/827) ([andrewelizondo](https://github.com/andrewelizondo)) - Add more options for WinRM [\#776](https://github.com/test-kitchen/test-kitchen/pull/776) ([smurawski](https://github.com/smurawski)) ## [v1.5.0.rc.1](https://github.com/test-kitchen/test-kitchen/tree/v1.5.0.rc.1) (2015-12-29) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.4.2...v1.5.0.rc.1) **Implemented enhancements:** - Drop Ruby 1.9 support [\#806](https://github.com/test-kitchen/test-kitchen/issues/806) - fixed SuSe OS busser install [\#816](https://github.com/test-kitchen/test-kitchen/pull/816) ([Peuserik](https://github.com/Peuserik)) - Honor proxy env vars. [\#813](https://github.com/test-kitchen/test-kitchen/pull/813) ([mcquin](https://github.com/mcquin)) - Drop Ruby 1.9.3 from TravisCI build matrix [\#804](https://github.com/test-kitchen/test-kitchen/pull/804) ([thommay](https://github.com/thommay)) - Use mixlib-install [\#782](https://github.com/test-kitchen/test-kitchen/pull/782) ([thommay](https://github.com/thommay)) **Fixed bugs:** - Make lazyhash enumerable [\#752](https://github.com/test-kitchen/test-kitchen/pull/752) ([caboteria](https://github.com/caboteria)) **Closed issues:** - WinrRM "The device is not ready" [\#891](https://github.com/test-kitchen/test-kitchen/issues/891) - kitchen starts linux machine with run level 2 by default [\#881](https://github.com/test-kitchen/test-kitchen/issues/881) - Failing to parse .kitchen.yml with ChefDK 0.9.0 on Windows 7 [\#877](https://github.com/test-kitchen/test-kitchen/issues/877) - policyfile\_zero doesn't use attributes in .kitchen.yml [\#870](https://github.com/test-kitchen/test-kitchen/issues/870) - http proxy for "Installing Chef Omnibus" part? [\#867](https://github.com/test-kitchen/test-kitchen/issues/867) - data\_munger, NoMethodError [\#865](https://github.com/test-kitchen/test-kitchen/issues/865) - Waiting for SSH service on 127.0.0.1:2222, retrying in 3 seconds [\#862](https://github.com/test-kitchen/test-kitchen/issues/862) - test-kitchen winrm w/proxies "The command line is too long." [\#854](https://github.com/test-kitchen/test-kitchen/issues/854) - kitchen converge error [\#853](https://github.com/test-kitchen/test-kitchen/issues/853) - /opt/chef/version-manifest.txt doesn't have proper version on line one, causing extra installations via Omnibus [\#846](https://github.com/test-kitchen/test-kitchen/issues/846) - SSL read error when attempting to download Ubuntu 12.04 box for simple converge [\#834](https://github.com/test-kitchen/test-kitchen/issues/834) - chefdk install issues [\#830](https://github.com/test-kitchen/test-kitchen/issues/830) - Test Kitchen does not detect ports listening to localhost on Windows [\#828](https://github.com/test-kitchen/test-kitchen/issues/828) - serverspec tests fail on windows [\#823](https://github.com/test-kitchen/test-kitchen/issues/823) - Error in test kitchen exits shell [\#822](https://github.com/test-kitchen/test-kitchen/issues/822) - Cannot use an http/https url pointing to a vagrant metadata json file for box\_url [\#819](https://github.com/test-kitchen/test-kitchen/issues/819) - kitchen converge does not execute sleep command [\#812](https://github.com/test-kitchen/test-kitchen/issues/812) - Serverspec `command` does not seem to be working... [\#773](https://github.com/test-kitchen/test-kitchen/issues/773) - Chef-Solo cache deleted by WinRM transport [\#680](https://github.com/test-kitchen/test-kitchen/issues/680) - Feature: 'vagrant reload' for kitchen [\#678](https://github.com/test-kitchen/test-kitchen/issues/678) **Merged pull requests:** - Adding the CHANGELOG and version.rb update for 1.5.0.rc.1 [\#898](https://github.com/test-kitchen/test-kitchen/pull/898) ([tyler-ball](https://github.com/tyler-ball)) - Fixing garbled output for chef\_zero provisioner [\#896](https://github.com/test-kitchen/test-kitchen/pull/896) ([someara](https://github.com/someara)) - Adding in ChefConfig support to enable loading proxy config from chef config files [\#895](https://github.com/test-kitchen/test-kitchen/pull/895) ([tyler-ball](https://github.com/tyler-ball)) - Adding the Travis config necessary to run the proxy\_tests [\#894](https://github.com/test-kitchen/test-kitchen/pull/894) ([tyler-ball](https://github.com/tyler-ball)) - Adding proxy tests to the Travis.yml [\#892](https://github.com/test-kitchen/test-kitchen/pull/892) ([tyler-ball](https://github.com/tyler-ball)) - Test suite maintenance, a.k.a. "Just Dots And Only Dots" [\#887](https://github.com/test-kitchen/test-kitchen/pull/887) ([fnichol](https://github.com/fnichol)) - Running the chef\_base provisioner install\_command via sudo, and command\_prefix support [\#885](https://github.com/test-kitchen/test-kitchen/pull/885) ([adamleff](https://github.com/adamleff)) - write install\_command to file and invoke on the instance to avoid command too long on windows [\#878](https://github.com/test-kitchen/test-kitchen/pull/878) ([mwrock](https://github.com/mwrock)) - Updates the gem path to install everything in /tmp/verifier [\#833](https://github.com/test-kitchen/test-kitchen/pull/833) ([scotthain](https://github.com/scotthain)) ## [v1.4.2](https://github.com/test-kitchen/test-kitchen/tree/v1.4.2) (2015-08-03) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.4.1...v1.4.2) **Implemented enhancements:** - silence some aruba warnings [\#770](https://github.com/test-kitchen/test-kitchen/pull/770) ([thommay](https://github.com/thommay)) - Fix monkey patching of IO.read [\#768](https://github.com/test-kitchen/test-kitchen/pull/768) ([375gnu](https://github.com/375gnu)) - Style/Lint Updates \(finstyle 1.5.0\) [\#762](https://github.com/test-kitchen/test-kitchen/pull/762) ([fnichol](https://github.com/fnichol)) - Adding appveyor config [\#689](https://github.com/test-kitchen/test-kitchen/pull/689) ([tyler-ball](https://github.com/tyler-ball)) **Fixed bugs:** - Appveyor CI not configured correctly [\#803](https://github.com/test-kitchen/test-kitchen/issues/803) - uninitialized constant Kitchen::Transport::Ssh::Connection::Timeout with net-ssh 2.10 [\#800](https://github.com/test-kitchen/test-kitchen/issues/800) - Possible bug in Getting Started Guide: 'could not settle on compression\_client algorithm' [\#729](https://github.com/test-kitchen/test-kitchen/issues/729) - Pinning net-ssh to 2.9 [\#805](https://github.com/test-kitchen/test-kitchen/pull/805) ([tyler-ball](https://github.com/tyler-ball)) - Rescue Errno::ETIMEDOUT instead of Timeout::Error on Establish [\#802](https://github.com/test-kitchen/test-kitchen/pull/802) ([Annih](https://github.com/Annih)) - Fix for net-ssh 2.10.0. [\#801](https://github.com/test-kitchen/test-kitchen/pull/801) ([coderanger](https://github.com/coderanger)) **Closed issues:** - kitchen exec -c "ipconfig" fails on winrm \(any other command too\) with Winrm authorization error. [\#795](https://github.com/test-kitchen/test-kitchen/issues/795) - Specifying Config File on CLI [\#792](https://github.com/test-kitchen/test-kitchen/issues/792) - Converge fails on "Configuring netowrk adapters within the VM..." [\#789](https://github.com/test-kitchen/test-kitchen/issues/789) - Converge only works on second try [\#785](https://github.com/test-kitchen/test-kitchen/issues/785) - is\_running shows failing upstart process on Redhat [\#784](https://github.com/test-kitchen/test-kitchen/issues/784) - Uninitialized constant Kitchen::Transport::Ssh::Connection::Timeout [\#775](https://github.com/test-kitchen/test-kitchen/issues/775) - attempting to copy file from /var/folders that does not exist [\#774](https://github.com/test-kitchen/test-kitchen/issues/774) - Can we copy .kitchen.yml into vagrant box? [\#763](https://github.com/test-kitchen/test-kitchen/issues/763) - Ruby regular expression doesn't work in z-shell [\#760](https://github.com/test-kitchen/test-kitchen/issues/760) - how to use a puppet apply shell script with test kitchen [\#719](https://github.com/test-kitchen/test-kitchen/issues/719) - server.rb:283:in `block in start\_background': undefined method `start' for nil:NilClass \(NoMethodError\) [\#710](https://github.com/test-kitchen/test-kitchen/issues/710) - Windows guests cannot use Gemfile with serverspec tests [\#616](https://github.com/test-kitchen/test-kitchen/issues/616) - ssl\_ca\_path cannot be set in kitchen client.rb [\#594](https://github.com/test-kitchen/test-kitchen/issues/594) - Test kitchen setup fails during busser serverspec plugin post install [\#461](https://github.com/test-kitchen/test-kitchen/issues/461) **Merged pull requests:** - Support specifying exact nightly/build [\#788](https://github.com/test-kitchen/test-kitchen/pull/788) ([jaym](https://github.com/jaym)) ## [v1.4.1](https://github.com/test-kitchen/test-kitchen/tree/v1.4.1) (2015-06-18) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.9.1...v1.4.1) **Implemented enhancements:** - 'kitchen init' should create a chefignore file [\#732](https://github.com/test-kitchen/test-kitchen/issues/732) - generate a chefignore during init, fixes \#732 [\#737](https://github.com/test-kitchen/test-kitchen/pull/737) ([metadave](https://github.com/metadave)) - Fixing issues to support windows in kitchen-ec2, fixes \#688, fixes \#733 [\#736](https://github.com/test-kitchen/test-kitchen/pull/736) ([tyler-ball](https://github.com/tyler-ball)) **Fixed bugs:** - Discovering more than 50 drivers fails a Cucumber scenario [\#733](https://github.com/test-kitchen/test-kitchen/issues/733) - Transport defaults windows username to ./administrator [\#688](https://github.com/test-kitchen/test-kitchen/issues/688) - Fixing issues to support windows in kitchen-ec2, fixes \\#688, fixes \\#733 [\#736](https://github.com/test-kitchen/test-kitchen/pull/736) ([tyler-ball](https://github.com/tyler-ball)) - Fix failing feature in `kitchen drvier discover` due to too many gems. [\#734](https://github.com/test-kitchen/test-kitchen/pull/734) ([fnichol](https://github.com/fnichol)) **Closed issues:** - SSH race condition with RHEL/CentOS instances in EC2 [\#735](https://github.com/test-kitchen/test-kitchen/issues/735) - Nested upload folders [\#725](https://github.com/test-kitchen/test-kitchen/issues/725) - Intermittent "No such file or directory" on Windows converge [\#699](https://github.com/test-kitchen/test-kitchen/issues/699) - "kitchen verify" output on windows is getting butchered [\#486](https://github.com/test-kitchen/test-kitchen/issues/486) **Merged pull requests:** - Updating CHANGELOG and version for 1.4.1 release [\#748](https://github.com/test-kitchen/test-kitchen/pull/748) ([tyler-ball](https://github.com/tyler-ball)) - Revert "Use a relative name for the connection class." [\#731](https://github.com/test-kitchen/test-kitchen/pull/731) ([metadave](https://github.com/metadave)) - Use a relative name for the connection class. [\#726](https://github.com/test-kitchen/test-kitchen/pull/726) ([coderanger](https://github.com/coderanger)) ## [v0.9.1](https://github.com/test-kitchen/test-kitchen/tree/v0.9.1) (2015-05-21) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.9.0...v0.9.1) **Closed issues:** - kitchen exec fails to show text content without linebreak [\#717](https://github.com/test-kitchen/test-kitchen/issues/717) - How to copy files from box to host machine? [\#716](https://github.com/test-kitchen/test-kitchen/issues/716) ## [v0.9.0](https://github.com/test-kitchen/test-kitchen/tree/v0.9.0) (2015-05-19) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.4.0...v0.9.0) **Implemented enhancements:** - platform centos-6.4, centos-6.5 cannot be downloaded [\#663](https://github.com/test-kitchen/test-kitchen/issues/663) - Update platform version defaults in `kitchen init` command. [\#711](https://github.com/test-kitchen/test-kitchen/pull/711) ([fnichol](https://github.com/fnichol)) - don't prompt for passwords when using public keys [\#704](https://github.com/test-kitchen/test-kitchen/pull/704) ([caboteria](https://github.com/caboteria)) **Fixed bugs:** - default-centos-64 is not available [\#707](https://github.com/test-kitchen/test-kitchen/issues/707) **Closed issues:** - Exception on kitchen create: Windows Server 2012 R2 box [\#696](https://github.com/test-kitchen/test-kitchen/issues/696) - Unable to run kitchen converge: Server 2012 R2 - WinRM [\#695](https://github.com/test-kitchen/test-kitchen/issues/695) - Windows guest doesn't update serverspec files [\#693](https://github.com/test-kitchen/test-kitchen/issues/693) - Busser sync is a bit slow [\#639](https://github.com/test-kitchen/test-kitchen/issues/639) - client key is invalid or not found at: 'C:/chef/client.pem' [\#636](https://github.com/test-kitchen/test-kitchen/issues/636) - Don't print extraneous equals signs to logs "================" [\#586](https://github.com/test-kitchen/test-kitchen/issues/586) **Merged pull requests:** - Bump to centos-6.6, fix \#663. [\#665](https://github.com/test-kitchen/test-kitchen/pull/665) ([lloydde](https://github.com/lloydde)) ## [v1.4.0](https://github.com/test-kitchen/test-kitchen/tree/v1.4.0) (2015-04-28) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.4.0.rc.1...v1.4.0) **Implemented enhancements:** - Add Multi-provisioner support [\#36](https://github.com/test-kitchen/test-kitchen/issues/36) **Fixed bugs:** - kitchen verify not updating tests on Windows guests [\#684](https://github.com/test-kitchen/test-kitchen/issues/684) **Closed issues:** - includes and excludes directives not working in 1.4.0.rc.1 [\#690](https://github.com/test-kitchen/test-kitchen/issues/690) - avoid forwarding port 22 if a Windows guest? [\#676](https://github.com/test-kitchen/test-kitchen/issues/676) - kitchen verify fails on opscode centos-6.6 vagrant box [\#664](https://github.com/test-kitchen/test-kitchen/issues/664) - test-kitchen/lib/kitchen/provisioner/chef/powershell\_shell.rb expand\_version fails if behind proxy and http\_proxy is set [\#638](https://github.com/test-kitchen/test-kitchen/issues/638) - kitchen hangs on converge [\#624](https://github.com/test-kitchen/test-kitchen/issues/624) - help info for "kitchen driver incorrect" [\#613](https://github.com/test-kitchen/test-kitchen/issues/613) - Detect and warn users about Powershell bug KB2842230 that causes Out of Memory Errors [\#604](https://github.com/test-kitchen/test-kitchen/issues/604) - Need solution/best practice for installing gem in VM chef-client [\#495](https://github.com/test-kitchen/test-kitchen/issues/495) - Multi-project chaining of shared CLI subcommands [\#47](https://github.com/test-kitchen/test-kitchen/issues/47) - Create kitchen driver for Razor [\#45](https://github.com/test-kitchen/test-kitchen/issues/45) ## [v1.4.0.rc.1](https://github.com/test-kitchen/test-kitchen/tree/v1.4.0.rc.1) (2015-03-29) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.4.0.beta.2...v1.4.0.rc.1) **Fixed bugs:** - Windows 7 fails with 'maximum number of 15 concurrent operations' on second converge [\#656](https://github.com/test-kitchen/test-kitchen/issues/656) - second converge fails with encrypted data bags [\#611](https://github.com/test-kitchen/test-kitchen/issues/611) - Support relative paths to SSH keys [\#389](https://github.com/test-kitchen/test-kitchen/issues/389) - Use of sudo -E breaks compatibility with CentOS 5 [\#307](https://github.com/test-kitchen/test-kitchen/issues/307) - re-adds PATH [\#666](https://github.com/test-kitchen/test-kitchen/pull/666) ([curiositycasualty](https://github.com/curiositycasualty)) **Closed issues:** - Wrong permissions in /tmp/verifier/gems/\[bin/cache/gems\] \(?\) / broken caching with 1.4.0.beta.2 [\#671](https://github.com/test-kitchen/test-kitchen/issues/671) - ChefZero,ChefSolo \#install\_command should bomb out when no downloaders are found [\#654](https://github.com/test-kitchen/test-kitchen/issues/654) - Files not available in temp/kitchen - Windows Guest [\#642](https://github.com/test-kitchen/test-kitchen/issues/642) - winrm: Use the rdp\_uri instead of trying to call specific application [\#595](https://github.com/test-kitchen/test-kitchen/issues/595) - How to pass a symbol instead of string in .kitchen.yml [\#556](https://github.com/test-kitchen/test-kitchen/issues/556) - Converge fails deleting non-cookbook files on Windows synced folder due to max path length [\#522](https://github.com/test-kitchen/test-kitchen/issues/522) - Create kitchen driver for Solaris/illumos Zones [\#44](https://github.com/test-kitchen/test-kitchen/issues/44) **Merged pull requests:** - \[Transport::Ssh\] Add default :compression & :compression\_level attrs. [\#675](https://github.com/test-kitchen/test-kitchen/pull/675) ([fnichol](https://github.com/fnichol)) - \[Transport::SSH\] Expand path for `:ssh\_key` if provided in kitchen.yml. [\#674](https://github.com/test-kitchen/test-kitchen/pull/674) ([fnichol](https://github.com/fnichol)) - \[ChefSolo,ChefZero\] Ensure that secret key is deleted before converge. [\#673](https://github.com/test-kitchen/test-kitchen/pull/673) ([fnichol](https://github.com/fnichol)) - \[Transport::Winrm\] Extract dependant code to winrm-transport gem. [\#672](https://github.com/test-kitchen/test-kitchen/pull/672) ([fnichol](https://github.com/fnichol)) - \[CommandExecutor\] Move ObjectSpace finalizer logic into executor. [\#669](https://github.com/test-kitchen/test-kitchen/pull/669) ([fnichol](https://github.com/fnichol)) - Add `plugin\_version` support for all plugin types. [\#668](https://github.com/test-kitchen/test-kitchen/pull/668) ([fnichol](https://github.com/fnichol)) - Add plugin diagnostics, exposed via `kitchen diagnose`. [\#667](https://github.com/test-kitchen/test-kitchen/pull/667) ([fnichol](https://github.com/fnichol)) - Updated for sh compatibility based on install.sh code [\#658](https://github.com/test-kitchen/test-kitchen/pull/658) ([scotthain](https://github.com/scotthain)) - \[ChefZero\] Consider `:require\_chef\_omnibus = 11` to be modern version. [\#653](https://github.com/test-kitchen/test-kitchen/pull/653) ([fnichol](https://github.com/fnichol)) - \[ChefZero,ChefSolo\] Support symbol values in solo.rb & client.rb. [\#652](https://github.com/test-kitchen/test-kitchen/pull/652) ([fnichol](https://github.com/fnichol)) - Add :sudo\_command to Provisioners, Verifiers, & ShellOut. [\#651](https://github.com/test-kitchen/test-kitchen/pull/651) ([fnichol](https://github.com/fnichol)) ## [v1.4.0.beta.2](https://github.com/test-kitchen/test-kitchen/tree/v1.4.0.beta.2) (2015-03-25) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.4.0.beta.1...v1.4.0.beta.2) **Merged pull requests:** - \[Provisioner::Shell\] Add HTTP proxy support to commands. [\#649](https://github.com/test-kitchen/test-kitchen/pull/649) ([fnichol](https://github.com/fnichol)) - \[Transport::Winrm\] Truncate destination file for overwriting. [\#648](https://github.com/test-kitchen/test-kitchen/pull/648) ([fnichol](https://github.com/fnichol)) ## [v1.4.0.beta.1](https://github.com/test-kitchen/test-kitchen/tree/v1.4.0.beta.1) (2015-03-24) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.3.1...v1.4.0.beta.1) **Closed issues:** - RubyZip is corrupting zip files on windows hosts [\#643](https://github.com/test-kitchen/test-kitchen/issues/643) - windows guest support broke recntly [\#641](https://github.com/test-kitchen/test-kitchen/issues/641) - Unable to parse WinRM response, missing attribute quote [\#635](https://github.com/test-kitchen/test-kitchen/issues/635) - Chef DownloadFile fails on Powershell 2.0/win 2003 [\#631](https://github.com/test-kitchen/test-kitchen/issues/631) - how can i pull the data from chef server policy environment override attributes [\#630](https://github.com/test-kitchen/test-kitchen/issues/630) - windows-guest-support branch does not download chef client rc version [\#626](https://github.com/test-kitchen/test-kitchen/issues/626) - Zip Transport fails on Windows Server Core [\#625](https://github.com/test-kitchen/test-kitchen/issues/625) - call capistrano deployment? [\#617](https://github.com/test-kitchen/test-kitchen/issues/617) - PR\#589 Causes chef-client installations to report as failed when they have actually succeeded [\#601](https://github.com/test-kitchen/test-kitchen/issues/601) - Kitchen converge on Windows guests takes two tries [\#596](https://github.com/test-kitchen/test-kitchen/issues/596) - Need support for keepalive for ssh connections [\#585](https://github.com/test-kitchen/test-kitchen/issues/585) - windows-guest-support: wrong path for chef-client [\#565](https://github.com/test-kitchen/test-kitchen/issues/565) - How to setup hostname of vm with .kitchen.yml ? [\#465](https://github.com/test-kitchen/test-kitchen/issues/465) - Can test-kitchen work with mingw32 [\#435](https://github.com/test-kitchen/test-kitchen/issues/435) - Filtering non-cookbook files leave empty directories that are still scp-ed [\#429](https://github.com/test-kitchen/test-kitchen/issues/429) - prepare\_chef\_home doesn't work on Windows guests [\#158](https://github.com/test-kitchen/test-kitchen/issues/158) - Add an option to clean up log files generated [\#85](https://github.com/test-kitchen/test-kitchen/issues/85) **Merged pull requests:** - Further backwards compatibility effort [\#646](https://github.com/test-kitchen/test-kitchen/pull/646) ([fnichol](https://github.com/fnichol)) - open zip file in binary mode to avoid corrupting zip files on windows [\#644](https://github.com/test-kitchen/test-kitchen/pull/644) ([mwrock](https://github.com/mwrock)) - Test Kitchen 1.4 Refactoring \(SSH/WinRM Transports, Windows Support, etc\) [\#640](https://github.com/test-kitchen/test-kitchen/pull/640) ([fnichol](https://github.com/fnichol)) - \[WIP\] Test Kitchen 1.4 Refactoring \(SSH/WinRM Transports, Windows Support, etc\) [\#637](https://github.com/test-kitchen/test-kitchen/pull/637) ([fnichol](https://github.com/fnichol)) - Fixing bad default setting - if ENV is not set we are accidently setting log\_level to nil for whole run [\#633](https://github.com/test-kitchen/test-kitchen/pull/633) ([tyler-ball](https://github.com/tyler-ball)) - Fixes Chef Client installation on Windows Guests [\#615](https://github.com/test-kitchen/test-kitchen/pull/615) ([robcoward](https://github.com/robcoward)) - Pinning winrm to newer version to support latest httpclient [\#612](https://github.com/test-kitchen/test-kitchen/pull/612) ([tyler-ball](https://github.com/tyler-ball)) - Windows2003 guest fix [\#610](https://github.com/test-kitchen/test-kitchen/pull/610) ([GolubevV](https://github.com/GolubevV)) - Proxy Implementation for Windows Chef Omnibus [\#603](https://github.com/test-kitchen/test-kitchen/pull/603) ([afiune](https://github.com/afiune)) - Adding --log-overwrite CLI option [\#600](https://github.com/test-kitchen/test-kitchen/pull/600) ([tyler-ball](https://github.com/tyler-ball)) - Powershell no longer re-installs chef if version constraint is only major version [\#590](https://github.com/test-kitchen/test-kitchen/pull/590) ([tyler-ball](https://github.com/tyler-ball)) - Check the exit code of msiexec [\#589](https://github.com/test-kitchen/test-kitchen/pull/589) ([jaym](https://github.com/jaym)) - Change getchef.com chef.io in Powershell provisioner [\#588](https://github.com/test-kitchen/test-kitchen/pull/588) ([jaym](https://github.com/jaym)) - winrm transport should use a single \(or minimal\) shell when transferring files. transfer via a zip file to optimize round trips [\#562](https://github.com/test-kitchen/test-kitchen/pull/562) ([mwrock](https://github.com/mwrock)) - Stop uploading empty directories [\#530](https://github.com/test-kitchen/test-kitchen/pull/530) ([whiteley](https://github.com/whiteley)) ## [v1.3.1](https://github.com/test-kitchen/test-kitchen/tree/v1.3.1) (2015-01-16) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.3.0...v1.3.1) **Closed issues:** - chef\_omnibus\_install\_options not appended properly [\#580](https://github.com/test-kitchen/test-kitchen/issues/580) - 1.3.0 contains a breaking change but the major version was not incremented [\#578](https://github.com/test-kitchen/test-kitchen/issues/578) **Merged pull requests:** - Fix omnibus install argument passing bug with missing space character. [\#581](https://github.com/test-kitchen/test-kitchen/pull/581) ([fnichol](https://github.com/fnichol)) - update README.md badges to use SVG [\#579](https://github.com/test-kitchen/test-kitchen/pull/579) ([miketheman](https://github.com/miketheman)) ## [v1.3.0](https://github.com/test-kitchen/test-kitchen/tree/v1.3.0) (2015-01-15) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.2.1...v1.3.0) **Fixed bugs:** - a way to override ~/.kitchen/config.yml [\#524](https://github.com/test-kitchen/test-kitchen/issues/524) **Closed issues:** - Bundler fails to install test-kitchen alongside chef 12.0.3 [\#577](https://github.com/test-kitchen/test-kitchen/issues/577) - Conflicts with chef 12 [\#570](https://github.com/test-kitchen/test-kitchen/issues/570) - Test Kitchen/Chef in non networked mode [\#569](https://github.com/test-kitchen/test-kitchen/issues/569) - http://kitchen.ci is down [\#551](https://github.com/test-kitchen/test-kitchen/issues/551) - chef-solo causes converge to fail after installation of rvm system wide [\#548](https://github.com/test-kitchen/test-kitchen/issues/548) - Failed to complete \#converge action: \[Berkshelf::UnknownCompressionType\] [\#547](https://github.com/test-kitchen/test-kitchen/issues/547) - busser not found [\#545](https://github.com/test-kitchen/test-kitchen/issues/545) - DNS Lookups [\#542](https://github.com/test-kitchen/test-kitchen/issues/542) - "ERROR: No such file or directory" on converge [\#537](https://github.com/test-kitchen/test-kitchen/issues/537) - Kitchen fail if cookbook named certain way [\#536](https://github.com/test-kitchen/test-kitchen/issues/536) - Integrate with Packer \(so passing 'builds' can be built into boxes, then saved\) [\#535](https://github.com/test-kitchen/test-kitchen/issues/535) - kitchen command shows also the docker usage. [\#532](https://github.com/test-kitchen/test-kitchen/issues/532) - Question: Chef install by default [\#523](https://github.com/test-kitchen/test-kitchen/issues/523) - Test Kitchen not seeing cookbooks? [\#517](https://github.com/test-kitchen/test-kitchen/issues/517) - Serverspec exit code 1 without error message [\#513](https://github.com/test-kitchen/test-kitchen/issues/513) - kitchen-ssh : SSH EXITED error. [\#509](https://github.com/test-kitchen/test-kitchen/issues/509) - difference between /tmp/kitchen/cache/cookbooks and /tmp/kitchen/cookbooks? [\#508](https://github.com/test-kitchen/test-kitchen/issues/508) - Running two kitchen converges parallely? [\#506](https://github.com/test-kitchen/test-kitchen/issues/506) - Failed to complete \#create action: \[undefined local variable or method `default\_port' for \#\ configuration [\#457](https://github.com/test-kitchen/test-kitchen/pull/457) ([michaelkirk](https://github.com/michaelkirk)) - Customize ssh\_timeout and ssh\_retries [\#454](https://github.com/test-kitchen/test-kitchen/pull/454) ([ekrupnik](https://github.com/ekrupnik)) - Help update [\#450](https://github.com/test-kitchen/test-kitchen/pull/450) ([MarkGibbons](https://github.com/MarkGibbons)) - Backfilling spec coverage and refactoring: technical debt edition [\#427](https://github.com/test-kitchen/test-kitchen/pull/427) ([fnichol](https://github.com/fnichol)) - Gem runner install driver [\#416](https://github.com/test-kitchen/test-kitchen/pull/416) ([mcquin](https://github.com/mcquin)) - Sleep before retrying SSH\#establish\_connection. [\#399](https://github.com/test-kitchen/test-kitchen/pull/399) ([fnichol](https://github.com/fnichol)) - make chef\_zero port configurable [\#397](https://github.com/test-kitchen/test-kitchen/pull/397) ([jtgiri](https://github.com/jtgiri)) - Use the full path to `chef-solo` and `chef-client` [\#381](https://github.com/test-kitchen/test-kitchen/pull/381) ([sethvargo](https://github.com/sethvargo)) - Add new subcommand 'exec' [\#373](https://github.com/test-kitchen/test-kitchen/pull/373) ([sawanoboly](https://github.com/sawanoboly)) - Use Ruby 2.1 instead of 2.1.0 for CI [\#370](https://github.com/test-kitchen/test-kitchen/pull/370) ([justincampbell](https://github.com/justincampbell)) - Nitpick spelling [\#366](https://github.com/test-kitchen/test-kitchen/pull/366) ([srenatus](https://github.com/srenatus)) - Ensure that integer chef config attributes get placed in solo.rb/client.rb properly [\#363](https://github.com/test-kitchen/test-kitchen/pull/363) ([benlangfeld](https://github.com/benlangfeld)) ## [v1.2.1](https://github.com/test-kitchen/test-kitchen/tree/v1.2.1) (2014-02-12) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.2.0...v1.2.1) **Fixed bugs:** - Test Kitchen 1.2.0 breaks Berkshelf 2.0 on \(OS X\) [\#357](https://github.com/test-kitchen/test-kitchen/issues/357) **Merged pull requests:** - Load needed \(dynamic\) dependencies for provisioners at creation time. [\#358](https://github.com/test-kitchen/test-kitchen/pull/358) ([fnichol](https://github.com/fnichol)) ## [v1.2.0](https://github.com/test-kitchen/test-kitchen/tree/v1.2.0) (2014-02-12) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.8.0...v1.2.0) **Fixed bugs:** - kitchen converge does not fail when chef run fails [\#346](https://github.com/test-kitchen/test-kitchen/issues/346) **Merged pull requests:** - Kamalika | added exit status check in chef-zero support for chef 10 [\#353](https://github.com/test-kitchen/test-kitchen/pull/353) ([kamalim](https://github.com/kamalim)) ## [v0.8.0](https://github.com/test-kitchen/test-kitchen/tree/v0.8.0) (2014-02-12) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.1.1...v0.8.0) **Fixed bugs:** - Failed to complete \#converge action: \[no implicit conversion of nil into String\] [\#335](https://github.com/test-kitchen/test-kitchen/issues/335) - SSH connection failed, connection closed by remote host [\#323](https://github.com/test-kitchen/test-kitchen/issues/323) - Command line errors don't set exit status [\#305](https://github.com/test-kitchen/test-kitchen/issues/305) - Commented out .kitchen.local.yml causes failure of test-kitchen [\#285](https://github.com/test-kitchen/test-kitchen/issues/285) - not proper response when part of node name same [\#282](https://github.com/test-kitchen/test-kitchen/issues/282) **Closed issues:** - support for command-line option to select driver \(fast local TDD vs. remote ci testing\) [\#345](https://github.com/test-kitchen/test-kitchen/issues/345) - Message: SSH exited \(1\) for command: \[sudo -E /tmp/kitchen/bootstrap.sh\] [\#342](https://github.com/test-kitchen/test-kitchen/issues/342) - Can't login to machine due to ambiguous name. [\#341](https://github.com/test-kitchen/test-kitchen/issues/341) - Unable to set a chef environment for a node [\#340](https://github.com/test-kitchen/test-kitchen/issues/340) - Multiple run on the same box [\#339](https://github.com/test-kitchen/test-kitchen/issues/339) - Using search functions. [\#337](https://github.com/test-kitchen/test-kitchen/issues/337) - Could not load the 'shell' provisioner from the load path [\#334](https://github.com/test-kitchen/test-kitchen/issues/334) - Shell Provisioner [\#331](https://github.com/test-kitchen/test-kitchen/issues/331) - cookbook files not copied to vagrant box [\#328](https://github.com/test-kitchen/test-kitchen/issues/328) - The SciFi Future of Provisioner Install Commands. [\#326](https://github.com/test-kitchen/test-kitchen/issues/326) - Reboot during Test Kitchen run? [\#324](https://github.com/test-kitchen/test-kitchen/issues/324) - Node attributes do not seem to prevail between converge operations. [\#320](https://github.com/test-kitchen/test-kitchen/issues/320) - Can't load data bags [\#317](https://github.com/test-kitchen/test-kitchen/issues/317) - wiki bats example on Getting Started is overcomplex/bad pattern [\#314](https://github.com/test-kitchen/test-kitchen/issues/314) - Subdirectories in "helpers" directory [\#312](https://github.com/test-kitchen/test-kitchen/issues/312) - Override config file location via environment variables [\#304](https://github.com/test-kitchen/test-kitchen/issues/304) - kitchen converge reinstalls chef using the omnibus installer even if its installed [\#299](https://github.com/test-kitchen/test-kitchen/issues/299) - Chef environment support missing? [\#297](https://github.com/test-kitchen/test-kitchen/issues/297) - Problem parsing metadata? [\#290](https://github.com/test-kitchen/test-kitchen/issues/290) - serverspec failing [\#274](https://github.com/test-kitchen/test-kitchen/issues/274) - I would like to execute some tasks before chef-client run at `kitchen converge`. [\#251](https://github.com/test-kitchen/test-kitchen/issues/251) - Reduce internet downloading during test runs [\#196](https://github.com/test-kitchen/test-kitchen/issues/196) - Allow to limit the number of parallel tests [\#176](https://github.com/test-kitchen/test-kitchen/issues/176) - Implement `kitchen remodel` [\#150](https://github.com/test-kitchen/test-kitchen/issues/150) - Make it possible \(or easier\) to run test-kitchen when off line [\#56](https://github.com/test-kitchen/test-kitchen/issues/56) - Add project types to test-kitchen [\#46](https://github.com/test-kitchen/test-kitchen/issues/46) - Create kitchen-fog driver that supports most Fog cloud providers [\#33](https://github.com/test-kitchen/test-kitchen/issues/33) - support "preflight" commands [\#26](https://github.com/test-kitchen/test-kitchen/issues/26) - If the project is a cookbook, attempt to use "test" cookbook in the default run list [\#24](https://github.com/test-kitchen/test-kitchen/issues/24) **Merged pull requests:** - Upload chef clients data [\#318](https://github.com/test-kitchen/test-kitchen/pull/318) ([jtimberman](https://github.com/jtimberman)) - Allow files in subdirectories in "helpers" directory [\#313](https://github.com/test-kitchen/test-kitchen/pull/313) ([mthssdrbrg](https://github.com/mthssdrbrg)) - Fix Windows path matching issues introduced by 1c924af2e9 [\#310](https://github.com/test-kitchen/test-kitchen/pull/310) ([rarenerd](https://github.com/rarenerd)) - adding /opt/local/bin to search path. smartmachines need this otherwise ... [\#309](https://github.com/test-kitchen/test-kitchen/pull/309) ([someara](https://github.com/someara)) - Add local & global file locations with environment variables. [\#306](https://github.com/test-kitchen/test-kitchen/pull/306) ([fnichol](https://github.com/fnichol)) - Use SafeYAML.load to avoid YAML monkeypatch in safe\_yaml. [\#303](https://github.com/test-kitchen/test-kitchen/pull/303) ([fnichol](https://github.com/fnichol)) - CLI refactoring to remove logic from cli.rb [\#302](https://github.com/test-kitchen/test-kitchen/pull/302) ([fnichol](https://github.com/fnichol)) - Base provisioner refactoring [\#298](https://github.com/test-kitchen/test-kitchen/pull/298) ([fnichol](https://github.com/fnichol)) - Fixing error when using more than one helper [\#296](https://github.com/test-kitchen/test-kitchen/pull/296) ([jschneiderhan](https://github.com/jschneiderhan)) - Add --concurrency option to specify number of multiple actions to perform at a time. [\#293](https://github.com/test-kitchen/test-kitchen/pull/293) ([ryotarai](https://github.com/ryotarai)) - Update omnibus URL to getchef.com. [\#288](https://github.com/test-kitchen/test-kitchen/pull/288) ([juliandunn](https://github.com/juliandunn)) - Fix Cucumber tests on Windows [\#287](https://github.com/test-kitchen/test-kitchen/pull/287) ([rarenerd](https://github.com/rarenerd)) - Fix failing minitest test on Windows [\#283](https://github.com/test-kitchen/test-kitchen/pull/283) ([rarenerd](https://github.com/rarenerd)) - Add `json\_attributes: true` config option to ChefZero provisioner. [\#280](https://github.com/test-kitchen/test-kitchen/pull/280) ([fnichol](https://github.com/fnichol)) ## [v1.1.1](https://github.com/test-kitchen/test-kitchen/tree/v1.1.1) (2013-12-09) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.1.0...v1.1.1) **Fixed bugs:** - Calling a test "database\_spec.rb" make it impossible to be played ! [\#276](https://github.com/test-kitchen/test-kitchen/issues/276) **Closed issues:** - not uploading database\_spec.rb test file [\#278](https://github.com/test-kitchen/test-kitchen/issues/278) **Merged pull requests:** - Fix SSH 'Too many authentication failures' error. [\#275](https://github.com/test-kitchen/test-kitchen/pull/275) ([zts](https://github.com/zts)) ## [v1.1.0](https://github.com/test-kitchen/test-kitchen/tree/v1.1.0) (2013-12-05) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0...v1.1.0) **Closed issues:** - Website Down? [\#271](https://github.com/test-kitchen/test-kitchen/issues/271) - test for service not work correctly [\#270](https://github.com/test-kitchen/test-kitchen/issues/270) - Document the newly introduced need to specify 'sudo: true' [\#269](https://github.com/test-kitchen/test-kitchen/issues/269) **Merged pull requests:** - drive by typo fix [\#272](https://github.com/test-kitchen/test-kitchen/pull/272) ([kisoku](https://github.com/kisoku)) ## [v1.0.0](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0) (2013-12-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.rc.2...v1.0.0) **Closed issues:** - crash on mac os x [\#268](https://github.com/test-kitchen/test-kitchen/issues/268) - kitchen list does not read state file when using --debug [\#267](https://github.com/test-kitchen/test-kitchen/issues/267) ## [v1.0.0.rc.2](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.rc.2) (2013-11-30) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.rc.1...v1.0.0.rc.2) **Closed issues:** - Does test-kitchen support aws provider ? [\#264](https://github.com/test-kitchen/test-kitchen/issues/264) - Fog driver: ship with a sane set of image\_id/flavor\_id combinations for default platforms [\#34](https://github.com/test-kitchen/test-kitchen/issues/34) **Merged pull requests:** - Make a nicer error on regexp failure [\#266](https://github.com/test-kitchen/test-kitchen/pull/266) ([juliandunn](https://github.com/juliandunn)) - Busser Fixes for Greybeard UNIX [\#265](https://github.com/test-kitchen/test-kitchen/pull/265) ([schisamo](https://github.com/schisamo)) ## [v1.0.0.rc.1](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.rc.1) (2013-11-28) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.beta.4...v1.0.0.rc.1) **Fixed bugs:** - "Destroy" flag does not behave consistently, and the docs appear to be wrong [\#255](https://github.com/test-kitchen/test-kitchen/issues/255) - Chef Zero provisioner does not respect `require\_chef\_omnibus` config [\#243](https://github.com/test-kitchen/test-kitchen/issues/243) - Gem path issues after test-kitchen beta 4 new sandbox. [\#242](https://github.com/test-kitchen/test-kitchen/issues/242) - Absolute Paths for Suite Data Bags, Roles, and Nodes are Set to Nil [\#227](https://github.com/test-kitchen/test-kitchen/pull/227) ([ajmath](https://github.com/ajmath)) - add `skip\_git` option to Init Generator [\#141](https://github.com/test-kitchen/test-kitchen/pull/141) ([reset](https://github.com/reset)) **Closed issues:** - is test-kitchen appropriate for running deploys? [\#252](https://github.com/test-kitchen/test-kitchen/issues/252) - role run\_lists seems to be ignored [\#250](https://github.com/test-kitchen/test-kitchen/issues/250) - Add default value for encrypted\_data\_bag\_secret\_key\_path [\#248](https://github.com/test-kitchen/test-kitchen/issues/248) - `uninitialized constant Berkshelf::Chef::Config::Ohai\]` [\#244](https://github.com/test-kitchen/test-kitchen/issues/244) - gem\_package using chef\_zero installing packages into /tmp/kitchen-chef-zero making binstubs unavailable to chef [\#240](https://github.com/test-kitchen/test-kitchen/issues/240) - Error on ubuntu images only [\#220](https://github.com/test-kitchen/test-kitchen/issues/220) - Allow test-kitchen to use different configs \(e.g. --config option\)? [\#210](https://github.com/test-kitchen/test-kitchen/issues/210) - solo.rb file content should be configurable [\#117](https://github.com/test-kitchen/test-kitchen/issues/117) - Documentation [\#110](https://github.com/test-kitchen/test-kitchen/issues/110) - Possible problems with parallel testing [\#68](https://github.com/test-kitchen/test-kitchen/issues/68) **Merged pull requests:** - Use a configurable glob pattern to select Chef cookbook files. [\#262](https://github.com/test-kitchen/test-kitchen/pull/262) ([fnichol](https://github.com/fnichol)) - Fix inconsistent date in CHANGELOG [\#259](https://github.com/test-kitchen/test-kitchen/pull/259) ([ryansouza](https://github.com/ryansouza)) - Fix Busser and chef-client-zero.rb Gem Sandboxing [\#258](https://github.com/test-kitchen/test-kitchen/pull/258) ([fnichol](https://github.com/fnichol)) - Changed 'passed' to 'passing' in the Destroy options [\#256](https://github.com/test-kitchen/test-kitchen/pull/256) ([scarolan](https://github.com/scarolan)) - update references to test-kitchen org [\#254](https://github.com/test-kitchen/test-kitchen/pull/254) ([josephholsten](https://github.com/josephholsten)) - Fix travis-ci badge [\#253](https://github.com/test-kitchen/test-kitchen/pull/253) ([arangamani](https://github.com/arangamani)) - Add data path as optional configuration [\#249](https://github.com/test-kitchen/test-kitchen/pull/249) ([oferrigni](https://github.com/oferrigni)) - Fix init generator to simplify YAML [\#246](https://github.com/test-kitchen/test-kitchen/pull/246) ([sethvargo](https://github.com/sethvargo)) - Bust out of gem sandbox before chef-client run; Fixes \#240 [\#241](https://github.com/test-kitchen/test-kitchen/pull/241) ([schisamo](https://github.com/schisamo)) - Show less output [\#238](https://github.com/test-kitchen/test-kitchen/pull/238) ([sethvargo](https://github.com/sethvargo)) - Add option to run a stanza on a fixed set of platforms [\#165](https://github.com/test-kitchen/test-kitchen/pull/165) ([coderanger](https://github.com/coderanger)) - Read CLI options from kitchen.yml [\#121](https://github.com/test-kitchen/test-kitchen/pull/121) ([atomic-penguin](https://github.com/atomic-penguin)) ## [v1.0.0.beta.4](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.beta.4) (2013-11-01) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.beta.3...v1.0.0.beta.4) **Fixed bugs:** - cannot load such file -- chef\_fs/chef\_fs\_data\_store \(LoadError\) [\#230](https://github.com/test-kitchen/test-kitchen/issues/230) - should\_update\_chef logic appears broken [\#191](https://github.com/test-kitchen/test-kitchen/issues/191) - chef-zero fails to install without build-essential [\#190](https://github.com/test-kitchen/test-kitchen/issues/190) - Pin dependency of safe\_yaml to 0.9.3 or wait on upstream to release and yank 0.9.4 [\#181](https://github.com/test-kitchen/test-kitchen/issues/181) - kitchen test --parallel never times out, never errors out, despite an error [\#169](https://github.com/test-kitchen/test-kitchen/issues/169) - Temporary files can be still uploaded [\#132](https://github.com/test-kitchen/test-kitchen/issues/132) - Kitchen destroy leaves orphans behind [\#109](https://github.com/test-kitchen/test-kitchen/issues/109) - kitchen uses 100% CPU after a failure with the --parallel flag [\#100](https://github.com/test-kitchen/test-kitchen/issues/100) **Closed issues:** - kitchen verify fails due to gem conflict [\#234](https://github.com/test-kitchen/test-kitchen/issues/234) - kitchen-test outputs "can't convert Symbol into Integer" [\#223](https://github.com/test-kitchen/test-kitchen/issues/223) - Failed require is not necessarily missing gem [\#215](https://github.com/test-kitchen/test-kitchen/issues/215) - Certain platforms \(e.g., solaris, omnios\) may not have /usr/bin symlinks for chef [\#213](https://github.com/test-kitchen/test-kitchen/issues/213) - Provide config option to add to the list of cookbook files. [\#211](https://github.com/test-kitchen/test-kitchen/issues/211) - Since Sept 27 I'm no longer able to bundle test-kitchen master with berkshelf 2.0.10 [\#209](https://github.com/test-kitchen/test-kitchen/issues/209) - 2.0 [\#207](https://github.com/test-kitchen/test-kitchen/issues/207) - Are Vagrant environments supported in .kitchen.yml [\#205](https://github.com/test-kitchen/test-kitchen/issues/205) - with OpenStack Driver, can not exec 'kitchen create' [\#204](https://github.com/test-kitchen/test-kitchen/issues/204) - Test kitchen fails to install busser properly when system-level rvm installed ruby exists [\#200](https://github.com/test-kitchen/test-kitchen/issues/200) - Environment support for Chef Solo [\#199](https://github.com/test-kitchen/test-kitchen/issues/199) - Tests are not picked up when using chef-zero provisioner [\#189](https://github.com/test-kitchen/test-kitchen/issues/189) - /tmp/kitchen-chef-solo permissions issue [\#186](https://github.com/test-kitchen/test-kitchen/issues/186) - Idea: Kitchenfile config [\#182](https://github.com/test-kitchen/test-kitchen/issues/182) - Automatically trigger berks install -o \ group on test run [\#173](https://github.com/test-kitchen/test-kitchen/issues/173) - Propose Switch to allow for only the test result output from each busser [\#168](https://github.com/test-kitchen/test-kitchen/issues/168) - Allow for site-cookbooks [\#166](https://github.com/test-kitchen/test-kitchen/issues/166) - Be more paranoid about dependencies [\#149](https://github.com/test-kitchen/test-kitchen/issues/149) - New .kitchen.yml syntax? [\#138](https://github.com/test-kitchen/test-kitchen/issues/138) - Could not find gem 'test-kitchen \(\>= 0\) ruby' [\#135](https://github.com/test-kitchen/test-kitchen/issues/135) - It says Starting Kitchen when destroying your test vm's [\#133](https://github.com/test-kitchen/test-kitchen/issues/133) - "sudo: unable to resolve host default-precise64-vmware-fusion.vagrantup.com" [\#127](https://github.com/test-kitchen/test-kitchen/issues/127) - Create a kitchen driver for SmartOS [\#125](https://github.com/test-kitchen/test-kitchen/issues/125) - Allow for enhanced Berksfile syntax within a given suite [\#93](https://github.com/test-kitchen/test-kitchen/issues/93) - Passing the -h flag to a command starts the suite [\#86](https://github.com/test-kitchen/test-kitchen/issues/86) - test-kitchen 1.0.0-alpha & chef-solo-search not working [\#70](https://github.com/test-kitchen/test-kitchen/issues/70) - Consider adding `driver\_config` to a Suite. [\#69](https://github.com/test-kitchen/test-kitchen/issues/69) - Don't remove code based configuration. [\#40](https://github.com/test-kitchen/test-kitchen/issues/40) **Merged pull requests:** - Added environments support for chef-solo [\#235](https://github.com/test-kitchen/test-kitchen/pull/235) ([ekrupnik](https://github.com/ekrupnik)) - Concurrent threads [\#226](https://github.com/test-kitchen/test-kitchen/pull/226) ([fnichol](https://github.com/fnichol)) - Improves Test Kitchen's support for older \(non-Linux\) Unixes [\#225](https://github.com/test-kitchen/test-kitchen/pull/225) ([schisamo](https://github.com/schisamo)) - Remove celluloid and use pure Ruby threads [\#222](https://github.com/test-kitchen/test-kitchen/pull/222) ([sethvargo](https://github.com/sethvargo)) - Add pessismestic locks to all gem requirements [\#206](https://github.com/test-kitchen/test-kitchen/pull/206) ([sethvargo](https://github.com/sethvargo)) - fixed berkself typo to berkshelf [\#203](https://github.com/test-kitchen/test-kitchen/pull/203) ([gmiranda23](https://github.com/gmiranda23)) - Multiple arguments to test \(verify, converge, etc\) [\#94](https://github.com/test-kitchen/test-kitchen/pull/94) ([miketheman](https://github.com/miketheman)) ## [v1.0.0.beta.3](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.beta.3) (2013-08-29) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.beta.2...v1.0.0.beta.3) **Closed issues:** - Set hostname fails on openSUSE 11.x [\#185](https://github.com/test-kitchen/test-kitchen/issues/185) - Ability to test recipes that require multiple VMs connected to a chef server [\#184](https://github.com/test-kitchen/test-kitchen/issues/184) - Berkshelf Missing [\#183](https://github.com/test-kitchen/test-kitchen/issues/183) - Invalid logger call? [\#175](https://github.com/test-kitchen/test-kitchen/issues/175) **Merged pull requests:** - truthy default\_configs can't be overridden [\#188](https://github.com/test-kitchen/test-kitchen/pull/188) ([thommay](https://github.com/thommay)) - \[KITCHEN-80\] added support for log file in chef solo [\#187](https://github.com/test-kitchen/test-kitchen/pull/187) ([arangamani](https://github.com/arangamani)) - Remove bundler references from README. [\#179](https://github.com/test-kitchen/test-kitchen/pull/179) ([juliandunn](https://github.com/juliandunn)) - Fix SSH\#wait's logger call to \#info [\#178](https://github.com/test-kitchen/test-kitchen/pull/178) ([ryansouza](https://github.com/ryansouza)) ## [v1.0.0.beta.2](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.beta.2) (2013-07-25) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.beta.1...v1.0.0.beta.2) ## [v1.0.0.beta.1](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.beta.1) (2013-07-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.5.1...v1.0.0.beta.1) **Fixed bugs:** - Kitchen.celluloid\_file\_logger creates .kitchen when using knife [\#153](https://github.com/test-kitchen/test-kitchen/issues/153) - Error during test hangs, steals CPU [\#89](https://github.com/test-kitchen/test-kitchen/issues/89) - unintuitive error message when kitchen driver specified in .kitchen.yml isn't found [\#80](https://github.com/test-kitchen/test-kitchen/issues/80) - and empty \(or commented out\) .kitchen.local.yml file causes failure. [\#42](https://github.com/test-kitchen/test-kitchen/issues/42) - kitchen commands should respond properly to CTL-C [\#30](https://github.com/test-kitchen/test-kitchen/issues/30) - File.exists? calls within init generator must include the destination root for portability purposes [\#140](https://github.com/test-kitchen/test-kitchen/pull/140) ([reset](https://github.com/reset)) **Closed issues:** - Set a more sane default PATH for installing Chef [\#163](https://github.com/test-kitchen/test-kitchen/issues/163) - Build is broken w/ RubyGems 1.8.25 + Ruby 2.0.0 [\#160](https://github.com/test-kitchen/test-kitchen/issues/160) - Build is broken! [\#159](https://github.com/test-kitchen/test-kitchen/issues/159) - `kitchen converge` not uploading definitions directory [\#156](https://github.com/test-kitchen/test-kitchen/issues/156) - The NSA censors your VM names when using a terminal with a light background [\#154](https://github.com/test-kitchen/test-kitchen/issues/154) - Update bucket name for Opscode's bento-built boxes [\#151](https://github.com/test-kitchen/test-kitchen/issues/151) - kitchen test fails with undefined method `full\_name' [\#146](https://github.com/test-kitchen/test-kitchen/issues/146) - safe\_yaml not found [\#137](https://github.com/test-kitchen/test-kitchen/issues/137) - Support for data bags in Cookbooks under test [\#129](https://github.com/test-kitchen/test-kitchen/issues/129) - Configuration management tools/provisioners should be pluggable [\#107](https://github.com/test-kitchen/test-kitchen/issues/107) - Provide option for running chef-client instead of chef-solo [\#103](https://github.com/test-kitchen/test-kitchen/issues/103) - Test-kitchen should not use the color red for non-error information [\#97](https://github.com/test-kitchen/test-kitchen/issues/97) - More colors! [\#96](https://github.com/test-kitchen/test-kitchen/issues/96) - Order of operations not clear. [\#88](https://github.com/test-kitchen/test-kitchen/issues/88) - logging should be configured by the .kitchen.yml or .kitchen.local.yml [\#63](https://github.com/test-kitchen/test-kitchen/issues/63) - Consider setting `driver\[:require\_chef\_omnibus\] = true` by default [\#62](https://github.com/test-kitchen/test-kitchen/issues/62) - kitchen subcommands should error out gracefully if .kitchen.yml cannot be properly loaded [\#37](https://github.com/test-kitchen/test-kitchen/issues/37) - init command should default to Berkshelf [\#28](https://github.com/test-kitchen/test-kitchen/issues/28) - if cookbook metadata specifies platforms, only run tests against those platforms [\#27](https://github.com/test-kitchen/test-kitchen/issues/27) - provide a converter for Kitchenfile -\> .kitchen.yml [\#19](https://github.com/test-kitchen/test-kitchen/issues/19) **Merged pull requests:** - \[Breaking\] Update signature of Driver.required\_config block. [\#172](https://github.com/test-kitchen/test-kitchen/pull/172) ([fnichol](https://github.com/fnichol)) - Support computed default values for Driver authors. [\#171](https://github.com/test-kitchen/test-kitchen/pull/171) ([fnichol](https://github.com/fnichol)) - add asterisk to wait\_for\_sshd argument [\#170](https://github.com/test-kitchen/test-kitchen/pull/170) ([ainoya](https://github.com/ainoya)) - set a default $PATH [\#164](https://github.com/test-kitchen/test-kitchen/pull/164) ([jtimberman](https://github.com/jtimberman)) - \[KITCHEN-77\] Allow custom paths [\#161](https://github.com/test-kitchen/test-kitchen/pull/161) ([gondoi](https://github.com/gondoi)) - Setting :on\_black when your default terminal text color is black results in unreadable \(black on black\) text. [\#155](https://github.com/test-kitchen/test-kitchen/pull/155) ([mconigliaro](https://github.com/mconigliaro)) - Fixes \#151 - Update the bucket name for Opscode's Bento Boxes [\#152](https://github.com/test-kitchen/test-kitchen/pull/152) ([jtimberman](https://github.com/jtimberman)) - Allow chef omnibus install.sh url to be configurable [\#147](https://github.com/test-kitchen/test-kitchen/pull/147) ([jrwesolo](https://github.com/jrwesolo)) - require a safe\_yaml release with correct permissions. Fixes \#137 [\#142](https://github.com/test-kitchen/test-kitchen/pull/142) ([josephholsten](https://github.com/josephholsten)) - Fixes bundler ref for 1.0. [\#136](https://github.com/test-kitchen/test-kitchen/pull/136) ([patcon](https://github.com/patcon)) - KITCHEN-75 - support cross suite helpers. [\#134](https://github.com/test-kitchen/test-kitchen/pull/134) ([rteabeault](https://github.com/rteabeault)) - Use ssh\_args for test\_ssh. [\#131](https://github.com/test-kitchen/test-kitchen/pull/131) ([jonsmorrow](https://github.com/jonsmorrow)) - Introduce Provisioners to support chef-client, puppet-apply, and puppet-agent [\#128](https://github.com/test-kitchen/test-kitchen/pull/128) ([fnichol](https://github.com/fnichol)) - Aggressively filter "non-cookbook" files before uploading to instances. [\#124](https://github.com/test-kitchen/test-kitchen/pull/124) ([fnichol](https://github.com/fnichol)) - Swap cookbook resolution strategy from shell outs to using Ruby APIs. [\#123](https://github.com/test-kitchen/test-kitchen/pull/123) ([fnichol](https://github.com/fnichol)) - Adding missing sudo calls to busser [\#122](https://github.com/test-kitchen/test-kitchen/pull/122) ([adamhjk](https://github.com/adamhjk)) ## [v0.5.1](https://github.com/test-kitchen/test-kitchen/tree/v0.5.1) (2013-05-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.7...v0.5.1) **Closed issues:** - berks install errors should not be swallowed [\#118](https://github.com/test-kitchen/test-kitchen/issues/118) ## [v1.0.0.alpha.7](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.7) (2013-05-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.6...v1.0.0.alpha.7) **Closed issues:** - Update kitchen.yml template with provisionerless baseboxes [\#114](https://github.com/test-kitchen/test-kitchen/issues/114) - Windows experience a non-starter [\#101](https://github.com/test-kitchen/test-kitchen/issues/101) - Destroy flag is ignored if parallel flag is given. [\#98](https://github.com/test-kitchen/test-kitchen/issues/98) - In the absence of a Berksfile, sadness abounds [\#92](https://github.com/test-kitchen/test-kitchen/issues/92) - support global user-level config files [\#31](https://github.com/test-kitchen/test-kitchen/issues/31) **Merged pull requests:** - Add http and https\_proxy support [\#120](https://github.com/test-kitchen/test-kitchen/pull/120) ([adamhjk](https://github.com/adamhjk)) - Test Kitchen works on Windows with Vagrant [\#119](https://github.com/test-kitchen/test-kitchen/pull/119) ([adamhjk](https://github.com/adamhjk)) - Require the 'name' attribute is present in `metadata.rb` [\#116](https://github.com/test-kitchen/test-kitchen/pull/116) ([sethvargo](https://github.com/sethvargo)) - Fixes \#114, use provisionerless baseboxes [\#115](https://github.com/test-kitchen/test-kitchen/pull/115) ([jtimberman](https://github.com/jtimberman)) - \[KITCHEN-74\] Handle case where YAML parses as nil [\#113](https://github.com/test-kitchen/test-kitchen/pull/113) ([smith](https://github.com/smith)) - Add the sink [\#111](https://github.com/test-kitchen/test-kitchen/pull/111) ([sethvargo](https://github.com/sethvargo)) - Add Kitchen::VERSION to `-----\> Starting Kitchen` output [\#108](https://github.com/test-kitchen/test-kitchen/pull/108) ([fnichol](https://github.com/fnichol)) - Expand documentation around run-time switches. [\#105](https://github.com/test-kitchen/test-kitchen/pull/105) ([grahamc](https://github.com/grahamc)) - Set the default ssh port. [\#104](https://github.com/test-kitchen/test-kitchen/pull/104) ([calavera](https://github.com/calavera)) - Allow to override sudo. [\#102](https://github.com/test-kitchen/test-kitchen/pull/102) ([calavera](https://github.com/calavera)) - Ensure that destroy option is respected when --parallel is used. [\#99](https://github.com/test-kitchen/test-kitchen/pull/99) ([stevendanna](https://github.com/stevendanna)) - Fix minitest test examples link. [\#91](https://github.com/test-kitchen/test-kitchen/pull/91) ([calavera](https://github.com/calavera)) - Add a global config file [\#90](https://github.com/test-kitchen/test-kitchen/pull/90) ([thommay](https://github.com/thommay)) ## [v1.0.0.alpha.6](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.6) (2013-05-08) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.5...v1.0.0.alpha.6) **Closed issues:** - UI nitpick [\#84](https://github.com/test-kitchen/test-kitchen/issues/84) **Merged pull requests:** - Add attribute encrypted\_data\_bag\_secret\_key\_path to Kitchen::Suite [\#77](https://github.com/test-kitchen/test-kitchen/pull/77) ([arunthampi](https://github.com/arunthampi)) ## [v1.0.0.alpha.5](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.5) (2013-04-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.4...v1.0.0.alpha.5) **Closed issues:** - Support wget and curl for omnibus installs \(in `Kitchen::Driver::SSHBase`\) [\#61](https://github.com/test-kitchen/test-kitchen/issues/61) **Merged pull requests:** - Install Omnibus package via either wget or curl. [\#82](https://github.com/test-kitchen/test-kitchen/pull/82) ([fnichol](https://github.com/fnichol)) - Error report formatting [\#81](https://github.com/test-kitchen/test-kitchen/pull/81) ([fnichol](https://github.com/fnichol)) - Swap out shell-based kb for Ruby-based Busser gem [\#76](https://github.com/test-kitchen/test-kitchen/pull/76) ([fnichol](https://github.com/fnichol)) ## [v1.0.0.alpha.4](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.4) (2013-04-10) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.3...v1.0.0.alpha.4) ## [v1.0.0.alpha.3](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.3) (2013-04-05) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.2...v1.0.0.alpha.3) **Closed issues:** - Use baseboxes updated to Chef 10.18.2 [\#21](https://github.com/test-kitchen/test-kitchen/issues/21) - init command should create Gemfile if it does not exist [\#20](https://github.com/test-kitchen/test-kitchen/issues/20) ## [v1.0.0.alpha.2](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.2) (2013-03-29) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.1...v1.0.0.alpha.2) ## [v1.0.0.alpha.1](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.1) (2013-03-23) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.4.0...v1.0.0.alpha.1) **Merged pull requests:** - Add Driver\#verify\_dependencies to be invoked once when Driver is loaded. [\#75](https://github.com/test-kitchen/test-kitchen/pull/75) ([fnichol](https://github.com/fnichol)) - switch driver alias \(-d\) to \(-D\) in Init generator [\#74](https://github.com/test-kitchen/test-kitchen/pull/74) ([reset](https://github.com/reset)) - \[Breaking\] Modify ShellOut\#run\_command to take an options Hash. [\#73](https://github.com/test-kitchen/test-kitchen/pull/73) ([fnichol](https://github.com/fnichol)) - Add flag to `kitchen init` to skip Gemfile creation by default. [\#72](https://github.com/test-kitchen/test-kitchen/pull/72) ([fnichol](https://github.com/fnichol)) - Updates to `kitchen init` to be non-interactive \(add `--driver` flag\), add subcommand support, and introduce `kitchen driver discover`. [\#71](https://github.com/test-kitchen/test-kitchen/pull/71) ([fnichol](https://github.com/fnichol)) - \[tailor\] fix for line length and style [\#65](https://github.com/test-kitchen/test-kitchen/pull/65) ([ChrisLundquist](https://github.com/ChrisLundquist)) - make "require\_chef\_omnibus: true" safe [\#64](https://github.com/test-kitchen/test-kitchen/pull/64) ([mattray](https://github.com/mattray)) ## [v0.4.0](https://github.com/test-kitchen/test-kitchen/tree/v0.4.0) (2013-03-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v1.0.0.alpha.0...v0.4.0) **Closed issues:** - support "exclude" configuration directive after \#17 [\#29](https://github.com/test-kitchen/test-kitchen/issues/29) ## [v1.0.0.alpha.0](https://github.com/test-kitchen/test-kitchen/tree/v1.0.0.alpha.0) (2013-03-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.beta4...v1.0.0.alpha.0) **Closed issues:** - Gem dependency collision [\#59](https://github.com/test-kitchen/test-kitchen/issues/59) - chef\_data\_uploader doesn't actually upload cookbooks w/ kitchen-vagrant [\#55](https://github.com/test-kitchen/test-kitchen/issues/55) - When 'box' is specified without 'box\_url', just use existing Vagrant base box [\#53](https://github.com/test-kitchen/test-kitchen/issues/53) - make "suites" stanza optional [\#48](https://github.com/test-kitchen/test-kitchen/issues/48) - move JR \(Jamie Runner\) code into appropriate test-kitchen repositories [\#43](https://github.com/test-kitchen/test-kitchen/issues/43) - add individual node definitions and global driver configuration to yaml format [\#41](https://github.com/test-kitchen/test-kitchen/issues/41) - Split classes into separate files \(a.k.a. The Big Split\) [\#39](https://github.com/test-kitchen/test-kitchen/issues/39) - Migrate the jamie-vagrant gem codebase to kitchen-vagrant [\#38](https://github.com/test-kitchen/test-kitchen/issues/38) - support `require\_chef\_omnibus` config option value of "latest" [\#32](https://github.com/test-kitchen/test-kitchen/issues/32) - create kitchen-openstack driver [\#25](https://github.com/test-kitchen/test-kitchen/issues/25) - rename .jamie.yml to .kitchen.yml [\#18](https://github.com/test-kitchen/test-kitchen/issues/18) - Merge "jamie" project with test-kitchen [\#17](https://github.com/test-kitchen/test-kitchen/issues/17) **Merged pull requests:** - YAML Serialization [\#58](https://github.com/test-kitchen/test-kitchen/pull/58) ([fnichol](https://github.com/fnichol)) - Suites should be able to exclude a platform \#29 [\#57](https://github.com/test-kitchen/test-kitchen/pull/57) ([sandfish8](https://github.com/sandfish8)) - add basic instructions [\#54](https://github.com/test-kitchen/test-kitchen/pull/54) ([bryanwb](https://github.com/bryanwb)) ## [v0.1.0.beta4](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.beta4) (2013-01-24) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.beta3...v0.1.0.beta4) ## [v0.1.0.beta3](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.beta3) (2013-01-14) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.beta2...v0.1.0.beta3) ## [v0.1.0.beta2](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.beta2) (2013-01-13) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.beta1...v0.1.0.beta2) ## [v0.1.0.beta1](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.beta1) (2013-01-12) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.3.0...v0.1.0.beta1) ## [v0.3.0](https://github.com/test-kitchen/test-kitchen/tree/v0.3.0) (2013-01-09) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha21...v0.3.0) ## [v0.1.0.alpha21](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha21) (2013-01-09) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha20...v0.1.0.alpha21) ## [v0.1.0.alpha20](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha20) (2013-01-04) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.2.0...v0.1.0.alpha20) ## [v0.2.0](https://github.com/test-kitchen/test-kitchen/tree/v0.2.0) (2013-01-03) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha19...v0.2.0) ## [v0.1.0.alpha19](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha19) (2013-01-03) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha18...v0.1.0.alpha19) ## [v0.1.0.alpha18](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha18) (2012-12-30) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha17...v0.1.0.alpha18) ## [v0.1.0.alpha17](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha17) (2012-12-27) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0...v0.1.0.alpha17) ## [v0.1.0](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0) (2012-12-27) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha16...v0.1.0) ## [v0.1.0.alpha16](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha16) (2012-12-27) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha15...v0.1.0.alpha16) ## [v0.1.0.alpha15](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha15) (2012-12-24) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha14...v0.1.0.alpha15) ## [v0.1.0.alpha14](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha14) (2012-12-22) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha13...v0.1.0.alpha14) ## [v0.1.0.alpha13](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha13) (2012-12-20) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha12...v0.1.0.alpha13) ## [v0.1.0.alpha12](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha12) (2012-12-20) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha11...v0.1.0.alpha12) ## [v0.1.0.alpha11](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha11) (2012-12-20) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha10...v0.1.0.alpha11) ## [v0.1.0.alpha10](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha10) (2012-12-20) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha9...v0.1.0.alpha10) ## [v0.1.0.alpha9](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha9) (2012-12-18) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha8...v0.1.0.alpha9) ## [v0.1.0.alpha8](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha8) (2012-12-17) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha7...v0.1.0.alpha8) ## [v0.1.0.alpha7](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha7) (2012-12-14) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha6...v0.1.0.alpha7) ## [v0.1.0.alpha6](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha6) (2012-12-13) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha5...v0.1.0.alpha6) ## [v0.1.0.alpha5](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha5) (2012-12-13) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha4...v0.1.0.alpha5) ## [v0.1.0.alpha4](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha4) (2012-12-11) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha3...v0.1.0.alpha4) ## [v0.1.0.alpha3](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha3) (2012-12-10) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha2...v0.1.0.alpha3) ## [v0.1.0.alpha2](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha2) (2012-12-03) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.7.0...v0.1.0.alpha2) ## [v0.7.0](https://github.com/test-kitchen/test-kitchen/tree/v0.7.0) (2012-12-03) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.1.0.alpha1...v0.7.0) ## [v0.1.0.alpha1](https://github.com/test-kitchen/test-kitchen/tree/v0.1.0.alpha1) (2012-12-01) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.7.0.rc.1...v0.1.0.alpha1) **Merged pull requests:** - minor formatting and spelling corrections [\#11](https://github.com/test-kitchen/test-kitchen/pull/11) ([mattray](https://github.com/mattray)) ## [v0.7.0.rc.1](https://github.com/test-kitchen/test-kitchen/tree/v0.7.0.rc.1) (2012-11-28) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.7.0.beta.1...v0.7.0.rc.1) **Merged pull requests:** - \[KITCHEN-23\] - load metadata.rb to get cookbook name [\#10](https://github.com/test-kitchen/test-kitchen/pull/10) ([jtimberman](https://github.com/jtimberman)) ## [v0.7.0.beta.1](https://github.com/test-kitchen/test-kitchen/tree/v0.7.0.beta.1) (2012-11-21) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.6.0...v0.7.0.beta.1) ## [v0.6.0](https://github.com/test-kitchen/test-kitchen/tree/v0.6.0) (2012-10-02) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.5.4...v0.6.0) **Merged pull requests:** - \[KITCHEN-29\] - implement --platform to limit test [\#8](https://github.com/test-kitchen/test-kitchen/pull/8) ([jtimberman](https://github.com/jtimberman)) - KITCHEN-22 - Include Databags in Vagrant Configuration if present [\#5](https://github.com/test-kitchen/test-kitchen/pull/5) ([brendanhay](https://github.com/brendanhay)) - KITCHEN-35 use minitest-handler from community.opscode.com [\#4](https://github.com/test-kitchen/test-kitchen/pull/4) ([bryanwb](https://github.com/bryanwb)) ## [v0.5.4](https://github.com/test-kitchen/test-kitchen/tree/v0.5.4) (2012-08-30) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.5.2...v0.5.4) **Merged pull requests:** - \[KITCHEN-17\] - support ignoring lint rules [\#3](https://github.com/test-kitchen/test-kitchen/pull/3) ([jtimberman](https://github.com/jtimberman)) ## [v0.5.2](https://github.com/test-kitchen/test-kitchen/tree/v0.5.2) (2012-08-18) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/v0.5.0...v0.5.2) ## [v0.5.0](https://github.com/test-kitchen/test-kitchen/tree/v0.5.0) (2012-08-16) [Full Changelog](https://github.com/test-kitchen/test-kitchen/compare/0.5.0...v0.5.0) ## [0.5.0](https://github.com/test-kitchen/test-kitchen/tree/0.5.0) (2012-08-16) \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* test-kitchen-1.23.2/.rubocop.yml0000644000004100000410000000012713377651062016515 0ustar www-datawww-dataNaming/FileName: Exclude: - 'support/**/*' Layout/EndOfLine: EnforcedStyle: lf test-kitchen-1.23.2/test-kitchen.gemspec0000644000004100000410000000363313377651062020217 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path("../lib", __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require "kitchen/version" require "English" Gem::Specification.new do |gem| gem.name = "test-kitchen" gem.version = Kitchen::VERSION gem.license = "Apache 2.0" gem.authors = ["Fletcher Nichol"] gem.email = ["fnichol@nichol.ca"] gem.description = "Test Kitchen is an integration tool for developing " \ "and testing infrastructure code and software on " \ "isolated target platforms." gem.summary = gem.description gem.homepage = "http://kitchen.ci" gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) gem.executables = %w{kitchen} gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] gem.required_ruby_version = ">= 2.3" gem.add_dependency "mixlib-shellout", ">= 1.2", "< 3.0" gem.add_dependency "net-scp", "~> 1.1" gem.add_dependency "net-ssh", ">= 2.9", "< 5.0" gem.add_dependency "net-ssh-gateway", "~> 1.2" gem.add_dependency "thor", "~> 0.19" gem.add_dependency "mixlib-install", "~> 3.6" gem.add_dependency "winrm", "~> 2.0" gem.add_dependency "winrm-elevated", "~> 1.0" gem.add_dependency "winrm-fs", "~> 1.1" gem.add_development_dependency "rb-readline" gem.add_development_dependency "overcommit", "= 0.33.0" gem.add_development_dependency "bundler" gem.add_development_dependency "rake" gem.add_development_dependency "aruba", "~> 0.11" gem.add_development_dependency "fakefs", "~> 0.4" gem.add_development_dependency "minitest", "~> 5.3", "< 5.11" gem.add_development_dependency "mocha", "~> 1.1" gem.add_development_dependency "cucumber", "~> 2.1" gem.add_development_dependency "countloc", "~> 0.4" gem.add_development_dependency "maruku", "~> 0.6" end test-kitchen-1.23.2/templates/0000755000004100000410000000000013377651062016241 5ustar www-datawww-datatest-kitchen-1.23.2/templates/driver/0000755000004100000410000000000013377651062017534 5ustar www-datawww-datatest-kitchen-1.23.2/templates/driver/gemspec.erb0000644000004100000410000000210513377651062021647 0ustar www-datawww-data# coding: utf-8 lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'kitchen/driver/<%= config[:name] %>_version' Gem::Specification.new do |spec| spec.name = '<%= config[:gem_name] %>' spec.version = Kitchen::Driver::<%= config[:constant_name] %>_VERSION spec.authors = ['<%= config[:author] %>'] spec.email = ['<%= config[:email] %>'] spec.description = %q{A Test Kitchen Driver for <%= config[:klass_name] %>} spec.summary = spec.description spec.homepage = '' spec.license = '<%= config[:license_string] %>' spec.files = `git ls-files`.split($/) spec.executables = [] spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] spec.add_dependency 'test-kitchen', '~> 1.0.0.alpha.3' spec.add_development_dependency 'bundler', '~> 1.3' spec.add_development_dependency 'rake' spec.add_development_dependency 'cane' spec.add_development_dependency 'tailor' spec.add_development_dependency 'countloc' end test-kitchen-1.23.2/templates/driver/travis.yml.erb0000644000004100000410000000014713377651062022340 0ustar www-datawww-datalanguage: ruby rvm: - 2.0.0 - 1.9.3 - 1.9.2 - ruby-head matrix: allow_failures: - rvm: ruby-head test-kitchen-1.23.2/templates/driver/Gemfile.erb0000644000004100000410000000004713377651062021577 0ustar www-datawww-datasource 'https://rubygems.org' gemspec test-kitchen-1.23.2/templates/driver/license_mit.erb0000644000004100000410000000216613377651062022526 0ustar www-datawww-dataAuthor:: <%= config[:author] %> (<<%= config[:email] %>>) Copyright (c) <%= config[:year] %>, <%= config[:author] %> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. test-kitchen-1.23.2/templates/driver/Rakefile.erb0000644000004100000410000000063213377651062021751 0ustar www-datawww-datarequire "bundler/gem_tasks" require 'cane/rake_task' require 'tailor/rake_task' desc "Run cane to check quality metrics" Cane::RakeTask.new do |cane| cane.canefile = './.cane' end Tailor::RakeTask.new desc "Display LOC stats" task :stats do puts "\n## Production Code Stats" sh "countloc -r lib" end desc "Run all quality tasks" task :quality => [:cane, :tailor, :stats] task :default => [:quality] test-kitchen-1.23.2/templates/driver/version.rb.erb0000644000004100000410000000033213377651062022313 0ustar www-datawww-data# -*- encoding: utf-8 -*- # <%= license_comment %> module Kitchen module Driver # Version string for <%= config[:klass_name] %> Kitchen driver <%= config[:constant_name] %>_VERSION = "0.1.0.dev" end end test-kitchen-1.23.2/templates/driver/license_lgplv3.erb0000644000004100000410000000133313377651062023137 0ustar www-datawww-dataAuthor:: <%= config[:author] %> (<<%= config[:email] %>>) Copyright (C) <%= config[:year] %> <%= config[:author] %> This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . test-kitchen-1.23.2/templates/driver/README.md.erb0000644000004100000410000000472013377651062021565 0ustar www-datawww-data# Kitchen::<%= config[:klass_name] %> A Test Kitchen Driver for <%= config[:klass_name] %>. ## Requirements **TODO:** document any software or library prerequisites that are required to use this driver. Implement the `#verify_dependencies` method in your Driver class to enforce these requirements in code, if possible. ## Installation and Setup Please read the [Driver usage][driver_usage] page for more details. ## Configuration **TODO:** Write descriptions of all configuration options ### require\_chef\_omnibus Determines whether or not a Chef [Omnibus package][chef_omnibus_dl] will be installed. There are several different behaviors available: * `true` - the latest release will be installed. Subsequent converges will skip re-installing if chef is present. * `latest` - the latest release will be installed. Subsequent converges will always re-install even if chef is present. * `` (ex: `10.24.0`) - the desired version string will be passed the the install.sh script. Subsequent converges will skip if the installed version and the desired version match. * `false` or `nil` - no chef is installed. The default value is unset, or `nil`. ## Development * Source hosted at [GitHub][repo] * Report issues/questions/feature requests on [GitHub Issues][issues] Pull requests are very welcome! Make sure your patches are well tested. Ideally create a topic branch for every separate change you make. For example: 1. Fork the repo 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Added some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ## Authors Created and maintained by [<%= config[:author] %>][author] (<<%= config[:email] %>>) ## License <%= config[:license_string] %> (see [LICENSE][license]) [author]: https://github.com/enter-github-user [issues]: https://github.com/enter-github-user/<%= config[:gem_name] %>/issues [license]: https://github.com/enter-github-user/<%= config[:gem_name] %>/blob/master/LICENSE [repo]: https://github.com/enter-github-user/<%= config[:gem_name] %> [driver_usage]: http://docs.kitchen-ci.org/drivers/usage [chef_omnibus_dl]: http://www.chef.io/chef/install/ test-kitchen-1.23.2/templates/driver/gitignore.erb0000644000004100000410000000023213377651062022212 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp test-kitchen-1.23.2/templates/driver/CHANGELOG.md.erb0000644000004100000410000000005113377651062022110 0ustar www-datawww-data## 0.1.0 / Unreleased * Initial release test-kitchen-1.23.2/templates/driver/license_reserved.erb0000644000004100000410000000024113377651062023544 0ustar www-datawww-dataAuthor:: <%= config[:author] %> (<<%= config[:email] %>>) Copyright (C) <%= config[:year] %>, <%= config[:author] %> All rights reserved - Do Not Redistribute test-kitchen-1.23.2/templates/driver/tailor.erb0000644000004100000410000000013113377651062021513 0ustar www-datawww-dataTailor.config do |config| config.formatters "text" config.file_set 'lib/**/*.rb' end test-kitchen-1.23.2/templates/driver/driver.rb.erb0000644000004100000410000000057113377651062022126 0ustar www-datawww-data# -*- encoding: utf-8 -*- # <%= license_comment %> require 'kitchen' module Kitchen module Driver # <%= config[:klass_name] %> driver for Kitchen. # # @author <%= config[:author] %> <<%= config[:email] %>> class <%= config[:klass_name] %> < Kitchen::Driver::SSHBase def create(state) end def destroy(state) end end end end test-kitchen-1.23.2/templates/driver/license_apachev2.erb0000644000004100000410000000120213377651062023414 0ustar www-datawww-dataAuthor:: <%= config[:author] %> (<<%= config[:email] %>>) Copyright (C) <%= config[:year] %>, <%= config[:author] %> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. test-kitchen-1.23.2/templates/init/0000755000004100000410000000000013377651062017204 5ustar www-datawww-datatest-kitchen-1.23.2/templates/init/kitchen.yml.erb0000644000004100000410000000043613377651062022126 0ustar www-datawww-data--- driver: name: <%= config[:driver_plugin] %> provisioner: name: <%= config[:provisioner] %> platforms: - name: ubuntu-16.04 - name: centos-7 suites: - name: default run_list: <% config[:run_list].each do |recipe| -%> - <%= recipe %> <% end -%> attributes: test-kitchen-1.23.2/templates/init/chefignore.erb0000644000004100000410000000001113377651062021777 0ustar www-datawww-data.kitchen test-kitchen-1.23.2/.gitignore0000644000004100000410000000050613377651062016234 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc binstubs Gemfile*.lock Gemfile.local InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp .rvmrc .rbenv-version .ruby-version .project .DS_Store .kitchen/ .kitchen.yml .kitchen.local.yml kitchen.yml kitchen.local.yml Berksfile.lock *.swp test-kitchen-1.23.2/.kitchen.dokken.yml0000644000004100000410000000101113377651062017734 0ustar www-datawww-data--- driver: name: dokken privileged: true # because Docker and SystemD/Upstart provisioner: name: dokken transport: name: dokken verifier: name: inspec platforms: - name: ubuntu-18.04 driver: image: dokken/ubuntu-18.04 pid_one_command: /bin/systemd intermediate_instructions: - RUN /usr/bin/apt-get update - name: centos-7 driver: image: dokken/centos-7 pid_one_command: /usr/lib/systemd/systemd suites: - name: default run_list: - recipe[test_cookbook::default] test-kitchen-1.23.2/LICENSE0000644000004100000410000000113413377651062015247 0ustar www-datawww-dataAuthor:: Fletcher Nichol () Copyright 2012 Fletcher Nichol Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. test-kitchen-1.23.2/Gemfile.proxy_tests0000644000004100000410000000014713377651062020142 0ustar www-datawww-data# -*- encoding: utf-8 -*- eval_gemfile File.join(File.dirname(__FILE__), "Gemfile") gem "chef-config" test-kitchen-1.23.2/.gitattributes0000644000004100000410000000016313377651062017136 0ustar www-datawww-data# Managing line ending conversions # See http://git-scm.com/docs/gitattributes#_end-of-line_conversion * text=auto test-kitchen-1.23.2/RELEASE_NOTES.md0000644000004100000410000001246613377651062016626 0ustar www-datawww-data# Test Kitchen 1.23.0 Release Notes ## Life Cycle Hooks The life cycle hooks system allows running commands before or after any phase of Test Kitchen (`create`, `converge`, `verify`, or `destroy`). Commands can be run either locally on your workstation (the default) or remotely on the test instance. These hooks are configured under a new `lifecycle:` section in `kitchen.yml`: ```yaml lifecycle: pre_create: echo before post_create: - echo after - local: echo also after - remote: echo after but in the instance ``` You can also configure hooks on a single platform or suite: ```yaml platforms: - name: ubuntu-18.04 lifecycle: pre_converge: - remote: apt update suites: - name: default lifecycle: post_verify: - my_coverage_formatter ``` Local commands automatically get some environment variables with information about which instance the hook is evaluating against: * `KITCHEN_INSTANCE_NAME` - The full name of the instance * `KITCHEN_SUITE_NAME` - The name of the suite of the instance * `KITCHEN_PLATFORM_NAME` - The name of the platform of the instance * `KITCHEN_INSTANCE_HOSTNAME` - The hostname of the instance as reported by the driver plugin You can also pass additional configuration for local commands: ```yaml lifecycle: pre_converge: - local: ./setup.sh environment: API_KEY: asdf1234 timeout: 60 ``` Remote commands are normally not allowed during `pre_create` or `post_destroy` hooks as there is generally no instance running at that point, but with `pre_destroy` hooks you may want to use the `skippable` flag so as to not fail during `kitchen test`: ```yaml lifecycle: pre_destroy: - remote: myapp --unregister-license skippable: true ``` # Test Kitchen 1.21.0 Release Notes ## Configuration UX improvements Having the kitchen configuration file be hidden has always been a bit odd and so we're moving to using `kitchen.yml` over `.kitchen.yml`. This also applies to `kitchen.local.yml` and we've made the change backwards compatible so you're not forced to move over right away. Additionally, we've added support for the environment variables `KITCHEN_YML` and KITCHEN_LOCAL_YML` again preserving compatibility if you're using the `*_YAML` forms. # Test Kitchen 1.20.0 Release Notes ## Multiple paths for data_bags Allows a user to use data_bags from an array of directories ``` data_bags_path: - 'data_bags' - 'test/integrations/data_bags' ``` ## Deprecation Warnings for Configuration Keys ``` $ kitchen list default-centos-7 $$$$$$ Deprecated configuration detected: require_chef_omnibus Run 'kitchen doctor' for details. ``` ``` $ kitchen doctor $$$$$$ Deprecated configuration detected: require_chef_omnibus Run 'kitchen doctor' for details. -----> The doctor is in **** require_chef_omnibus deprecated The 'require_chef_omnibus' attribute with version values will change to use the new 'product_version' attribute. Note: 'product_name' must be set in order to use 'product_version' until 'product_name' replaces 'require_chef_omnibus' as the default. # New Usage # provisioner: product_name: product_version: 12.0.3 ``` ## SSH via an HTTP Proxy This allows configuring the SSH transport to utilize an HTTP Proxy. The following configuration keys have been added to `transport`: ``` ssh_http_proxy_user ssh_http_proxy_password ssh_http_proxy_port ssh_http_proxy ``` # Test Kitchen 1.20.0 Release Notes ## Driver Commands Removed The `kitchen driver` family of commands have been removed. It was not recommended to use them and it was judged to be more harm than good to leave them in. If you regularly create new drivers and relied on the skeleton generator, check out other code skeleton projects like [`chef generate`](https://blog.chef.io/2014/12/09/guest-post-creating-your-own-chef-cookbook-generator/), and [Cookiecutter](https://github.com/audreyr/cookiecutter). ## `kitchen converge -D` When you want to get debug logging for your provisioner or verifier, you can now use the new `-D` (or `--debug`) command line option for `kitchen converge`, `kitchen verify`, and `kitchen test`. Support has been added to the Chef provisioners, avoiding the need to use the `log_level: debug` configuration option every time. ## `exec` Driver A new driver named `exec` is included with Test Kitchen which runs all the provisioning and verification commands locally, rather than on a VM. This can be used for testing on systems where you've already created the VM yourself and installed Test Kitchen on it. Note that this is related but different from the included `proxy` driver, which also connects to an existing server, but over SSH/WinRM rather than running commands locally. ## `shell` Provisioner `command` Previously the included `shell` provisioner allowed running a user-specified bootstrap script. This has been extended to allow specifying a `command` option with a string to run, rather than managing a script file. ## Faster Busser The `busser` verifier has been improved to be faster on the second (or beyond) verification, or in other cases where the required gems are already present. ## `kitchen doctor` A `kitchen doctor` command has been added, modeled on Homebrew's `brew doctor`. This currently doesn't do much, but if you are a Kitchen plugin author, consider adding more detailed debugging checks and troubleshooting help to your plugin via this system. test-kitchen-1.23.2/Rakefile0000644000004100000410000000350713377651062015715 0ustar www-datawww-data# -*- encoding: utf-8 -*- require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new(:unit) do |t| t.libs.push "lib" t.test_files = FileList["spec/**/*_spec.rb"] t.verbose = true end begin require "cucumber" require "cucumber/rake/task" Cucumber::Rake::Task.new(:features) do |t| t.cucumber_opts = ["features", "-x", "--format progress", "--no-color", "--tags ~@ignore"] end rescue LoadError puts "cucumber is not available. (sudo) gem install cucumber to run tests." end desc "Run all test suites" task test: [:unit, :features] desc "Display LOC stats" task :stats do puts "\n## Production Code Stats" sh "countloc -r lib/kitchen lib/kitchen.rb" puts "\n## Test Code Stats" sh "countloc -r spec features" end begin require "chefstyle" require "rubocop/rake_task" RuboCop::RakeTask.new(:style) do |task| task.options += ["--display-cop-names", "--no-color"] end rescue LoadError puts "chefstyle is not available. (sudo) gem install chefstyle to do style checking." end desc "Run all quality tasks" task quality: [:style, :stats] begin require "yard" YARD::Rake::YardocTask.new rescue LoadError puts "yard is not available. (sudo) gem install yard to generate yard documentation." end task default: [:test, :quality] begin require "github_changelog_generator/task" require "kitchen/version" GitHubChangelogGenerator::RakeTask.new :changelog do |config| config.future_release = "v#{Kitchen::VERSION}" config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature,Improvement".split(",") config.bug_labels = "bug,Bug".split(",") config.exclude_labels = %w{Duplicate Question Discussion No_Changelog} end rescue LoadError puts "github_changelog_generator is not available." \ " (sudo) gem install github_changelog_generator to generate changelogs" end test-kitchen-1.23.2/lib/0000755000004100000410000000000013377651062015011 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen.rb0000644000004100000410000001051513377651062016765 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, 2014 Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "pathname" require "thread" require "kitchen/errors" require "kitchen/logger" require "kitchen/logging" require "kitchen/shell_out" require "kitchen/configurable" require "kitchen/util" require "kitchen/provisioner" require "kitchen/provisioner/base" require "kitchen/color" require "kitchen/collection" require "kitchen/config" require "kitchen/data_munger" require "kitchen/driver" require "kitchen/driver/base" require "kitchen/driver/ssh_base" require "kitchen/driver/proxy" require "kitchen/instance" require "kitchen/lifecycle_hooks" require "kitchen/transport" require "kitchen/transport/base" require "kitchen/loader/yaml" require "kitchen/metadata_chopper" require "kitchen/platform" require "kitchen/state_file" require "kitchen/ssh" require "kitchen/suite" require "kitchen/verifier" require "kitchen/verifier/base" require "kitchen/version" # Test Kitchen base module. # # @author Fletcher Nichol module Kitchen class << self # @return [Logger] the common Kitchen logger attr_accessor :logger # @return [Mutex] a common mutex for global coordination attr_accessor :mutex # @return [Mutex] a mutex used for Dir.chdir coordination attr_accessor :mutex_chdir # Returns the root path of the Kitchen gem source code. # # @return [Pathname] root path of gem def source_root @source_root ||= Pathname.new(File.expand_path("../../", __FILE__)) end # Returns a default logger which emits on standard output. # # @return [Logger] a logger def default_logger Logger.new(stdout: $stdout, level: Util.to_logger_level(env_log)) end # Returns a default file logger which emits on standard output and to a # log file. # # @param [Symbol] level logging level # @param [Boolean] log_overwrite logging level # @return [Logger] a logger def default_file_logger(level = nil, log_overwrite = nil) level ||= env_log log_overwrite = log_overwrite.nil? ? env_log_overwrite : log_overwrite log_location = File.expand_path(File.join(DEFAULT_LOG_DIR, "kitchen.log")) log_location = log_location.to_s Logger.new( stdout: $stdout, logdev: log_location, level: Util.to_logger_level(level), log_overwrite: log_overwrite ) end # Returns whether or not standard output is associated with a terminal # device (tty). # # @return [true,false] is there a tty? def tty? $stdout.tty? end # Determine the default log level from an environment variable, if it is # set. # # @return [Symbol,nil] a log level or nil if not set # @api private def env_log ENV["KITCHEN_LOG"] && ENV["KITCHEN_LOG"].downcase.to_sym end # Determine the log overwriting logic from an environment variable, # if it is set. # # @return [Boolean,nil] # @api private def env_log_overwrite case ENV["KITCHEN_LOG_OVERWRITE"] && ENV["KITCHEN_LOG_OVERWRITE"].downcase when nil, "" nil when "false", "f", "no" false else true end end end # Default log level verbosity DEFAULT_LOG_LEVEL = :info # Overwrite the log file when Test Kitchen runs DEFAULT_LOG_OVERWRITE = true # Default base directory for integration tests, fixtures, etc. DEFAULT_TEST_DIR = "test/integration".freeze # Default base directory for instance and common log files DEFAULT_LOG_DIR = ".kitchen/logs".freeze end # Initialize the base logger Kitchen.logger = Kitchen.default_logger # Setup a collection of instance crash exceptions for error reporting Kitchen.mutex = Mutex.new # Initialize the mutex for Dir.chdir coordination Kitchen.mutex_chdir = Mutex.new test-kitchen-1.23.2/lib/vendor/0000755000004100000410000000000013377651062016306 5ustar www-datawww-datatest-kitchen-1.23.2/lib/vendor/hash_recursive_merge.rb0000644000004100000410000000473413377651062023034 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # = Hash Recursive Merge # # Merges a Ruby Hash recursively, Also known as deep merge. # Recursive version of Hash#merge and Hash#merge!. # # Category:: Ruby # Package:: Hash # Author:: Simone Carletti # Copyright:: 2007-2008 The Authors # License:: MIT License # Link:: http://www.simonecarletti.com/ # Source:: http://gist.github.com/gists/6391/ # module HashRecursiveMerge # # Recursive version of Hash#merge! # # Adds the contents of +other_hash+ to +hsh+, # merging entries in +hsh+ with duplicate keys with those from +other_hash+. # # Compared with Hash#merge!, this method supports nested hashes. # When both +hsh+ and +other_hash+ contains an entry with the same key, # it merges and returns the values from both arrays. # # @example # # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}} # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}} # h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}} # # Simply using Hash#merge! would return # # @example # # h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}} # def rmerge!(other_hash) merge!(other_hash) do |_key, oldval, newval| oldval.class == self.class ? oldval.rmerge!(newval) : newval end end # # Recursive version of Hash#merge # # Compared with Hash#merge!, this method supports nested hashes. # When both +hsh+ and +other_hash+ contains an entry with the same key, # it merges and returns the values from both arrays. # # Compared with Hash#merge, this method provides a different approch # for merging nasted hashes. # If the value of a given key is an Hash and both +other_hash+ abd +hsh # includes the same key, the value is merged instead replaced with # +other_hash+ value. # # @example # # h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}} # h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}} # h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}} # # Simply using Hash#merge would return # # @example # # h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}} # def rmerge(other_hash) r = {} merge(other_hash) do |key, oldval, newval| r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval end end end class Hash include HashRecursiveMerge end test-kitchen-1.23.2/lib/kitchen/0000755000004100000410000000000013377651062016436 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/driver/0000755000004100000410000000000013377651062017731 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/driver/exec.rb0000644000004100000410000000367313377651062021213 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "kitchen/driver/base" require "kitchen/shell_out" require "kitchen/transport/exec" require "kitchen/version" module Kitchen module Driver # Simple driver that runs commands locally. As with the proxy driver, this # has no isolation in general. class Exec < Kitchen::Driver::Base include ShellOut plugin_version Kitchen::VERSION default_config :reset_command, nil no_parallel_for :create, :converge, :destroy # Hack to force using the exec transport when using this driver. # If someone comes up with a use case for using the driver with a different # transport, please let us know. # # @api private def finalize_config!(instance) super.tap do instance.transport = Kitchen::Transport::Exec.new end end # (see Base#create) def create(state) super reset_instance(state) end # (see Base#destroy) def destroy(state) reset_instance(state) end private # Resets the non-Kitchen managed instance using by issuing a command # over SSH. # # @param state [Hash] the state hash # @api private def reset_instance(state) if (cmd = config[:reset_command]) info("Resetting instance state with command: #{cmd}") run_command(cmd) end end end end end test-kitchen-1.23.2/lib/kitchen/driver/proxy.rb0000644000004100000410000000422613377651062021443 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Seth Chisamore # # Copyright:: Copyright (c) 2013 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "kitchen/driver/ssh_base" require "kitchen/version" module Kitchen module Driver # Simple driver that proxies commands through to a test instance whose # lifecycle is not managed by Test Kitchen. This driver is useful for long- # lived non-ephemeral test instances that are simply "reset" between test # runs. Think executing against devices like network switches--this is why # the driver was created. # # @author Seth Chisamore class Proxy < Kitchen::Driver::SSHBase plugin_version Kitchen::VERSION required_config :host default_config :reset_command, nil no_parallel_for :create, :destroy # (see Base#create) def create(state) # TODO: Once this isn't using SSHBase, it should call `super` to support pre_create_command. state[:hostname] = config[:host] reset_instance(state) end # (see Base#destroy) def destroy(state) return if state[:hostname].nil? reset_instance(state) state.delete(:hostname) end private # Resets the non-Kitchen managed instance using by issuing a command # over SSH. # # @param state [Hash] the state hash # @api private def reset_instance(state) if (cmd = config[:reset_command]) info("Resetting instance state with command: #{cmd}") ssh(build_ssh_args(state), cmd) end end end end end test-kitchen-1.23.2/lib/kitchen/driver/ssh_base.rb0000644000004100000410000003375413377651062022061 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor/util" require "kitchen/lazy_hash" require "benchmark" module Kitchen module Driver # Legacy base class for a driver that uses SSH to communication with an # instance. This class has been updated to use the Instance's Transport to # issue commands and transfer files and no longer uses the `Kitchen:SSH` # class directly. # # **NOTE:** Authors of new Drivers are encouraged to inherit from # `Kitchen::Driver::Base` instead and existing Driver authors are # encouraged to update their Driver class to inherit from # `Kitchen::Driver::SSHBase`. # # A subclass must implement the following methods: # * #create(state) # * #destroy(state) # # @author Fletcher Nichol # @deprecated While all possible effort has been made to preserve the # original behavior of this class, future improvements to the Driver, # Transport, and Verifier subsystems may not be picked up in these # Drivers. When legacy Driver::SSHBase support is removed, this class # will no longer be available. class SSHBase include ShellOut include Configurable include Logging default_config :sudo, true default_config :port, 22 # needs to be one less than the configured sshd_config MaxSessions default_config :max_ssh_sessions, 9 # Creates a new Driver object using the provided configuration data # which will be merged with any default configuration. # # @param config [Hash] provided driver configuration def initialize(config = {}) init_config(config) end # (see Base#create) def create(state) # rubocop:disable Lint/UnusedMethodArgument raise ClientError, "#{self.class}#create must be implemented" end # (see Base#converge) def converge(state) # rubocop:disable Metrics/AbcSize provisioner = instance.provisioner provisioner.create_sandbox sandbox_dirs = Util.list_directory(provisioner.sandbox_path) instance.transport.connection(backcompat_merged_state(state)) do |conn| conn.execute(env_cmd(provisioner.install_command)) conn.execute(env_cmd(provisioner.init_command)) info("Transferring files to #{instance.to_str}") conn.upload(sandbox_dirs, provisioner[:root_path]) debug("Transfer complete") conn.execute(env_cmd(provisioner.prepare_command)) conn.execute(env_cmd(provisioner.run_command)) info("Downloading files from #{instance.to_str}") provisioner[:downloads].to_h.each do |remotes, local| debug("Downloading #{Array(remotes).join(', ')} to #{local}") conn.download(remotes, local) end debug("Download complete") end rescue Kitchen::Transport::TransportFailed => ex raise ActionFailed, ex.message ensure instance.provisioner.cleanup_sandbox end # (see Base#setup) def setup(state) verifier = instance.verifier instance.transport.connection(backcompat_merged_state(state)) do |conn| conn.execute(env_cmd(verifier.install_command)) end rescue Kitchen::Transport::TransportFailed => ex raise ActionFailed, ex.message end # (see Base#verify) def verify(state) # rubocop:disable Metrics/AbcSize verifier = instance.verifier verifier.create_sandbox sandbox_dirs = Util.list_directory(verifier.sandbox_path) instance.transport.connection(backcompat_merged_state(state)) do |conn| conn.execute(env_cmd(verifier.init_command)) info("Transferring files to #{instance.to_str}") conn.upload(sandbox_dirs, verifier[:root_path]) debug("Transfer complete") conn.execute(env_cmd(verifier.prepare_command)) conn.execute(env_cmd(verifier.run_command)) end rescue Kitchen::Transport::TransportFailed => ex raise ActionFailed, ex.message ensure instance.verifier.cleanup_sandbox end # (see Base#destroy) def destroy(state) # rubocop:disable Lint/UnusedMethodArgument raise ClientError, "#{self.class}#destroy must be implemented" end def legacy_state(state) backcompat_merged_state(state) end # Package an instance. # # (see Base#package) def package(state) # rubocop:disable Lint/UnusedMethodArgument end # (see Base#login_command) def login_command(state) instance.transport.connection(backcompat_merged_state(state)) .login_command end # Executes an arbitrary command on an instance over an SSH connection. # # @param state [Hash] mutable instance and driver state # @param command [String] the command to be executed # @raise [ActionFailed] if the command could not be successfully completed def remote_command(state, command) instance.transport.connection(backcompat_merged_state(state)) do |conn| conn.execute(env_cmd(command)) end end # **(Deprecated)** Executes a remote command over SSH. # # @param ssh_args [Array] ssh arguments # @param command [String] remote command to invoke # @deprecated This method should no longer be called directly and exists # to support very old drivers. This will be removed in the future. def ssh(ssh_args, command) pseudo_state = { hostname: ssh_args[0], username: ssh_args[1] } pseudo_state.merge!(ssh_args[2]) connection_state = backcompat_merged_state(pseudo_state) instance.transport.connection(connection_state) do |conn| conn.execute(env_cmd(command)) end end # Performs whatever tests that may be required to ensure that this driver # will be able to function in the current environment. This may involve # checking for the presence of certain directories, software installed, # etc. # # @raise [UserError] if the driver will not be able to perform or if a # documented dependency is missing from the system def verify_dependencies end class << self # @return [Array] an array of action method names that cannot # be run concurrently and must be run in serial via a shared mutex attr_reader :serial_actions end # Registers certain driver actions that cannot be safely run concurrently # in threads across multiple instances. Typically this might be used # for create or destroy actions that use an underlying resource that # cannot be used at the same time. # # A shared mutex for this driver object will be used to synchronize all # registered methods. # # @example a single action method that cannot be run concurrently # # no_parallel_for :create # # @example multiple action methods that cannot be run concurrently # # no_parallel_for :create, :destroy # # @param methods [Array] one or more actions as symbols # @raise [ClientError] if any method is not a valid action method name def self.no_parallel_for(*methods) action_methods = [:create, :converge, :setup, :verify, :destroy] Array(methods).each do |meth| next if action_methods.include?(meth) raise ClientError, "##{meth} is not a valid no_parallel_for method" end @serial_actions ||= [] @serial_actions += methods end # Cache directory that a driver could implement to inform the provisioner # that it can leverage it internally # # @return path [String] a path of the cache directory def cache_directory end private def backcompat_merged_state(state) driver_ssh_keys = %w{ forward_agent hostname password port ssh_key username }.map(&:to_sym) config.select { |key, _| driver_ssh_keys.include?(key) }.rmerge(state) end # Builds arguments for constructing a `Kitchen::SSH` instance. # # @param state [Hash] state hash # @return [Array] SSH constructor arguments # @api private def build_ssh_args(state) combined = config.to_hash.merge(state) opts = {} opts[:user_known_hosts_file] = "/dev/null" opts[:verify_host_key] = false opts[:keys_only] = true if combined[:ssh_key] opts[:password] = combined[:password] if combined[:password] opts[:forward_agent] = combined[:forward_agent] if combined.key? :forward_agent opts[:port] = combined[:port] if combined[:port] opts[:keys] = Array(combined[:ssh_key]) if combined[:ssh_key] opts[:logger] = logger [combined[:hostname], combined[:username], opts] end # Adds http, https and ftp proxy environment variables to a command, if # set in configuration data or on local workstation. # # @param cmd [String] command string # @return [String] command string # @api private # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize def env_cmd(cmd) return if cmd.nil? env = "env" http_proxy = config[:http_proxy] || ENV["http_proxy"] || ENV["HTTP_PROXY"] https_proxy = config[:https_proxy] || ENV["https_proxy"] || ENV["HTTPS_PROXY"] ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] || ENV["FTP_PROXY"] no_proxy = if (!config[:http_proxy] && http_proxy) || (!config[:https_proxy] && https_proxy) || (!config[:ftp_proxy] && ftp_proxy) ENV["no_proxy"] || ENV["NO_PROXY"] end env << " http_proxy=#{http_proxy}" if http_proxy env << " https_proxy=#{https_proxy}" if https_proxy env << " ftp_proxy=#{ftp_proxy}" if ftp_proxy env << " no_proxy=#{no_proxy}" if no_proxy env == "env" ? cmd : "#{env} #{cmd}" end # Executes a remote command over SSH. # # @param command [String] remove command to run # @param connection [Kitchen::SSH] an SSH connection # @raise [ActionFailed] if an exception occurs # @api private def run_remote(command, connection) return if command.nil? connection.exec(env_cmd(command)) rescue SSHFailed, Net::SSH::Exception => ex raise ActionFailed, ex.message end # Transfers one or more local paths over SSH. # # @param locals [Array] array of local paths # @param remote [String] remote destination path # @param connection [Kitchen::SSH] an SSH connection # @raise [ActionFailed] if an exception occurs # @api private def transfer_path(locals, remote, connection) return if locals.nil? || Array(locals).empty? info("Transferring files to #{instance.to_str}") debug("TIMING: scp asynch upload (Kitchen::Driver::SSHBase)") elapsed = Benchmark.measure do transfer_path_async(locals, remote, connection) end delta = Util.duration(elapsed.real) debug("TIMING: scp async upload (Kitchen::Driver::SSHBase) took #{delta}") debug("Transfer complete") rescue SSHFailed, Net::SSH::Exception => ex raise ActionFailed, ex.message end def transfer_path_async(locals, remote, connection) waits = [] locals.map do |local| waits.push connection.upload_path(local, remote) waits.shift.wait while waits.length >= config[:max_ssh_sessions] end waits.each(&:wait) end # Blocks until a TCP socket is available where a remote SSH server # should be listening. # # @param hostname [String] remote SSH server host # @param username [String] SSH username (default: `nil`) # @param options [Hash] configuration hash (default: `{}`) # @api private def wait_for_sshd(hostname, username = nil, options = {}) pseudo_state = { hostname: hostname } pseudo_state[:username] = username if username pseudo_state.merge!(options) instance.transport.connection(backcompat_merged_state(pseudo_state)) .wait_until_ready end # Intercepts any bare #puts calls in subclasses and issues an INFO log # event instead. # # @param msg [String] message string def puts(msg) info(msg) end # Intercepts any bare #print calls in subclasses and issues an INFO log # event instead. # # @param msg [String] message string def print(msg) info(msg) end # Delegates to Kitchen::ShellOut.run_command, overriding some default # options: # # * `:use_sudo` defaults to the value of `config[:use_sudo]` in the # Driver object # * `:log_subject` defaults to a String representation of the Driver's # class name # # @see ShellOut#run_command def run_command(cmd, options = {}) base_options = { use_sudo: config[:use_sudo], log_subject: Thor::Util.snake_case(self.class.to_s), }.merge(options) super(cmd, base_options) end # Returns the Busser object associated with the driver. # # @return [Busser] a busser def busser instance.verifier end end end end test-kitchen-1.23.2/lib/kitchen/driver/base.rb0000644000004100000410000001231513377651062021172 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/lazy_hash" require "kitchen/shell_out" module Kitchen module Driver # Base class for a driver. # # @author Fletcher Nichol class Base include Configurable include Logging include ShellOut default_config :pre_create_command, nil # Creates a new Driver object using the provided configuration data # which will be merged with any default configuration. # # @param config [Hash] provided driver configuration def initialize(config = {}) init_config(config) end # Creates an instance. # # @param state [Hash] mutable instance and driver state # @raise [ActionFailed] if the action could not be completed def create(state) # rubocop:disable Lint/UnusedMethodArgument pre_create_command end # Destroys an instance. # # @param state [Hash] mutable instance and driver state # @raise [ActionFailed] if the action could not be completed def destroy(state) # rubocop:disable Lint/UnusedMethodArgument end # Package an instance. # # @param state [Hash] mutable instance and driver state # @raise [ActionFailed] if the action could not be completed def package(state) # rubocop:disable Lint/UnusedMethodArgument end # Check system and configuration for common errors. # # @param state [Hash] mutable instance and driver state # @returns [Boolean] Return true if a problem is found. def doctor(state) false end class << self # @return [Array] an array of action method names that cannot # be run concurrently and must be run in serial via a shared mutex attr_reader :serial_actions end # Registers certain driver actions that cannot be safely run concurrently # in threads across multiple instances. Typically this might be used # for create or destroy actions that use an underlying resource that # cannot be used at the same time. # # A shared mutex for this driver object will be used to synchronize all # registered methods. # # @example a single action method that cannot be run concurrently # # no_parallel_for :create # # @example multiple action methods that cannot be run concurrently # # no_parallel_for :create, :destroy # # @param methods [Array] one or more actions as symbols # @raise [ClientError] if any method is not a valid action method name def self.no_parallel_for(*methods) action_methods = [:create, :setup, :converge, :verify, :destroy] Array(methods).each do |meth| next if action_methods.include?(meth) raise ClientError, "##{meth} is not a valid no_parallel_for method" end @serial_actions ||= [] @serial_actions += methods end # Sets the API version for this driver. If the driver does not set this # value, then `nil` will be used and reported. # # Sets the API version for this driver # # @example setting an API version # # module Kitchen # module Driver # class NewDriver < Kitchen::Driver::Base # # kitchen_driver_api_version 2 # # end # end # end # # @param version [Integer,String] a version number # def self.kitchen_driver_api_version(version) @api_version = version end # Cache directory that a driver could implement to inform the provisioner # that it can leverage it internally # # @return path [String] a path of the cache directory def cache_directory end private # Run command if config[:pre_create_command] is set def pre_create_command if config[:pre_create_command] begin run_command(config[:pre_create_command]) rescue ShellCommandFailed => error raise ActionFailed, "pre_create_command '#{config[:pre_create_command]}' failed to execute #{error}" end end end # Intercepts any bare #puts calls in subclasses and issues an INFO log # event instead. # # @param msg [String] message string def puts(msg) info(msg) end # Intercepts any bare #print calls in subclasses and issues an INFO log # event instead. # # @param msg [String] message string def print(msg) info(msg) end end end end test-kitchen-1.23.2/lib/kitchen/driver/dummy.rb0000644000004100000410000000626713377651062021424 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, 2014 Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen" module Kitchen module Driver # Dummy driver for Kitchen. This driver does nothing but report what would # happen if this driver did anything of consequence. As a result it may # be a useful driver to use when debugging or developing new features or # plugins. # # @author Fletcher Nichol class Dummy < Kitchen::Driver::Base kitchen_driver_api_version 2 plugin_version Kitchen::VERSION default_config :sleep, 0 default_config :random_failure, false # (see Base#create) def create(state) # Intentionally not calling `super` to avoid pre_create_command. state[:my_id] = "#{instance.name}-#{Time.now.to_i}" report(:create, state) end # (see Base#setup) def setup(state) report(:setup, state) end # (see Base#verify) def verify(state) report(:verify, state) end # (see Base#destroy) def destroy(state) report(:destroy, state) state.delete(:my_id) end private # Report what action is taking place, sleeping if so configured, and # possibly fail randomly. # # @param action [Symbol] the action currently taking place # @param state [Hash] the state hash # @api private def report(action, state) what = action.capitalize info("[Dummy] #{what} on instance=#{instance} with state=#{state}") sleep_if_set failure_if_set(action) debug("[Dummy] #{what} completed (#{config[:sleep]}s).") end # Sleep for a period of time, if a value is set in the config. # # @api private def sleep_if_set sleep(config[:sleep].to_f) if config[:sleep].to_f > 0.0 end # Simulate a failure in an action, if set in the config. # # @param action [Symbol] the action currently taking place # @api private def failure_if_set(action) if config[:"fail_#{action}"] debug("[Dummy] Failure for action ##{action}.") raise ActionFailed, "Action ##{action} failed for #{instance.to_str}." elsif config[:random_failure] && randomly_fail? debug("[Dummy] Random failure for action ##{action}.") raise ActionFailed, "Action ##{action} failed for #{instance.to_str}." end end # Determine whether or not to randomly fail. # # @return [true, false] # @api private def randomly_fail? [true, false].sample end end end end test-kitchen-1.23.2/lib/kitchen/driver.rb0000644000004100000410000000366513377651062020270 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor/util" module Kitchen # A driver is responsible for carrying out the lifecycle activities of an # instance, such as creating and destroying an instance. # # @author Fletcher Nichol module Driver # Default driver plugin to use DEFAULT_PLUGIN = "dummy".freeze # Returns an instance of a driver given a plugin type string. # # @param plugin [String] a driver plugin type, which will be constantized # @param config [Hash] a configuration hash to initialize the driver # @return [Driver::Base] a driver instance # @raise [ClientError] if a driver instance could not be created # @raise [UserError] if the driver's dependencies could not be met def self.for_plugin(plugin, config) first_load = require("kitchen/driver/#{plugin}") str_const = Thor::Util.camel_case(plugin) klass = const_get(str_const) object = klass.new(config) object.verify_dependencies if first_load object rescue UserError raise rescue LoadError, NameError raise ClientError, "Could not load the '#{plugin}' driver from the load path." \ " Please ensure that your driver is installed as a gem or included" \ " in your Gemfile if using Bundler." end end end test-kitchen-1.23.2/lib/kitchen/provisioner/0000755000004100000410000000000013377651062021015 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/provisioner/chef/0000755000004100000410000000000013377651062021722 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/provisioner/chef/policyfile.rb0000644000004100000410000001240113377651062024404 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "shellwords" require "rbconfig" require "kitchen/errors" require "kitchen/logging" require "kitchen/shell_out" module Kitchen module Provisioner module Chef # Chef cookbook resolver that uses Policyfiles to calculate dependencies. # # @author Fletcher Nichol class Policyfile include Logging include ShellOut # Creates a new cookbook resolver. # # @param policyfile [String] path to a Policyfile # @param path [String] path in which to vendor the resulting # cookbooks # @param logger [Kitchen::Logger] a logger to use for output, defaults # to `Kitchen.logger` def initialize(policyfile, path, logger: Kitchen.logger, always_update: false) @policyfile = policyfile @path = path @logger = logger @always_update = always_update end # Loads the library code required to use the resolver. # # @param logger [Kitchen::Logger] a logger to use for output, defaults # to `Kitchen.logger` def self.load!(logger: Kitchen.logger) detect_chef_command!(logger) end # Performs the cookbook resolution and vendors the resulting cookbooks # in the desired path. def resolve info("Exporting cookbook dependencies from Policyfile #{path}...") run_command("chef export #{escape_path(policyfile)} #{escape_path(path)} --force") end # Runs `chef install` to determine the correct cookbook set and # generate the policyfile lock. def compile if always_update info("Updating policy lock using `chef update`") run_command("chef update #{escape_path(policyfile)}") end if File.exist?(lockfile) info("Installing cookbooks for Policyfile #{policyfile} using `chef install`") else info("Policy lock file doesn't exist, running `chef install` for "\ "Policyfile #{policyfile}...") end run_command("chef install #{escape_path(policyfile)}") end # Return the path to the lockfile corresponding to this policyfile. # # @return [String] def lockfile policyfile.gsub(/\.rb\Z/, ".lock.json") end private # @return [String] path to a Berksfile # @api private attr_reader :policyfile # @return [String] path in which to vendor the resulting cookbooks # @api private attr_reader :path # @return [Kitchen::Logger] a logger to use for output # @api private attr_reader :logger # @return [Boolean] If true, always update cookbooks in the policy. # @api private attr_reader :always_update # Escape spaces in a path in way that works with both Sh (Unix) and # Windows. # # @param path [String] Path to escape # @return [String] # @api private def escape_path(path) if RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ # I know what you're thinking: "just use Shellwords.escape". That # method produces incorrect results on Windows with certain input # which would be a metacharacter in Sh but is not for one or more of # Windows command line parsing libraries. This covers the 99% case of # spaces in the path without breaking other stuff. if path =~ /[ \t\n\v"]/ "\"#{path.gsub(/[ \t\n\v\"\\]/) { |m| '\\' + m[0] }}\"" else path end else Shellwords.escape(path) end end class << self private # Ensure the `chef` command is in the path. # # @param logger [Kitchen::Logger] the logger to use # @raise [UserError] if the `chef` command is not in the PATH # @api private def detect_chef_command!(logger) unless ENV["PATH"].split(File::PATH_SEPARATOR).any? do |p| File.exist?(File.join(p, "chef")) end logger.fatal("The `chef` executable cannot be found in your " \ "PATH. Ensure you have installed ChefDK from " \ "https://downloads.chef.io and that your PATH " \ "setting includes the path to the `chef` comand.") raise UserError, "Could not find the chef executable in your PATH." end end end end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef/librarian.rb0000644000004100000410000000743013377651062024216 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/errors" require "kitchen/logging" module Kitchen module Provisioner module Chef # Chef cookbook resolver that uses Librarian-Chef and a Cheffile to # calculate dependencies. # # @author Fletcher Nichol class Librarian include Logging # Creates a new cookbook resolver. # # @param cheffile [String] path to a Cheffile # @param path [String] path in which to vendor the resulting # cookbooks # @param logger [Kitchen::Logger] a logger to use for output, defaults # to `Kitchen.logger` def initialize(cheffile, path, logger: Kitchen.logger) @cheffile = cheffile @path = path @logger = logger end # Loads the library code required to use the resolver. # # @param logger [Kitchen::Logger] a logger to use for output, defaults # to `Kitchen.logger` def self.load!(logger: Kitchen.logger) load_librarian!(logger) end # Performs the cookbook resolution and vendors the resulting cookbooks # in the desired path. def resolve version = ::Librarian::Chef::VERSION info("Resolving cookbook dependencies with Librarian-Chef #{version}...") debug("Using Cheffile from #{cheffile}") env = ::Librarian::Chef::Environment.new( project_path: File.dirname(cheffile)) env.config_db.local["path"] = path ::Librarian::Action::Resolve.new(env).run ::Librarian::Action::Install.new(env).run end private # @return [String] path to a Cheffile # @api private attr_reader :cheffile # @return [String] path in which to vendor the resulting cookbooks # @api private attr_reader :path # @return [Kitchen::Logger] a logger to use for output # @api private attr_reader :logger class << self private # Load the Librarian-specific libary code. # # @param logger [Kitchen::Logger] the logger to use # @raise [UserError] if the library couldn't be loaded # @api private def load_librarian!(logger) first_load = require "librarian/chef/environment" require "librarian/action/resolve" require "librarian/action/install" version = ::Librarian::Chef::VERSION if first_load logger.debug("Librarian-Chef #{version} library loaded") else logger.debug("Librarian-Chef #{version} previously loaded") end rescue LoadError => e logger.fatal("The `librarian-chef' gem is missing and must be installed" \ " or cannot be properly activated. Run" \ " `gem install librarian-chef` or add the following to your" \ " Gemfile if you are using Bundler: `gem 'librarian-chef'`.") raise UserError, "Could not load or activate Librarian-Chef (#{e.message})" end end end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef/common_sandbox.rb0000644000004100000410000003122313377651062025256 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "json" module Kitchen module Provisioner module Chef # Internal object to manage common sandbox preparation for # Chef-related provisioners. # # @author Fletcher Nichol # @api private class CommonSandbox include Logging # Constructs a new object, taking config, a sandbox path, and an # instance. # # @param config [Hash] configuration hash # @param sandbox_path [String] path to local sandbox directory # @param instance [Instance] an instance def initialize(config, sandbox_path, instance) @config = config @sandbox_path = sandbox_path @instance = instance end # Populate the sandbox. def populate prepare_json prepare_cache prepare_cookbooks prepare(:data) prepare(:data_bags) prepare(:environments) prepare(:nodes) prepare(:roles) prepare(:clients) prepare( :secret, type: :file, dest_name: "encrypted_data_bag_secret", key_name: :encrypted_data_bag_secret_key_path ) end private # @return [Hash] configuration hash # @api private attr_reader :config # @return [Instance] an instance # @api private attr_reader :instance # @return [String] path to local sandbox directory # @api private attr_reader :sandbox_path # Generates a list of all files in the cookbooks directory in the # sandbox path. # # @return [Array] an array of absolute paths to files # @api private def all_files_in_cookbooks Util.list_directory(tmpbooks_dir, include_dot: true, recurse: true) .select { |fn| File.file?(fn) } end # @return [String] an absolute path to a Policyfile, relative to the # kitchen root # @api private def policyfile basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb" File.join(config[:kitchen_root], basename) end # @return [String] an absolute path to a Berksfile, relative to the # kitchen root # @api private def berksfile File.join(config[:kitchen_root], "Berksfile") end # @return [String] an absolute path to a Cheffile, relative to the # kitchen root # @api private def cheffile File.join(config[:kitchen_root], "Cheffile") end # @return [String] an absolute path to a cookbooks/ directory, relative # to the kitchen root # @api private def cookbooks_dir File.join(config[:kitchen_root], "cookbooks") end # Copies a cookbooks/ directory into the sandbox path. # # @api private def cp_cookbooks info("Preparing cookbooks from project directory") debug("Using cookbooks from #{cookbooks_dir}") FileUtils.mkdir_p(tmpbooks_dir) FileUtils.cp_r(File.join(cookbooks_dir, "."), tmpbooks_dir) cp_site_cookbooks if File.directory?(site_cookbooks_dir) cp_this_cookbook if File.exist?(metadata_rb) end # Copies a site-cookbooks/ directory into the sandbox path. # # @api private def cp_site_cookbooks info("Preparing site-cookbooks from project directory") debug("Using cookbooks from #{site_cookbooks_dir}") FileUtils.mkdir_p(tmpsitebooks_dir) FileUtils.cp_r(File.join(site_cookbooks_dir, "."), tmpsitebooks_dir) end # Copies the current project, assumed to be a Chef cookbook into the # sandbox path. # # @api private def cp_this_cookbook info("Preparing current project directory as a cookbook") debug("Using metadata.rb from #{metadata_rb}") cb_name = MetadataChopper.extract(metadata_rb).first || raise(UserError, "The metadata.rb does not define the 'name' key." \ " Please add: `name ''` to metadata.rb and retry") cb_path = File.join(tmpbooks_dir, cb_name) glob = Util.list_directory(config[:kitchen_root]) FileUtils.mkdir_p(cb_path) FileUtils.cp_r(glob, cb_path) end # Removes all non-cookbook files in the sandbox path. # # @api private def filter_only_cookbook_files info("Removing non-cookbook files before transfer") FileUtils.rm(all_files_in_cookbooks - only_cookbook_files) Util.list_directory(tmpbooks_dir, recurse: true) .reverse_each { |fn| FileUtils.rmdir(fn) if File.directory?(fn) && Dir.entries(fn).size == 2 } end # @return [Logger] the instance's logger or Test Kitchen's common # logger otherwise # @api private def logger instance ? instance.logger : Kitchen.logger end # Creates a minimal, no-op cookbook in the sandbox path. # # @api private def make_fake_cookbook info("Berksfile, Cheffile, cookbooks/, or metadata.rb not found " \ "so Chef will run with effectively no cookbooks. Is this intended?") name = File.basename(config[:kitchen_root]) fake_cb = File.join(tmpbooks_dir, name) FileUtils.mkdir_p(fake_cb) File.open(File.join(fake_cb, "metadata.rb"), "wb") do |file| file.write(%{name "#{name}"\n}) end end # @return [String] an absolute path to a metadata.rb, relative to the # kitchen root # @api private def metadata_rb File.join(config[:kitchen_root], "metadata.rb") end # Generates a list of all typical cookbook files needed in a Chef run, # located in the cookbooks directory in the sandbox path. # # @return [Array] an array of absolute paths to files # @api private def only_cookbook_files glob = File.join("*", "{#{config[:cookbook_files_glob]}}") Util.safe_glob(tmpbooks_dir, glob, File::FNM_DOTMATCH) .select { |fn| File.file?(fn) && ! %w{. ..}.include?(fn) } end # Prepares a generic Chef component source directory or file for # inclusion in the sandbox path. These components might includes nodes, # roles, etc. # # @param component [Symbol,String] a component name such as `:node` # @param opts [Hash] optional configuration # @option opts [Symbol] :type whether the component is a directory or # file (default: `:directory`) # @option opts [Symbol] :key_name the key name in the config hash from # which to pull the source path (default: `"#{component}_path"`) # @option opts [String] :dest_name the destination file or directory # basename in the sandbox path (default: `component.to_s`) # @api private def prepare(component, opts = {}) opts = { type: :directory }.merge(opts) key_name = opts.fetch(:key_name, "#{component}_path") src = config[key_name.to_sym] return if src.nil? info("Preparing #{component}") debug("Using #{component} from #{src}") dest = File.join(sandbox_path, opts.fetch(:dest_name, component.to_s)) case opts[:type] when :directory FileUtils.mkdir_p(dest) Array(src).each { |dir| FileUtils.cp_r(Util.list_directory(dir), dest) } when :file FileUtils.mkdir_p(File.dirname(dest)) Array(src).each { |file| FileUtils.cp_r(file, dest) } end end # Prepares a cache directory for inclusion in the sandbox path. # # @api private def prepare_cache FileUtils.mkdir_p(File.join(sandbox_path, "cache")) end # Prepares Chef cookbooks for inclusion in the sandbox path. # # @api private # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def prepare_cookbooks if File.exist?(policyfile) resolve_with_policyfile elsif File.exist?(berksfile) resolve_with_berkshelf elsif File.exist?(cheffile) resolve_with_librarian cp_site_cookbooks if File.directory?(site_cookbooks_dir) elsif File.directory?(cookbooks_dir) cp_cookbooks elsif File.exist?(metadata_rb) cp_this_cookbook else make_fake_cookbook end filter_only_cookbook_files end # Prepares a Chef JSON file, sometimes called a dna.json or # first-boot.json, for inclusion in the sandbox path. # # @api private def prepare_json dna = if File.exist?(policyfile) update_dna_for_policyfile else config[:attributes].merge(run_list: config[:run_list]) end info("Preparing dna.json") debug("Creating dna.json from #{dna.inspect}") File.open(File.join(sandbox_path, "dna.json"), "wb") do |file| file.write(dna.to_json) end end def update_dna_for_policyfile if !config[:run_list].nil? && !config[:run_list].empty? warn("You must set your run_list in your policyfile instead of "\ "kitchen config. The run_list in your config will be ignored.") warn("Ignored run_list: #{config[:run_list].inspect}") end policy = Chef::Policyfile.new(policyfile, sandbox_path, logger: logger, always_update: config[:always_update_cookbooks]) Kitchen.mutex.synchronize do policy.compile end policy_name = JSON.parse(IO.read(policy.lockfile))["name"] policy_group = "local" config[:attributes].merge(policy_name: policy_name, policy_group: policy_group) end # Performs a Policyfile cookbook resolution inside a common mutex. # # @api private def resolve_with_policyfile Kitchen.mutex.synchronize do Chef::Policyfile.new(policyfile, sandbox_path, logger: logger, always_update: config[:always_update_cookbooks]).resolve end end # Performs a Berkshelf cookbook resolution inside a common mutex. # # @api private def resolve_with_berkshelf Kitchen.mutex.synchronize do Chef::Berkshelf.new(berksfile, tmpbooks_dir, logger: logger, always_update: config[:always_update_cookbooks]).resolve end end # Performs a Librarin-Chef cookbook resolution inside a common mutex. # # @api private def resolve_with_librarian Kitchen.mutex.synchronize do Chef::Librarian.new(cheffile, tmpbooks_dir, logger: logger).resolve end end # @return [String] an absolute path to a site-cookbooks/ directory, # relative to the kitchen root # @api private def site_cookbooks_dir File.join(config[:kitchen_root], "site-cookbooks") end # @return [String] an absolute path to a cookbooks/ directory in the # sandbox path # @api private def tmpbooks_dir File.join(sandbox_path, "cookbooks") end # @return [String] an absolute path to a site cookbooks directory in the # sandbox path # @api private def tmpsitebooks_dir File.join(sandbox_path, "cookbooks") end end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef/berkshelf.rb0000644000004100000410000000763113377651062024223 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/errors" require "kitchen/logging" module Kitchen module Provisioner module Chef # Chef cookbook resolver that uses Berkshelf and a Berksfile to calculate # dependencies. # # @author Fletcher Nichol class Berkshelf include Logging # Creates a new cookbook resolver. # # @param berksfile [String] path to a Berksfile # @param path [String] path in which to vendor the resulting # cookbooks # @param logger [Kitchen::Logger] a logger to use for output, defaults # to `Kitchen.logger` def initialize(berksfile, path, logger: Kitchen.logger, always_update: false) @berksfile = berksfile @path = path @logger = logger @always_update = always_update end # Loads the library code required to use the resolver. # # @param logger [Kitchen::Logger] a logger to use for output, defaults # to `Kitchen.logger` def self.load!(logger: Kitchen.logger) load_berkshelf!(logger) end # Performs the cookbook resolution and vendors the resulting cookbooks # in the desired path. def resolve version = ::Berkshelf::VERSION info("Resolving cookbook dependencies with Berkshelf #{version}...") debug("Using Berksfile from #{berksfile}") ::Berkshelf.ui.mute do berksfile_obj = ::Berkshelf::Berksfile.from_file(berksfile) berksfile_obj.update if always_update && berksfile_obj.lockfile.present? # Berkshelf requires the directory to not exist FileUtils.rm_rf(path) berksfile_obj.vendor(path) end end private # @return [String] path to a Berksfile # @api private attr_reader :berksfile # @return [String] path in which to vendor the resulting cookbooks # @api private attr_reader :path # @return [Kitchen::Logger] a logger to use for output # @api private attr_reader :logger # @return [Boolean] If true, always update cookbooks in Berkshelf. # @api private attr_reader :always_update class << self private # Load the Berkshelf-specific libary code. # # @param logger [Kitchen::Logger] the logger to use # @raise [UserError] if the library couldn't be loaded # @api private def load_berkshelf!(logger) first_load = require "berkshelf" version = ::Berkshelf::VERSION if first_load logger.debug("Berkshelf #{version} library loaded") else logger.debug("Berkshelf #{version} previously loaded") end rescue LoadError => e logger.fatal("The `berkshelf' gem is missing and must be installed" \ " or cannot be properly activated. Run" \ " `gem install berkshelf` or add the following to your" \ " Gemfile if you are using Bundler: `gem 'berkshelf'`.") raise UserError, "Could not load or activate Berkshelf (#{e.message})" end end end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef_zero.rb0000644000004100000410000001705213377651062023313 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/provisioner/chef_base" module Kitchen module Provisioner # Chef Zero provisioner. # # @author Fletcher Nichol class ChefZero < ChefBase kitchen_provisioner_api_version 2 plugin_version Kitchen::VERSION default_config :client_rb, {} default_config :named_run_list, {} default_config :json_attributes, true default_config :chef_zero_host, nil default_config :chef_zero_port, 8889 default_config :chef_client_path do |provisioner| provisioner .remote_path_join(%W{#{provisioner[:chef_omnibus_root]} bin chef-client}) .tap { |path| path.concat(".bat") if provisioner.windows_os? } end default_config :ruby_bindir do |provisioner| provisioner .remote_path_join(%W{#{provisioner[:chef_omnibus_root]} embedded bin}) end # (see Base#create_sandbox) def create_sandbox super prepare_chef_client_zero_rb prepare_validation_pem prepare_config_rb end # (see Base#prepare_command) def prepare_command return if modern? gem_bin = remote_path_join(config[:ruby_bindir], "gem") .tap { |path| path.concat(".bat") if windows_os? } vars = [ chef_client_zero_env, shell_var("gem", sudo(gem_bin)), ].join("\n").concat("\n") prefix_command(shell_code_from_file(vars, "chef_zero_prepare_command_legacy")) end # (see Base#run_command) def run_command cmd = modern? ? local_mode_command : shim_command chef_cmd(cmd) end private # Adds optional flags to a chef-client command, depending on # configuration data. Note that this method mutates the incoming Array. # # @param args [Array] array of flags # @api private # rubocop:disable Metrics/CyclomaticComplexity def add_optional_chef_client_args!(args) if config[:json_attributes] json = remote_path_join(config[:root_path], "dna.json") args << "--json-attributes #{json}" end args << "--logfile #{config[:log_file]}" if config[:log_file] return unless modern? # these flags are modern/chef-client local most only and will not work # on older versions of chef-client if config[:chef_zero_host] args << "--chef-zero-host #{config[:chef_zero_host]}" end if config[:chef_zero_port] args << "--chef-zero-port #{config[:chef_zero_port]}" end args << "--profile-ruby" if config[:profile_ruby] end # rubocop:enable Metrics/CyclomaticComplexity # Returns an Array of command line arguments for the chef client. # # @return [Array] an array of command line arguments # @api private def chef_args(client_rb_filename) level = config[:log_level] args = [ "--config #{remote_path_join(config[:root_path], client_rb_filename)}", "--log_level #{level}", "--force-formatter", "--no-color", ] add_optional_chef_client_args!(args) args end # Generates a string of shell environment variables needed for the # chef-client-zero.rb shim script to properly function. # # @return [String] a shell script string # @api private def chef_client_zero_env root = config[:root_path] gem_home = gem_path = remote_path_join(root, "chef-client-zero-gems") gem_cache = remote_path_join(gem_home, "cache") [ shell_env_var("CHEF_REPO_PATH", root), shell_env_var("GEM_HOME", gem_home), shell_env_var("GEM_PATH", gem_path), shell_env_var("GEM_CACHE", gem_cache), ].join("\n").concat("\n") end # Returns the command that will run chef client in local mode (a.k.a. # chef zero mode). # # @return [String] the command string # @api private def local_mode_command "#{sudo(config[:chef_client_path])} --local-mode" .tap { |str| str.insert(0, "& ") if powershell_shell? } end # Determines whether or not local mode (a.k.a chef zero mode) is # supported in the version of Chef as determined by inspecting the # require_chef_omnibus config variable. # # The only way this method returns false is if require_chef_omnibus has # an explicit version set to less than 11.8.0, when chef zero mode was # introduced. Otherwise a modern Chef installation is assumed. # # @return [true,false] whether or not the desired version of Chef # supports local mode # @api private def modern? version = config[:require_chef_omnibus] case version when nil, false, true, 11, "11", "latest" true else if Gem::Version.correct?(version) Gem::Version.new(version) >= Gem::Version.new("11.8.0") ? true : false else # Build versions of chef, for example # 12.5.0-current.0+20150721082808.git.14.c91b337-1 true end end end # Writes a chef-client local-mode shim script to the sandbox directory # only if the desired version of Chef is old enough. The version of Chef # is determined using the `config[:require_chef_omnibus]` value. # # @api private def prepare_chef_client_zero_rb return if modern? info("Preparing chef-client-zero.rb") debug("Using a vendored chef-client-zero.rb") source = File.join(File.dirname(__FILE__), %w{.. .. .. support chef-client-zero.rb}) FileUtils.cp(source, File.join(sandbox_path, "chef-client-zero.rb")) end # Writes a fake (but valid) validation.pem into the sandbox directory. # # @api private def prepare_validation_pem info("Preparing validation.pem") debug("Using a dummy validation.pem") source = File.join(File.dirname(__FILE__), %w{.. .. .. support dummy-validation.pem}) FileUtils.cp(source, File.join(sandbox_path, "validation.pem")) end # Returns the command that will run a backwards compatible shim script # that approximates local mode in a modern chef-client run. # # @return [String] the command string # @api private def shim_command ruby = remote_path_join(config[:ruby_bindir], "ruby") .tap { |path| path.concat(".exe") if windows_os? } shim = remote_path_join(config[:root_path], "chef-client-zero.rb") "#{chef_client_zero_env}\n#{sudo(ruby)} #{shim}" end # This provisioner supports policyfiles, so override the default (which # is false) # @return [true] always returns true # @api private def supports_policyfile? true end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef_solo.rb0000644000004100000410000000543013377651062023305 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/provisioner/chef_base" module Kitchen module Provisioner # Chef Solo provisioner. # # @author Fletcher Nichol class ChefSolo < ChefBase kitchen_provisioner_api_version 2 plugin_version Kitchen::VERSION default_config :solo_rb, {} default_config :chef_solo_path do |provisioner| provisioner .remote_path_join(%W{#{provisioner[:chef_omnibus_root]} bin chef-solo}) .tap { |path| path.concat(".bat") if provisioner.windows_os? } end # (see Base#config_filename) def config_filename "solo.rb" end # (see Base#create_sandbox) def create_sandbox super prepare_config_rb end def modern? version = config[:require_chef_omnibus] case version when nil, false, true, 11, "11", "latest" true else if Gem::Version.correct?(version) Gem::Version.new(version) >= Gem::Version.new("11.0") ? true : false else true end end end # (see Base#run_command) def run_command config[:log_level] = "info" if !modern? && config[:log_level] == "auto" cmd = sudo(config[:chef_solo_path]).dup .tap { |str| str.insert(0, "& ") if powershell_shell? } chef_cmd(cmd) end private # Returns an Array of command line arguments for the chef client. # # @return [Array] an array of command line arguments # @api private def chef_args(solo_rb_filename) args = [ "--config #{remote_path_join(config[:root_path], solo_rb_filename)}", "--log_level #{config[:log_level]}", "--no-color", "--json-attributes #{remote_path_join(config[:root_path], 'dna.json')}", ] args << " --force-formatter" if modern? args << "--logfile #{config[:log_file]}" if config[:log_file] args << "--profile-ruby" if config[:profile_ruby] args << "--legacy-mode" if config[:legacy_mode] args end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/base.rb0000644000004100000410000002150513377651062022257 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen module Provisioner # Base class for a provisioner. # # @author Fletcher Nichol class Base include Configurable include Logging default_config :http_proxy, nil default_config :https_proxy, nil default_config :ftp_proxy, nil default_config :retry_on_exit_code, [] default_config :max_retries, 1 default_config :wait_for_retry, 30 default_config :root_path do |provisioner| provisioner.windows_os? ? '$env:TEMP\\kitchen' : "/tmp/kitchen" end default_config :sudo do |provisioner| provisioner.windows_os? ? nil : true end default_config :sudo_command do |provisioner| provisioner.windows_os? ? nil : "sudo -E" end default_config :command_prefix, nil default_config :downloads, {} expand_path_for :test_base_path # Constructs a new provisioner by providing a configuration hash. # # @param config [Hash] initial provided configuration def initialize(config = {}) init_config(config) end # Runs the provisioner on the instance. # # @param state [Hash] mutable instance state # @raise [ActionFailed] if the action could not be completed # rubocop:disable Metrics/AbcSize def call(state) create_sandbox sandbox_dirs = Util.list_directory(sandbox_path) instance.transport.connection(state) do |conn| conn.execute(install_command) conn.execute(init_command) info("Transferring files to #{instance.to_str}") conn.upload(sandbox_dirs, config[:root_path]) debug("Transfer complete") conn.execute(prepare_command) conn.execute_with_retry( run_command, config[:retry_on_exit_code], config[:max_retries], config[:wait_for_retry] ) info("Downloading files from #{instance.to_str}") config[:downloads].to_h.each do |remotes, local| debug("Downloading #{Array(remotes).join(', ')} to #{local}") conn.download(remotes, local) end debug("Download complete") end rescue Kitchen::Transport::TransportFailed => ex raise ActionFailed, ex.message ensure cleanup_sandbox end # Check system and configuration for common errors. # # @param state [Hash] mutable instance state # @returns [Boolean] Return true if a problem is found. def doctor(state) false end # Generates a command string which will install and configure the # provisioner software on an instance. If no work is required, then `nil` # will be returned. # # @return [String] a command string def install_command end # Generates a command string which will perform any data initialization # or configuration required after the provisioner software is installed # but before the sandbox has been transferred to the instance. If no work # is required, then `nil` will be returned. # # @return [String] a command string def init_command end # Generates a command string which will perform any commands or # configuration required just before the main provisioner run command but # after the sandbox has been transferred to the instance. If no work is # required, then `nil` will be returned. # # @return [String] a command string def prepare_command end # Generates a command string which will invoke the main provisioner # command on the prepared instance. If no work is required, then `nil` # will be returned. # # @return [String] a command string def run_command end # Creates a temporary directory on the local workstation into which # provisioner related files and directories can be copied or created. The # contents of this directory will be copied over to the instance before # invoking the provisioner's run command. After this method completes, it # is expected that the contents of the sandbox is complete and ready for # copy to the remote instance. # # **Note:** any subclasses would be well advised to call super first when # overriding this method, for example: # # @example overriding `#create_sandbox` # # class MyProvisioner < Kitchen::Provisioner::Base # def create_sandbox # super # # any further file copies, preparations, etc. # end # end def create_sandbox @sandbox_path = Dir.mktmpdir("#{instance.name}-sandbox-") File.chmod(0755, sandbox_path) info("Preparing files for transfer") debug("Creating local sandbox in #{sandbox_path}") end # Returns the absolute path to the sandbox directory or raises an # exception if `#create_sandbox` has not yet been called. # # @return [String] the absolute path to the sandbox directory # @raise [ClientError] if the sandbox directory has no yet been created # by calling `#create_sandbox` def sandbox_path @sandbox_path ||= begin raise ClientError, "Sandbox directory has not yet " \ "been created. Please run #{self.class}#create_sandox before " \ "trying to access the path." end end # Deletes the sandbox path. Without calling this method, the sandbox path # will persist after the process terminates. In other words, cleanup is # explicit. This method is safe to call multiple times. def cleanup_sandbox return if sandbox_path.nil? debug("Cleaning up local sandbox in #{sandbox_path}") FileUtils.rmtree(sandbox_path) end # Sets the API version for this provisioner. If the provisioner does not # set this value, then `nil` will be used and reported. # # Sets the API version for this provisioner # # @example setting an API version # # module Kitchen # module Provisioner # class NewProvisioner < Kitchen::Provisioner::Base # # kitchen_provisioner_api_version 2 # # end # end # end # # @param version [Integer,String] a version number # def self.kitchen_provisioner_api_version(version) @api_version = version end private # Builds a complete command given a variables String preamble and a file # containing shell code. # # @param vars [String] shell variables, as a String # @param file [String] file basename (without extension) containing # shell code # @return [String] command # @api private def shell_code_from_file(vars, file) src_file = File.join( File.dirname(__FILE__), %w{.. .. .. support}, file + (powershell_shell? ? ".ps1" : ".sh") ) wrap_shell_code([vars, "", IO.read(src_file)].join("\n")) end # Conditionally prefixes a command with a sudo command. # # @param command [String] command to be prefixed # @return [String] the command, conditionally prefixed with sudo # @api private def sudo(script) "#{sudo_command} #{script}".lstrip end # Returns the sudo command to use or empty string if sudo is not configured # # @return [String] the sudo command if sudo config is true # @api private def sudo_command config[:sudo] ? config[:sudo_command].to_s : "" end # Conditionally prefixes a command with a command prefix. # This should generally be done after a command has been # conditionally prefixed by #sudo as certain platforms, such as # Cisco Nexus, require all commands to be run with a prefix to # obtain outbound network access. # # @param command [String] command to be prefixed # @return [String] the command, conditionally prefixed with the configured prefix # @api private def prefix_command(script) config[:command_prefix] ? "#{config[:command_prefix]} #{script}" : script end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/shell.rb0000644000004100000410000001103413377651062022450 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Chris Lundquist () # # Copyright (C) 2013, Chris Lundquist # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "shellwords" require "kitchen/provisioner/base" require "kitchen/version" module Kitchen module Provisioner # Basic shell provisioner. # # @author Chris Lundquist () class Shell < Base kitchen_provisioner_api_version 2 plugin_version Kitchen::VERSION default_config :script do |provisioner| src = provisioner.powershell_shell? ? "bootstrap.ps1" : "bootstrap.sh" provisioner.calculate_path(src, type: :file) end expand_path_for :script # Run a single command instead of managing and running a script. default_config :command, nil # Add extra arguments to the converge script. default_config :arguments, [] default_config :data_path do |provisioner| provisioner.calculate_path("data") end expand_path_for :data_path # (see Base#create_sandbox) def create_sandbox super prepare_data prepare_script end # (see Base#init_command) def init_command return nil if config[:command] root = config[:root_path] data = remote_path_join(root, "data") code = if powershell_shell? Util.outdent!(<<-POWERSHELL) if (Test-Path "#{data}") { Remove-Item "#{data}" -Recurse -Force } if (-Not (Test-Path "#{root}")) { New-Item "#{root}" -ItemType directory | Out-Null } POWERSHELL else "#{sudo('rm')} -rf #{data} ; mkdir -p #{root}" end prefix_command(wrap_shell_code(code)) end # (see Base#prepare_command) def prepare_command # On a windows host, the supplied script does not get marked as executable # due to windows not having the concept of an executable flag # # When the guest instance is *nix, `chmod +x` the script in the guest, prior to executing return unless unix_os? && config[:script] && !config[:command] debug "Marking script as executable" script = remote_path_join( config[:root_path], File.basename(config[:script]) ) prefix_command(wrap_shell_code(sudo("chmod +x #{script}"))) end # (see Base#run_command) def run_command return prefix_command(wrap_shell_code(config[:command])) if config[:command] return unless config[:script] script = remote_path_join( config[:root_path], File.basename(config[:script]) ) if config[:arguments] && !config[:arguments].empty? if config[:arguments].is_a?(Array) script = Shellwords.join([script] + config[:arguments]) else script.concat(" ").concat(config[:arguments].to_s) end end code = powershell_shell? ? %{& "#{script}"} : sudo(script) prefix_command(wrap_shell_code(code)) end private # Creates a data directory in the sandbox directory, if a data directory # can be found and copies in the tree. # # @api private def prepare_data return unless config[:data_path] info("Preparing data") debug("Using data from #{config[:data_path]}") tmpdata_dir = File.join(sandbox_path, "data") FileUtils.mkdir_p(tmpdata_dir) FileUtils.cp_r(Util.list_directory(config[:data_path]), tmpdata_dir) end # Copies the executable script to the sandbox directory or creates a # stub script if one cannot be found. # # @api private def prepare_script info("Preparing script") if config[:script] debug("Using script from #{config[:script]}") FileUtils.cp_r(config[:script], sandbox_path) else info("No provisioner script file specified, skipping") end end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef_apply.rb0000644000004100000410000000627713377651062023470 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: SAWANOBORI Yukihiko ) # # Copyright (C) 2015, HiganWorks LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Usage: # # puts your recipes to` apply/` directory. # # An example of .kitchen.yml. # # --- # driver: # name: vagrant # # provisioner: # name: chef_apply # # platforms: # - name: ubuntu-16.04 # - name: centos-7 # # suites: # - name: default # run_list: # - recipe1 # - recipe2 # # # The chef-apply runs twice below. # # chef-apply apply/recipe1.rb # chef-apply apply/recipe2.rb require "kitchen/provisioner/chef_base" module Kitchen module Provisioner # Chef Apply provisioner. # # @author SAWANOBORI Yukihiko ) class ChefApply < ChefBase kitchen_provisioner_api_version 2 plugin_version Kitchen::VERSION default_config :chef_apply_path do |provisioner| provisioner .remote_path_join(%W{#{provisioner[:chef_omnibus_root]} bin chef-apply}) .tap { |path| path.concat(".bat") if provisioner.windows_os? } end default_config :apply_path do |provisioner| provisioner.calculate_path("apply") end expand_path_for :apply_path # (see ChefBase#create_sandbox) def create_sandbox @sandbox_path = Dir.mktmpdir("#{instance.name}-sandbox-") File.chmod(0755, sandbox_path) info("Preparing files for transfer") debug("Creating local sandbox in #{sandbox_path}") prepare_json prepare(:apply) end # (see ChefBase#init_command) def init_command dirs = %w{ apply }.sort.map { |dir| remote_path_join(config[:root_path], dir) } vars = if powershell_shell? init_command_vars_for_powershell(dirs) else init_command_vars_for_bourne(dirs) end prefix_command(shell_code_from_file(vars, "chef_base_init_command")) end # (see ChefSolo#run_command) def run_command level = config[:log_level] lines = [] config[:run_list].map do |recipe| cmd = sudo(config[:chef_apply_path]).dup .tap { |str| str.insert(0, "& ") if powershell_shell? } args = [ "apply/#{recipe}.rb", "--log_level #{level}", "--no-color", ] args << "--logfile #{config[:log_file]}" if config[:log_file] lines << wrap_shell_code( [cmd, *args].join(" ") .tap { |str| str.insert(0, reload_ps1_path) if windows_os? } ) end prefix_command(lines.join("\n")) end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/chef_base.rb0000644000004100000410000005502313377651062023246 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "fileutils" require "pathname" require "json" require "cgi" require "kitchen/provisioner/chef/policyfile" require "kitchen/provisioner/chef/berkshelf" require "kitchen/provisioner/chef/common_sandbox" require "kitchen/provisioner/chef/librarian" require "kitchen/util" require "mixlib/install" require "mixlib/install/script_generator" begin require "chef-config/config" require "chef-config/workstation_config_loader" rescue LoadError # rubocop:disable Lint/HandleExceptions # This space left intentionally blank. end module Kitchen module Provisioner # Common implementation details for Chef-related provisioners. # # @author Fletcher Nichol class ChefBase < Base default_config :require_chef_omnibus, true default_config :chef_omnibus_url, "https://omnitruck.chef.io/install.sh" default_config :chef_omnibus_install_options, nil default_config :run_list, [] default_config :attributes, {} default_config :config_path, nil default_config :log_file, nil default_config :log_level do |provisioner| provisioner[:debug] ? "debug" : "auto" end default_config :profile_ruby, false # The older policyfile_zero used `policyfile` so support it for compat. default_config :policyfile, nil # Will try to autodetect by searching for `Policyfile.rb` if not set. # If set, will error if the file doesn't exist. default_config :policyfile_path, nil # If set to true (which is the default from `chef generate`), try to update # backend cookbook downloader on every kitchen run. default_config :always_update_cookbooks, false default_config :cookbook_files_glob, %w( README.* metadata.{json,rb} attributes/**/* definitions/**/* files/**/* libraries/**/* providers/**/* recipes/**/* resources/**/* templates/**/* ).join(",") # to ease upgrades, allow the user to turn deprecation warnings into errors default_config :deprecations_as_errors, false # Override the default from Base so reboot handling works by default for Chef. default_config :retry_on_exit_code, [35, 213] default_config :multiple_converge, 1 default_config :enforce_idempotency, false default_config :data_path do |provisioner| provisioner.calculate_path("data") end expand_path_for :data_path default_config :data_bags_path do |provisioner| provisioner.calculate_path("data_bags") end expand_path_for :data_bags_path default_config :environments_path do |provisioner| provisioner.calculate_path("environments") end expand_path_for :environments_path default_config :nodes_path do |provisioner| provisioner.calculate_path("nodes") end expand_path_for :nodes_path default_config :roles_path do |provisioner| provisioner.calculate_path("roles") end expand_path_for :roles_path default_config :clients_path do |provisioner| provisioner.calculate_path("clients") end expand_path_for :clients_path default_config :encrypted_data_bag_secret_key_path do |provisioner| provisioner.calculate_path("encrypted_data_bag_secret_key", type: :file) end expand_path_for :encrypted_data_bag_secret_key_path # # New configuration options per RFC 091 # https://github.com/chef/chef-rfc/blob/master/rfc091-deprecate-kitchen-settings.md # # Setting product_name to nil. It is currently the pivot point # between the two install paths (Mixlib::Install::ScriptGenerator and Mixlib::Install) default_config :product_name default_config :product_version, :latest default_config :channel, :stable default_config :install_strategy, "once" default_config :platform default_config :platform_version default_config :architecture default_config :download_url default_config :checksum deprecate_config_for :require_chef_omnibus do |provisioner| case when provisioner[:require_chef_omnibus] == false Util.outdent!(<<-MSG) The 'require_chef_omnibus' attribute with value of 'false' will change to use the new 'install_strategy' attribute with a value of 'skip'. Note: 'product_name' must be set in order to use 'install_strategy'. Although this seems counterintuitive, it is necessary until 'product_name' replaces 'require_chef_omnibus' as the default. # New Usage # provisioner: product_name: install_strategy: skip MSG when provisioner[:require_chef_omnibus].to_s.match(/\d/) Util.outdent!(<<-MSG) The 'require_chef_omnibus' attribute with version values will change to use the new 'product_version' attribute. Note: 'product_name' must be set in order to use 'product_version' until 'product_name' replaces 'require_chef_omnibus' as the default. # New Usage # provisioner: product_name: product_version: #{provisioner[:require_chef_omnibus]} MSG when provisioner[:require_chef_omnibus] == "latest" Util.outdent!(<<-MSG) The 'require_chef_omnibus' attribute with value of 'latest' will change to use the new 'install_strategy' attribute with a value of 'always'. Note: 'product_name' must be set in order to use 'install_strategy' until 'product_name' replaces 'require_chef_omnibus' as the default. # New Usage # provisioner: product_name: install_strategy: always MSG end end deprecate_config_for :chef_omnibus_url, Util.outdent!(<<-MSG) Changing the 'chef_omnibus_url' attribute breaks existing functionality. It will be removed in a future version. MSG deprecate_config_for :chef_omnibus_install_options, Util.outdent!(<<-MSG) The 'chef_omnibus_install_options' attribute will be replaced by using 'product_name' and 'channel' attributes. Note: 'product_name' must be set in order to use 'channel' until 'product_name' replaces 'require_chef_omnibus' as the default. # Deprecated Example # provisioner: chef_omnibus_install_options: -P chefdk -c current # New Usage # provisioner: product_name: chefdk channel: current MSG deprecate_config_for :install_msi_url, Util.outdent!(<<-MSG) The 'install_msi_url' will be relaced by the 'download_url' attribute. 'download_url' will be applied to Bourne and Powershell download scripts. Note: 'product_name' must be set in order to use 'download_url' until 'product_name' replaces 'require_chef_omnibus' as the default. # New Usage # provisioner: product_name: download_url: http://direct-download-url MSG deprecate_config_for :chef_metadata_url, Util.outdent!(<<-MSG) The 'chef_metadata_url' will be removed. The Windows metadata URL will be fully managed by using attribute settings. MSG # Reads the local Chef::Config object (if present). We do this because # we want to start bring Chef config and ChefDK tool config closer # together. For example, we want to configure proxy settings in 1 # location instead of 3 configuration files. # # @param config [Hash] initial provided configuration def initialize(config = {}) super(config) if defined?(ChefConfig::WorkstationConfigLoader) ChefConfig::WorkstationConfigLoader.new(config[:config_path]).load end # This exports any proxy config present in the Chef config to # appropriate environment variables, which Test Kitchen respects ChefConfig::Config.export_proxies if defined?(ChefConfig::Config.export_proxies) end def doctor(state) deprecated_config.each do |attr, msg| info("**** #{attr} deprecated\n#{msg}") end end # (see Base#create_sandbox) def create_sandbox super sanity_check_sandbox_options! Chef::CommonSandbox.new(config, sandbox_path, instance).populate end # (see Base#init_command) def init_command dirs = %w{ cookbooks data data_bags environments roles clients encrypted_data_bag_secret }.sort.map { |dir| remote_path_join(config[:root_path], dir) } vars = if powershell_shell? init_command_vars_for_powershell(dirs) else init_command_vars_for_bourne(dirs) end prefix_command(shell_code_from_file(vars, "chef_base_init_command")) end # (see Base#install_command) def install_command return unless config[:require_chef_omnibus] || config[:product_name] return if config[:product_name] && config[:install_strategy] == "skip" prefix_command(sudo(install_script_contents)) end private def last_exit_code "; exit $LastExitCode" if powershell_shell? end # @return [Hash] an option hash for the install commands # @api private def install_options add_omnibus_directory_option if instance.driver.cache_directory project = /\s*-P (\w+)\s*/.match(config[:chef_omnibus_install_options]) { omnibus_url: config[:chef_omnibus_url], project: project.nil? ? nil : project[1], install_flags: config[:chef_omnibus_install_options], sudo_command: sudo_command, }.tap do |opts| opts[:root] = config[:chef_omnibus_root] if config.key? :chef_omnibus_root [:install_msi_url, :http_proxy, :https_proxy].each do |key| opts[key] = config[key] if config.key? key end end end # Verify if the "omnibus_dir_option" has already been passed, if so we # don't use the @driver.cache_directory # # @api private def add_omnibus_directory_option cache_dir_option = "#{omnibus_dir_option} #{instance.driver.cache_directory}" if config[:chef_omnibus_install_options].nil? config[:chef_omnibus_install_options] = cache_dir_option elsif config[:chef_omnibus_install_options].match(/\s*#{omnibus_dir_option}\s*/).nil? config[:chef_omnibus_install_options] << " " << cache_dir_option end end # @return [String] an absolute path to a Policyfile, relative to the # kitchen root # @api private def policyfile policyfile_basename = config[:policyfile_path] || config[:policyfile] || "Policyfile.rb" File.join(config[:kitchen_root], policyfile_basename) end # @return [String] an absolute path to a Berksfile, relative to the # kitchen root # @api private def berksfile File.join(config[:kitchen_root], "Berksfile") end # @return [String] an absolute path to a Cheffile, relative to the # kitchen root # @api private def cheffile File.join(config[:kitchen_root], "Cheffile") end # Generates a Hash with default values for a solo.rb or client.rb Chef # configuration file. # # @return [Hash] a configuration hash # @api private def default_config_rb # rubocop:disable Metrics/MethodLength root = config[:root_path].gsub("$env:TEMP", "\#{ENV['TEMP']\}") { node_name: instance.name, checksum_path: remote_path_join(root, "checksums"), file_cache_path: remote_path_join(root, "cache"), file_backup_path: remote_path_join(root, "backup"), cookbook_path: [ remote_path_join(root, "cookbooks"), remote_path_join(root, "site-cookbooks"), ], data_bag_path: remote_path_join(root, "data_bags"), environment_path: remote_path_join(root, "environments"), node_path: remote_path_join(root, "nodes"), role_path: remote_path_join(root, "roles"), client_path: remote_path_join(root, "clients"), user_path: remote_path_join(root, "users"), validation_key: remote_path_join(root, "validation.pem"), client_key: remote_path_join(root, "client.pem"), chef_server_url: "http://127.0.0.1:8889", encrypted_data_bag_secret: remote_path_join( root, "encrypted_data_bag_secret" ), treat_deprecation_warnings_as_errors: config[:deprecations_as_errors], } end # Generates a rendered client.rb/solo.rb/knife.rb formatted file as a # String. # # @param data [Hash] a key/value pair hash of configuration # @return [String] a rendered Chef config file as a String # @api private def format_config_file(data) data.each.map do |attr, value| [attr, format_value(value)].join(" ") end.join("\n") end # Converts a Ruby object to a String interpretation suitable for writing # out to a client.rb/solo.rb/knife.rb file. # # @param obj [Object] an object # @return [String] a string representation # @api private def format_value(obj) if obj.is_a?(String) && obj =~ /^:/ obj elsif obj.is_a?(String) %{"#{obj.gsub(/\\/, '\\\\\\\\')}"} elsif obj.is_a?(Array) %{[#{obj.map { |i| format_value(i) }.join(', ')}]} else obj.inspect end end # Generates the init command variables for Bourne shell-based platforms. # # @param dirs [Array] directories # @return [String] shell variable lines # @api private def init_command_vars_for_bourne(dirs) [ shell_var("sudo_rm", sudo("rm")), shell_var("dirs", dirs.join(" ")), shell_var("root_path", config[:root_path]), ].join("\n") end # Generates the init command variables for PowerShell-based platforms. # # @param dirs [Array] directories # @return [String] shell variable lines # @api private def init_command_vars_for_powershell(dirs) [ %{$dirs = @(#{dirs.map { |d| %{"#{d}"} }.join(', ')})}, shell_var("root_path", config[:root_path]), ].join("\n") end # Load cookbook dependency resolver code, if required. # # (see Base#load_needed_dependencies!) def load_needed_dependencies! super if File.exist?(policyfile) debug("Policyfile found at #{policyfile}, using Policyfile to resolve dependencies") Chef::Policyfile.load!(logger: logger) elsif File.exist?(berksfile) debug("Berksfile found at #{berksfile}, loading Berkshelf") Chef::Berkshelf.load!(logger: logger) elsif File.exist?(cheffile) debug("Cheffile found at #{cheffile}, loading Librarian-Chef") Chef::Librarian.load!(logger: logger) end end # @return [String] contents of the install script # @api private def install_script_contents # by default require_chef_omnibus is set to true. Check config[:product_name] first # so that we can use it if configured. if config[:product_name] script_for_product elsif config[:require_chef_omnibus] script_for_omnibus_version end end # @return [String] contents of product based install script # @api private def script_for_product installer = Mixlib::Install.new({ product_name: config[:product_name], product_version: config[:product_version], channel: config[:channel].to_sym, install_command_options: { install_strategy: config[:install_strategy], }, }.tap do |opts| opts[:shell_type] = :ps1 if powershell_shell? [:platform, :platform_version, :architecture].each do |key| opts[key] = config[key] if config[key] end if config[:download_url] opts[:install_command_options][:download_url_override] = config[:download_url] opts[:install_command_options][:checksum] = config[:checksum] if config[:checksum] end if instance.driver.cache_directory download_dir_option = windows_os? ? :download_directory : :cmdline_dl_dir opts[:install_command_options][download_dir_option] = instance.driver.cache_directory end proxies = {}.tap do |prox| [:http_proxy, :https_proxy, :ftp_proxy, :no_proxy].each do |key| prox[key] = config[key] if config[key] end # install.ps1 only supports http_proxy prox.delete_if { |p| [:https_proxy, :ftp_proxy, :no_proxy].include?(p) } if powershell_shell? end opts[:install_command_options].merge!(proxies) end) config[:chef_omnibus_root] = installer.root if powershell_shell? installer.install_command else install_from_file(installer.install_command) end end # @return [String] Correct option per platform to specify the the # cache directory # @api private def omnibus_dir_option windows_os? ? "-download_directory" : "-d" end def install_from_file(command) install_file = "/tmp/chef-installer.sh" script = ["cat > #{install_file} <<\"EOL\""] script << command script << "EOL" script << "chmod +x #{install_file}" script << sudo(install_file) script.join("\n") end # @return [String] contents of version based install script # @api private def script_for_omnibus_version installer = Mixlib::Install::ScriptGenerator.new( config[:require_chef_omnibus], powershell_shell?, install_options) config[:chef_omnibus_root] = installer.root installer.install_command end # Hook used in subclasses to indicate support for policyfiles. # # @abstract # @return [Boolean] # @api private def supports_policyfile? false end # @return [void] # @raise [UserError] # @api private def sanity_check_sandbox_options! if (config[:policyfile_path] || config[:policyfile]) && !File.exist?(policyfile) raise UserError, "policyfile_path set in config "\ "(#{config[:policyfile_path]} could not be found. " \ "Expected to find it at full path #{policyfile} " \ end if File.exist?(policyfile) && !supports_policyfile? raise UserError, "policyfile detected, but provisioner " \ "#{self.class.name} doesn't support policyfiles. " \ "Either use a different provisioner, or delete/rename " \ "#{policyfile}" end end # Writes a configuration file to the sandbox directory. # @api private def prepare_config_rb data = default_config_rb.merge(config[config_filename.tr(".", "_").to_sym]) data = data.merge(:named_run_list => config[:named_run_list]) if config[:named_run_list] info("Preparing #{config_filename}") debug("Creating #{config_filename} from #{data.inspect}") File.open(File.join(sandbox_path, config_filename), "wb") do |file| file.write(format_config_file(data)) end prepare_config_idempotency_check(data) if config[:enforce_idempotency] end # Writes a configuration file to the sandbox directory # to check for idempotency of the run. # @api private def prepare_config_idempotency_check(data) handler_filename = "chef-client-fail-if-update-handler.rb" source = File.join( File.dirname(__FILE__), %w{.. .. .. support }, handler_filename ) FileUtils.cp(source, File.join(sandbox_path, handler_filename)) File.open(File.join(sandbox_path, "client_no_updated_resources.rb"), "wb") do |file| file.write(format_config_file(data)) file.write("\n\n") file.write("handler_file = File.join(File.dirname(__FILE__), '#{handler_filename}')\n") file.write "Chef::Config.from_file(handler_file)\n" end end # Returns an Array of command line arguments for the chef client. # # @return [Array] an array of command line arguments # @api private def chef_args(_config_filename) raise "You must override in sub classes!" end # Returns a filename for the configuration file # defaults to client.rb # # @return [String] a filename # @api private def config_filename "client.rb" end # Gives the command used to run chef # @api private def chef_cmd(base_cmd) if windows_os? separator = [ "; if ($LastExitCode -ne 0) { ", "throw \"Command failed with exit code $LastExitCode.\" } ;", ].join else separator = " && " end chef_cmds(base_cmd).join(separator) end # Gives an array of command # @api private def chef_cmds(base_cmd) cmd = prefix_command(wrap_shell_code( [base_cmd, *chef_args(config_filename), last_exit_code].join(" "). tap { |str| str.insert(0, reload_ps1_path) if windows_os? } )) cmds = [cmd].cycle(config[:multiple_converge].to_i).to_a if config[:enforce_idempotency] idempotent_cmd = prefix_command(wrap_shell_code( [base_cmd, *chef_args("client_no_updated_resources.rb"), last_exit_code].join(" "). tap { |str| str.insert(0, reload_ps1_path) if windows_os? } )) cmds[-1] = idempotent_cmd end cmds end end end end test-kitchen-1.23.2/lib/kitchen/provisioner/dummy.rb0000644000004100000410000000451413377651062022501 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen" module Kitchen module Provisioner # Dummy provisioner for Kitchen. This driver does nothing but report what # would happen if this provisioner did anything of consequence. As a result # it may be a useful provisioner to use when debugging or developing new # features or plugins. # # @author Fletcher Nichol class Dummy < Kitchen::Provisioner::Base kitchen_provisioner_api_version 2 plugin_version Kitchen::VERSION default_config :sleep, 0 default_config :random_failure, false # (see Base#call) def call(state) info("[#{name}] Converge on instance=#{instance} with state=#{state}") sleep_if_set failure_if_set debug("[#{name}] Converge completed (#{config[:sleep]}s).") end private # Sleep for a period of time, if a value is set in the config. # # @api private def sleep_if_set sleep(config[:sleep].to_f) if config[:sleep].to_f > 0.0 end # Simulate a failure in an action, if set in the config. # # @api private def failure_if_set if config[:fail] debug("Failure for Provisioner #{name}.") raise ActionFailed, "Action #converge failed for #{instance.to_str}." elsif config[:random_failure] && randomly_fail? debug("Random failure for Provisioner #{name}.") raise ActionFailed, "Action #converge failed for #{instance.to_str}." end end # Determine whether or not to randomly fail. # # @return [true, false] # @api private def randomly_fail? [true, false].sample end end end end test-kitchen-1.23.2/lib/kitchen/version.rb0000644000004100000410000000131413377651062020447 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen VERSION = "1.23.2".freeze end test-kitchen-1.23.2/lib/kitchen/command/0000755000004100000410000000000013377651062020054 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/command/exec.rb0000644000004100000410000000221113377651062021321 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: SAWANOBORI Yukihiko () # # Copyright (C) 2014, HiganWorks LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" module Kitchen module Command # Execute command on remote instance. # # @author SAWANOBORI Yukihiko () class Exec < Kitchen::Command::Base # Invoke the command. def call results = parse_subcommand(args.first) results.each do |instance| banner "Execute command on #{instance.name}." instance.remote_exec(options[:command]) end end end end end test-kitchen-1.23.2/lib/kitchen/command/list.rb0000644000004100000410000001073513377651062021362 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" require "json" module Kitchen module Command # Command to list one or more instances. # # @author Fletcher Nichol class List < Kitchen::Command::Base # Invoke the command. def call result = parse_subcommand(args.first) if options[:debug] die "The --debug flag on the list subcommand is deprecated, " \ "please use `kitchen diagnose'." elsif options[:bare] puts Array(result).map(&:name).join("\n") elsif options[:json] puts JSON.pretty_generate(Array(result).map { |r| to_hash(r) }) else list_table(result) end end private # Add a trailing ansi color escape code to line up columns of colored # output. # # @param string [String] a string # @return [String] # @api private def color_pad(string) string + colorize("", :white) end # Generate the display rows for an instance. # # @param instance [Instance] an instance # @return [Array] # @api private def display_instance(instance) [ color_pad(instance.name), color_pad(instance.driver.name), color_pad(instance.provisioner.name), color_pad(instance.verifier.name), color_pad(instance.transport.name), format_last_action(instance.last_action), format_last_error(instance.last_error), ] end # Format and color the given last action. # # @param last_action [String] the last action # @return [String] formated last action # @api private def format_last_action(last_action) case last_action when "create" then colorize("Created", :cyan) when "converge" then colorize("Converged", :magenta) when "setup" then colorize("Set Up", :blue) when "verify" then colorize("Verified", :yellow) when nil then colorize("", :red) else colorize("", :white) end end # Format and color the given last error. # # @param last_error [String] the last error # @return [String] formated last error # @api private def format_last_error(last_error) case last_error when nil then colorize("", :white) else colorize(last_error, :red) end end # Constructs a list display table and output it to the screen. # # @param result [Array] an array of instances # @api private def list_table(result) table = [ [ colorize("Instance", :green), colorize("Driver", :green), colorize("Provisioner", :green), colorize("Verifier", :green), colorize("Transport", :green), colorize("Last Action", :green), colorize("Last Error", :green) ], ] table += Array(result).map { |i| display_instance(i) } print_table(table) end # Constructs a hashtable representation of a single instance. # # @param result [Hash{Symbol => String}] hash of a single instance # @api private def to_hash(result) { instance: result.name, driver: result.driver.name, provisioner: result.provisioner.name, verifier: result.verifier.name, transport: result.transport.name, last_action: result.last_action, last_error: result.last_error, } end # Outputs a formatted display table. # # @api private def print_table(*args) shell.print_table(*args) end # Colorize a string. # # @return [String] a colorized string # @api private def colorize(*args) shell.set_color(*args) end end end end test-kitchen-1.23.2/lib/kitchen/command/sink.rb0000644000004100000410000000337313377651062021353 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" module Kitchen module Command # Command to... include the sink. # # @author Seth Vargo class Sink < Kitchen::Command::Base # Invoke the command. def call puts [ "", " ___ ", " ' _ '. ", ' / /` `\\ \\ ', " | | [__] ", " | | {{ ", " | | }} ", " _ | | _ {{ ", " ___________<_>_| |_<_>}}________ ", " .=======^=(___)=^={{====. ", ' / .----------------}}---. \\ ', ' / / {{ \\ \\ ', ' / / }} \\ \\ ', " ( '=========================' ) ", " '-----------------------------' ", " ", # necessary newline "", ].map(&:rstrip).join("\n") end end end end test-kitchen-1.23.2/lib/kitchen/command/package.rb0000644000004100000410000000171313377651062021776 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" module Kitchen module Command # Execute command on remote instance. # class Package < Kitchen::Command::Base # Invoke the command. def call results = parse_subcommand(args.first) results.each do |instance| banner "Package on #{instance.name}." instance.package_action end end end end end test-kitchen-1.23.2/lib/kitchen/command/diagnose.rb0000644000004100000410000000434013377651062022173 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" require "kitchen/diagnostic" require "yaml" module Kitchen module Command # Command to log into to instance. # # @author Fletcher Nichol class Diagnose < Kitchen::Command::Base # Invoke the command. def call instances = record_failure { load_instances } loader = record_failure { load_loader } puts Kitchen::Diagnostic.new( loader: loader, instances: instances, plugins: plugins? ).read.to_yaml end private def plugins? options[:all] || options[:plugins] end # Loads and returns instances if they are requested. # # @return [Array] an array of instances or an empty array # @api private def load_instances if options[:all] || options[:instances] parse_subcommand(args.first) else [] end end # Loads and returns loader configuration if it is requested. # # @return [Hash,nil] a hash or nil # @api private def load_loader @loader if options[:all] || options[:loader] end # Returns a hash with exception detail if an exception is raised in the # yielded block. # # @return [yield,Hash] the result of the yielded block or an error hash # @api private def record_failure yield rescue => e { error: { exception: e.inspect, message: e.message, backtrace: e.backtrace, }, } end end end end test-kitchen-1.23.2/lib/kitchen/command/test.rb0000644000004100000410000000270213377651062021361 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" require "benchmark" module Kitchen module Command # Command to test one or more instances. # # @author Fletcher Nichol class Test < Kitchen::Command::Base include RunAction # Invoke the command. def call unless %w{passing always never}.include?(options[:destroy]) raise ArgumentError, "Destroy mode must be passing, always, or never." end banner "Starting Kitchen (v#{Kitchen::VERSION})" elapsed = Benchmark.measure do destroy_mode = options[:destroy].to_sym results = parse_subcommand(args.join("|")) run_action(:test, results, destroy_mode) end banner "Kitchen is finished. #{Util.duration(elapsed.real)}" end end end end test-kitchen-1.23.2/lib/kitchen/command/login.rb0000644000004100000410000000226213377651062021513 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" module Kitchen module Command # Command to log into to instance. # # @author Fletcher Nichol class Login < Kitchen::Command::Base # Invoke the command. def call results = parse_subcommand(args.first) if results.size > 1 die "Argument `#{args.first}' returned multiple results:\n" + results.map { |i| " * #{i.name}" }.join("\n") end instance = results.pop instance.login end end end end test-kitchen-1.23.2/lib/kitchen/command/doctor.rb0000644000004100000410000000251213377651062021673 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" module Kitchen module Command # Check for common system or configuration problems. # class Doctor < Kitchen::Command::Base # Invoke the command. def call results = parse_subcommand(args.first) if results.empty? error("No instances configured, cannot check configuration. Please check your .kitchen.yml and confirm it has platform and suites sections.") exit(1) end # By default only doctor the first instance to avoid output spam. results = [results.first] unless options[:all] failed = results.any? do |instance| debug "Doctor on #{instance.name}." instance.doctor_action end exit(1) if failed end end end end test-kitchen-1.23.2/lib/kitchen/command/action.rb0000644000004100000410000000235513377651062021663 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" require "benchmark" module Kitchen module Command # Command to run a single action one or more instances. # # @author Fletcher Nichol class Action < Kitchen::Command::Base include RunAction # Invoke the command. def call banner "Starting Kitchen (v#{Kitchen::VERSION})" elapsed = Benchmark.measure do results = parse_subcommand(args.first) run_action(action, results) end banner "Kitchen is finished. #{Util.duration(elapsed.real)}" end end end end test-kitchen-1.23.2/lib/kitchen/command/console.rb0000644000004100000410000000322413377651062022044 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/command" module Kitchen module Command # Command to launch a Pry-based Kitchen console.. # # @author Fletcher Nichol class Console < Kitchen::Command::Base # Invoke the command. def call require "pry" Pry.start(@config, prompt: [prompt(">"), prompt("*")]) rescue LoadError warn %{Make sure you have the pry gem installed. You can install it with:} warn %{`gem install pry` or including 'gem "pry"' in your Gemfile.} exit 1 end private # Construct a custom Pry prompt proc. # # @param char [String] prompt character # @return [proc] a prompt proc # @api private def prompt(char) proc do |target_self, nest_level, pry| [ "[#{pry.input_array.size}] ", "kc(#{Pry.view_clip(target_self.class)})", "#{":#{nest_level}" unless nest_level == 0}#{char} ", ].join end end end end end test-kitchen-1.23.2/lib/kitchen/transport.rb0000644000004100000410000000356413377651062021027 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Salim Afiune () # # Copyright (C) 2014, Salim Afiune # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor/util" module Kitchen # A transport is responsible for the communication with an instance, # that is remote comands and other actions such as file transfer, # login, etc. # # @author Salim Afiune module Transport # Default transport to use DEFAULT_PLUGIN = "ssh".freeze # Returns an instance of a transport given a plugin type string. # # @param plugin [String] a transport plugin type, to be constantized # @param config [Hash] a configuration hash to initialize the transport # @return [Transport::Base] a transport instance # @raise [ClientError] if a transport instance could not be created def self.for_plugin(plugin, config) first_load = require("kitchen/transport/#{plugin}") str_const = Thor::Util.camel_case(plugin) klass = const_get(str_const) object = klass.new(config) object.verify_dependencies if first_load object rescue LoadError, NameError raise ClientError, "Could not load the '#{plugin}' transport from the load path." \ " Please ensure that your transport is installed as a gem or" \ " included in your Gemfile if using Bundler." end end end test-kitchen-1.23.2/lib/kitchen/base64_stream.rb0000644000004100000410000000356413377651062021432 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # Base64 encoder/decoder that operates on IO objects so as to minimize # memory allocations on large payloads. # # @author Fletcher Nichol module Base64Stream # Encodes an input stream into a Base64 output stream. The input and ouput # objects must be opened IO resources. In other words, opening and closing # the resources are not the responsibilty of this method. # # @param io_in [#read] input stream # @param io_out [#write] output stream def self.strict_encode(io_in, io_out) buffer = "" io_out.write([buffer].pack("m0")) while io_in.read(3 * 1000, buffer) buffer = nil # rubocop:disable Lint/UselessAssignment end # Decodes a Base64 input stream into an output stream. The input and ouput # objects must be opened IO resources. In other words, opening and closing # the resources are not the responsibilty of this method. # # @param io_in [#read] input stream # @param io_out [#write] output stream def self.strict_decode(io_in, io_out) buffer = "" io_out.write(buffer.unpack("m0").first) while io_in.read(3 * 1000, buffer) buffer = nil # rubocop:disable Lint/UselessAssignment end end end test-kitchen-1.23.2/lib/kitchen/logging.rb0000644000004100000410000000350313377651062020412 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # Mixin module that delegates logging methods to a local `#logger`. # # @author Fletcher Nichol module Logging class << self private # @api private # @!macro logger_method # @method $1($2) # Log a message with severity of $1 # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless # the logger's level is sufficient to log the message. This allows # you to create potentially expensive logging messages that are # only called when the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for # this particular logger), log no message, and return true def logger_method(meth) define_method(meth) do |*args| logger.public_send(meth, *args) end end end logger_method :banner logger_method :debug logger_method :info logger_method :warn logger_method :error logger_method :fatal end end test-kitchen-1.23.2/lib/kitchen/data_munger.rb0000644000004100000410000010047513377651062021260 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "vendor/hash_recursive_merge" module Kitchen # Class to handle recursive merging of configuration between platforms, # suites, and common data. # # This object will mutate the data Hash passed into its constructor and so # should not be reused or shared across threads. # # If you are squeamish or faint of heart, then you might want to skip this # class. Just remember, you were warned. And if you made it this far, be # sure to tweet at @fnichol and let him know your fear factor level. # # @author Fletcher Nichol class DataMunger # Constructs a new DataMunger object. # # @param data [Hash] the incoming user data hash # @param kitchen_config [Hash] the incoming Test Kitchen-provided # configuration hash def initialize(data, kitchen_config = {}) @data = data @kitchen_config = kitchen_config convert_legacy_driver_format! convert_legacy_chef_paths_format! convert_legacy_require_chef_omnibus_format! convert_legacy_busser_format! convert_legacy_driver_http_proxy_format! move_chef_data_to_provisioner! convert_legacy_pre_create_command! end # Generate a new Hash of configuration data that can be used to construct # a new Driver object. # # @param suite [String] a suite name # @param platform [String] a platform name # @return [Hash] a new configuration Hash that can be used to construct a # new Driver def driver_data_for(suite, platform) merged_data_for(:driver, suite, platform).tap do |ddata| set_kitchen_config_at!(ddata, :kitchen_root) set_kitchen_config_at!(ddata, :test_base_path) set_kitchen_config_at!(ddata, :log_level) end end # Generate a new Hash of configuration data that can be used to construct # a new LifecycleHooks object. # # @param suite [String] a suite name # @param platform [String] a platform name # @return [Hash] a new configuration Hash that can be used to construct a # new LifecycleHooks def lifecycle_hooks_data_for(suite, platform) merged_data_for(:lifecycle, suite, platform).tap do |lhdata| lhdata.each_key do |k| combine_arrays!(lhdata, k, :common, :platform, :suite) end set_kitchen_config_at!(lhdata, :kitchen_root) set_kitchen_config_at!(lhdata, :test_base_path) set_kitchen_config_at!(lhdata, :log_level) set_kitchen_config_at!(lhdata, :debug) end end # Returns an Array of platform Hashes. # # @return [Array] an Array of Hashes def platform_data data.fetch(:platforms, []) end # Generate a new Hash of configuration data that can be used to construct # a new Provisioner object. # # @param suite [String] a suite name # @param platform [String] a platform name # @return [Hash] a new configuration Hash that can be used to construct a # new Provisioner def provisioner_data_for(suite, platform) merged_data_for(:provisioner, suite, platform).tap do |pdata| set_kitchen_config_at!(pdata, :kitchen_root) set_kitchen_config_at!(pdata, :test_base_path) set_kitchen_config_at!(pdata, :debug) combine_arrays!(pdata, :run_list, :platform, :suite) end end # Returns an Array of suite Hashes. # # @return [Array] an Array of Hashes def suite_data data.fetch(:suites, []) end # Generate a new Hash of configuration data that can be used to construct # a new Transport object. # # @param suite [String] a suite name # @param platform [String] a platform name # @return [Hash] a new configuration Hash that can be used to construct a # new Transport def transport_data_for(suite, platform) merged_data_for(:transport, suite, platform).tap do |tdata| set_kitchen_config_at!(tdata, :kitchen_root) set_kitchen_config_at!(tdata, :test_base_path) set_kitchen_config_at!(tdata, :log_level) end end # Generate a new Hash of configuration data that can be used to construct # a new Verifier object. # # @param suite [String] a suite name # @param platform [String] a platform name # @return [Hash] a new configuration Hash that can be used to construct a # new Verifier def verifier_data_for(suite, platform) merged_data_for(:verifier, suite, platform).tap do |vdata| set_kitchen_config_at!(vdata, :kitchen_root) set_kitchen_config_at!(vdata, :test_base_path) set_kitchen_config_at!(vdata, :log_level) set_kitchen_config_at!(vdata, :debug) end end private # @return [Hash] the user data hash # @api private attr_reader :data # @return [Hash] the Test Kitchen-provided configuration hash # @api private attr_reader :kitchen_config def combine_arrays!(root, key, *namespaces) if root.key?(key) root[key] = namespaces .map { |namespace| root.fetch(key).fetch(namespace, []) }.flatten .compact end end # Destructively moves old-style `:busser` configuration hashes into the # correct `:verifier` hash. # # This method converts the following: # # { # :busser => { :one => "two" }, # # :platforms => [ # { # :name => "ubuntu-16.04", # :busser => "bar" # } # ], # # :suites => [ # { # :name => "alpha", # :busser => { :three => "four" } # } # ] # } # # into the following: # # { # :verifier => { # :name => "busser", # :one => "two" # } # # :platforms => [ # { # :name => "ubuntu-16.04", # :verifier => { # :name => "busser", # :version => "bar # } # } # ], # # :suites => [ # { # :name => "alpha", # :verifier => { # :name => "busser", # :three => "four" # } # } # ] # } # # @deprecated The following configuration hashes should no longer be # created in a `:platform`, `:suite`, or common location: # `:busser`. Use a `:verifier` hash block in their place. # @api private def convert_legacy_busser_format! convert_legacy_busser_format_at!(data) data.fetch(:platforms, []).each do |platform| convert_legacy_busser_format_at!(platform) end data.fetch(:suites, []).each do |suite| convert_legacy_busser_format_at!(suite) end end # Destructively moves old-style `:busser` configuration hashes into the # correct `:verifier` hashes. This method has no knowledge of suites, # platforms, or the like, just a vanilla hash. # # @param root [Hash] a hash to use as the root of the conversion # @deprecated The following configuration hashses should no longer be # created in a Test Kitchen hash: `:busser`. Use a `:verifier` hash # block in their place. # @api private def convert_legacy_busser_format_at!(root) if root.key?(:busser) bdata = root.delete(:busser) bdata = { version: bdata } if bdata.is_a?(String) bdata[:name] = "busser" if bdata[:name].nil? vdata = root.fetch(:verifier, {}) vdata = { name: vdata } if vdata.is_a?(String) root[:verifier] = bdata.rmerge(vdata) end end # Destructively moves Chef-related paths out of a suite hash and into the # suite's provisioner sub-hash. # # This method converts the following: # # { # :suites => [ # { # :name => "alpha", # :nodes_path => "/a/b/c" # }, # { # :name => "beta", # :roles_path => "/tmp/roles", # :data_bags_path => "/bags" # }, # ] # } # # into the following: # # { # :suites => [ # { # :name => "alpha", # :provisioner => { # :nodes_path => "/a/b/c" # } # }, # { # :name => "beta", # :provisioner => { # :roles_path => "/tmp/roles", # :data_bags_path => "/bags" # } # }, # ] # } # # @deprecated The following Chef paths should no longer be created directly # under a suite hash: [`data_path`, `data_bags_path`, # `encrypted_data_bag_secret_key_path`, `environments_path`, `nodes_path`, # `roles_path`]. Instead put these key/value pairs directly inside a # `provisioner` hash. # @api private def convert_legacy_chef_paths_format! data.fetch(:suites, []).each do |suite| %w{ data data_bags encrypted_data_bag_secret_key environments nodes roles }.each do |key| move_chef_data_to_provisioner_at!(suite, "#{key}_path".to_sym) end end end # Destructively moves old-style `:driver_plugin` and `:driver_config` # configuration hashes into the correct `:driver` hash. # # This method converts the following: # # { # :driver_plugin => "foo", # :driver_config => { :one => "two" }, # # :platforms => [ # { # :name => "ubuntu-16.04", # :driver_plugin => "bar" # } # ], # # :suites => [ # { # :name => "alpha", # :driver_plugin => "baz" # :driver_config => { :three => "four" } # } # ] # } # # into the following: # # { # :driver => { # :name => "foo", # :one => "two" # } # # :platforms => [ # { # :name => "ubuntu-16.04", # :driver => { # :name => "bar" # } # } # ], # # :suites => [ # { # :name => "alpha", # :driver => { # :name => "baz", # :three => "four" # } # } # ] # } # # @deprecated The following configuration hashes should no longer be # created in a `:platform`, `:suite`, or common location: # [`:driver_plugin`, `:driver_config`]. Use a `:driver` hash block in # their place. # @api private def convert_legacy_driver_format! convert_legacy_driver_format_at!(data) data.fetch(:platforms, []).each do |platform| convert_legacy_driver_format_at!(platform) end data.fetch(:suites, []).each do |suite| convert_legacy_driver_format_at!(suite) end end # Destructively moves old-style `:driver_plugin` and `:driver_config` # configuration hashes into the correct `:driver` in the first level depth # of a hash. This method has no knowledge of suites, platforms, or the # like, just a vanilla hash. # # @param root [Hash] a hash to use as the root of the conversion # @deprecated The following configuration hashes should no longer be # created in a Test Kitche hash: [`:driver_plugin`, `:driver_config`]. # Use a `:driver` hash block in their place. # @api private def convert_legacy_driver_format_at!(root) if root.key?(:driver_config) ddata = root.fetch(:driver, {}) ddata = { name: ddata } if ddata.is_a?(String) root[:driver] = root.delete(:driver_config).rmerge(ddata) end if root.key?(:driver_plugin) ddata = root.fetch(:driver, {}) ddata = { name: ddata } if ddata.is_a?(String) root[:driver] = { name: root.delete(:driver_plugin) }.rmerge(ddata) end end # Copies `:http_proxy` and `:https_proxy` values in a driver hash into the # provisioner and verifier hashes. For backwards compatibility with legacy # Drivers (those inheriting directly from `SSHBase`), the original # values are maintained in the driver hash. # # This method converts the following: # # { # :driver => { # :http_proxy => "http://proxy", # :https_proxy => "https://proxy" # }, # # :platforms => [ # { # :name => "ubuntu-16.04", # :driver => { # :http_proxy => "foo" # } # } # ], # # :suites => [ # { # :name => "alpha", # :driver => { # :https_proxy => "bar" # } # } # ] # } # # into the following: # # { # :driver => { # :http_proxy => "http://proxy", # :https_proxy => "https://proxy" # }, # # :provisioner => { # :http_proxy => "http://proxy", # :https_proxy => "https://proxy" # }, # # :verifier => { # :http_proxy => "http://proxy", # :https_proxy => "https://proxy" # }, # # :platforms => [ # { # :name => "ubuntu-16.04", # :driver => { # :http_proxy => "foo" # }, # :provisioner => { # :http_proxy => "foo" # }, # :verifier => { # :http_proxy => "foo" # } # } # ], # # :suites => [ # { # :name => "alpha", # :driver => { # :https_proxy => "bar" # }, # :provisioner => { # :https_proxy => "bar" # }, # :verifier => { # :https_proxy => "bar" # } # } # ] # } # # @deprecated The `:http_proxy` and `:https_proxy` should no longer be # used in driver blocks, they should be added to the provisioner and # verifier blocks so that they can be independantly configured. # Provisioners and Verifiers are responsible for HTTP proxying and no # longer are Drivers responsible for this. # @api private def convert_legacy_driver_http_proxy_format! convert_legacy_driver_http_proxy_format_at!(data) data.fetch(:platforms, []).each do |platform| convert_legacy_driver_http_proxy_format_at!(platform) end data.fetch(:suites, []).each do |suite| convert_legacy_driver_http_proxy_format_at!(suite) end end # Copies `:http_proxy` and `:https_proxy` values in a driver hash into # the provisioner and verifier hashes. This method has no knowledge of # suites, platforms, or the like, just a vanilla hash. # # @param root [Hash] a hash to use as the root of the conversion # @deprecated The `:http_proxy` and `:https_proxy` should no longer be # used in driver blocks, they should be added to the provisioner and # verifier blocks so that they can be independantly configured. # Provisioners and Verifiers are responsible for HTTP proxying and no # longer are Drivers responsible for this. # @api private def convert_legacy_driver_http_proxy_format_at!(root) ddata = root.fetch(:driver, {}) [:http_proxy, :https_proxy].each do |key| next unless ddata.is_a?(Hash) && ddata.key?(key) pdata = root.fetch(:provisioner, {}) pdata = { name: pdata } if pdata.is_a?(String) root[:provisioner] = { key => ddata.fetch(key) }.rmerge(pdata) vdata = root.fetch(:verifier, {}) vdata = { name: vdata } if vdata.is_a?(String) root[:verifier] = { key => ddata.fetch(key) }.rmerge(vdata) end end # Destructively moves a `:require_chef_omnibus` key/value pair from a # `:driver` hash block to a `:provisioner` hash block. # # This method converts the following: # # { # :driver => { # :require_chef_omnibus => true # } # # :platforms => [ # { # :name => "ubuntu-16.04", # :driver => { # :require_chef_omnibus => "10.8.2" # } # } # ], # # :suites => [ # { # :name => "alpha", # :driver => { # :require_chef_omnibus => "11" # } # } # ] # } # # into the following: # # { # :provisioner => { # :require_chef_omnibus => true # } # # :platforms => [ # { # :name => "ubuntu-16.04", # :provisioner => { # :require_chef_omnibus => "10.8.2" # } # } # ], # # :suites => [ # { # :name => "alpha", # :provisioner => { # :require_chef_omnibus => "11" # } # } # ] # } # # @deprecated The `:require_chef_omnibus` key/value pair should no longer # be created inside a `:driver` hash block. Put it in a `:provisioner` # hash block instead. # @api private def convert_legacy_require_chef_omnibus_format! convert_legacy_require_chef_omnibus_format_at!(data) data.fetch(:platforms, []).each do |platform| convert_legacy_require_chef_omnibus_format_at!(platform) end data.fetch(:suites, []).each do |suite| convert_legacy_require_chef_omnibus_format_at!(suite) end end # Destructively moves a `:require_chef_omnibus` key/value pair from a # `:driver` hash block to a `:provisioner` hash block in the first leve # depth of a hash. This method has no knowledge of suites, platforms, or # the like, just a vanilla haash. # # @param root [Hash] a hash to use as the root of the conversion # @deprecated The `:require_chef_omnibus` key/value pair should no longer # be created inside a `:driver` hash block. Put it in a `:provisioner` # hash block instead. # @api private def convert_legacy_require_chef_omnibus_format_at!(root) key = :require_chef_omnibus ddata = root.fetch(:driver, {}) if ddata.is_a?(Hash) && ddata.key?(key) pdata = root.fetch(:provisioner, {}) pdata = { name: pdata } if pdata.is_a?(String) root[:provisioner] = { key => root.fetch(:driver).delete(key) }.rmerge(pdata) end end def convert_legacy_pre_create_command! convert_legacy_pre_create_command_at!(data) data.fetch(:platforms, []).each do |platform| convert_legacy_pre_create_command_at!(platform) end data.fetch(:suites, []).each do |suite| convert_legacy_pre_create_command_at!(suite) end end def convert_legacy_pre_create_command_at!(root) ddata = root[:driver] || {} if ddata.is_a?(Hash) && ddata.include?(:pre_create_command) root[:lifecycle] ||= {} root[:lifecycle][:pre_create] ||= [] root[:lifecycle][:pre_create] = Array(root[:lifecycle][:pre_create]) root[:lifecycle][:pre_create] << { local: ddata[:pre_create_command] } ddata.delete(:pre_create_command) end end # Performs a prioritized recursive merge of several source Hashes and # returns a new merged Hash. For these data sub-hash structures, there are # 4 sources for configuration data: # # 1. defaults, provided by Test Kitchen code # 2. user-provided in the common root-level of the incoming data hash # 3. user-provided in a platform sub-hash # 4. user-provided in a suite sub-hash # # The merge order is 4 -> 3 -> 2 -> 1, meaning that the highest number in # the above list has merge precedence over any lower numbered source. Put # another way, a key/value pair in a suite sub-hash will be used over the # key/value pair in a platform sub-hash. # # @param key [Symbol] the data sub-hash(es) to merge # @param suite [String] a suite name # @param platform [String] a platform name # @param default_key [Symbol] the default key to use when normalizing the # data sub-hashes (default: `:name`) # @return [Hash] a new merged Hash # @api private def merged_data_for(key, suite, platform, default_key = :name) ddata = normalized_default_data(key, default_key, suite, platform) cdata = normalized_common_data(key, default_key) pdata = normalized_platform_data(key, default_key, platform) sdata = normalized_suite_data(key, default_key, suite) ddata.rmerge(cdata.rmerge(pdata.rmerge(sdata))) end # Destructively moves key Chef configuration key/value pairs from being # directly under a suite or platform into a `:provisioner` sub-hash. # # There are three key Chef configuration key/value pairs: # # 1. `:attributes` # 2. `:run_list` # 3. `:named_run_list` # # This method converts the following: # # { # :platforms => [ # { # :name => "ubuntu-16.04", # :attributes => { :one => "two" }, # :run_list => ["alpha", "bravo"] # } # ], # # :suites => [ # { # :name => "alpha", # :attributes => { :three => "four" }, # :run_list => ["charlie", "delta"] # } # ] # } # # into the following: # # { # :platforms => [ # { # :name => "ubuntu-16.04", # :provisioner => { # :attributes => { :one => "two" }, # :run_list => ["alpha", "bravo"] # } # } # ], # # :suites => [ # { # :name => "alpha", # :provisioner => { # :attributes => { :three => "four" }, # :run_list => ["charlie", "delta"] # } # } # ] # } # # @api private def move_chef_data_to_provisioner! data.fetch(:suites, []).each do |suite| move_chef_data_to_provisioner_at!(suite, :attributes) move_chef_data_to_provisioner_at!(suite, :run_list) move_chef_data_to_provisioner_at!(suite, :named_run_list) end data.fetch(:platforms, []).each do |platform| move_chef_data_to_provisioner_at!(platform, :attributes) move_chef_data_to_provisioner_at!(platform, :run_list) move_chef_data_to_provisioner_at!(platform, :named_run_list) end end # Destructively moves key Chef configuration key/value pairs from being # directly under a hash into a `:provisioner` sub-hash block. This method # has no knowledge of suites, platforms, or the like, just a vanilla hash. # # @param root [Hash] a hash to use as the root of the conversion # @param key [Symbol] a key in the root hash to move into a `:provisioner` # sub-hash block # @api private def move_chef_data_to_provisioner_at!(root, key) if root.key?(key) pdata = root.fetch(:provisioner, {}) pdata = { name: pdata } if pdata.is_a?(String) unless root.fetch(key, nil).nil? root[:provisioner] = pdata.rmerge(key => root.delete(key)) end end end # Vicious hack to allow for Array-appending merge semantics. This method # takes an array value and transforms it into a hash with a bucket name # containing the original Array. This way semantic Hash merging will do # its thing and another process can collapse the hash into a flat array # afterwards, given a strategy (like use the array segmenet from one # bucket first, then another one second). To anyone who made it this far, # Fletcher appologizes. # # @param root [Hash] a hash to use as the root of the conversion # @param key [Symbol] a key in the root hash that, if exists, has its # value transformed into a sub-hash # @param bucket [Symbol] a key to use for the sub-hash # @api private def namespace_array!(root, key, bucket) root[key] = { bucket => root.fetch(key) } if root.key?(key) end # Normalizes a specific key in the root of the data hash to be a proper # sub-hash in all cases. Specifically handled are the following cases: # # * If the value for a key is set to `nil`, a new Hash will be put in # its place. # * If the value is a String, then convert the value to a new Hash with # a default key pointing to the original String # # Given a hash: # # { :driver => nil } # # this method (`normalized_common_data(:driver, :name)`) would return: # # {} # # Given a hash: # # { :driver => "coolbeans" } # # this method (`normalized_common_data(:driver, :name)`) would return: # # { :name => "coolbeans" } # # @param key [Symbol] the value to normalize # @param default_key [Symbol] the implicit default key if a String value # is given # @return [Hash] a shallow Hash copy of the original if not modified, or a # new Hash otherwise # @api private def normalized_common_data(key, default_key) cdata = data.fetch(key, {}) cdata = cdata.nil? ? {} : cdata.dup cdata = { default_key => cdata } if cdata.is_a?(String) case key when :lifecycle cdata.each_key do |k| namespace_array!(cdata, k, :common) end end cdata end # Normalizes a specific key in the `:defaults` data sub-hash to be a proper # sub-hash in all cases. Specifically handled are the following cases: # # * If the value for a key is not set, a new Hash will be put in its place # * If the value is a String, then convert the value to a new Hash with # a default key pointing to the original String # # Given a hash: # # { # :defaults => {} # } # # this method (`normalized_default_data(:driver, :name)`) would return: # # {} # # Given a hash: # # { # :defaults => { # :driver => "coolbeans" # } # } # # this method (`normalized_default_data(:driver, :name)`) would return: # # { :name => "coolbeans" } # # @param key [Symbol] the value to normalize # @param default_key [Symbol] the implicit default key if a String value # is given # @param suite [String] name of a suite # @param platform [String] name of a platform # @return [Hash] a shallow Hash copy of the original if not modified, or a # new Hash otherwise # @api private def normalized_default_data(key, default_key, suite, platform) ddata = kitchen_config.fetch(:defaults, {}).fetch(key, {}).dup ddata = { default_key => ddata.call(suite, platform) } if ddata.is_a?(Proc) ddata = { default_key => ddata } if ddata.is_a?(String) ddata end # Normalizes a specific key in a platform hash data sub-hash to be a proper # sub-hash in all cases. Specifically handled are the following cases: # # * If the value for a key is set to `nil`, a new Hash will be put in # its place. # * If the value is a String, then convert the value to a new Hash with # a default key pointing to the original String # # Given a hash: # # { # :platforms => [ # { # :name => "alpha", # :driver => nil # } # ] # } # # this method (`normalized_platform_data(:driver, :name, "alpha)`) would # return: # # {} # # Given a hash: # # { # :platforms => [ # { # :name => "alpha", # :driver => "coolbeans" # } # ] # } # # this method (`normalized_common_data(:driver, :name, "alpha")`) would # return: # # { :name => "coolbeans" } # # @param key [Symbol] the value to normalize # @param default_key [Symbol] the implicit default key if a String value # is given # @param platform [String] name of a platform # @return [Hash] a shallow Hash copy of the original if not modified, or a # new Hash otherwise # @api private def normalized_platform_data(key, default_key, platform) pdata = platform_data_for(platform).fetch(key, {}) pdata = pdata.nil? ? {} : pdata.dup pdata = { default_key => pdata } if pdata.is_a?(String) case key when :provisioner namespace_array!(pdata, :run_list, :platform) when :lifecycle pdata.each_key do |k| namespace_array!(pdata, k, :platform) end end pdata end # Normalizes a specific key in a suite hash data sub-hash to be a proper # sub-hash in all cases. Specifically handled are the following cases: # # * If the value for a key is set to `nil`, a new Hash will be put in # its place. # * If the value is a String, then convert the value to a new Hash with # a default key pointing to the original String # # Given a hash: # # { # :suites => [ # { # :name => "full", # :driver => nil # } # ] # } # # this method (`normalized_platform_data(:driver, :name, "full)`) would # return: # # {} # # Given a hash: # # { # :suites => [ # { # :name => "full", # :driver => "coolbeans" # } # ] # } # # this method (`normalized_common_data(:driver, :name, "full")`) would # return: # # { :name => "coolbeans" } # # @param key [Symbol] the value to normalize # @param default_key [Symbol] the implicit default key if a String value # is given # @param suite [String] name of a suite # @return [Hash] a shallow Hash copy of the original if not modified, or a # new Hash otherwise # @api private def normalized_suite_data(key, default_key, suite) sdata = suite_data_for(suite).fetch(key, {}) sdata = sdata.nil? ? {} : sdata.dup sdata = { default_key => sdata } if sdata.is_a?(String) case key when :provisioner namespace_array!(sdata, :run_list, :suite) when :lifecycle sdata.each_key do |k| namespace_array!(sdata, k, :suite) end end sdata end # Returns the hash for a platform by name, or an empty Hash if none # could be found. # # @param name [String] name of a platform # @return [Hash] the configuration hash for the platform, or an empty # Hash if not found # @api private def platform_data_for(name) data.fetch(:platforms, {}).find(-> { Hash.new }) do |platform| platform.fetch(:name, nil) == name end end # Destructively sets a base kitchen config key/value pair at the root of # the given hash. If the key is present in the given Hash, it is deleted # and will not be used. If the key is found in the `kitchen_config` hash # (default values), then its value will be used and set. Finally, if # the key is found in `:kitchen` data sub-hash, then its value will be used # and set. # # @param root [Hash] a hash to use as the root of the conversion # @param key [Symbol] the key to search for # @api private def set_kitchen_config_at!(root, key) kdata = data.fetch(:kitchen, {}) root.delete(key) if root.key?(key) root[key] = kitchen_config.fetch(key) if kitchen_config.key?(key) root[key] = kdata.fetch(key) if kdata.key?(key) end # Returns the hash for a suite by name, or an empty Hash if none # could be found. # # @param name [String] name of a suite # @return [Hash] the configuration hash for the suite, or an empty # Hash if not found # @api private def suite_data_for(name) data.fetch(:suites, {}).find(-> { Hash.new }) do |suite| suite.fetch(:name, nil) == name end end end end test-kitchen-1.23.2/lib/kitchen/verifier.rb0000644000004100000410000000355013377651062020601 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor/util" require "kitchen/errors" module Kitchen # A verifier is responsible for running tests post-converge to confirm that # the instance is in a known/consistent state. # # @author Fletcher Nichol module Verifier # Default verifier to use DEFAULT_PLUGIN = "busser".freeze # Returns an instance of a verifier given a plugin type string. # # @param plugin [String] a verifier plugin type, to be constantized # @param config [Hash] a configuration hash to initialize the verifier # @return [Verifier::Base] a verifier instance # @raise [ClientError] if a verifier instance could not be created def self.for_plugin(plugin, config) first_load = require("kitchen/verifier/#{plugin}") str_const = Thor::Util.camel_case(plugin) klass = const_get(str_const) object = klass.new(config) object.verify_dependencies if first_load object rescue LoadError, NameError raise ClientError, "Could not load the '#{plugin}' verifier from the load path." \ " Please ensure that your transport is installed as a gem or" \ " included in your Gemfile if using Bundler." end end end test-kitchen-1.23.2/lib/kitchen/generator/0000755000004100000410000000000013377651062020424 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/generator/init.rb0000644000004100000410000001714513377651062021724 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "rubygems/gem_runner" require "thor/group" module Kitchen module Generator # A project initialization generator, to help prepare a cookbook project # for testing with Kitchen. # # @author Fletcher Nichol class Init < Thor::Group include Thor::Actions class_option :driver, type: :array, aliases: "-D", default: %w{kitchen-vagrant}, desc: <<-D.gsub(/^\s+/, "").tr("\n", " ") One or more Kitchen Driver gems to be installed or added to a Gemfile D class_option :provisioner, type: :string, aliases: "-P", default: "chef_solo", desc: <<-D.gsub(/^\s+/, "").tr("\n", " ") The default Kitchen Provisioner to use D class_option :create_gemfile, type: :boolean, default: false, desc: <<-D.gsub(/^\s+/, "").tr("\n", " ") Whether or not to create a Gemfile if one does not exist. Default: false D # Invoke the command. def init self.class.source_root(Kitchen.source_root.join("templates", "init")) create_kitchen_yaml create_chefignore prepare_rakefile prepare_thorfile create_test_dir prepare_gitignore prepare_gemfile display_bundle_message end private # Creates the `.kitchen.yml` file. # # @api private def create_kitchen_yaml cookbook_name = if File.exist?(File.expand_path("metadata.rb")) MetadataChopper.extract("metadata.rb").first end run_list = cookbook_name ? "recipe[#{cookbook_name}::default]" : nil driver_plugin = Array(options[:driver]).first || "dummy" template("kitchen.yml.erb", "kitchen.yml", driver_plugin: driver_plugin.sub(/^kitchen-/, ""), provisioner: options[:provisioner], run_list: Array(run_list) ) end # Creates the `chefignore` file. # # @api private def create_chefignore template("chefignore.erb", "chefignore") end # @return [true,false] whether or not a Gemfile needs to be initialized # @api private def init_gemfile? File.exist?(File.join(destination_root, "Gemfile")) || options[:create_gemfile] end # @return [true,false] whether or not a Rakefile needs to be initialized # @api private def init_rakefile? File.exist?(File.join(destination_root, "Rakefile")) && not_in_file?("Rakefile", %r{require 'kitchen/rake_tasks'}) end # @return [true,false] whether or not a Thorfile needs to be initialized # @api private def init_thorfile? File.exist?(File.join(destination_root, "Thorfile")) && not_in_file?("Thorfile", %r{require 'kitchen/thor_tasks'}) end # @return [true,false] whether or not a test directory needs to be # initialized # @api private def init_test_dir? Util.list_directory("test/integration/").select { |d| File.directory?(d) }.empty? end # @return [true,false] whether or not a `.gitignore` file needs to be # initialized # @api private def init_git? File.directory?(File.join(destination_root, ".git")) end # Prepares a Rakefile. # # @api private def prepare_rakefile return unless init_rakefile? rakedoc = <<-RAKE.gsub(/^ {10}/, "") begin require 'kitchen/rake_tasks' Kitchen::RakeTasks.new rescue LoadError puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] end RAKE append_to_file(File.join(destination_root, "Rakefile"), rakedoc) end # Prepares a Thorfile. # # @api private def prepare_thorfile return unless init_thorfile? thordoc = <<-THOR.gsub(/^ {10}/, "") begin require 'kitchen/thor_tasks' Kitchen::ThorTasks.new rescue LoadError puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] end THOR append_to_file(File.join(destination_root, "Thorfile"), thordoc) end # Create the default test directory # # @api private def create_test_dir empty_directory "test/integration/default" if init_test_dir? end # Prepares the .gitignore file # # @api private def prepare_gitignore return unless init_git? append_to_gitignore(".kitchen/") append_to_gitignore(".kitchen.local.yml") end # Appends a line to the .gitignore file. # # @api private def append_to_gitignore(line) create_file(".gitignore") unless File.exist?(File.join(destination_root, ".gitignore")) if IO.readlines(File.join(destination_root, ".gitignore")).grep(/^#{line}/).empty? append_to_file(".gitignore", "#{line}\n") end end # Prepares a Gemfile. # # @api private def prepare_gemfile return unless init_gemfile? create_gemfile_if_missing add_gem_to_gemfile end # Creates a Gemfile if missing # # @api private def create_gemfile_if_missing unless File.exist?(File.join(destination_root, "Gemfile")) create_file("Gemfile", %{source "https://rubygems.org"\n\n}) end end # Appends entries to a Gemfile. # # @api private def add_gem_to_gemfile if not_in_file?("Gemfile", /gem ('|")test-kitchen('|")/) append_to_file("Gemfile", %{gem "test-kitchen"\n}) @display_bundle_msg = true end end # Displays a bundle warning message to the user. # # @api private def display_bundle_message if @display_bundle_msg say "You must run `bundle install' to fetch any new gems.", :red end end # Determines whether or not a pattern is found in a file. # # @param filename [String] filename to read # @param regexp [Regexp] a regular expression # @return [true,false] whether or not a pattern is found in a file # @api private def not_in_file?(filename, regexp) IO.readlines(File.join(destination_root, filename)).grep(regexp).empty? end # Save off any Bundler/Ruby-related environment variables so that the # yielded block can run "bundler-free" (and restore at the end). # # @api private def unbundlerize keys = ENV.keys.select { |key| key =~ /^BUNDLER?_/ } + %w{RUBYOPT} keys.each { |key| ENV["__#{key}"] = ENV[key]; ENV.delete(key) } yield keys.each { |key| ENV[key] = ENV.delete("__#{key}") } end end end end test-kitchen-1.23.2/lib/kitchen/shell_out.rb0000644000004100000410000000732013377651062020763 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "mixlib/shellout" module Kitchen # Mixin that wraps a command shell out invocation, providing a #run_command # method. # # @author Fletcher Nichol module ShellOut # Wrapped exception for any interally raised shell out commands. class ShellCommandFailed < TransientFailure; end # Executes a command in a subshell on the local running system. # # @param cmd [String] command to be executed locally # @param options [Hash] additional configuration of command # @option options [TrueClass, FalseClass] :use_sudo whether or not to use # sudo # @option options [String] :sudo_command custom sudo command to use. # Default is "sudo -E". # @option options [String] :log_subject used in the output or log header # for clarity and context. Default is "local". # @option options [String] :cwd the directory to chdir to before running # the command # @option options [Hash] :environment a Hash of environment variables to # set before the command is run. By default, the environment will # *always* be set to `'LC_ALL' => 'C'` to prevent issues with multibyte # characters in Ruby 1.8. To avoid this, use :environment => nil for # *no* extra environment settings, or # `:environment => {'LC_ALL'=>nil, ...}` to set other environment # settings without changing the locale. # @option options [Integer] :timeout Numeric value for the number of # seconds to wait on the child process before raising an Exception. # This is calculated as the total amount of time that ShellOut waited on # the child process without receiving any output (i.e., IO.select # returned nil). Default is 60000 seconds. Note: the stdlib Timeout # library is not used. # @return [String] the standard output of the command as a String # @raise [ShellCommandFailed] if the command fails # @raise [Error] for all other unexpected exceptions def run_command(cmd, options = {}) if options.fetch(:use_sudo, false) cmd = "#{options.fetch(:sudo_command, 'sudo -E')} #{cmd}" end subject = "[#{options.fetch(:log_subject, 'local')} command]" debug("#{subject} BEGIN (#{cmd})") sh = Mixlib::ShellOut.new(cmd, shell_opts(options)) sh.run_command debug("#{subject} END #{Util.duration(sh.execution_time)}") sh.error! sh.stdout rescue Mixlib::ShellOut::ShellCommandFailed => ex raise ShellCommandFailed, ex.message rescue Exception => error # rubocop:disable Lint/RescueException error.extend(Kitchen::Error) raise end private # Returns a hash of MixLib::ShellOut options for the command. # # @param options [Hash] a Hash of options # @return [Hash] a new Hash of options, filterd and merged with defaults # @api private def shell_opts(options) filtered_opts = options.reject do |key, _value| [:use_sudo, :sudo_command, :log_subject, :quiet].include?(key) end { live_stream: logger, timeout: 60_000 }.merge(filtered_opts) end end end test-kitchen-1.23.2/lib/kitchen/platform.rb0000644000004100000410000000411313377651062020606 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # A target operating system environment in which convergence integration # will take place. This may represent a specific operating system, version, # and machine architecture. # # @author Fletcher Nichol class Platform # @return [String] logical name of this platform attr_reader :name # @return [String] operating system type hint (default: `"unix"`) attr_reader :os_type # @return [String] shell command flavor hint (default: `"bourne"`) attr_reader :shell_type # Constructs a new platform. # # @param [Hash] options configuration for a new platform # @option options [String] :name logical name of this platform # (**Required**) def initialize(options = {}) @name = options.fetch(:name) do raise ClientError, "Platform#new requires option :name" end @os_type = options.fetch(:os_type) do windows?(options) ? "windows" : "unix" end @shell_type = options.fetch(:shell_type) do windows?(options) ? "powershell" : "bourne" end end def windows?(options) @name.downcase =~ /^win/ || ( !options[:transport].nil? && options[:transport][:name] == "winrm" ) end # Returns a Hash of configuration and other useful diagnostic information. # # @return [Hash] a diagnostic hash def diagnose { os_type: os_type, shell_type: shell_type } end end end test-kitchen-1.23.2/lib/kitchen/lifecycle_hooks.rb0000644000004100000410000001143713377651062022133 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Noah Kantrowitz # # Copyright (C) 2018, Noah Kantrowitz # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/errors" require "kitchen/shell_out" module Kitchen # A helper object used by {Instance} to coordinate lifecycle hook calls from # the `lifecycle:` configuration section. # # @api internal # @since 1.22 class LifecycleHooks include Configurable include Logging include ShellOut def initialize(config) init_config(config) end # Run a lifecycle phase with the pre and post hooks. # # @param phase [String] Lifecycle phase which is being executed. # @param state_file [StateFile] Instance state file object. # @param block [Proc] Block of code implementing the lifecycle phase. # @return [void] def run_with_hooks(phase, state_file, &block) run(instance, phase, state_file, :pre) yield run(instance, phase, state_file, :post) end private # Execute a specific lifecycle hook. # # @param instance [Instance] The instance object to run against. # @param phase [String] Lifecycle phase which is being executed. # @param state_file [StateFile] Instance state file object. # @param hook_timing [Symbol] `:pre` or `:post` to indicate which hook to run. # @return [void] def run(instance, phase, state_file, hook_timing) # Yes this has to be a symbol because of how data munger works. hook_key = :"#{hook_timing}_#{phase}" # No hooks? We're outta here. hook_data = Array(config[hook_key]) return if hook_data.empty? hook_data.each do |hook| # Coerce the common case of a bare string to be a local command. This # is to match the behavior of the old `pre_create_command` semi-hook. hook = { local: hook } if hook.is_a?(String) if hook.include?(:local) # Local command execution on the workstation. run_local_hook(instance, state_file, hook) elsif hook.include?(:remote) # Remote command execution on the test instance. run_remote_hook(instance, state_file, hook) else raise UserError, "Unknown lifecycle hook target #{hook.inspect}" end end end # Execute a specific local command hook. # # @param instance [Instance] The instance object to run against. # @param state_file [StateFile] Instance state file object. # @param hook [Hash] Hook configration to use. # @return [void] def run_local_hook(instance, state_file, hook) cmd = hook.delete(:local) state = state_file.read # Set up some environment variables with instance info. environment = { "KITCHEN_INSTANCE_NAME" => instance.name, "KITCHEN_SUITE_NAME" => instance.suite.name, "KITCHEN_PLATFORM_NAME" => instance.platform.name, "KITCHEN_INSTANCE_HOSTNAME" => state[:hostname].to_s, } # If the user specified env vars too, fix them up because symbol keys # make mixlib-shellout sad. if hook[:environment] hook[:environment].each do |k, v| environment[k.to_s] = v.to_s end end # Default the cwd to the kitchen root and resolve a relative input cwd against that. cwd = if hook[:cwd] File.expand_path(hook[:cwd], config[:kitchen_root]) else config[:kitchen_root] end # Build the options for mixlib-shellout. opts = {}.merge(hook).merge(cwd: cwd, environment: environment) run_command(cmd, opts) end # Execute a specific remote command hook. # # @param instance [Instance] The instance object to run against. # @param state_file [StateFile] Instance state file object. # @param hook [Hash] Hook configration to use. # @return [void] def run_remote_hook(instance, state_file, hook) # Check if we're in a state that makes sense to even try. unless instance.last_action if hook[:skippable] # Just not even trying. return else raise UserError, "Cannot use remote lifecycle hooks during phases when the instance is not available" end end cmd = hook.delete(:remote) conn = instance.transport.connection(state_file.read) conn.execute(cmd) end end end test-kitchen-1.23.2/lib/kitchen/errors.rb0000644000004100000410000002054713377651062020307 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "English" module Kitchen # All Kitchen errors and exceptions. # # @author Fletcher Nichol module Error # Creates an array of strings, representing a formatted exception, # containing backtrace and nested exception info as necessary, that can # be viewed by a human. # # For example: # # ------Exception------- # Class: Kitchen::StandardError # Message: Failure starting the party # ---Nested Exception--- # Class: IOError # Message: not enough directories for a party # ------Backtrace------- # nil # ---------------------- # # @param exception [::StandardError] an exception # @return [Array] a formatted message def self.formatted_trace(exception, title = "Exception") arr = formatted_exception(exception, title).dup arr += formatted_backtrace(exception) if exception.respond_to?(:original) && exception.original arr += if exception.original.is_a? Array exception.original.map do |composite_exception| formatted_trace(composite_exception, "Composite Exception").flatten end else [ formatted_exception(exception.original, "Nested Exception"), formatted_backtrace(exception), ].flatten end end arr.flatten end def self.formatted_backtrace(exception) if exception.backtrace.nil? [] else [ "Backtrace".center(22, "-"), exception.backtrace, "End Backtrace".center(22, "-"), ] end end # Creates an array of strings, representing a formatted exception that # can be viewed by a human. Thanks to MiniTest for the inspiration # upon which this output has been designed. # # For example: # # ------Exception------- # Class: Kitchen::StandardError # Message: I have failed you # ---------------------- # # @param exception [::StandardError] an exception # @param title [String] a custom title for the message # (default: `"Exception"`) # @return [Array] a formatted message def self.formatted_exception(exception, title = "Exception") [ title.center(22, "-"), "Class: #{exception.class}", "Message: #{exception.message}", "".center(22, "-"), ] end end # Base exception class from which all Kitchen exceptions derive. This class # nests an exception when this class is re-raised from a rescue block. class StandardError < ::StandardError include Error # @return [::StandardError] the original (wrapped) exception attr_reader :original # Creates a new StandardError exception which optionally wraps an original # exception if given or detected by checking the `$!` global variable. # # @param msg [String] exception message # @param original [::StandardError] an original exception which will be # wrapped (default: `$ERROR_INFO`) def initialize(msg, original = $ERROR_INFO) super(msg) @original = original end end # Base exception class for all exceptions that are caused by user input # errors. class UserError < StandardError; end # Base exception class for all exceptions that are caused by incorrect use # of an API. class ClientError < StandardError; end # Base exception class for exceptions that are caused by external library # failures which may be temporary. class TransientFailure < StandardError; end # Exception class for any exceptions raised when performing an instance # action. class ActionFailed < TransientFailure; end # Exception class capturing what caused an instance to die. class InstanceFailure < TransientFailure; end # Yields to a code block in order to consistently emit a useful crash/error # message and exit appropriately. There are two primary failure conditions: # an expected instance failure, and any other unexpected failures. # # **Note** This method may call `Kernel.exit` so may not return if the # yielded code block raises an exception. # # ## Instance Failure # # This is an expected failure scenario which could happen if an instance # couldn't be created, a Chef run didn't successfully converge, a # post-convergence test suite failed, etc. In other words, you can count on # encountering these failures all the time--this is Kitchen's worldview: # crash early and often. In this case a cleanly formatted exception is # written to `STDERR` and the exception message is written to # the common Kitchen file logger. # # ## Unexpected Failure # # All other forms of `Kitchen::Error` exceptions are considered unexpected # or unplanned exceptions, typically from user configuration errors, driver # or provisioner coding issues or bugs, or internal code issues. Given # a stable release of Kitchen and a solid set of drivers and provisioners, # the most likely cause of this is user configuration error originating in # the `.kitchen.yml` setup. For this reason, the exception is written to # `STDERR`, a full formatted exception trace is written to the common # Kitchen file logger, and a message is displayed on `STDERR` to the user # informing them to check the log files and check their configuration with # the `kitchen diagnose` subcommand. # # @raise [SystemExit] if an exception is raised in the yielded block def self.with_friendly_errors yield rescue Kitchen::InstanceFailure => e Kitchen.mutex.synchronize do handle_instance_failure(e) end exit 10 rescue Kitchen::Error => e Kitchen.mutex.synchronize do handle_error(e) end exit 20 end # Writes an array of lines to the common Kitchen logger's file device at the # given severity level. If the Kitchen logger is set to debug severity, then # the array of lines will also be written to the console output. # # @param level [Symbol,String] the desired log level # @param lines [Array] an array of strings to log # @api private def self.file_log(level, lines) Array(lines).each do |line| if Kitchen.logger.debug? Kitchen.logger.debug(line) else Kitchen.logger.logdev && Kitchen.logger.logdev.public_send(level, line) end end end # Writes an array of lines to the `STDERR` device. # # @param lines [Array] an array of strings to log # @api private def self.stderr_log(lines) Array(lines).map { |line| ">>>>>> #{line}" }.each do |line| line = Color.colorize(line, :red) if Kitchen.tty? $stderr.puts(line) end end # Writes an array of lines to the common Kitchen debugger with debug # severity. # # @param lines [Array] an array of strings to log # @api private def self.debug_log(lines) Array(lines).each { |line| Kitchen.logger.debug(line) } end # Handles an instance failure exception. # # @param e [StandardError] an exception to handle # @see Kitchen.with_friendly_errors # @api private def self.handle_instance_failure(e) stderr_log(e.message.split(/\s{2,}/)) stderr_log(Error.formatted_exception(e.original)) file_log(:error, e.message.split(/\s{2,}/).first) debug_log(Error.formatted_trace(e)) end # Handles an unexpected failure exception. # # @param e [StandardError] an exception to handle # @see Kitchen.with_friendly_errors # @api private def self.handle_error(e) stderr_log(Error.formatted_exception(e)) stderr_log("Please see .kitchen/logs/kitchen.log for more details") stderr_log("Also try running `kitchen diagnose --all` for configuration\n") file_log(:error, Error.formatted_trace(e)) end end test-kitchen-1.23.2/lib/kitchen/color.rb0000644000004100000410000000424713377651062020110 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # Utility methods to help ouput colorized text in a terminal. The # implementation is a compressed mashup of code from the Thor and Foreman # projects. # # @author Fletcher Nichol module Color ANSI = { reset: 0, black: 30, red: 31, green: 32, yellow: 33, blue: 34, magenta: 35, cyan: 36, white: 37, bright_black: 90, bright_red: 91, bright_green: 92, bright_yellow: 93, bright_blue: 94, bright_magenta: 95, bright_cyan: 96, bright_white: 97 }.freeze COLORS = %w{ cyan yellow green magenta blue bright_cyan bright_yellow bright_green bright_magenta bright_blue }.freeze # Returns an ansi escaped string representing a color control sequence. # # @param name [Symbol] a valid color representation, taken from # Kitchen::Color::ANSI # @return [String] an ansi escaped string if the color is valid and an # empty string otherwise def self.escape(name) return "" if name.nil? return "" unless ANSI[name] "\e[#{ANSI[name]}m" end # Returns a colorized ansi escaped string with the given color. # # @param str [String] a string to colorize # @param name [Symbol] a valid color representation, taken from # Kitchen::Color::ANSI # @return [String] an ansi escaped string if the color is valid and an # unescaped string otherwise def self.colorize(str, name) color = escape(name) color.empty? ? str : "#{color}#{str}#{escape(:reset)}" end end end test-kitchen-1.23.2/lib/kitchen/instance.rb0000644000004100000410000005623313377651062020600 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "benchmark" require "fileutils" module Kitchen # An instance of a suite running on a platform. A created instance may be a # local virtual machine, cloud instance, container, or even a bare metal # server, which is determined by the platform's driver. # # @author Fletcher Nichol class Instance include Logging class << self # @return [Hash] a hash of mutxes, arranged by Driver class names # @api private attr_accessor :mutexes # Generates a name for an instance given a suite and platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [String] a normalized, consistent name for an instance def name_for(suite, platform) "#{suite.name}-#{platform.name}".gsub(%r{[_,/]}, "-").delete(".") end end # @return [Suite] the test suite configuration attr_reader :suite # @return [Platform] the target platform configuration attr_reader :platform # @return [String] name of this instance attr_reader :name # @return [Driver::Base] driver object which will manage this instance's # lifecycle actions attr_accessor :driver # @return [LifecycleHooks] lifecycle hooks manager object attr_accessor :lifecycle_hooks # @return [Provisioner::Base] provisioner object which will the setup # and invocation instructions for configuration management and other # automation tools attr_accessor :provisioner # @return [Transport::Base] transport object which will communicate with # an instance. attr_accessor :transport # @return [Verifier] verifier object for instance to manage the verifier # installation on this instance attr_accessor :verifier # @return [Logger] the logger for this instance attr_reader :logger # Creates a new instance, given a suite and a platform. # # @param [Hash] options configuration for a new suite # @option options [Suite] :suite the suite (**Required**) # @option options [Platform] :platform the platform (**Required**) # @option options [Driver::Base] :driver the driver (**Required**) # @option options [Provisioner::Base] :provisioner the provisioner # @option options [Transport::Base] :transport the transport # (**Required**) # @option options [Verifier] :verifier the verifier logger (**Required**) # @option options [Logger] :logger the instance logger # (default: Kitchen.logger) # @option options [StateFile] :state_file the state file object to use # when tracking instance state (**Required**) # @raise [ClientError] if one or more required options are omitted def initialize(options = {}) validate_options(options) @suite = options.fetch(:suite) @platform = options.fetch(:platform) @name = self.class.name_for(@suite, @platform) @driver = options.fetch(:driver) @lifecycle_hooks = options.fetch(:lifecycle_hooks) @provisioner = options.fetch(:provisioner) @transport = options.fetch(:transport) @verifier = options.fetch(:verifier) @logger = options.fetch(:logger) { Kitchen.logger } @state_file = options.fetch(:state_file) setup_driver setup_provisioner setup_transport setup_verifier setup_lifecycle_hooks end # Returns a displayable representation of the instance. # # @return [String] an instance display string def to_str "<#{name}>" end # Creates this instance. # # @see Driver::Base#create # @return [self] this instance, used to chain actions # # @todo rescue Driver::ActionFailed and return some kind of null object # to gracfully stop action chaining def create transition_to(:create) end # Converges this running instance. # # @see Provisioner::Base#call # @return [self] this instance, used to chain actions # # @todo rescue Driver::ActionFailed and return some kind of null object # to gracfully stop action chaining def converge transition_to(:converge) end # Sets up this converged instance for suite tests. # # @see Driver::Base#setup # @return [self] this instance, used to chain actions # # @todo rescue Driver::ActionFailed and return some kind of null object # to gracfully stop action chaining def setup transition_to(:setup) end # Verifies this set up instance by executing suite tests. # # @see Driver::Base#verify # @return [self] this instance, used to chain actions # # @todo rescue Driver::ActionFailed and return some kind of null object # to gracfully stop action chaining def verify transition_to(:verify) end # Destroys this instance. # # @see Driver::Base#destroy # @return [self] this instance, used to chain actions # # @todo rescue Driver::ActionFailed and return some kind of null object # to gracfully stop action chaining def destroy transition_to(:destroy) end # Tests this instance by creating, converging and verifying. If this # instance is running, it will be pre-emptively destroyed to ensure a # clean slate. The instance will be left post-verify in a running state. # # @param destroy_mode [Symbol] strategy used to cleanup after instance # has finished verifying (default: `:passing`) # @return [self] this instance, used to chain actions # # @todo rescue Driver::ActionFailed and return some kind of null object # to gracfully stop action chaining def test(destroy_mode = :passing) elapsed = Benchmark.measure do banner "Cleaning up any prior instances of #{to_str}" destroy banner "Testing #{to_str}" verify destroy if destroy_mode == :passing end info "Finished testing #{to_str} #{Util.duration(elapsed.real)}." self ensure destroy if destroy_mode == :always end # Logs in to this instance by invoking a system command, provided by the # instance's transport. This could be an SSH command, telnet, or serial # console session. # # **Note** This method calls exec and will not return. # # @see Kitchen::LoginCommand # @see Transport::Base::Connection#login_command def login state = state_file.read if state[:last_action].nil? raise UserError, "Instance #{to_str} has not yet been created" end lc = if legacy_ssh_base_driver? legacy_ssh_base_login(state) else transport.connection(state).login_command end debug(%{Login command: #{lc.command} #{lc.arguments.join(' ')} } \ "(Options: #{lc.options})") Kernel.exec(*lc.exec_args) end # Executes an arbitrary command on this instance. # # @param command [String] a command string to execute def remote_exec(command) transport.connection(state_file.read) do |conn| conn.execute(command) end end # Perform package. # def package_action banner "Packaging remote instance" driver.package(state_file.read) end # Check system and configuration for common errors. # def doctor_action banner "The doctor is in" [driver, provisioner, transport, verifier].any? do |obj| obj.doctor(state_file.read) end end # Returns a Hash of configuration and other useful diagnostic information. # # @return [Hash] a diagnostic hash def diagnose result = {} [ :platform, :state_file, :driver, :provisioner, :transport, :verifier ].each do |sym| obj = send(sym) result[sym] = obj.respond_to?(:diagnose) ? obj.diagnose : :unknown end result end # Returns a Hash of configuration and other useful diagnostic information # associated with plugins (such as loaded version, class name, etc.). # # @return [Hash] a diagnostic hash def diagnose_plugins result = {} [:driver, :provisioner, :verifier, :transport].each do |sym| obj = send(sym) result[sym] = if obj.respond_to?(:diagnose_plugin) obj.diagnose_plugin else :unknown end end result end # Returns the last successfully completed action state of the instance. # # @return [String] a named action which was last successfully completed def last_action state_file.read[:last_action] end # Returns the error encountered on the last action on the instance # # @return [String] the message of the last error def last_error state_file.read[:last_error] end # Clean up any per-instance resources before exiting. # # @return [void] def cleanup! @transport.cleanup! if @transport end private # @return [StateFile] a state file object that can be read from or written # to # @api private attr_reader :state_file # Validate the initial internal state of this object and raising an # exception if any preconditions are not met. # # @param options[Hash] options hash passed into the constructor # @raise [ClientError] if any validations fail # @api private def validate_options(options) [ :suite, :platform, :driver, :provisioner, :transport, :verifier, :state_file ].each do |k| next if options.key?(k) raise ClientError, "Instance#new requires option :#{k}" end end # Perform any final configuration or preparation needed for the driver # object carry out its duties. # # @api private def setup_driver @driver.finalize_config!(self) if driver.class.serial_actions Kitchen.mutex.synchronize do self.class.mutexes ||= {} self.class.mutexes[driver.class] = Mutex.new end end end # Perform any final configuration or preparation needed for the lifecycle hooks # object carry out its duties. # # @api private def setup_lifecycle_hooks lifecycle_hooks.finalize_config!(self) end # Perform any final configuration or preparation needed for the provisioner # object carry out its duties. # # @api private def setup_provisioner @provisioner.finalize_config!(self) end # Perform any final configuration or preparation needed for the transport # object carry out its duties. # # @api private def setup_transport transport.finalize_config!(self) end # Perform any final configuration or preparation needed for the verifier # object carry out its duties. # # @api private def setup_verifier verifier.finalize_config!(self) end # Perform all actions in order from last state to desired state. # # @param desired [Symbol] a symbol representing the desired action state # @return [self] this instance, used to chain actions # @api private def transition_to(desired) result = nil FSM.actions(last_action, desired).each do |transition| @lifecycle_hooks.run_with_hooks(transition, state_file) do result = send("#{transition}_action") end end result end # Perform the create action. # # @see Driver::Base#create # @return [self] this instance, used to chain actions # @api private def create_action perform_action(:create, "Creating") end # Perform the converge action. # # @see Provisioner::Base#call # @return [self] this instance, used to chain actions # @api private def converge_action banner "Converging #{to_str}..." elapsed = action(:converge) do |state| if legacy_ssh_base_driver? legacy_ssh_base_converge(state) else provisioner.call(state) end end info("Finished converging #{to_str} #{Util.duration(elapsed.real)}.") self end # Perform the setup action. # # @see Driver::Base#setup # @return [self] this instance, used to chain actions # @api private def setup_action banner "Setting up #{to_str}..." elapsed = action(:setup) do |state| legacy_ssh_base_setup(state) if legacy_ssh_base_driver? end info("Finished setting up #{to_str} #{Util.duration(elapsed.real)}.") self end # returns true, if the verifier is busser def verifier_busser?(verifier) !defined?(Kitchen::Verifier::Busser).nil? && verifier.is_a?(Kitchen::Verifier::Busser) end # returns true, if the verifier is dummy def verifier_dummy?(verifier) !defined?(Kitchen::Verifier::Dummy).nil? && verifier.is_a?(Kitchen::Verifier::Dummy) end def use_legacy_ssh_verifier?(verifier) verifier_busser?(verifier) || verifier_dummy?(verifier) end # Perform the verify action. # # @see Driver::Base#verify # @return [self] this instance, used to chain actions # @api private def verify_action banner "Verifying #{to_str}..." elapsed = action(:verify) do |state| # use special handling for legacy driver if legacy_ssh_base_driver? && use_legacy_ssh_verifier?(verifier) legacy_ssh_base_verify(state) elsif legacy_ssh_base_driver? # read ssh options from legacy driver verifier.call(driver.legacy_state(state)) else verifier.call(state) end end info("Finished verifying #{to_str} #{Util.duration(elapsed.real)}.") self end # Perform the destroy action. # # @see Driver::Base#destroy # @return [self] this instance, used to chain actions # @api private def destroy_action perform_action(:destroy, "Destroying") { state_file.destroy } end # Perform an arbitrary action and provide useful logging. # # @param verb [Symbol] the action to be performed # @param output_verb [String] a verb representing the action, suitable for # use in output logging # @yield perform optional work just after action has complted # @return [self] this instance, used to chain actions # @api private def perform_action(verb, output_verb) banner "#{output_verb} #{to_str}..." elapsed = action(verb) { |state| driver.public_send(verb, state) } info("Finished #{output_verb.downcase} #{to_str}" \ " #{Util.duration(elapsed.real)}.") yield if block_given? self end # Times a call to an action block and handles any raised exceptions. This # method ensures that the last successfully completed action is persisted # to the state file. The last action state will either be the desired # action that is passed in or the previous action that was persisted to the # state file. # # @param what [Symbol] the action to be performed # @param block [Proc] a block to be called # @return [Benchmark::Tms] timing information for the given action # @raise [InstanceFailed] if a driver action fails to complete, signaled # by a driver raising an ActionFailed exception. Typical reasons for this # would be a driver create action failing, a chef convergence crashing # in normal course of development, failing acceptance tests in the # verify action, etc. # @raise [ActionFailed] if an unforseen or unplanned exception is raised. # This would usually indicate that a race condition was triggered, a # bug exists in a driver, provisioner, or core, a transient IO error # occured, etc. # @api private def action(what, &block) state = state_file.read elapsed = Benchmark.measure do synchronize_or_call(what, state, &block) end state[:last_action] = what.to_s state[:last_error] = nil elapsed rescue ActionFailed => e log_failure(what, e) state[:last_error] = e.class.name raise(InstanceFailure, failure_message(what) + " Please see .kitchen/logs/#{name}.log for more details", e.backtrace) rescue Exception => e # rubocop:disable Lint/RescueException log_failure(what, e) state[:last_error] = e.class.name raise ActionFailed, "Failed to complete ##{what} action: [#{e.message}]", e.backtrace ensure state_file.write(state) end # Runs a given action block through a common driver mutex if required or # runs it directly otherwise. If a driver class' `.serial_actions` array # includes the desired action, then the action must be run with a muxtex # lock. Otherwise, it is assumed that the action can happen concurrently, # or fully in parallel. # # @param what [Symbol] the action to be performed # @param state [Hash] a mutable state hash for this instance # @param block [Proc] a block to be called # @api private def synchronize_or_call(what, state) if Array(driver.class.serial_actions).include?(what) debug("#{to_str} is synchronizing on #{driver.class}##{what}") self.class.mutexes[driver.class].synchronize do debug("#{to_str} is messaging #{driver.class}##{what}") yield(state) end else yield(state) end end # Writes a high level message for logging and/or output. # # In this case, all instance banner messages will be written to the common # Kitchen logger so that the high level flow of a run can be followed in # the kitchen.log file. # # @api private def banner(*args) Kitchen.logger.logdev && Kitchen.logger.logdev.banner(*args) super end # Logs a failure (message and backtrace) to the instance's file logger # to help with debugging and diagnosing issues without overwhelming the # console output in the default case (i.e. running kitchen with :info # level debugging). # # @param what [String] an action # @param e [Exception] an exception # @api private def log_failure(what, e) return if logger.logdev.nil? logger.logdev.error(failure_message(what)) Error.formatted_trace(e).each { |line| logger.logdev.error(line) } end # Returns a string explaining what action failed, at a high level. Used # for displaying to end user. # # @param what [String] an action # @return [String] a failure message # @api private def failure_message(what) "#{what.capitalize} failed on instance #{to_str}." end # Invokes `Driver#converge` on a legacy Driver, which inherits from # `Kitchen::Driver::SSHBase`. # # @param state [Hash] mutable instance state # @deprecated When legacy Driver::SSHBase support is removed, the # `#converge` method will no longer be called on the Driver. # @api private def legacy_ssh_base_converge(state) warn("Running legacy converge for '#{driver.name}' Driver") # TODO: Document upgrade path and provide link # warn("Driver authors: please read http://example.com for more details.") driver.converge(state) end # @return [TrueClass,FalseClass] whether or not the Driver inherits from # `Kitchen::Driver::SSHBase` # @deprecated When legacy Driver::SSHBase support is removed, the # `#converge` method will no longer be called on the Driver. # @api private def legacy_ssh_base_driver? driver.class < Kitchen::Driver::SSHBase end # Invokes `Driver#login_command` on a legacy Driver, which inherits from # `Kitchen::Driver::SSHBase`. # # @param state [Hash] mutable instance state # @deprecated When legacy Driver::SSHBase support is removed, the # `#login_command` method will no longer be called on the Driver. # @api private def legacy_ssh_base_login(state) warn("Running legacy login for '#{driver.name}' Driver") # TODO: Document upgrade path and provide link # warn("Driver authors: please read http://example.com for more details.") driver.login_command(state) end # Invokes `Driver#setup` on a legacy Driver, which inherits from # `Kitchen::Driver::SSHBase`. # # @param state [Hash] mutable instance state # @deprecated When legacy Driver::SSHBase support is removed, the # `#setup` method will no longer be called on the Driver. # @api private def legacy_ssh_base_setup(state) warn("Running legacy setup for '#{driver.name}' Driver") # TODO: Document upgrade path and provide link # warn("Driver authors: please read http://example.com for more details.") driver.setup(state) end # Invokes `Driver#verify` on a legacy Driver, which inherits from # `Kitchen::Driver::SSHBase`. # # @param state [Hash] mutable instance state # @deprecated When legacy Driver::SSHBase support is removed, the # `#verify` method will no longer be called on the Driver. # @api private def legacy_ssh_base_verify(state) warn("Running legacy verify for '#{driver.name}' Driver") # TODO: Document upgrade path and provide link # warn("Driver authors: please read http://example.com for more details.") driver.verify(state) end # The simplest finite state machine pseudo-implementation needed to manage # an Instance. # # @api private # @author Fletcher Nichol class FSM # Returns an Array of all transitions to bring an Instance from its last # reported transistioned state into the desired transitioned state. # # @param last [String,Symbol,nil] the last known transitioned state of # the Instance, defaulting to `nil` (for unknown or no history) # @param desired [String,Symbol] the desired transitioned state for the # Instance # @return [Array] an Array of transition actions to perform # @api private def self.actions(last = nil, desired) last_index = index(last) desired_index = index(desired) if last_index == desired_index || last_index > desired_index Array(TRANSITIONS[desired_index]) else TRANSITIONS.slice(last_index + 1, desired_index - last_index) end end TRANSITIONS = [:destroy, :create, :converge, :setup, :verify].freeze # Determines the index of a state in the state lifecycle vector. Woah. # # @param transition [Symbol,#to_sym] a state # @param [Integer] the index position # @api private def self.index(transition) if transition.nil? 0 else TRANSITIONS.find_index { |t| t == transition.to_sym } end end end end end test-kitchen-1.23.2/lib/kitchen/login_command.rb0000644000004100000410000000307113377651062021572 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # Value object to track a shell command that will be passed to Kernel.exec # for execution. # # @author Fletcher Nichol class LoginCommand # @return [String] login command attr_reader :command # @return [Array] array of arguments to the command attr_reader :arguments # @return [Hash] options hash, passed to `Kernel#exec` attr_reader :options # Constructs a new LoginCommand instance. # # @param command [String] command # @param arguments [Array] array of arguments to the command # @param options [Hash] options hash, passed to `Kernel#exec` # @see http://www.ruby-doc.org/core-2.1.2/Kernel.html#method-i-exec def initialize(command, arguments, options = {}) @command = command @arguments = Array(arguments) @options = options end def exec_args [command, *arguments, options] end end end test-kitchen-1.23.2/lib/kitchen/transport/0000755000004100000410000000000013377651062020472 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/transport/exec.rb0000644000004100000410000000321213377651062021741 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "fileutils" require "kitchen/shell_out" require "kitchen/transport/base" require "kitchen/version" module Kitchen module Transport # Exec transport for Kitchen. This transport runs all commands locally. # # @since 1.19 class Exec < Kitchen::Transport::Base kitchen_transport_api_version 1 plugin_version Kitchen::VERSION def connection(state, &block) options = config.to_hash.merge(state) Kitchen::Transport::Exec::Connection.new(options, &block) end # Fake connection which just does local operations. class Connection < Kitchen::Transport::Base::Connection include ShellOut # (see Base#execute) def execute(command) return if command.nil? run_command(command) end # "Upload" the files by copying them locally. # # @see Base#upload def upload(locals, remote) FileUtils.mkdir_p(remote) Array(locals).each do |local| FileUtils.cp_r(local, remote) end end end end end end test-kitchen-1.23.2/lib/kitchen/transport/base.rb0000644000004100000410000002014513377651062021733 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Salim Afiune () # Author:: Fletcher Nichol () # # Copyright (C) 2014, Salim Afiune # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/errors" require "kitchen/lazy_hash" require "kitchen/login_command" module Kitchen module Transport # Wrapped exception for any internally raised Transport errors. # # @author Salim Afiune class TransportFailed < TransientFailure attr_reader :exit_code def initialize(message, exit_code = nil) @exit_code = exit_code super(message) end end # Base class for a transport. # # @author Salim Afiune # @author Fletcher Nichol class Base include Configurable include Logging # Create a new transport by providing a configuration hash. # # @param config [Hash] initial provided configuration def initialize(config = {}) @connection = nil init_config(config) end # Creates a new Connection, configured by a merging of configuration # and state data. Depending on the implementation, the Connection could # be saved or cached to speed up multiple calls, given the same state # hash as input. # # @param state [Hash] mutable instance state # @return [Connection] a connection for this transport # @raise [TransportFailed] if a connection could not be returned # rubocop:disable Lint/UnusedMethodArgument def connection(state) raise ClientError, "#{self.class}#connection must be implemented" end # Check system and configuration for common errors. # # @param state [Hash] mutable instance state # @returns [Boolean] Return true if a problem is found. def doctor(state) false end # Closes the connection, if it is still active. # # @return [void] def cleanup! # This method may be left unimplemented if that is applicable end # A Connection instance can be generated and re-generated, given new # connection details such as connection port, hostname, credentials, etc. # This object is responsible for carrying out the actions on the remote # host such as executing commands, transferring files, etc. # # @author Fletcher Nichol class Connection include Logging # Create a new Connection instance. # # @param options [Hash] connection options # @yield [self] yields itself for block-style invocation def initialize(options = {}) init_options(options) yield self if block_given? end # Closes the session connection, if it is still active. def close # this method may be left unimplemented if that is applicable end # Execute a command on the remote host. # # @param command [String] command string to execute # @raise [TransportFailed] if the command does not exit successfully, # which may vary by implementation def execute(command) raise ClientError, "#{self.class}#execute must be implemented" end # Execute a command on the remote host and retry # # @param command [String] command string to execute # @param retryable_exit_codes [Array] Array of exit codes to retry against # @param max_retries [Fixnum] maximum number of retry attempts # @param wait_time [Fixnum] number of seconds to wait before retrying command # @raise [TransportFailed] if the command does not exit successfully, # which may vary by implementation def execute_with_retry(command, retryable_exit_codes = [], max_retries = 1, wait_time = 30) tries = 0 begin tries += 1 debug("Attempting to execute command - try #{tries} of #{max_retries}.") execute(command) rescue Kitchen::Transport::TransportFailed => e if retry?(tries, max_retries, retryable_exit_codes, e.exit_code) close sleep wait_time retry else raise e end end end def retry?(current_try, max_retries, retryable_exit_codes, exit_code) current_try <= max_retries && !retryable_exit_codes.nil? && retryable_exit_codes.flatten.include?(exit_code) end # Builds a LoginCommand which can be used to open an interactive # session on the remote host. # # @return [LoginCommand] an object containing the array of command line # tokens and exec options to be used in a fork/exec # @raise [ActionFailed] if the action could not be completed def login_command raise ActionFailed, "Remote login not supported in #{self.class}." end # Uploads local files or directories to remote host. # # @param locals [Array] paths to local files or directories # @param remote [String] path to remote destination # @raise [TransportFailed] if the files could not all be uploaded # successfully, which may vary by implementation def upload(locals, remote) # rubocop:disable Lint/UnusedMethodArgument raise ClientError, "#{self.class}#upload must be implemented" end # Download remote files or directories to local host. # # @param remotes [Array] paths to remote files or directories # @param local [String] path to local destination. If `local` is an # existing directory, `remote` will be downloaded into the directory # using its original name # @raise [TransportFailed] if the files could not all be downloaded # successfully, which may vary by implementation def download(remotes, local) # rubocop:disable Lint/UnusedMethodArgument raise ClientError, "#{self.class}#download must be implemented" end # Block and return only when the remote host is prepared and ready to # execute command and upload files. The semantics and details will # vary by implementation, but a round trip through the hosted # service is preferred to simply waiting on a socket to become # available. def wait_until_ready # this method may be left unimplemented if that is applicable end private # @return [Kitchen::Logger] a logger # @api private attr_reader :logger # @return [Hash] connection options # @api private attr_reader :options # Initialize incoming options for use by the object. # # @param options [Hash] configuration options def init_options(options) @options = options.dup @logger = @options.delete(:logger) || Kitchen.logger end end # Sets the API version for this transport. If the transport does not set # this value, then `nil` will be used and reported. # # Sets the API version for this transport # # @example setting an API version # # module Kitchen # module Transport # class NewTransport < Kitchen::Transport::Base # # kitchen_transport_api_version 2 # # end # end # end # # @param version [Integer,String] a version number # def self.kitchen_transport_api_version(version) @api_version = version end end end end test-kitchen-1.23.2/lib/kitchen/transport/winrm.rb0000644000004100000410000004164013377651062022160 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Salim Afiune () # Author:: Matt Wrock () # Author:: Fletcher Nichol () # # Copyright (C) 2014, Salim Afiune # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "rbconfig" require "uri" require "kitchen" module Kitchen module Transport # Wrapped exception for any internally raised WinRM-related errors. # # @author Fletcher Nichol class WinrmFailed < TransportFailed; end # A Transport which uses WinRM to execute commands and transfer files. # # @author Matt Wrock # @author Salim Afiune # @author Fletcher Nichol class Winrm < Kitchen::Transport::Base kitchen_transport_api_version 1 plugin_version Kitchen::VERSION default_config :username, "administrator" default_config :password, nil default_config :elevated, false default_config :rdp_port, 3389 default_config :connection_retries, 5 default_config :connection_retry_sleep, 1 default_config :max_wait_until_ready, 600 default_config :winrm_transport, :negotiate default_config :scheme do |transport| transport[:winrm_transport] == :ssl ? "https" : "http" end default_config :port do |transport| transport[:winrm_transport] == :ssl ? 5986 : 5985 end def finalize_config!(instance) super config[:winrm_transport] = config[:winrm_transport].to_sym self end # (see Base#connection) def connection(state, &block) options = connection_options(config.to_hash.merge(state)) if @connection && @connection_options == options reuse_connection(&block) else create_new_connection(options, &block) end end # A Connection instance can be generated and re-generated, given new # connection details such as connection port, hostname, credentials, etc. # This object is responsible for carrying out the actions on the remote # host such as executing commands, transferring files, etc. # # @author Fletcher Nichol class Connection < Kitchen::Transport::Base::Connection # (see Base::Connection#initialize) def initialize(config = {}) super(config) @unelevated_session = nil @elevated_session = nil end # (see Base::Connection#close) def close @unelevated_session.close if @unelevated_session @elevated_session.close if @elevated_session ensure @unelevated_session = nil @elevated_session = nil @file_transporter = nil end # (see Base::Connection#execute) def execute(command) return if command.nil? logger.debug("[WinRM] #{self} (#{command})") exit_code, stderr = execute_with_exit_code(command) if logger.debug? && exit_code == 0 log_stderr_on_warn(stderr) elsif exit_code != 0 log_stderr_on_warn(stderr) raise Transport::WinrmFailed.new( "WinRM exited (#{exit_code}) for command: [#{command}]", exit_code ) end end # (see Base::Connection#login_command) def login_command case RbConfig::CONFIG["host_os"] when /darwin/ login_command_for_mac when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ login_command_for_windows when /linux/ login_command_for_linux else raise ActionFailed, "Remote login not supported in #{self.class} " \ "from host OS '#{RbConfig::CONFIG['host_os']}'." end end # (see Base::Connection#upload) def upload(locals, remote) file_transporter.upload(locals, remote) end # (see Base::Connection#download) def download(remotes, local) # ensure the parent dir of the local target exists FileUtils.mkdir_p(File.dirname(local)) Array(remotes).each do |remote| file_manager.download(remote, local) end end # @return [Winrm::FileManager] a file transporter # @api private def file_manager @file_manager ||= WinRM::FS::FileManager.new(connection) end # (see Base::Connection#wait_until_ready) def wait_until_ready delay = 3 unelevated_session( retry_limit: max_wait_until_ready / delay, retry_delay: delay ) execute(PING_COMMAND.dup) end private PING_COMMAND = "Write-Host '[WinRM] Established\n'".freeze # @return [Integer] how many times to retry when failing to execute # a command or transfer files # @api private attr_reader :connection_retries # @return [Float] how many seconds to wait before attempting a retry # when failing to execute a command or transfer files # @api private attr_reader :connection_retry_sleep # @return [String] display name for the associated instance # @api private attr_reader :instance_name # @return [String] local path to the root of the project # @api private attr_reader :kitchen_root # @return [Integer] how many times to retry when invoking # `#wait_until_ready` before failing # @api private attr_reader :max_wait_until_ready # @return [Integer] the TCP port number to use when connection to the # remote WinRM host # @api private attr_reader :rdp_port # @return [Boolean] whether to use winrm-elevated for running commands # @api private attr_reader :elevated # Writes an RDP document to the local file system. # # @param opts [Hash] file options # @option opts [true,false] :mac whether or not the document is for a # Mac system # @api private def create_rdp_doc(opts = {}) content = Util.outdent!(<<-RDP) full address:s:#{URI.parse(options[:endpoint]).host}:#{rdp_port} prompt for credentials:i:1 username:s:#{options[:user]} RDP content.prepend("drivestoredirect:s:*\n") if opts[:mac] File.open(rdp_doc_path, "wb") { |f| f.write(content) } if logger.debug? debug("Creating RDP document for #{instance_name} (#{rdp_doc_path})") debug("------------") IO.read(rdp_doc_path).each_line { |l| debug(l.chomp.to_s) } debug("------------") end end # Execute a Powershell script over WinRM and return the command's # exit code and standard error. # # @param command [String] Powershell script to execute # @return [[Integer,String]] an array containing the exit code of the # script and the standard error stream # @api private def execute_with_exit_code(command) if elevated session = elevated_session command = "$env:temp='#{unelevated_temp_dir}';#{command}" else session = unelevated_session end response = session.run(command) do |stdout, _| logger << stdout if stdout end [response.exitcode, response.stderr] end def unelevated_temp_dir @unelevated_temp_dir ||= unelevated_session.run("$env:temp").stdout.chomp end # @return [Winrm::FileTransporter] a file transporter # @api private def file_transporter @file_transporter ||= WinRM::FS::Core::FileTransporter.new(unelevated_session) end # (see Base#init_options) def init_options(options) super @instance_name = @options.delete(:instance_name) @kitchen_root = @options.delete(:kitchen_root) @rdp_port = @options.delete(:rdp_port) @connection_retries = @options.delete(:connection_retries) @connection_retry_sleep = @options.delete(:connection_retry_sleep) @max_wait_until_ready = @options.delete(:max_wait_until_ready) @elevated = @options.delete(:elevated) end # Logs formatted standard error output at the warning level. # # @param stderr [String] standard error output # @api private def log_stderr_on_warn(stderr) error_regexp = // if error_regexp.match(stderr) stderr .split(error_regexp)[1..-2] .map! { |line| line.sub(/_x000D__x000A_<\/S>/, "").rstrip } .each { |line| logger.warn(line) } else stderr .split("\r\n") .each { |line| logger.warn(line) } end end # Builds a `LoginCommand` for use by Linux-based platforms. # # TODO: determine whether or not `desktop` exists # # @return [LoginCommand] a login command # @api private def login_command_for_linux args = %W{-u #{options[:user]}} args += %W{-p #{options[:password]}} if options.key?(:password) args += %W{#{URI.parse(options[:endpoint]).host}:#{rdp_port}} LoginCommand.new("rdesktop", args) end # Builds a `LoginCommand` for use by Mac-based platforms. # # @return [LoginCommand] a login command # @api private def login_command_for_mac create_rdp_doc(mac: true) LoginCommand.new("open", rdp_doc_path) end # Builds a `LoginCommand` for use by Windows-based platforms. # # @return [LoginCommand] a login command # @api private def login_command_for_windows create_rdp_doc LoginCommand.new("mstsc", rdp_doc_path) end # @return [String] path to the local RDP document # @api private def rdp_doc_path File.join(kitchen_root, ".kitchen", "#{instance_name}.rdp") end # Establishes a remote shell session, or establishes one when invoked # the first time. # # @param retry_options [Hash] retry options for the initial connection # @return [Winrm::Shells::Powershell] the command shell session # @api private def unelevated_session(retry_options = {}) @unelevated_session ||= connection(retry_options).shell(:powershell) end # Creates an elevated session for running commands via a scheduled task # # @return [Winrm::Shells::Elevated] the elevated shell # @api private def elevated_session(retry_options = {}) @elevated_session ||= begin connection(retry_options).shell(:elevated).tap do |shell| shell.username = options[:elevated_username] shell.password = options[:elevated_password] end end end # Creates a winrm Connection instance # # @param retry_options [Hash] retry options for the initial connection # @return [Winrm::Connection] the winrm connection # @api private def connection(retry_options = {}) @connection ||= begin opts = { retry_limit: connection_retries.to_i, retry_delay: connection_retry_sleep.to_i, }.merge(retry_options) ::WinRM::Connection.new(options.merge(opts)).tap do |conn| conn.logger = logger end end end # String representation of object, reporting its connection details and # configuration. # # @api private def to_s "<#{options.inspect}>" end end private WINRM_SPEC_VERSION = ["~> 2.0"].freeze WINRM_FS_SPEC_VERSION = ["~> 1.0"].freeze WINRM_ELEVATED_SPEC_VERSION = ["~> 1.0"].freeze # Builds the hash of options needed by the Connection object on # construction. # # @param data [Hash] merged configuration and mutable state data # @return [Hash] hash of connection options # @api private def connection_options(data) endpoint = URI::Generic.build( scheme: data.fetch(:scheme), host: data.fetch(:hostname), port: data.fetch(:port), path: "/wsman" ).to_s elevated_password = data[:password] elevated_password = data[:elevated_password] if data.key?(:elevated_password) opts = { instance_name: instance.name, kitchen_root: data[:kitchen_root], logger: logger, endpoint: endpoint, user: data[:username], password: data[:password], rdp_port: data[:rdp_port], connection_retries: data[:connection_retries], connection_retry_sleep: data[:connection_retry_sleep], max_wait_until_ready: data[:max_wait_until_ready], transport: data[:winrm_transport], elevated: data[:elevated], elevated_username: data[:elevated_username] || data[:username], elevated_password: elevated_password, } opts.merge!(additional_transport_args(opts[:transport])) opts end def additional_transport_args(transport_type) case transport_type.to_sym when :ssl, :negotiate { no_ssl_peer_verification: true, disable_sspi: false, basic_auth_only: false, } when :plaintext { disable_sspi: true, basic_auth_only: true, } else {} end end # Creates a new WinRM Connection instance and save it for potential # future reuse. # # @param options [Hash] conneciton options # @return [Ssh::Connection] a WinRM Connection instance # @api private def create_new_connection(options, &block) if @connection logger.debug("[WinRM] shutting previous connection #{@connection}") @connection.close end @connection_options = options @connection = Kitchen::Transport::Winrm::Connection.new(options, &block) end # (see Base#load_needed_dependencies!) def load_needed_dependencies! super load_with_rescue!("winrm", WINRM_SPEC_VERSION.dup) load_with_rescue!("winrm-fs", WINRM_FS_SPEC_VERSION.dup) load_with_rescue!("winrm-elevated", WINRM_ELEVATED_SPEC_VERSION.dup) if config[:elevated] end def load_with_rescue!(gem_name, spec_version) logger.debug("#{gem_name} requested," \ " loading #{gem_name} gem (#{spec_version})") attempt_load = false gem gem_name, spec_version silence_warnings { attempt_load = require gem_name } if attempt_load logger.debug("#{gem_name} is loaded.") else logger.debug("#{gem_name} was already loaded.") end rescue LoadError => e message = fail_to_load_gem_message(gem_name, spec_version) logger.fatal(message) raise UserError, "Could not load or activate #{gem_name}. (#{e.message})" end def fail_to_load_gem_message(name, version = nil) version_cmd = "--version '#{version}'" if version version_file = "', '#{version}" "The `#{name}` gem is missing and must" \ " be installed or cannot be properly activated. Run" \ " `gem install #{name} #{version_cmd}`" \ " or add the following to your Gemfile if you are using Bundler:" \ " `gem '#{name} #{version_file}'`." end def host_os_windows? case RbConfig::CONFIG["host_os"] when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ true else false end end # Return the last saved WinRM connection instance. # # @return [Winrm::Connection] a WinRM Connection instance # @api private def reuse_connection logger.debug("[WinRM] reusing existing connection #{@connection}") yield @connection if block_given? @connection end def silence_warnings old_verbose = $VERBOSE $VERBOSE = nil yield ensure $VERBOSE = old_verbose end end end end test-kitchen-1.23.2/lib/kitchen/transport/ssh.rb0000644000004100000410000005000713377651062021616 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen" require "fileutils" require "net/ssh" require "net/ssh/gateway" require "net/ssh/proxy/http" require "net/scp" require "timeout" require "benchmark" module Kitchen module Transport # Wrapped exception for any internally raised SSH-related errors. # # @author Fletcher Nichol class SshFailed < TransportFailed; end # A Transport which uses the SSH protocol to execute commands and transfer # files. # # @author Fletcher Nichol class Ssh < Kitchen::Transport::Base kitchen_transport_api_version 1 plugin_version Kitchen::VERSION default_config :port, 22 default_config :username, "root" default_config :keepalive, true default_config :keepalive_interval, 60 # needs to be one less than the configured sshd_config MaxSessions default_config :max_ssh_sessions, 9 default_config :connection_timeout, 15 default_config :connection_retries, 5 default_config :connection_retry_sleep, 1 default_config :max_wait_until_ready, 600 default_config :ssh_gateway, nil default_config :ssh_gateway_port, 22 default_config :ssh_gateway_username, nil default_config :ssh_http_proxy, nil default_config :ssh_http_proxy_port, nil default_config :ssh_http_proxy_user, nil default_config :ssh_http_proxy_password, nil default_config :ssh_key, nil expand_path_for :ssh_key # compression disabled by default for speed default_config :compression, false required_config :compression default_config :compression_level do |transport| transport[:compression] == false ? 0 : 6 end def finalize_config!(instance) super # zlib was never a valid value and breaks in net-ssh >= 2.10 # TODO: remove these backwards compatiable casts in 2.0 case config[:compression] when "zlib" config[:compression] = "zlib@openssh.com" when "none" config[:compression] = false end self end # (see Base#connection) def connection(state, &block) options = connection_options(config.to_hash.merge(state)) if @connection && @connection_options == options reuse_connection(&block) else create_new_connection(options, &block) end end # (see Base#cleanup!) def cleanup! if @connection logger.debug("[SSH] shutting previous connection #{@connection}") @connection.close @connection = @connection_options = nil end end # A Connection instance can be generated and re-generated, given new # connection details such as connection port, hostname, credentials, etc. # This object is responsible for carrying out the actions on the remote # host such as executing commands, transferring files, etc. # # @author Fletcher Nichol class Connection < Kitchen::Transport::Base::Connection # (see Base::Connection#initialize) def initialize(config = {}) super(config) @session = nil end # (see Base::Connection#close) def close return if @session.nil? logger.debug("[SSH] closing connection to #{self}") session.close ensure @session = nil end # (see Base::Connection#execute) def execute(command) return if command.nil? logger.debug("[SSH] #{self} (#{command})") exit_code = execute_with_exit_code(command) if exit_code != 0 raise Transport::SshFailed.new( "SSH exited (#{exit_code}) for command: [#{command}]", exit_code ) end rescue Net::SSH::Exception => ex raise SshFailed, "SSH command failed (#{ex.message})" end # (see Base::Connection#login_command) def login_command args = %w{ -o UserKnownHostsFile=/dev/null } args += %w{ -o StrictHostKeyChecking=no } args += %w{ -o IdentitiesOnly=yes } if options[:keys] args += %W{ -o LogLevel=#{logger.debug? ? 'VERBOSE' : 'ERROR'} } if options.key?(:forward_agent) args += %W{ -o ForwardAgent=#{options[:forward_agent] ? 'yes' : 'no'} } end if ssh_gateway gateway_command = "ssh -q #{ssh_gateway_username}@#{ssh_gateway} nc #{hostname} #{port}" args += %W{ -o ProxyCommand=#{gateway_command} -p #{ssh_gateway_port} } end Array(options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key} } } args += %W{ -p #{port} } args += %W{ #{username}@#{hostname} } LoginCommand.new("ssh", args) end # (see Base::Connection#upload) def upload(locals, remote) logger.debug("TIMING: scp async upload (Kitchen::Transport::Ssh)") elapsed = Benchmark.measure do waits = [] Array(locals).map do |local| opts = File.directory?(local) ? { recursive: true } : {} waits.push session.scp.upload(local, remote, opts) do |_ch, name, sent, total| logger.debug("Async Uploaded #{name} (#{total} bytes)") if sent == total end waits.shift.wait while waits.length >= max_ssh_sessions end waits.each(&:wait) end delta = Util.duration(elapsed.real) logger.debug("TIMING: scp async upload (Kitchen::Transport::Ssh) took #{delta}") rescue Net::SSH::Exception => ex raise SshFailed, "SCP upload failed (#{ex.message})" end # (see Base::Connection#download) def download(remotes, local) # ensure the parent dir of the local target exists FileUtils.mkdir_p(File.dirname(local)) Array(remotes).each do |file| begin logger.debug("Attempting to download '#{file}' as file") session.scp.download!(file, local) rescue Net::SCP::Error begin logger.debug("Attempting to download '#{file}' as directory") session.scp.download!(file, local, recursive: true) rescue Net::SCP::Error logger.warn( "SCP download failed for file or directory '#{file}', perhaps it does not exist?" ) end end end rescue Net::SSH::Exception => ex raise SshFailed, "SCP download failed (#{ex.message})" end # (see Base::Connection#wait_until_ready) def wait_until_ready delay = 3 session( retries: max_wait_until_ready / delay, delay: delay, message: "Waiting for SSH service on #{hostname}:#{port}, " \ "retrying in #{delay} seconds" ) execute(PING_COMMAND.dup) end private PING_COMMAND = "echo '[SSH] Established'".freeze RESCUE_EXCEPTIONS_ON_ESTABLISH = [ Errno::EACCES, Errno::EALREADY, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EPIPE, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout, Timeout::Error ].freeze # @return [Integer] cap on number of parallel ssh sessions we can use # @api private attr_reader :max_ssh_sessions # @return [Integer] how many times to retry when failing to execute # a command or transfer files # @api private attr_reader :connection_retries # @return [Float] how many seconds to wait before attempting a retry # when failing to execute a command or transfer files # @api private attr_reader :connection_retry_sleep # @return [String] the hostname or IP address of the remote SSH host # @api private attr_reader :hostname # @return [Integer] how many times to retry when invoking # `#wait_until_ready` before failing # @api private attr_reader :max_wait_until_ready # @return [String] the username to use when connecting to the remote # SSH host # @api private attr_reader :username # @return [Integer] the TCP port number to use when connecting to the # remote SSH host # @api private attr_reader :port # @return [String] The ssh gateway to use when connecting to the # remote SSH host # @api private attr_reader :ssh_gateway # @return [String] The username to use when using an ssh gateway # @api private attr_reader :ssh_gateway_username # @return [Integer] The port to use when using an ssh gateway # @api private attr_reader :ssh_gateway_port # @return [String] The kitchen ssh proxy to use when connecting to the # remote SSH host via http proxy # @api private attr_reader :ssh_http_proxy # @return [Integer] The port to use when using an kitchen ssh proxy # remote SSH host via http proxy # @api private attr_reader :ssh_http_proxy_port # @return [String] The username to use when using an kitchen ssh proxy # remote SSH host via http proxy # @api private attr_reader :ssh_http_proxy_user # @return [String] The password to use when using an kitchen ssh proxy # remote SSH host via http proxy # @api private attr_reader :ssh_http_proxy_password # Establish an SSH session on the remote host using a gateway host. # # @param opts [Hash] retry options # @option opts [Integer] :retries the number of times to retry before # failing # @option opts [Float] :delay the number of seconds to wait until # attempting a retry # @option opts [String] :message an optional message to be logged on # debug (overriding the default) when a rescuable exception is raised # @return [Net::SSH::Connection::Session] the SSH connection session # @api private def establish_connection_via_gateway(opts) retry_connection(opts) do gateway_options = options.merge(:port => ssh_gateway_port) Net::SSH::Gateway.new(ssh_gateway, ssh_gateway_username, gateway_options).ssh(hostname, username, options) end end # Establish an SSH session on the remote host. # # @param opts [Hash] retry options # @option opts [Integer] :retries the number of times to retry before # failing # @option opts [Float] :delay the number of seconds to wait until # attempting a retry # @option opts [String] :message an optional message to be logged on # debug (overriding the default) when a rescuable exception is raised # @return [Net::SSH::Connection::Session] the SSH connection session # @api private def establish_connection(opts) retry_connection(opts) do Net::SSH.start(hostname, username, options) end end # Connecto to a host executing passed block and properly handling retreis. # # @param opts [Hash] retry options # @option opts [Integer] :retries the number of times to retry before # failing # @option opts [Float] :delay the number of seconds to wait until # attempting a retry # @option opts [String] :message an optional message to be logged on # debug (overriding the default) when a rescuable exception is raised # @return [Net::SSH::Connection::Session] the SSH connection session # @api private def retry_connection(opts) log_msg = "[SSH] opening connection to #{self}" log_msg += " via #{ssh_gateway_username}@#{ssh_gateway}:#{ssh_gateway_port}" if ssh_gateway logger.debug(log_msg) yield rescue *RESCUE_EXCEPTIONS_ON_ESTABLISH => e if (opts[:retries] -= 1) > 0 message = if opts[:message] logger.debug("[SSH] connection failed (#{e.inspect})") opts[:message] else "[SSH] connection failed, retrying in #{opts[:delay]} seconds " \ "(#{e.inspect})" end logger.info(message) sleep(opts[:delay]) retry else logger.warn("[SSH] connection failed, terminating (#{e.inspect})") raise SshFailed, "SSH session could not be established" end end # Execute a remote command over SSH and return the command's exit code. # # @param command [String] command string to execute # @return [Integer] the exit code of the command # @api private def execute_with_exit_code(command) exit_code = nil session.open_channel do |channel| channel.request_pty channel.exec(command) do |_ch, _success| channel.on_data do |_ch, data| logger << data end channel.on_extended_data do |_ch, _type, data| logger << data end channel.on_request("exit-status") do |_ch, data| exit_code = data.read_long end end end session.loop exit_code end # (see Base::Connection#init_options) def init_options(options) super @username = @options.delete(:username) @hostname = @options.delete(:hostname) @port = @options[:port] # don't delete from options @connection_retries = @options.delete(:connection_retries) @connection_retry_sleep = @options.delete(:connection_retry_sleep) @max_ssh_sessions = @options.delete(:max_ssh_sessions) @max_wait_until_ready = @options.delete(:max_wait_until_ready) @ssh_gateway = @options.delete(:ssh_gateway) @ssh_gateway_username = @options.delete(:ssh_gateway_username) @ssh_gateway_port = @options.delete(:ssh_gateway_port) @ssh_http_proxy = @options.delete(:ssh_http_proxy) @ssh_http_proxy_user = @options.delete(:ssh_http_proxy_user) @ssh_http_proxy_password = @options.delete(:ssh_http_proxy_password) @ssh_http_proxy_port = @options.delete(:ssh_http_proxy_port) end # Returns a connection session, or establishes one when invoked the # first time. # # @param retry_options [Hash] retry options for the initial connection # @return [Net::SSH::Connection::Session] the SSH connection session # @api private def session(retry_options = {}) if ssh_gateway @session ||= establish_connection_via_gateway({ retries: connection_retries.to_i, delay: connection_retry_sleep.to_i, }.merge(retry_options)) else @session ||= establish_connection({ retries: connection_retries.to_i, delay: connection_retry_sleep.to_i, }.merge(retry_options)) end end # String representation of object, reporting its connection details and # configuration. # # @api private def to_s "#{username}@#{hostname}<#{options.inspect}>" end end private # Builds the hash of options needed by the Connection object on # construction. # # @param data [Hash] merged configuration and mutable state data # @return [Hash] hash of connection options # @api private # rubocop:disable Metrics/MethodLength, Metrics/AbcSize def connection_options(data) opts = { logger: logger, user_known_hosts_file: "/dev/null", hostname: data[:hostname], port: data[:port], username: data[:username], compression: data[:compression], compression_level: data[:compression_level], keepalive: data[:keepalive], keepalive_interval: data[:keepalive_interval], timeout: data[:connection_timeout], connection_retries: data[:connection_retries], connection_retry_sleep: data[:connection_retry_sleep], max_ssh_sessions: data[:max_ssh_sessions], max_wait_until_ready: data[:max_wait_until_ready], ssh_gateway: data[:ssh_gateway], ssh_gateway_username: data[:ssh_gateway_username], ssh_gateway_port: data[:ssh_gateway_port], } if data[:ssh_key] && !data[:password] opts[:keys_only] = true opts[:keys] = Array(data[:ssh_key]) opts[:auth_methods] = ["publickey"] end if data[:ssh_http_proxy] options_http_proxy = {} options_http_proxy[:user] = data[:ssh_http_proxy_user] options_http_proxy[:password] = data[:ssh_http_proxy_password] opts[:proxy] = Net::SSH::Proxy::HTTP.new(data[:ssh_http_proxy], data[:ssh_http_proxy_port], options_http_proxy) end if data[:ssh_key_only] opts[:auth_methods] = ["publickey"] end opts[:password] = data[:password] if data.key?(:password) opts[:forward_agent] = data[:forward_agent] if data.key?(:forward_agent) opts[:verbose] = data[:verbose].to_sym if data.key?(:verbose) # disable host key verification. The hash key to use # depends on the version of net-ssh in use. opts[verify_host_key_option] = false opts end # # Returns the correct host-key-verification option key to use depending # on what version of net-ssh is in use. In net-ssh <= 4.1, the supported # parameter is `paranoid` but in 4.2, it became `verify_host_key` # # `verify_host_key` does not work in <= 4.1, and `paranoid` throws # deprecation warnings in >= 4.2. # # While the "right thing" to do would be to pin train's dependency on # net-ssh to ~> 4.2, this will prevent InSpec from being used in # Chef v12 because of it pinning to a v3 of net-ssh. # def verify_host_key_option current_net_ssh = Net::SSH::Version::CURRENT new_option_version = Net::SSH::Version[4, 2, 0] current_net_ssh >= new_option_version ? :verify_host_key : :paranoid end # Creates a new SSH Connection instance and save it for potential future # reuse. # # @param options [Hash] conneciton options # @return [Ssh::Connection] an SSH Connection instance # @api private def create_new_connection(options, &block) cleanup! @connection_options = options @connection = Kitchen::Transport::Ssh::Connection.new(options, &block) end # Return the last saved SSH connection instance. # # @return [Ssh::Connection] an SSH Connection instance # @api private def reuse_connection logger.debug("[SSH] reusing existing connection #{@connection}") yield @connection if block_given? @connection end end end end test-kitchen-1.23.2/lib/kitchen/transport/dummy.rb0000644000004100000410000000473313377651062022161 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Salim Afiune () # # Copyright (C) 2013, Salim Afiune # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen" module Kitchen module Transport # Dummy transport for Kitchen. This transport does nothing but report what would # happen if this transport did anything of consequence. As a result it may # be a useful transport to use when debugging or developing new features or # plugins. class Dummy < Kitchen::Transport::Base kitchen_transport_api_version 1 plugin_version Kitchen::VERSION default_config :sleep, 1 default_config :random_exit_code, 0 def connection(state, &block) options = config.to_hash.merge(state) Kitchen::Transport::Dummy::Connection.new(options, &block) end # TODO: comment class Connection < Kitchen::Transport::Base::Connection # (see Base#execute) def execute(command) report(:execute, command) if options[:random_exit_code] != 0 info("Dummy exited (#{exit_code}) for command: [#{command}]") end end def upload(locals, remote) report(:upload, "#{locals.inspect} => #{remote}") end def download(remotes, local) report(:download, "#{remotes.inspect} => #{local}") end private # Report what action is taking place, sleeping if so configured, and # possibly fail randomly. # # @param action [Symbol] the action currently taking place # @param state [Hash] the state hash # @api private def report(action, msg = "") what = action.capitalize info("[Dummy] #{what} #{msg} on Transport=Dummy") sleep_if_set debug("[Dummy] #{what} #{msg} completed (#{options[:sleep]}s).") end def sleep_if_set sleep(options[:sleep].to_f) if options[:sleep].to_f > 0.0 end end end end end test-kitchen-1.23.2/lib/kitchen/provisioner.rb0000644000004100000410000000362413377651062021347 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor/util" module Kitchen # A provisioner is responsible for generating the commands necessary to # install set up and use a configuration management tool such as Chef and # Puppet. # # @author Fletcher Nichol module Provisioner # Default provisioner to use DEFAULT_PLUGIN = "chef_solo".freeze # Returns an instance of a provisioner given a plugin type string. # # @param plugin [String] a provisioner plugin type, to be constantized # @param config [Hash] a configuration hash to initialize the provisioner # @return [Provisioner::Base] a provisioner instance # @raise [ClientError] if a provisioner instance could not be created def self.for_plugin(plugin, config) first_load = require("kitchen/provisioner/#{plugin}") str_const = Thor::Util.camel_case(plugin) klass = const_get(str_const) object = klass.new(config) object.verify_dependencies if first_load object rescue LoadError, NameError raise ClientError, "Could not load the '#{plugin}' provisioner from the load path." \ " Please ensure that your provisioner is installed as a gem or" \ " included in your Gemfile if using Bundler." end end end test-kitchen-1.23.2/lib/kitchen/command.rb0000644000004100000410000001504713377651062020410 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thread" module Kitchen module Command # Base class for CLI commands. # # @author Fletcher Nichol class Base include Logging # Contstructs a new Command object. # # @param cmd_args [Array] remainder of the arguments from processed ARGV # @param cmd_options [Hash] hash of Thor options # @param options [Hash] configuration options # @option options [String] :action action to take, usually corresponding # to the subcommand name (default: `nil`) # @option options [proc] :help a callable that displays help for the # command # @option options [Config] :config a Config object (default: `nil`) # @option options [Loader] :loader a Loader object (default: `nil`) # @option options [String] :shell a Thor shell object def initialize(cmd_args, cmd_options, options = {}) @args = cmd_args @options = cmd_options @action = options.fetch(:action, nil) @help = options.fetch(:help, -> { "No help provided" }) @config = options.fetch(:config, nil) @loader = options.fetch(:loader, nil) @shell = options.fetch(:shell) end private # @return [Array] remainder of the arguments from processed ARGV # @api private attr_reader :args # @return [Hash] hash of Thor options # @api private attr_reader :options # @return [proc] a callable that displays help for the command # @api private attr_reader :help # @return [Config] a Config object # @api private attr_reader :config # @return [Thor::Shell] a Thor shell object # @api private attr_reader :shell # @return [String] the action to perform # @api private attr_reader :action # Emit an error message, display contextual help and then exit with a # non-zero exit code. # # **Note** This method calls exit and will not return. # # @param msg [String] error message # @api private def die(msg) error "\n#{msg}\n\n" help.call exit 1 end # @return [Array] an array of instances # @raise [SystemExit] if no instances are returned # @api private def all_instances result = @config.instances if result.empty? die "No instances defined" else result end end # Return an array on instances whos name matches the regular expression. # # @param regexp [Regexp] a regular expression matching on instance names # @return [Array] an array of instances # @raise [SystemExit] if no instances are returned or the regular # expression is invalid # @api private def filtered_instances(regexp) result = begin @config.instances.get(regexp) || @config.instances.get_all(/#{regexp}/) rescue RegexpError => e die "Invalid Ruby regular expression, " \ "you may need to single quote the argument. " \ "Please try again or consult http://rubular.com/ (#{e.message})" end result = Array(result) if result.empty? die "No instances for regex `#{regexp}', try running `kitchen list'" else result end end # @return [Logger] the common logger # @api private def logger Kitchen.logger end # Return an array on instances whos name matches the regular expression, # the full instance name, or the `"all"` literal. # # @param arg [String] an instance name, a regular expression, the literal # `"all"`, or `nil` # @return [Array] an array of instances # @api private def parse_subcommand(arg = nil) arg == "all" ? all_instances : filtered_instances(arg) end end # Common module to execute a Kitchen action such as create, converge, etc. # # @author Fletcher Nichol module RunAction # Run an instance action (create, converge, setup, verify, destroy) on # a collection of instances. The instance actions will take place in a # seperate thread of execution which may or may not be running # concurrently. # # @param action [String] action to perform # @param instances [Array] an array of instances def run_action(action, instances, *args) concurrency = concurrency_setting(instances) queue = Queue.new instances.each { |i| queue << i } concurrency.times { queue << nil } threads = [] @action_errors = [] concurrency.times do threads << Thread.new do while (instance = queue.pop) run_action_in_thread(action, instance, *args) end end end threads.map(&:join) report_errors end # private def report_errors unless @action_errors.empty? msg = ["#{@action_errors.length} actions failed.", @action_errors.map { |e| ">>>>>> #{e.message}" }].join("\n") raise ActionFailed.new(msg, @action_errors) end end def concurrency_setting(instances) concurrency = 1 if options[:concurrency] concurrency = options[:concurrency] || instances.size concurrency = instances.size if concurrency > instances.size end concurrency end def run_action_in_thread(action, instance, *args) instance.public_send(action, *args) rescue Kitchen::InstanceFailure => e @action_errors << e rescue Kitchen::ActionFailed => e new_error = Kitchen::ActionFailed.new("#{e.message} on #{instance.name}") new_error.set_backtrace(e.backtrace) @action_errors << new_error ensure instance.cleanup! end end end end test-kitchen-1.23.2/lib/kitchen/ssh.rb0000644000004100000410000002127613377651062017570 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "logger" require "net/ssh" require "net/scp" require "socket" require "kitchen/errors" require "kitchen/login_command" module Kitchen # Wrapped exception for any internally raised SSH-related errors. # # @author Fletcher Nichol class SSHFailed < TransientFailure; end # Class to help establish SSH connections, issue remote commands, and # transfer files between a local system and remote node. # # @author Fletcher Nichol class SSH # Constructs a new SSH object. # # @example basic usage # # ssh = Kitchen::SSH.new("remote.example.com", "root") # ssh.exec("sudo apt-get update") # ssh.upload!("/tmp/data.txt", "/var/lib/data.txt") # ssh.shutdown # # @example block usage # # Kitchen::SSH.new("remote.example.com", "root") do |ssh| # ssh.exec("sudo apt-get update") # ssh.upload!("/tmp/data.txt", "/var/lib/data.txt") # end # # @param hostname [String] the remote hostname (IP address, FQDN, etc.) # @param username [String] the username for the remote host # @param options [Hash] configuration options # @option options [Logger] :logger the logger to use # (default: `::Logger.new(STDOUT)`) # @yield [self] if a block is given then the constructed object yields # itself and calls `#shutdown` at the end, closing the remote connection def initialize(hostname, username, options = {}) @hostname = hostname @username = username @options = options.dup @logger = @options.delete(:logger) || ::Logger.new(STDOUT) if block_given? yield self shutdown end end # Execute a command on the remote host. # # @param cmd [String] command string to execute # @raise [SSHFailed] if the command does not exit with a 0 code def exec(cmd) logger.debug("[SSH] #{self} (#{cmd})") exit_code = exec_with_exit(cmd) if exit_code != 0 raise SSHFailed, "SSH exited (#{exit_code}) for command: [#{cmd}]" end end # Uploads a local file to remote host. # # @param local [String] path to local file # @param remote [String] path to remote file destination # @param options [Hash] configuration options that are passed to # `Net::SCP.upload` # @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload def upload!(local, remote, options = {}, &progress) if progress.nil? progress = lambda do |_ch, name, sent, total| logger.debug("Uploaded #{name} (#{total} bytes)") if sent == total end end session.scp.upload!(local, remote, options, &progress) end def upload(local, remote, options = {}, &progress) if progress.nil? progress = lambda do |_ch, name, sent, total| if sent == total logger.debug("Async Uploaded #{name} (#{total} bytes)") end end end session.scp.upload(local, remote, options, &progress) end # Uploads a recursive directory to remote host. # # @param local [String] path to local file or directory # @param remote [String] path to remote file destination # @param options [Hash] configuration options that are passed to # `Net::SCP.upload` # @option options [true,false] :recursive recursive copy (default: `true`) # @see http://net-ssh.github.io/net-scp/classes/Net/SCP.html#method-i-upload def upload_path!(local, remote, options = {}, &progress) options = { recursive: true }.merge(options) upload!(local, remote, options, &progress) end def upload_path(local, remote, options = {}, &progress) options = { recursive: true }.merge(options) upload(local, remote, options, &progress) end # Shuts down the session connection, if it is still active. def shutdown return if @session.nil? logger.debug("[SSH] closing connection to #{self}") session.shutdown! ensure @session = nil end # Blocks until the remote host's SSH TCP port is listening. def wait logger.info("Waiting for #{hostname}:#{port}...") until test_ssh end # Builds a LoginCommand which can be used to open an interactive session # on the remote host. # # @return [LoginCommand] the login command def login_command args = %w{ -o UserKnownHostsFile=/dev/null } args += %w{ -o StrictHostKeyChecking=no } args += %w{ -o IdentitiesOnly=yes } if options[:keys] args += %W{ -o LogLevel=#{logger.debug? ? 'VERBOSE' : 'ERROR'} } if options.key?(:forward_agent) args += %W{ -o ForwardAgent=#{options[:forward_agent] ? 'yes' : 'no'} } end Array(options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key} } } args += %W{ -p #{port} } args += %W{ #{username}@#{hostname} } LoginCommand.new("ssh", args) end private # TCP socket exceptions SOCKET_EXCEPTIONS = [ SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError ].freeze # @return [String] the remote hostname # @api private attr_reader :hostname # @return [String] the username for the remote host # @api private attr_reader :username # @return [Hash] SSH options, passed to `Net::SSH.start` attr_reader :options # @return [Logger] the logger to use # @api private attr_reader :logger # Builds the Net::SSH session connection or returns the existing one if # built. # # @return [Net::SSH::Connection::Session] the SSH connection session # @api private def session @session ||= establish_connection end # Establish a connection session to the remote host. # # @return [Net::SSH::Connection::Session] the SSH connection session # @api private def establish_connection rescue_exceptions = [ Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Net::SSH::Disconnect, Net::SSH::AuthenticationFailed, Net::SSH::ConnectionTimeout ] retries = options[:ssh_retries] || 3 begin logger.debug("[SSH] opening connection to #{self}") Net::SSH.start(hostname, username, options) rescue *rescue_exceptions => e retries -= 1 if retries > 0 logger.info("[SSH] connection failed, retrying (#{e.inspect})") sleep options[:ssh_timeout] || 1 retry else logger.warn("[SSH] connection failed, terminating (#{e.inspect})") raise end end end # String representation of object, reporting its connection details and # configuration. # # @api private def to_s "#{username}@#{hostname}:#{port}<#{options.inspect}>" end # @return [Integer] SSH port (default: 22) # @api private def port options.fetch(:port, 22) end # Execute a remote command and return the command's exit code. # # @param cmd [String] command string to execute # @return [Integer] the exit code of the command # @api private def exec_with_exit(cmd) exit_code = nil session.open_channel do |channel| channel.request_pty channel.exec(cmd) do |_ch, _success| channel.on_data do |_ch, data| logger << data end channel.on_extended_data do |_ch, _type, data| logger << data end channel.on_request("exit-status") do |_ch, data| exit_code = data.read_long end end end session.loop exit_code end # Test a remote TCP socket (presumably SSH) for connectivity. # # @return [true,false] a truthy value if the socket is ready and false # otherwise # @api private def test_ssh socket = TCPSocket.new(hostname, port) IO.select([socket], nil, nil, 5) rescue *SOCKET_EXCEPTIONS sleep options[:ssh_timeout] || 2 false rescue Errno::EPERM, Errno::ETIMEDOUT false ensure socket && socket.close end end end test-kitchen-1.23.2/lib/kitchen/configurable.rb0000644000004100000410000005105213377651062021426 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor/util" require "kitchen/lazy_hash" module Kitchen # A mixin for providing configuration-related behavior such as default # config (static, computed, inherited), required config, local path # expansion, etc. # # @author Fletcher Nichol module Configurable def self.included(base) base.extend(ClassMethods) end # @return [Kitchen::Instance] the associated instance attr_reader :instance # A lifecycle method that should be invoked when the object is about ready # to be used. A reference to an Instance is required as configuration # dependant data may be access through an Instance. This also acts as a # hook point where the object may wish to perform other last minute # checks, validations, or configuration expansions. # # @param instance [Instance] an associated instance # @return [self] itself, for use in chaining # @raise [ClientError] if instance parameter is nil def finalize_config!(instance) raise ClientError, "Instance must be provided to #{self}" if instance.nil? @instance = instance expand_paths! deprecate_config! validate_config! load_needed_dependencies! self end # Provides hash-like access to configuration keys. # # @param attr [Object] configuration key # @return [Object] value at configuration key def [](attr) config[attr] end # @return [TrueClass,FalseClass] true if `:shell_type` is `"bourne"` (or # unset, for backwards compatability) def bourne_shell? ["bourne", nil].include?(instance.platform.shell_type) end # Find an appropriate path to a file or directory, based on graceful # fallback rules or returns nil if path cannot be determined. # # Given an instance with suite named `"server"`, a `test_base_path` of # `"/a/b"`, and a path segement of `"roles"` then following will be tried # in order (first match that exists wins): # # 1. /a/b/server/roles # 2. /a/b/roles # 3. $PWD/roles # # @param path [String] the base path segment to search for # @param opts [Hash] options # @option opts [Symbol] :type either `:file` or `:directory` (default) # @option opts [Symbol] :base_path a custom base path to search under, # default uses value from `config[:test_base_path]` # @return [String] path to the existing file or directory, or nil if file # or directory was not found # @raise [UserError] if `config[:test_base_path]` is used and is not set def calculate_path(path, opts = {}) type = opts.fetch(:type, :directory) base = opts.fetch(:base_path) do config.fetch(:test_base_path) do |key| raise UserError, "#{key} is not found in #{self}" end end [ File.join(base, instance.suite.name, path), File.join(base, path), File.join(Dir.pwd, path), ].find do |candidate| type == :directory ? File.directory?(candidate) : File.file?(candidate) end end # Returns an array of configuration keys. # # @return [Array] array of configuration keys def config_keys config.keys end # Returns a Hash of configuration and other useful diagnostic information. # # @return [Hash] a diagnostic hash def diagnose result = {} config_keys.sort.each { |k| result[k] = config[k] } result end # Returns a Hash of configuration and other useful diagnostic information # associated with the plugin itself (such as loaded version, class name, # etc.). # # @return [Hash] a diagnostic hash def diagnose_plugin result = {} result[:name] = name result.merge!(self.class.diagnose) result end # Returns the name of this plugin, suitable for display in a CLI. # # @return [String] name of this plugin def name self.class.name.split("::").last end # @return [TrueClass,FalseClass] true if `:shell_type` is `"powershell"` def powershell_shell? ["powershell"].include?(instance.platform.shell_type) end # Builds a file path based on the `:os_type` (`"windows"` or `"unix"`). # # @return [String] joined path for instance's os_type def remote_path_join(*parts) path = File.join(*parts) windows_os? ? path.tr("/", '\\') : path.tr('\\', "/") end # @return [TrueClass,FalseClass] true if `:os_type` is `"unix"` (or # unset, for backwards compatibility) def unix_os? ["unix", nil].include?(instance.platform.os_type) end # Performs whatever tests that may be required to ensure that this plugin # will be able to function in the current environment. This may involve # checking for the presence of certain directories, software installed, # etc. # # @raise [UserError] if the plugin will not be able to perform or if a # documented dependency is missing from the system def verify_dependencies # this method may be left unimplemented if that is applicable end # @return [TrueClass,FalseClass] true if `:os_type` is `"windows"` def windows_os? ["windows"].include?(instance.platform.os_type) end private # @return [LzayHash] a configuration hash # @api private attr_reader :config # @return [Hash] a hash of the detected deprecated config attributes # @api private attr_reader :deprecated_config # @return [Hash] user provided configuration hash # @api private attr_reader :provided_config # Initializes an internal configuration hash. The hash may contain # callable blocks as values that are meant to be called lazily. This # method is intended to be included in an object's .initialize method. # # @param config [Hash] initial provided configuration # @api private def init_config(config) @config = LazyHash.new(config, self) @provided_config = config.dup self.class.defaults.each do |attr, value| @config[attr] = value unless @config.key?(attr) end end # Expands file paths for certain configuration values. A configuration # value is marked for file expansion with a expand_path_for declaration # in the included class. # # @api private def expand_paths! root_path = config[:kitchen_root] || Dir.pwd expanded_paths = LazyHash.new(self.class.expanded_paths, self).to_hash expanded_paths.each do |key, should_expand| next if !should_expand || config[key].nil? || config[key] == false config[key] = if config[key].is_a?(Array) config[key].map { |path| File.expand_path(path, root_path) } else File.expand_path(config[key], root_path) end end end # Initialize detected deprecated configuration hash. # Display warning if deprecations have been detected. # # @api private def deprecate_config! # We only want to display the deprecation list of config values once per execution on the default config. # This prevents the output of deprecations from being printed for each permutation of test suites. return if defined? @@has_been_warned_of_deprecations deprecated_attributes = LazyHash.new(self.class.deprecated_attributes, self) # Remove items from hash when not provided in the loaded config or when the rendered message is nil @deprecated_config = deprecated_attributes.delete_if { |attr, obj| !provided_config.key?(attr) || obj.nil? } if !deprecated_config.empty? warning = Util.outdent!(<<-MSG) Deprecated configuration detected: #{deprecated_config.keys.join("\n")} Run 'kitchen doctor' for details. MSG warn(warning) # Set global var that the deprecation message has been printed @@has_been_warned_of_deprecations = true end end # Loads any required third party Ruby libraries or runs any shell out # commands to prepare the plugin. This method will be called in the # context of the main thread of execution and so does not necessarily # have to be thread safe. # # **Note:** any subclasses overriding this method would be well advised # to call super when overriding this method, for example: # # @example overriding `#load_needed_dependencies!` # # class MyProvisioner < Kitchen::Provisioner::Base # def load_needed_dependencies! # super # # any further work # end # end # # @raise [ClientError] if any library loading fails or any of the # dependency requirements cannot be satisfied # @api private def load_needed_dependencies! # this method may be left unimplemented if that is applicable end # @return [Logger] the instance's logger or Test Kitchen's common logger # otherwise # @api private def logger instance ? instance.logger : Kitchen.logger end # @return [String] a powershell command to reload the `PATH` environment # variable, only to be used to support old Omnibus Chef packages that # require `PATH` to find the `ruby.exe` binary # @api private def reload_ps1_path [ "$env:PATH = try {", "[System.Environment]::GetEnvironmentVariable('PATH','Machine')", "} catch { $env:PATH }\n\n", ].join("\n") end # Builds a shell environment variable assignment string for the # required shell type. # # @param name [String] variable name # @param value [String] variable value # @return [String] shell variable assignment # @api private def shell_env_var(name, value) if powershell_shell? shell_var("env:#{name}", value) else "#{shell_var(name, value)}; export #{name}" end end # Builds a shell variable assignment string for the required shell type. # # @param name [String] variable name # @param value [String] variable value # @return [String] shell variable assignment # @api private def shell_var(name, value) if powershell_shell? %{$#{name} = "#{value}"} else %{#{name}="#{value}"} end end # Runs all validations set up for the included class. Each validation is # for a specific configuration attribute and has an associated callable # block. Each validation block is called with the attribute, its value, # and the included object for context. # # @api private def validate_config! self.class.validations.each do |attr, block| block.call(attr, config[attr], self) end end # Wraps a body of shell code with common context appropriate for the type # of shell. # # @param code [String] the shell code to be wrapped # @return [String] wrapped shell code # @api private def wrap_shell_code(code) return env_wrapped(code) if powershell_shell? Util.wrap_command((env_wrapped code)) end def env_wrapped(code) code_parts = resolve_proxy_settings_from_config code_parts << shell_env_var("TEST_KITCHEN", 1) code_parts << shell_env_var("CI", ENV["CI"]) if ENV["CI"] code_parts << code code_parts.join("\n") end def proxy_setting_keys [:http_proxy, :https_proxy, :ftp_proxy, :no_proxy] end def resolve_proxy_settings_from_config proxy_setting_keys.each_with_object([]) do |protocol, set_env| if !config.key?(protocol) || config[protocol].nil? export_proxy(set_env, protocol) elsif proxy_config_setting_present?(protocol) set_env << shell_env_var(protocol.downcase.to_s, config[protocol]) set_env << shell_env_var(protocol.upcase.to_s, config[protocol]) end end end def proxy_from_config? proxy_setting_keys.any? do |protocol| !config[protocol].nil? end end def proxy_from_environment? proxy_setting_keys.any? do |protocol| !ENV[protocol.downcase.to_s].nil? || !ENV[protocol.upcase.to_s].nil? end end def proxy_config_setting_present?(protocol) config.key?(protocol) && !config[protocol].nil? && !config[protocol].empty? end # Helper method to export # # @param env [Array] the environment to modify # @param code [String] the type of proxy to export, one of 'http', 'https' or 'ftp' # @api private def export_proxy(env, type) env << shell_env_var(type.to_s, ENV[type.to_s]) if ENV[type.to_s] env << shell_env_var(type.upcase.to_s, ENV[type.upcase.to_s]) if ENV[type.upcase.to_s] end # Class methods which will be mixed in on inclusion of Configurable module. module ClassMethods # Sets the loaded version of this plugin, usually corresponding to the # RubyGems version of the plugin's library. If the plugin does not set # this value, then `nil` will be used and reported. # # @example setting a version used by RubyGems # # require "kitchen/driver/vagrant_version" # # module Kitchen # module Driver # class Vagrant < Kitchen::Driver::Base # # plugin_version Kitchen::Driver::VAGRANT_VERSION # # end # end # end # # @param version [String] a version string def plugin_version(version) # rubocop:disable Style/TrivialAccessors @plugin_version = version end # Returns a Hash of configuration and other useful diagnostic # information. # # @return [Hash] a diagnostic hash def diagnose { class: name, version: @plugin_version ||= nil, api_version: @api_version ||= nil, } end # Sets a sane default value for a configuration attribute. These values # can be overridden by provided configuration or in a subclass with # another default_config declaration. # # @example a nil default value # # default_config :i_am_nil # # @example a primitive default value # # default_config :use_sudo, true # # @example a block to compute a default value # # default_config :box_name do |subject| # subject.instance.platform.name # end # # @param attr [String] configuration attribute name # @param value [Object, nil] static default value for attribute # @yieldparam object [Object] a reference to the instantiated object # @yieldreturn [Object, nil] dynamically computed value for the attribute def default_config(attr, value = nil, &block) defaults[attr] = block_given? ? block : value end # Ensures that an attribute which is a path will be fully expanded at # the right time. This helps make the configuration unambiguous and much # easier to debug and diagnose. # # Note that the file path expansion is only intended for paths on the # local workstation invking the Test Kitchen code. # # @example the default usage # # expand_path_for :data_path # # @example disabling path expansion with a falsey value # # expand_path_for :relative_path, false # # @example using a block to determine whether or not to expand # # expand_path_for :relative_or_not_path do |subject| # subject.instance.name =~ /default/ # end # # @param attr [String] configuration attribute name # @param value [Object, nil] whether or not to exand the file path # @yieldparam object [Object] a reference to the instantiated object # @yieldreturn [Object, nil] dynamically compute whether or not to # perform the file expansion def expand_path_for(attr, value = true, &block) expanded_paths[attr] = block_given? ? block : value end # Set the appropriate deprecation message for a given attribute name # # @example the default usage # # deprecate_config_for :attribute_name, "Detailed deprecation message." # # @example using a block # # deprecate_config_for :attribute_name do |subject| # "Detailed deprecation message." if subject == true # end # # @param attr [String] configuration attribute name # @param value [Object, nil] static default value for attribute # @yieldparam object [Object] a reference to the instantiated object # @yieldreturn [Object, nil] dynamically computed value for the attribute def deprecate_config_for(attr, value = nil, &block) deprecated_attributes[attr] = block_given? ? block : value end # Ensures that an attribute must have a non-nil, non-empty String value. # The default behavior will be to raise a user error and thereby halting # further configuration processing. Good use cases for require_config # might be cloud provider credential keys and other similar data. # # @example a value that must not be nil or an empty String # # required_config :cloud_api_token # # @example using a block to use custom validation logic # # required_config :email do |attr, value, subject| # raise UserError, "Must be an email address" unless value =~ /@/ # end # # @param attr [String] configuration attribute name # @yieldparam attr [Symbol] the attribute name # @yieldparam value [Object] the current value of the attribute # @yieldparam object [Object] a reference to the instantiated object def required_config(attr, &block) unless block_given? klass = self block = lambda do |_, value, thing| if value.nil? || value.to_s.empty? attribute = "#{klass}#{thing.instance.to_str}#config[:#{attr}]" raise UserError, "#{attribute} cannot be blank" end end end validations[attr] = block end # @return [Hash] a hash of attribute keys and default values which has # been merged with any superclass defaults # @api private def defaults @defaults ||= {}.merge(super_defaults) end # @return [Hash] a hash of defaults from the included class' superclass # if defined in the superclass, or an empty hash otherwise # @api private def super_defaults if superclass.respond_to?(:defaults) superclass.defaults else {} end end # @return [Hash] a hash of attribute keys and truthy/falsey values to # determine if said attribute needs to be fully file path expanded, # which has been merged with any superclass expanded paths # @api private def expanded_paths @expanded_paths ||= {}.merge(super_expanded_paths) end # @return [Hash] a hash of expanded paths from the included class' # superclass if defined in the superclass, or an empty hash otherwise # @api private def super_expanded_paths if superclass.respond_to?(:expanded_paths) superclass.expanded_paths else {} end end def deprecated_attributes @deprecated_attributes ||= {}.merge(super_deprecated_attributes) end def super_deprecated_attributes if superclass.respond_to?(:deprecated_attributes) superclass.deprecated_attributes else {} end end # @return [Hash] a hash of attribute keys and valudation callable blocks # which has been merged with any superclass valudations # @api private def validations @validations ||= {}.merge(super_validations) end # @return [Hash] a hash of validations from the included class' # superclass if defined in the superclass, or an empty hash otherwise # @api private def super_validations if superclass.respond_to?(:validations) superclass.validations else {} end end end end end test-kitchen-1.23.2/lib/kitchen/rake_tasks.rb0000644000004100000410000000450413377651062021115 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "rake/tasklib" require "kitchen" module Kitchen # Kitchen Rake task generator. # # @author Fletcher Nichol class RakeTasks < ::Rake::TaskLib # Creates Kitchen Rake tasks and allows the callee to configure it. # # @yield [self] gives itself to the block def initialize(cfg = {}) @loader = Kitchen::Loader::YAML.new( project_config: ENV["KITCHEN_YAML"], local_config: ENV["KITCHEN_LOCAL_YAML"], global_config: ENV["KITCHEN_GLOBAL_YAML"] ) @config = Kitchen::Config.new( { loader: @loader }.merge(cfg) ) Kitchen.logger = Kitchen.default_file_logger(nil, false) yield self if block_given? define end # @return [Config] a Kitchen::Config attr_reader :config private # Generates a test Rake task for each instance and one to test all # instances in serial. # # @api private def define namespace "kitchen" do kitchen_commands = %w{create converge setup verify destroy} config.instances.each do |instance| desc "Run #{instance.name} test instance" task instance.name do instance.test(:always) end kitchen_commands.each do |cmd| namespace cmd do task instance.name do instance.send(cmd) end desc "Run all #{cmd} instances" task "all" => config.instances.map(&:name) end end end desc "Run all test instances" task "all" => config.instances.map(&:name) kitchen_commands.each { |cmd| task cmd => "#{cmd}:all" } end end end end test-kitchen-1.23.2/lib/kitchen/lazy_hash.rb0000644000004100000410000001106213377651062020745 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "delegate" module Kitchen # A modifed Hash object that may contain callables as a value which must be # executed in the context of another object. This allows for delayed # evaluation of a hash value while still looking and largely feeling like a # normal Ruby Hash. # # @example normal hash accessing with regular values # # data = { # :symbol => true, # "string" => "stuff" # } # context = "any object" # lazy = Kitchen::Hash.new(data, context) # # lazy[:symbol] # => true # lazy.fetch("string") # => "stuff" # # @example hash with callable blocks as values # # data = { # :lambda => ->(c) { c.length }, # :proc => Proc.new { |c| c.reverse }, # :simple => "value" # } # context = "any object" # lazy = Kitchen::Hash.new(data, context) # # lazy[:lambda] # => 10 # lazy.fetch(:proc) # => "tcejbo yna" # lazy[:simple] # => "value" # # @author Fletcher Nichol class LazyHash < SimpleDelegator include Enumerable # Creates a new LazyHash using a Hash-like object to populate itself and # an object that can be used as context in value-callable blocks. The # context object can be used to compute values for keys at the time of # fetching the value. # # @param obj [Hash, Object] a hash-like object # @param context [Object] an object that can be used to compute values def initialize(obj, context) @context = context super(obj) end # Retrieves the rendered value object corresponding to the key object. If # not found, returns the default value. # # @param key [Object] hash key # @return [Object, nil] the value for key or the default value if key is # not found def [](key) proc_or_val(__getobj__[key]) end # Returns a rendered value from the hash for the given key. If the key # can't be found, there are several options: With no other arguments, it # will raise an KeyError exception; if default is given, then that will be # returned; if the optional code block is specified, then that will be run # and its result returned. # # @param key [Object] hash key # @param default [Object] default value if key is not set (optional) # @return [Object, nil] the value for the key or the default value if key # is not found # @raise [KeyError] if the key is not found def fetch(key, default = :__undefined__, &block) case default when :__undefined__ proc_or_val(__getobj__.fetch(key, &block)) else proc_or_val(__getobj__.fetch(key, default, &block)) end end # Returns a new Hash with all keys and rendered values of the LazyHash. # # @return [Hash] a new hash def to_hash hash = {} __getobj__.each_key { |key| hash[key] = self[key] } hash end # Yields each key/value pair to the provided block. Returns a new # Hash with only the keys and rendered values for which the block # returns true. # # @return [Hash] a new hash def select(&block) to_hash.select(&block) end # If no block provided, returns an enumerator over the keys and # rendered values in the underlying object. If a block is # provided, calls the block once for each [key, rendered_value] # pair in the underlying object. # # @return [Enumerator, Array] def each(&block) to_hash.each(&block) end # Returns a new Hash after deleting the key-value pairs for which the block # returns true. # # @return [Hash] a new hash def delete_if(&block) to_hash.delete_if(&block) end private # Returns an object or invokes call with context if object is callable. # # @return [Object] an object # @api private def proc_or_val(thing) if thing.respond_to?(:call) thing.call(@context) else thing end end end end test-kitchen-1.23.2/lib/kitchen/cli.rb0000644000004100000410000003261313377651062017537 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor" require "kitchen" require "kitchen/generator/init" module Kitchen # The command line runner for Kitchen. # # @author Fletcher Nichol class CLI < Thor # Common module to load and invoke a CLI-implementation agnostic command. module PerformCommand # Perform a CLI subcommand. # # @param task [String] action to take, usually corresponding to the # subcommand name # @param command [String] command class to create and invoke] # @param args [Array] remainder arguments from processed ARGV # (default: `nil`) # @param additional_options [Hash] additional configuration needed to # set up the command class (default: `{}`) def perform(task, command, args = nil, additional_options = {}) require "kitchen/command/#{command}" command_options = { action: task, help: -> { help(task) }, config: @config, shell: shell, }.merge(additional_options) str_const = Thor::Util.camel_case(command) klass = ::Kitchen::Command.const_get(str_const) klass.new(args, options, command_options).call end end include Logging include PerformCommand # The maximum number of concurrent instances that can run--which is a bit # high MAX_CONCURRENCY = 9999 attr_reader :config # Constructs a new instance. def initialize(*args) super $stdout.sync = true @loader = Kitchen::Loader::YAML.new( project_config: ENV["KITCHEN_YAML"] || ENV["KITCHEN_YML"], local_config: ENV["KITCHEN_LOCAL_YAML"] || ENV["KITCHEN_LOCAL_YML"], global_config: ENV["KITCHEN_GLOBAL_YAML"] || ENV["KITCHEN_GLOBAL_YML"] ) @config = Kitchen::Config.new( loader: @loader ) @config.log_level = Kitchen.env_log unless Kitchen.env_log.nil? @config.log_overwrite = Kitchen.env_log_overwrite unless Kitchen.env_log_overwrite.nil? end # Sets the logging method_options # @api private def self.log_options method_option :log_level, aliases: "-l", desc: "Set the log level (debug, info, warn, error, fatal)" method_option :log_overwrite, desc: "Set to false to prevent log overwriting each time Test Kitchen runs", type: :boolean method_option :color, type: :boolean, lazy_default: $stdout.tty?, desc: "Toggle color output for STDOUT logger" end # Sets the test_base_path method_options # @api private def self.test_base_path method_option :test_base_path, aliases: "-t", desc: "Set the base path of the tests" end desc "list [INSTANCE|REGEXP|all]", "Lists one or more instances" method_option :bare, aliases: "-b", type: :boolean, desc: "List the name of each instance only, one per line" method_option :json, aliases: "-j", type: :boolean, desc: "Print data as JSON" method_option :debug, aliases: "-d", type: :boolean, desc: "[Deprecated] Please use `kitchen diagnose'" log_options def list(*args) update_config! perform("list", "list", args) end map status: :list desc "diagnose [INSTANCE|REGEXP|all]", "Show computed diagnostic configuration" method_option :loader, type: :boolean, desc: "Include data loader diagnostics" method_option :plugins, type: :boolean, desc: "Include plugin diagnostics" method_option :instances, type: :boolean, default: true, desc: "Include instances diagnostics" method_option :all, type: :boolean, desc: "Include all diagnostics" log_options test_base_path def diagnose(*args) update_config! perform("diagnose", "diagnose", args, loader: @loader) end { create: "Change instance state to create. " \ "Start one or more instances", converge: "Change instance state to converge. " \ "Use a provisioner to configure one or more instances", setup: "Change instance state to setup. " \ "Prepare to run automated tests. " \ "Install busser and related gems on one or more instances", verify: "Change instance state to verify. " \ "Run automated tests on one or more instances", destroy: "Change instance state to destroy. " \ "Delete all information for one or more instances", }.each do |action, short_desc| desc( "#{action} [INSTANCE|REGEXP|all]", short_desc ) long_desc <<-DESC The instance states are in order: destroy, create, converge, setup, verify, destroy. Change one or more instances from the current state to the #{action} state. Actions for all intermediate states will be executed. See http://kitchen.ci for further explanation. DESC method_option :concurrency, aliases: "-c", type: :numeric, lazy_default: MAX_CONCURRENCY, desc: <<-DESC.gsub(/^\s+/, "").tr("\n", " ") Run a #{action} against all matching instances concurrently. Only N instances will run at the same time if a number is given. DESC method_option :parallel, aliases: "-p", type: :boolean, desc: <<-DESC.gsub(/^\s+/, "").tr("\n", " ") [Future DEPRECATION, use --concurrency] Run a #{action} against all matching instances concurrently. DESC if action == :converge || action == :verify method_option :debug, aliases: "-D", type: :boolean, default: false, desc: "Run the #{action} with debugging enabled." end test_base_path log_options define_method(action) do |*args| update_config! perform(action, "action", args) end end desc "test [INSTANCE|REGEXP|all]", "Test (destroy, create, converge, setup, verify and destroy) one or more instances" long_desc <<-DESC The instance states are in order: destroy, create, converge, setup, verify, destroy. Test changes the state of one or more instances to destroyed, then executes the actions for each state up to destroy. At any sign of failure, executing the actions stops and the instance is left in the last successful execution state. There are 3 post-verify modes for instance cleanup, triggered with the `--destroy' flag: * passing: instances passing verify will be destroyed afterwards.\n * always: instances will always be destroyed afterwards.\n * never: instances will never be destroyed afterwards. DESC method_option :concurrency, aliases: "-c", type: :numeric, lazy_default: MAX_CONCURRENCY, desc: <<-DESC.gsub(/^\s+/, "").tr("\n", " ") Run a test against all matching instances concurrently. Only N instances will run at the same time if a number is given. DESC method_option :parallel, aliases: "-p", type: :boolean, desc: <<-DESC.gsub(/^\s+/, "").tr("\n", " ") [Future DEPRECATION, use --concurrency] Run a test against all matching instances concurrently. DESC method_option :destroy, aliases: "-d", default: "passing", desc: "Destroy strategy to use after testing (passing, always, never)." method_option :auto_init, type: :boolean, default: false, desc: "Invoke init command if .kitchen.yml is missing" method_option :debug, aliases: "-D", type: :boolean, default: false, desc: "Run the converge and verify with debugging enabled." test_base_path log_options def test(*args) update_config! ensure_initialized perform("test", "test", args) end desc "login INSTANCE|REGEXP", "Log in to one instance" log_options def login(*args) update_config! perform("login", "login", args) end desc "package INSTANCE|REGEXP", "package an instance" log_options def package(*args) update_config! perform("package", "package", args) end desc "doctor INSTANCE|REGEXP", "Check for common system problems" log_options method_option :all, aliases: "-a", desc: "Check all instances" def doctor(*args) update_config! perform("doctor", "doctor", args) end desc "exec INSTANCE|REGEXP -c REMOTE_COMMAND", "Execute command on one or more instance" method_option :command, aliases: "-c", desc: "execute via ssh" log_options def exec(*args) update_config! perform("exec", "exec", args) end desc "version", "Print Kitchen's version information" def version puts "Test Kitchen version #{Kitchen::VERSION}" end map %w{-v --version} => :version desc "sink", "Show the Kitchen sink!", hide: true def sink perform("sink", "sink") end desc "console", "Kitchen Console!" def console perform("console", "console") end register Kitchen::Generator::Init, "init", "init", "Adds some configuration to your cookbook so Kitchen can rock" long_desc <<-D, for: "init" Init will add Test Kitchen support to an existing project for convergence integration testing. A default .kitchen.yml file (which is intended to be customized) is created in the project's root directory and one or more gems will be added to the project's Gemfile. D tasks["init"].options = Kitchen::Generator::Init.class_options class << self private # Ensure the any failing commands exit non-zero. # # @return [true] you die always on failure # @api private def exit_on_failure? true end end private # @return [Logger] the common logger # @api private def logger Kitchen.logger end # Update and finalize options for logging, concurrency, and other concerns. # # @api private def update_config! @config.log_level = log_level if log_level unless options[:log_overwrite].nil? @config.log_overwrite = options[:log_overwrite] end @config.colorize = options[:color] unless options[:color].nil? if options[:test_base_path] # ensure we have an absolute path @config.test_base_path = File.absolute_path(options[:test_base_path]) end @config.debug = options[:debug] # Now that we have required configs, lets create our file logger Kitchen.logger = Kitchen.default_file_logger( log_level, options[:log_overwrite] ) update_parallel! end # Validate the log level from the config / CLI options, defaulting # to :info if the supplied level is empty or invalid # # @api private def log_level return unless options[:log_level] return @log_level if @log_level level = options[:log_level].downcase.to_sym unless valid_log_level?(level) level = :info banner "WARNING - invalid log level specified: " \ "\"#{options[:log_level]}\" - reverting to :info log level." end @log_level = level end # Check to whether a provided log level is valid # # @api private def valid_log_level?(level) !Util.to_logger_level(level).nil? end # Set parallel concurrency options for Thor # # @api private def update_parallel! if options[:parallel] # warn here in a future release when option is used @options = Thor::CoreExt::HashWithIndifferentAccess.new(options.to_hash) if options[:parallel] && !options[:concurrency] options[:concurrency] = MAX_CONCURRENCY end options.delete(:parallel) options.freeze end end # If auto_init option is active, invoke the init generator. # # @api private def ensure_initialized yaml = ENV["KITCHEN_YAML"] || ENV["KITCHEN_YML"] || ".kitchen.yml" if options[:auto_init] && !File.exist?(yaml) banner "Invoking init as '#{yaml}' file is missing" invoke "init" end end end end test-kitchen-1.23.2/lib/kitchen/metadata_chopper.rb0000644000004100000410000000346513377651062022273 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # A rather insane and questionable class to quickly consume a metadata.rb # file and return the cookbook name and version attributes. # # @see https://twitter.com/fnichol/status/281650077901144064 # @see https://gist.github.com/4343327 class MetadataChopper < Hash # Return an Array containing the cookbook name and version attributes, # or nil values if they could not be parsed. # # @param metadata_file [String] path to a metadata.rb file # @return [Array] array containing the cookbook name and version # attributes or nil values if they could not be determined def self.extract(metadata_file) mc = new(File.expand_path(metadata_file)) [mc[:name], mc[:version]] end # Creates a new instances and loads in the contents of the metdata.rb # file. If you value your life, you may want to avoid reading the # implementation. # # @param metadata_file [String] path to a metadata.rb file def initialize(metadata_file) instance_eval(IO.read(metadata_file), metadata_file) end def method_missing(meth, *args, &_block) self[meth] = args.first end end end test-kitchen-1.23.2/lib/kitchen/thor_tasks.rb0000644000004100000410000000343313377651062021147 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "thor" require "kitchen" module Kitchen # Kitchen Thor task generator. # # @author Fletcher Nichol class ThorTasks < Thor namespace :kitchen # Creates Kitchen Thor tasks and allows the callee to configure it. # # @yield [self] gives itself to the block def initialize(*args) super @config = Kitchen::Config.new Kitchen.logger = Kitchen.default_file_logger(nil, false) yield self if block_given? define end private # @return [Config] a Kitchen::Config attr_reader :config # Generates a test Thor task for each instance and one to test all # instances in serial. # # @api private def define config.instances.each do |instance| self.class.desc instance.name, "Run #{instance.name} test instance" self.class.send(:define_method, instance.name.tr("-", "_")) do instance.test(:always) end end self.class.desc "all", "Run all test instances" self.class.send(:define_method, :all) do config.instances.each { |i| invoke i.name.tr("-", "_") } end end end end test-kitchen-1.23.2/lib/kitchen/loader/0000755000004100000410000000000013377651062017704 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/loader/yaml.rb0000644000004100000410000003206013377651062021174 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "erb" require "vendor/hash_recursive_merge" require "yaml" module Kitchen module Loader # YAML file loader for Test Kitchen configuration. This class is # responisble for parsing the main YAML file and the local YAML if it # exists. Local file configuration will win over the default configuration. # The client of this class should not require any YAML loading or parsing # logic. # # @author Fletcher Nichol class YAML # Creates a new loader that can parse and load YAML files. # # @param options [Hash] configuration for a new loader # @option options [String] :project_config path to the Kitchen # config YAML file (default: `./kitchen.yml`) # @option options [String] :local_config path to the Kitchen local # config YAML file (default: `./kitchen.local.yml`) # @option options [String] :global_config path to the Kitchen global # config YAML file (default: `$HOME/.kitchen/config.yml`) # @option options [String] :process_erb whether or not to process YAML # through an ERB processor (default: `true`) # @option options [String] :process_local whether or not to process a # local kitchen YAML file, if it exists (default: `true`) def initialize(options = {}) @config_file = File.expand_path(options[:project_config] || default_config_file) @local_config_file = File.expand_path(options[:local_config] || default_local_config_file) @global_config_file = File.expand_path(options[:global_config] || default_global_config_file) @process_erb = options.fetch(:process_erb, true) @process_local = options.fetch(:process_local, true) @process_global = options.fetch(:process_global, true) end # Reads, parses, and merges YAML configuration files and returns a Hash # of tne merged data. # # @return [Hash] merged configuration data def read unless File.exist?(config_file) raise UserError, "Kitchen YAML file #{config_file} does not exist." end Util.symbolized_hash(combined_hash) end # Returns a Hash of configuration and other useful diagnostic information. # # @return [Hash] a diagnostic hash def diagnose result = {} result[:process_erb] = @process_erb result[:process_local] = @process_local result[:process_global] = @process_global result[:global_config] = diagnose_component(:global_yaml, global_config_file) result[:project_config] = diagnose_component(:yaml, config_file) result[:local_config] = diagnose_component(:local_yaml, local_config_file) result[:combined_config] = diagnose_component(:combined_hash) result end private # @return [String] the absolute path to the Kitchen config YAML file # @api private attr_reader :config_file # @return [String] the absolute path to the Kitchen local config YAML # file # @api private attr_reader :local_config_file # @return [String] the absolute path to the Kitchen global config YAML # file # @api private attr_reader :global_config_file # Performed a prioritized recursive merge of several source Hashes and # returns a new merged Hash. There are 3 sources of configuration data: # # 1. local config # 2. project config # 3. global config # # The merge order is local -> project -> global, meaning that elements at # the top of the above list will be merged last, and have greater # precedence than elements at the bottom of the list. # # @return [Hash] a new merged Hash # @api private def combined_hash y = if @process_global normalize(global_yaml).rmerge(normalize(yaml)) else normalize(yaml) end @process_local ? y.rmerge(normalize(local_yaml)) : y end # Loads and returns the Kitchen config YAML as a Hash. # # @return [Hash] the config hash # @api private def yaml parse_yaml_string(yaml_string(config_file), config_file) end # Loads and returns the Kitchen local config YAML as a Hash. # # @return [Hash] the config hash # @api private def local_yaml parse_yaml_string(yaml_string(local_config_file), local_config_file) end # Loads and returns the Kitchen global config YAML as a Hash. # # @return [Hash] the config hash # @api private def global_yaml parse_yaml_string(yaml_string(global_config_file), global_config_file) end # Loads a file to a string and optionally passes it through an ERb # process. # # @return [String] a file's contents as a string # @api private def yaml_string(file) string = read_file(file) @process_erb ? process_erb(string, file) : string end # Passes a string through ERb to evaulate any ERb blocks. # # @param string [String] the string to process # @param file [String] an absolute path to the file represented as the # passed in string, used for error reporting # @return [String] a new string, passed through an ERb process # @raise [UserError] if an ERb parsing error occurs # @api private def process_erb(string, file) tpl = ERB.new(string) tpl.filename = file tpl.result rescue => e raise UserError, "Error parsing ERB content in #{file} " \ "(#{e.class}: #{e.message}).\n" \ "Please run `kitchen diagnose --no-instances --loader' to help " \ "debug your issue." end # Reads a file and returns its contents as a string. # # @param file [String] a path to a file # @return [String] the files contents, or an empty string if the file # does not exist # @api private def read_file(file) File.exist?(file.to_s) ? IO.read(file) : "" end # Determines the default absolute path to the Kitchen config YAML file, # based on current working directory. We prefer `kitchen.yml` to the # older `.kitchen.yml`. # # @return [String] an absolute path to a Kitchen config YAML file # @api private def default_config_file if File.exist?(kitchen_yml) && File.exist?(dot_kitchen_yml) raise UserError, "Both #{kitchen_yml} and #{dot_kitchen_yml} found. Please use the un-dotted variant: #{kitchen_yml}." end File.exist?(kitchen_yml) ? kitchen_yml : dot_kitchen_yml end # The absolute path to an un-hidden Kitchen config YAML file. def kitchen_yml File.join(Dir.pwd, "kitchen.yml") end # The absolute path to an hidden Kitchen config YAML file. def dot_kitchen_yml File.join(Dir.pwd, ".kitchen.yml") end # Determines the default absolute path to the Kitchen local YAML file, # based on the base Kitchen config YAML file. # @return [String] an absolute path to a Kitchen local YAML file # @raise [UserError] if both dotted and undotted versions of the default # local YAML file exist, e.g. both kitchen.local.yml and .kitchen.local.yml # @api private def default_local_config_file config_dir, default_local_config = File.split(config_file.sub(/(#{File.extname(config_file)})$/, '.local\1')) undot_config = default_local_config.sub(/^\./, "") dot_config = ".#{undot_config}" if File.exist?(File.join(config_dir, undot_config)) && File.exist?(File.join(config_dir, dot_config)) raise UserError, "Both #{undot_config} and #{dot_config} found in #{config_dir}. Please use #{default_local_config} which matches your #{config_file}." end File.join(config_dir, default_local_config) end # Determines the default absolute path to the Kitchen global YAML file, # based on the base Kitchen config YAML file. # # @return [String] an absolute path to a Kitchen global YAML file # @api private def default_global_config_file File.join(File.expand_path(ENV["HOME"]), ".kitchen", "config.yml") end # Generate a diganose Hash for a particular YAML file Hash. If an error # occurs when loading the data, then a failure hash will be inserted # into the `:raw_data` sub-hash. # # @param component [Symbol] a YAML source component # @param file [String] the absolute path to a file which is used for # reporting (default: `nil`) # @return [Hash] a hash data structure # @api private def diagnose_component(component, file = nil) return if file && !File.exist?(file) hash = begin send(component) rescue => e failure_hash(e, file) end { filename: file, raw_data: hash } end # Generates a Hash respresenting a failure, given an Exception object. # # @param e [Exception] an exception # @param file [String] the absolute path to a file (default: `nil`) # @return [Hash] a hash data structure # @api private def failure_hash(e, file = nil) result = { error: { exception: e.inspect, message: e.message, backtrace: e.backtrace, }, } result[:error][:raw_file] = IO.read(file) unless file.nil? result end # Destructively modify an object containing one or more hashes so that # the resulting formatted data can be consumed upstream. # # @param obj [Object] an object # @return [Object] an object # @api private def normalize(obj) if obj.is_a?(Hash) obj.inject({}) { |h, (k, v)| normalize_hash(h, k, v); h } else obj end end # Normalizes certain keys in the root of a data hash to be a proper # sub-hash in all cases. Specifically handled are the following cases: # # * If the value for certain keys (`"driver"`, `"provisioner"`, # `"busser"`) are set to `nil`, a new Hash will be put in its place. # * If the value for certain keys is a String, then the value is # converted to a new Hash with a default key pointing to the original # String. # # Given a hash: # # { "driver" => nil } # # this method would return: # # { "driver" => {} } # # Given a hash: # # { :driver => "coolbeans" } # # this method would return: # # { :name => { "driver" => "coolbeans" } } # # # @param hash [Hash] the Hash to normalize # @param key [Symbol] the key to normalize # @param value [Object] the value to normalize # @api private def normalize_hash(hash, key, value) case key when "driver", "provisioner", "busser" hash[key] = if value.nil? {} elsif value.is_a?(String) default_key = key == "busser" ? "version" : "name" { default_key => value } else normalize(value) end else hash[key] = normalize(value) end end # Parses a YAML string and returns a Hash. # # @param string [String] a yaml document as a string # @param file_name [String] an absolute path to the file represented as # the passed in string, used for error reporting # @return [Hash] a hash # @raise [UserError] if the string document cannot be parsed # @api private def parse_yaml_string(string, file_name) return {} if string.nil? || string.empty? result = ::YAML.safe_load(string, [Symbol], [], true) || {} unless result.is_a?(Hash) raise UserError, "Error parsing #{file_name} as YAML " \ "(Result of parse was not a Hash, but was a #{result.class}).\n" \ "Please run `kitchen diagnose --no-instances --loader' to help " \ "debug your issue." end result rescue SyntaxError, Psych::SyntaxError, Psych::DisallowedClass raise UserError, "Error parsing #{file_name} as YAML.\n" \ "Please run `kitchen diagnose --no-instances --loader' to help " \ "debug your issue." end end end end test-kitchen-1.23.2/lib/kitchen/diagnostic.rb0000644000004100000410000001007313377651062021110 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/util" require "kitchen/version" module Kitchen # Combines and compiles diagnostic information about a Test Kitchen # configuration suitable for support and troubleshooting. # # @author Fletcher Nichol class Diagnostic # Constructs a new Diagnostic object with an optional loader and optional # instances array. # # @param options [Hash] optional configuration # @option options [#diagnose,Hash] :loader a loader instance that responds # to `#diagnose` or an error Hash # @option options [Array<#diagnose>,Hash] :instances an Array of instances # that respond to `#diagnose` or an error Hash # @option options [true,false] :plugins whether or not plugins should be # returned def initialize(options = {}) @loader = options.fetch(:loader, nil) @instances = options.fetch(:instances, []) @plugins = options.fetch(:plugins, false) @result = {} end # Returns a Hash with stringified keys containing diagnostic information. # # @return [Hash] a configuration Hash def read prepare_common prepare_plugins prepare_loader prepare_instances Util.stringified_hash(result) end private # @return [Hash] a result hash # @api private attr_reader :result # @return [#diagnose,Hash] a loader instance that responds to `#diagnose` # or an error Hash # @api private attr_reader :loader # @return [Array<#diagnose>,Hash] an Array of instances that respond to # `#diagnose` or an error Hash # @api private attr_reader :instances # Adds common information to the result Hash. # # @api private def prepare_common result[:timestamp] = Time.now.gmtime.to_s result[:kitchen_version] = Kitchen::VERSION end # Adds loader information to the result Hash. # # @api private def prepare_loader if error_hash?(loader) result[:loader] = loader else result[:loader] = loader.diagnose if loader end end # Adds plugin information to the result Hash. # # @api private def prepare_plugins return unless @plugins if error_hash?(instances) result[:plugins] = { error: instances[:error] } elsif instances.empty? result[:plugins] = {} else plugins = { driver: [], provisioner: [], transport: [], verifier: [] } instances.map(&:diagnose_plugins).each do |plugin_hash| plugin_hash.each { |type, plugin| plugins[type] << plugin } end plugins.each do |type, list| plugins[type] = Hash[list.uniq.map { |hash| [hash.delete(:name), hash] }] end result[:plugins] = plugins end end # Adds instance information to the result Hash. # # @api private def prepare_instances result[:instances] = {} if error_hash?(instances) result[:instances][:error] = instances[:error] else Array(instances).each { |i| result[:instances][i.name] = i.diagnose } end end # Determins whether or not the object is an error hash. An error hash is # defined as a Hash containing an `:error` key. # # @param obj [Object] an object # @return [true,false] whether or not the object is an error hash # @api private def error_hash?(obj) obj.is_a?(Hash) && obj.key?(:error) end end end test-kitchen-1.23.2/lib/kitchen/verifier/0000755000004100000410000000000013377651062020251 5ustar www-datawww-datatest-kitchen-1.23.2/lib/kitchen/verifier/busser.rb0000644000004100000410000002213413377651062022103 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "base64" require "digest" require "kitchen/verifier/base" module Kitchen module Verifier # Command string generator to interface with Busser. The commands that are # generated are safe to pass to an SSH command or as an unix command # argument (escaped in single quotes). # # @author Fletcher Nichol class Busser < Kitchen::Verifier::Base kitchen_verifier_api_version 1 plugin_version Kitchen::VERSION default_config :busser_bin do |verifier| verifier .remote_path_join(%W{#{verifier[:root_path]} bin busser}) .tap { |path| path.concat(".bat") if verifier.windows_os? } end default_config :ruby_bindir do |verifier| if verifier.windows_os? '$env:systemdrive\\opscode\\chef\\embedded\\bin' else verifier.remote_path_join(%W{#{verifier[:chef_omnibus_root]} embedded bin}) end end default_config :version, "busser" expand_path_for :test_base_path # Creates a new Busser object using the provided configuration data # which will be merged with any default configuration. # # @param config [Hash] provided driver configuration def initialize(config = {}) init_config(config) end # (see Base#create_sandbox) def create_sandbox super prepare_helpers prepare_suites end # (see Base#init_command) def init_command return if local_suite_files.empty? cmd = sudo(config[:busser_bin]).dup .tap { |str| str.insert(0, "& ") if powershell_shell? } prefix_command(wrap_shell_code(Util.outdent!(<<-CMD))) #{busser_env} #{cmd} suite cleanup CMD end # (see Base#install_command) def install_command return if local_suite_files.empty? vars = install_command_vars prefix_command(shell_code_from_file(vars, "busser_install_command")) end # (see Base#run_command) def run_command return if local_suite_files.empty? cmd = sudo(config[:busser_bin]).dup .tap { |str| str.insert(0, "& ") if powershell_shell? } prefix_command(wrap_shell_code(Util.outdent!(<<-CMD))) #{busser_env} #{cmd} test #{plugins.join(" ").gsub!("busser-", "")} CMD end # Legacy method stub for `#setup_cmd` which calls `#install_command`. # # @return [String] command string # @deprecated When backwards compatibility for old Busser methods is # removed, this method will no longer be available. Use # `#install_command` in its place. define_method(:setup_cmd) { install_command } # Legacy method stub for `#run_cmd` which calls `#run_command`. # # @return [String] command string # @deprecated When backwards compatibility for old Busser methods is # removed, this method will no longer be available. Use # `#run_command` in its place. define_method(:run_cmd) { run_command } # Legacy method stub for `#sync_cmd`. # # @deprecated When backwards compatibility for old Busser methods is # removed, this method will no longer be available. Use # `transport#upload` to transfer test files in its place. def sync_cmd warn("Legacy call to #sync_cmd cannot be preserved, meaning that " \ "test files will not be uploaded. " \ "Code that calls #sync_cmd can now use the transport#upload " \ "method to transfer files.") end private # Returns a command string that sets appropriate environment variables for # busser commands. # # @return [String] command string # @api private def busser_env root = config[:root_path] gem_home = gem_path = remote_path_join(root, "gems") gem_cache = remote_path_join(gem_home, "cache") [ shell_env_var("BUSSER_ROOT", root), shell_env_var("GEM_HOME", gem_home), shell_env_var("GEM_PATH", gem_path), shell_env_var("GEM_CACHE", gem_cache), ].join("\n") .tap { |str| str.insert(0, reload_ps1_path) if windows_os? } end # Determines whether or not a local workstation file exists under a # Chef-related directory. # # @return [truthy,falsey] whether or not a given file is some kind of # Chef-related file # @api private def chef_data_dir?(base, file) file =~ %r{^#{base}/(data|data_bags|environments|nodes|roles)/} end # Returns arguments to a `gem install` command, suitable to install the # Busser gem. # # @return [String] arguments string # @api private def gem_install_args gem, version = config[:version].split("@") if gem =~ /^\d+\.\d+\.\d+/ version = gem gem = "busser" end root = config[:root_path] gem_bin = remote_path_join(root, "bin") # We don't want the gems to be installed in the home directory, # this will force the bindir and the gem install location both # to be under /tmp/verifier args = gem args += " --version #{version}" if version args += " --no-rdoc --no-ri --no-format-executable -n #{gem_bin}" args += " --no-user-install" args end # Returns an Array of common helper filenames currently residing on the # local workstation. # # @return [Array] array of helper files # @api private def helper_files glob = File.join("helpers", "*/**/*") Util.safe_glob(config[:test_base_path], glob).reject { |f| File.directory?(f) } end def install_command_vars ruby = remote_path_join(config[:ruby_bindir], "ruby") .tap { |path| path.concat(".exe") if windows_os? } gem = remote_path_join(config[:ruby_bindir], "gem") [ busser_env, shell_var("ruby", ruby), shell_var("gem", gem), shell_var("version", config[:version]), shell_var("gem_install_args", gem_install_args), shell_var("busser", sudo(config[:busser_bin])), shell_var("plugins", plugins.join(" ")), ].join("\n") end # Returns an Array of test suite filenames for the related suite currently # residing on the local workstation. Any special provisioner-specific # directories (such as a Chef roles/ directory) are excluded. # # @return [Array] array of suite files # @api private def local_suite_files base = File.join(config[:test_base_path], config[:suite_name]) Util.safe_glob(base, "*/**/*").reject do |f| chef_data_dir?(base, f) || File.directory?(f) end end # Returns a uniquely sorted Array of Busser plugin gems that need to # be installed for the related suite. # # @return [Array] a lexically sorted, unique item array of Busser # plugin gem names # @api private def plugins non_suite_dirs = %w{data data_bags environments nodes roles} Util.list_directory(File.join(config[:test_base_path], config[:suite_name])).reject do |d| !File.directory?(d) || non_suite_dirs.include?(File.basename(d)) end.map { |d| "busser-#{File.basename(d)}" }.sort.uniq end # Copies all common testing helper files into the suites directory in # the sandbox. # # @api private def prepare_helpers base = File.join(config[:test_base_path], "helpers") helper_files.each do |src| dest = File.join(sandbox_suites_dir, src.sub("#{base}/", "")) FileUtils.mkdir_p(File.dirname(dest)) FileUtils.cp(src, dest, preserve: true) end end # Copies all test suite files into the suites directory in the sandbox. # # @api private def prepare_suites base = File.join(config[:test_base_path], config[:suite_name]) local_suite_files.each do |src| dest = File.join(sandbox_suites_dir, src.sub("#{base}/", "")) FileUtils.mkdir_p(File.dirname(dest)) FileUtils.cp(src, dest, preserve: true) end end # @return [String] path to suites directory under sandbox path # @api private def sandbox_suites_dir File.join(sandbox_path, "suites") end end end end test-kitchen-1.23.2/lib/kitchen/verifier/base.rb0000644000004100000410000002020513377651062021507 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/errors" require "kitchen/configurable" require "kitchen/logging" module Kitchen module Verifier # Base class for a verifier. # # @author Fletcher Nichol class Base include Configurable include Logging default_config :http_proxy, nil default_config :https_proxy, nil default_config :ftp_proxy, nil default_config :root_path do |verifier| verifier.windows_os? ? '$env:TEMP\\verifier' : "/tmp/verifier" end default_config :sudo do |verifier| verifier.windows_os? ? nil : true end default_config :chef_omnibus_root, "/opt/chef" default_config :sudo_command do |verifier| verifier.windows_os? ? nil : "sudo -E" end default_config :command_prefix, nil default_config(:suite_name) { |busser| busser.instance.suite.name } # Creates a new Verifier object using the provided configuration data # which will be merged with any default configuration. # # @param config [Hash] provided verifier configuration def initialize(config = {}) init_config(config) end # Runs the verifier on the instance. # # @param state [Hash] mutable instance state # @raise [ActionFailed] if the action could not be completed def call(state) create_sandbox sandbox_dirs = Util.list_directory(sandbox_path) instance.transport.connection(state) do |conn| conn.execute(install_command) conn.execute(init_command) info("Transferring files to #{instance.to_str}") conn.upload(sandbox_dirs, config[:root_path]) debug("Transfer complete") conn.execute(prepare_command) conn.execute(run_command) end rescue Kitchen::Transport::TransportFailed => ex raise ActionFailed, ex.message ensure cleanup_sandbox end # Check system and configuration for common errors. # # @param state [Hash] mutable instance state # @returns [Boolean] Return true if a problem is found. def doctor(state) false end # Deletes the sandbox path. Without calling this method, the sandbox path # will persist after the process terminates. In other words, cleanup is # explicit. This method is safe to call multiple times. def cleanup_sandbox return if sandbox_path.nil? debug("Cleaning up local sandbox in #{sandbox_path}") FileUtils.rmtree(sandbox_path) end # Creates a temporary directory on the local workstation into which # verifier related files and directories can be copied or created. The # contents of this directory will be copied over to the instance before # invoking the verifier's run command. After this method completes, it # is expected that the contents of the sandbox is complete and ready for # copy to the remote instance. # # **Note:** any subclasses would be well advised to call super first when # overriding this method, for example: # # @example overriding `#create_sandbox` # # class MyVerifier < Kitchen::Verifier::Base # def create_sandbox # super # # any further file copies, preparations, etc. # end # end def create_sandbox @sandbox_path = Dir.mktmpdir("#{instance.name}-sandbox-") File.chmod(0755, sandbox_path) info("Preparing files for transfer") debug("Creating local sandbox in #{sandbox_path}") end # Generates a command string which will install and configure the # verifier software on an instance. If no work is required, then `nil` # will be returned. # # @return [String] a command string def install_command end # Generates a command string which will perform any data initialization # or configuration required after the verifier software is installed # but before the sandbox has been transferred to the instance. If no work # is required, then `nil` will be returned. # # @return [String] a command string def init_command end # Generates a command string which will perform any commands or # configuration required just before the main verifier run command but # after the sandbox has been transferred to the instance. If no work is # required, then `nil` will be returned. # # @return [String] a command string def prepare_command end # Generates a command string which will invoke the main verifier # command on the prepared instance. If no work is required, then `nil` # will be returned. # # @return [String] a command string def run_command end # Returns the absolute path to the sandbox directory or raises an # exception if `#create_sandbox` has not yet been called. # # @return [String] the absolute path to the sandbox directory # @raise [ClientError] if the sandbox directory has no yet been created # by calling `#create_sandbox` def sandbox_path @sandbox_path ||= begin raise ClientError, "Sandbox directory has not yet " \ "been created. Please run #{self.class}#create_sandox before " \ "trying to access the path." end end # Sets the API version for this verifier. If the verifier does not set # this value, then `nil` will be used and reported. # # Sets the API version for this verifier # # @example setting an API version # # module Kitchen # module Verifier # class NewVerifier < Kitchen::Verifier::Base # # kitchen_verifier_api_version 2 # # end # end # end # # @param version [Integer,String] a version number # def self.kitchen_verifier_api_version(version) @api_version = version end private # Builds a complete command given a variables String preamble and a file # containing shell code. # # @param vars [String] shell variables, as a String # @param file [String] file basename (without extension) containing # shell code # @return [String] command # @api private def shell_code_from_file(vars, file) src_file = File.join( File.dirname(__FILE__), %w{.. .. .. support}, file + (powershell_shell? ? ".ps1" : ".sh") ) wrap_shell_code([vars, "", IO.read(src_file)].join("\n")) end # Conditionally prefixes a command with a sudo command. # # @param command [String] command to be prefixed # @return [String] the command, conditionaly prefixed with sudo # @api private def sudo(script) config[:sudo] ? "#{config[:sudo_command]} #{script}" : script end # Conditionally prefixes a command with a command prefix. # This should generally be done after a command has been # conditionally prefixed by #sudo as certain platforms, such as # Cisco Nexus, require all commands to be run with a prefix to # obtain outbound network access. # # @param command [String] command to be prefixed # @return [String] the command, conditionally prefixed with the configured prefix # @api private def prefix_command(script) config[:command_prefix] ? "#{config[:command_prefix]} #{script}" : script end end end end test-kitchen-1.23.2/lib/kitchen/verifier/shell.rb0000644000004100000410000000557513377651062021721 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: SAWANOBORI Yukihiko () # # Copyright (C) 2015, HiganWorks LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/verifier/base" module Kitchen module Verifier # Shell verifier for Kitchen. This verifier just execute shell command from local. # # @author SAWANOBORI Yukihiko () class Shell < Kitchen::Verifier::Base require "mixlib/shellout" kitchen_verifier_api_version 1 plugin_version Kitchen::VERSION default_config :sleep, 0 default_config :command, "true" default_config :shellout_opts, {} default_config :live_stream, $stdout default_config :remote_exec, false # (see Base#call) def call(state) info("[#{name}] Verify on instance #{instance.name} with state=#{state}") sleep_if_set merge_state_to_env(state) if config[:remote_exec] instance.transport.connection(state) do |conn| conn.execute(config[:command]) end else shellout end debug("[#{name}] Verify completed.") end # for legacy drivers. def run_command if config[:remote_exec] config[:command] else shellout nil end end private # Sleep for a period of time, if a value is set in the config. # # @api private def sleep_if_set config[:sleep].to_i.times do info(".") sleep 1 end end def shellout cmd = Mixlib::ShellOut.new(config[:command], config[:shellout_opts]) cmd.live_stream = config[:live_stream] cmd.run_command begin cmd.error! rescue Mixlib::ShellOut::ShellCommandFailed raise ActionFailed, "Action #verify failed for #{instance.to_str}." end end def merge_state_to_env(state) env_state = { environment: {} } env_state[:environment]["KITCHEN_INSTANCE"] = instance.name env_state[:environment]["KITCHEN_PLATFORM"] = instance.platform.name env_state[:environment]["KITCHEN_SUITE"] = instance.suite.name state.each_pair do |key, value| env_state[:environment]["KITCHEN_" + key.to_s.upcase] = value.to_s end config[:shellout_opts].merge!(env_state) end end end end test-kitchen-1.23.2/lib/kitchen/verifier/dummy.rb0000644000004100000410000000447413377651062021742 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2015, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "kitchen/verifier/base" module Kitchen module Verifier # Dummy verifier for Kitchen. This verifier does nothing but report what # would happen if this verifier did anything of consequence. As a result # it may be a useful verifier to use when debugging or developing new # features or plugins. # # @author Fletcher Nichol class Dummy < Kitchen::Verifier::Base kitchen_verifier_api_version 1 plugin_version Kitchen::VERSION default_config :sleep, 0 default_config :random_failure, false # (see Base#call) def call(state) info("[#{name}] Verify on instance=#{instance} with state=#{state}") sleep_if_set failure_if_set debug("[#{name}] Verify completed (#{config[:sleep]}s).") end private # Sleep for a period of time, if a value is set in the config. # # @api private def sleep_if_set sleep(config[:sleep].to_f) if config[:sleep].to_f > 0.0 end # Simulate a failure in an action, if set in the config. # # @api private def failure_if_set if config[:fail] debug("Failure for Verifier #{name}.") raise ActionFailed, "Action #verify failed for #{instance.to_str}." elsif config[:random_failure] && randomly_fail? debug("Random failure for Verifier #{name}.") raise ActionFailed, "Action #verify failed for #{instance.to_str}." end end # Determine whether or not to randomly fail. # # @return [true, false] # @api private def randomly_fail? [true, false].sample end end end end test-kitchen-1.23.2/lib/kitchen/logger.rb0000644000004100000410000003527713377651062020260 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "fileutils" require "logger" module Kitchen # Logging implementation for Kitchen. By default the console/stdout output # will be displayed differently than the file log output. Therefor, this # class wraps multiple loggers that conform to the stdlib `Logger` class # behavior. # # @author Fletcher Nichol class Logger include ::Logger::Severity # @return [IO] the log device attr_reader :logdev # @return [Boolean] whether logger is configured for # overwriting attr_reader :log_overwrite # Constructs a new logger. # # @param options [Hash] configuration for a new logger # @option options [Symbol] :color color to use when when outputting # messages # @option options [Integer] :level the logging severity threshold # (default: `Kitchen::DEFAULT_LOG_LEVEL`) # @option options [Boolean] whether to overwrite the log # when Test Kitchen runs. Only applies if the :logdev is a String. # (default: `Kitchen::DEFAULT_LOG_OVERWRITE`) # @option options [String,IO] :logdev filepath String or IO object to be # used for logging (default: `nil`) # @option options [String] :progname program name to include in log # messages (default: `"Kitchen"`) # @option options [IO] :stdout a standard out IO object to use # (default: `$stdout`) # @option options [Boolean] :colorize whether to colorize output # when Test Kitchen runs. # (default: `$stdout.tty?`) def initialize(options = {}) @log_overwrite = if options[:log_overwrite].nil? default_log_overwrite else options[:log_overwrite] end @logdev = logdev_logger(options[:logdev], log_overwrite) if options[:logdev] populate_loggers(options) # These setters cannot be called until @loggers are populated because # they are delegated self.progname = options[:progname] || "Kitchen" self.level = options[:level] || default_log_level end # Pulled out for Rubocop complexity issues # # @api private def populate_loggers(options) @loggers = [] @loggers << logdev unless logdev.nil? @loggers << stdout_logger(options[:stdout], options[:color], options[:colorize]) if options[:stdout] @loggers << stdout_logger($stdout, options[:color], options[:colorize]) if @loggers.empty? end private :populate_loggers class << self private # @api private # @!macro delegate_to_first_logger # @method $1() def delegate_to_first_logger(meth) define_method(meth) { |*args| @loggers.first.public_send(meth, *args) } end # @api private # @!macro delegate_to_all_loggers # @method $1() def delegate_to_all_loggers(meth) define_method(meth) do |*args| result = nil @loggers.each { |l| result = l.public_send(meth, *args) } result end end end # @return [Integer] the logging severity threshold # @see http://is.gd/Okuy5p delegate_to_first_logger :level # Sets the logging severity threshold. # # @param level [Integer] the logging severity threshold # @see http://is.gd/H1VBFH delegate_to_all_loggers :level= # @return [String] program name to include in log messages # @see http://is.gd/5uHGK0 delegate_to_first_logger :progname # Sets the program name to include in log messages. # # @param progname [String] the program name to include in log messages # @see http://is.gd/f2U5Xj delegate_to_all_loggers :progname= # @return [String] the date format being used # @see http://is.gd/btmFWJ delegate_to_first_logger :datetime_format # Sets the date format being used. # # @param format [String] the date format # @see http://is.gd/M36ml8 delegate_to_all_loggers :datetime_format= # Log a message if the given severity is high enough. # # @see http://is.gd/5opBW0 delegate_to_all_loggers :add # Dump one or more messages to info. # # @param message [#to_s] the message to log # @see http://is.gd/BCp5KV delegate_to_all_loggers :<< # Log a message with severity of banner (high level). # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/pYUCYU delegate_to_all_loggers :banner # Log a message with severity of debug. # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/Re97Zp delegate_to_all_loggers :debug # @return [true,false] whether or not the current severity level # allows for the printing of debug messages # @see http://is.gd/Iq08xB delegate_to_first_logger :debug? # Log a message with severity of info. # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/pYUCYU delegate_to_all_loggers :info # @return [true,false] whether or not the current severity level # allows for the printing of info messages # @see http://is.gd/lBtJkT delegate_to_first_logger :info? # Log a message with severity of error. # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/mLwYMl delegate_to_all_loggers :error # @return [true,false] whether or not the current severity level # allows for the printing of error messages # @see http://is.gd/QY19JL delegate_to_first_logger :error? # Log a message with severity of warn. # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/PX9AIS delegate_to_all_loggers :warn # @return [true,false] whether or not the current severity level # allows for the printing of warn messages # @see http://is.gd/Gdr4lD delegate_to_first_logger :warn? # Log a message with severity of fatal. # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/5ElFPK delegate_to_all_loggers :fatal # @return [true,false] whether or not the current severity level # allows for the printing of fatal messages # @see http://is.gd/7PgwRl delegate_to_first_logger :fatal? # Log a message with severity of unknown. # # @param message_or_progname [#to_s] the message to log. In the block # form, this is the progname to use in the log message. # @yield evaluates to the message to log. This is not evaluated unless the # logger's level is sufficient to log the message. This allows you to # create potentially expensive logging messages that are only called when # the logger is configured to show them. # @return [nil,true] when the given severity is not high enough (for this # particular logger), log no message, and return true # @see http://is.gd/Y4hqpf delegate_to_all_loggers :unknown # Close the logging devices. # # @see http://is.gd/b13cVn delegate_to_all_loggers :close private # @return [Integer] the default logger level # @api private def default_log_level Util.to_logger_level(Kitchen::DEFAULT_LOG_LEVEL) end # @return [Boolean] whether to overwrite logs by default # @api private def default_log_overwrite Kitchen::DEFAULT_LOG_OVERWRITE end # Construct a new standard out logger. # # @param stdout [IO] the IO object that represents stdout (or similar) # @param color [Symbol] color to use when outputing messages # @param colorize [Boolean] whether to enable color # @return [StdoutLogger] a new logger # @api private def stdout_logger(stdout, color, colorize) logger = StdoutLogger.new(stdout) if colorize logger.formatter = proc do |_severity, _datetime, _progname, msg| Color.colorize(msg.to_s, color).concat("\n") end else logger.formatter = proc do |_severity, _datetime, _progname, msg| msg.concat("\n") end end logger end # Construct a new logdev logger. # # @param filepath_or_logdev [String,IO] a filepath String or IO object # @param log_overwrite [Boolean] apply log overwriting # if filepath_or_logdev is a file path # @return [LogdevLogger] a new logger # @api private def logdev_logger(filepath_or_logdev, log_overwrite) LogdevLogger.new(resolve_logdev(filepath_or_logdev, log_overwrite)) end # Return an IO object from a filepath String or the IO object itself. # # @param filepath_or_logdev [String,IO] a filepath String or IO object # @param log_overwrite [Boolean] apply log overwriting # if filepath_or_logdev is a file path # @return [IO] an IO object # @api private def resolve_logdev(filepath_or_logdev, log_overwrite) if filepath_or_logdev.is_a? String mode = log_overwrite ? "wb" : "ab" FileUtils.mkdir_p(File.dirname(filepath_or_logdev)) file = File.open(File.expand_path(filepath_or_logdev), mode) file.sync = true file else filepath_or_logdev end end # Internal class which adds a #banner method call that displays the # message with a callout arrow. class LogdevLogger < ::Logger alias super_info info # Dump one or more messages to info. # # @param msg [String] a message def <<(msg) @buffer ||= "" @buffer += msg while (i = @buffer.index("\n")) format_line(@buffer[0, i].chomp) @buffer[0, i + 1] = "" end end # Log a banner message. # # @param msg [String] a message def banner(msg = nil, &block) super_info("-----> #{msg}", &block) end private # Reformat a line if it already contains log formatting. # # @param line [String] a message line # @api private def format_line(line) case line when /^-----> / then banner(line.gsub(/^[ >-]{6} /, "")) when /^>>>>>> / then error(line.gsub(/^[ >-]{6} /, "")) when /^ / then info(line.gsub(/^[ >-]{6} /, "")) else info(line) end end end # Internal class which reformats logging methods for display as console # output. class StdoutLogger < LogdevLogger # Log a debug message # # @param msg [String] a message def debug(msg = nil, &block) super("D #{msg}", &block) end # Log an info message # # @param msg [String] a message def info(msg = nil, &block) super(" #{msg}", &block) end # Log a warn message # # @param msg [String] a message def warn(msg = nil, &block) super("$$$$$$ #{msg}", &block) end # Log an error message # # @param msg [String] a message def error(msg = nil, &block) super(">>>>>> #{msg}", &block) end # Log a fatal message # # @param msg [String] a message def fatal(msg = nil, &block) super("!!!!!! #{msg}", &block) end # Log an unknown message # # @param msg [String] a message def unknown(msg = nil, &block) super("?????? #{msg}", &block) end end end end test-kitchen-1.23.2/lib/kitchen/state_file.rb0000644000004100000410000000674713377651062021120 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "yaml" module Kitchen # Exception class for any exceptions raised when reading and parsing a state # file from disk class StateFileLoadError < StandardError; end # State persistence manager for instances between actions and invocations. # # @author Fletcher Nichol class StateFile # Constructs an new instance taking the kitchen root and instance name. # # @param kitchen_root [String] path to the Kitchen project's root directory # @param name [String] name of the instance representing this state def initialize(kitchen_root, name) @file_name = File.expand_path( File.join(kitchen_root, ".kitchen", "#{name}.yml") ) end # Reads and loads an instance's state into a Hash data structure which is # returned. # # @return [Hash] a hash representation of an instance's state # @raise [StateFileLoadError] if there is a problem loading the state file # from disk and loading it into a Hash def read if File.exist?(file_name) && !File.zero?(file_name) Util.symbolized_hash(deserialize_string(read_file)) else {} end end # Serializes the state hash and writes a state file to disk. # # @param state [Hash] the current state of the instance def write(state) dir = File.dirname(file_name) serialized_string = serialize_hash(Util.stringified_hash(state)) FileUtils.mkdir_p(dir) unless File.directory?(dir) File.open(file_name, "wb") { |f| f.write(serialized_string) } end # Destroys a state file on disk if it exists. def destroy FileUtils.rm_f(file_name) if File.exist?(file_name) end # Returns a Hash of configuration and other useful diagnostic information. # # @return [Hash] a diagnostic hash def diagnose raw = read result = {} raw.keys.sort.each { |k| result[k] = raw[k] } result end private # @return [String] absolute path to the yaml state file on disk # @api private attr_reader :file_name # @return [String] a string representation of the yaml state file # @api private def read_file IO.read(file_name) end # Parses a YAML string and returns a Hash. # # @param string [String] a yaml document as a string # @return [Hash] a hash # @raise [StateFileLoadError] if the string document cannot be parsed # @api private def deserialize_string(string) YAML.safe_load(string) rescue SyntaxError, Psych::SyntaxError, Psych::DisallowedClass => ex raise StateFileLoadError, "Error parsing #{file_name} (#{ex.message})" end # Serializes a Hash into a YAML string. # # @param hash [Hash] a hash # @return [String] a yaml document as a string # @api private def serialize_hash(hash) ::YAML.dump(hash) end end end test-kitchen-1.23.2/lib/kitchen/util.rb0000644000004100000410000001670713377651062017753 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # Stateless utility methods used in different contexts. Essentially a mini # PassiveSupport library. # # @author Fletcher Nichol module Util # Returns the standard library Logger level constants for a given symbol # representation. # # @param symbol [Symbol] symbol representation of a logger level (:debug, # :info, :warn, :error, :fatal) # @return [Integer] Logger::Severity constant value or nil if input is not # valid def self.to_logger_level(symbol) return nil unless [:debug, :info, :warn, :error, :fatal].include?(symbol) Logger.const_get(symbol.to_s.upcase) end # Returns the symbol represenation of a logging levels for a given # standard library Logger::Severity constant. # # @param const [Integer] Logger::Severity constant value for a logging # level (Logger::DEBUG, Logger::INFO, Logger::WARN, Logger::ERROR, # Logger::FATAL) # @return [Symbol] symbol representation of the logging level def self.from_logger_level(const) case const when Logger::DEBUG then :debug when Logger::INFO then :info when Logger::WARN then :warn when Logger::ERROR then :error else :fatal end end # Returns a new Hash with all key values coerced to symbols. All keys # within a Hash are coerced by calling #to_sym and hashes within arrays # and other hashes are traversed. # # @param obj [Object] the hash to be processed. While intended for # hashes, this method safely processes arbitrary objects # @return [Object] a converted hash with all keys as symbols def self.symbolized_hash(obj) if obj.is_a?(Hash) obj.inject({}) { |h, (k, v)| h[k.to_sym] = symbolized_hash(v); h } elsif obj.is_a?(Array) obj.inject([]) { |a, e| a << symbolized_hash(e); a } else obj end end # Returns a new Hash with all key values coerced to strings. All keys # within a Hash are coerced by calling #to_s and hashes with arrays # and other hashes are traversed. # # @param obj [Object] the hash to be processed. While intended for # hashes, this method safely processes arbitrary objects # @return [Object] a converted hash with all keys as strings def self.stringified_hash(obj) if obj.is_a?(Hash) obj.inject({}) { |h, (k, v)| h[k.to_s] = stringified_hash(v); h } elsif obj.is_a?(Array) obj.inject([]) { |a, e| a << stringified_hash(e); a } else obj end end # Returns a formatted string representing a duration in seconds. # # @param total [Integer] the total number of seconds # @return [String] a formatted string of the form (XmYY.00s) def self.duration(total) total = 0 if total.nil? minutes = (total / 60).to_i seconds = (total - (minutes * 60)) format("(%dm%.2fs)", minutes, seconds) end # Generates a command (or series of commands) wrapped so that it can be # invoked on a remote instance or locally. # # This method uses the Bourne shell (/bin/sh) to maximize the chance of # cross platform portability on Unixlike systems. # # @param [String] the command # @return [String] a wrapped command string def self.wrap_command(cmd) cmd = "false" if cmd.nil? cmd = "true" if cmd.to_s.empty? cmd = cmd.sub(/\n\Z/, "") if cmd =~ /\n\Z/ "sh -c '\n#{cmd}\n'" end # Modifes the given string to strip leading whitespace on each line, the # amount which is calculated by using the first line of text. # # @example # # string = <<-STRING # a # b # c # STRING # Util.outdent!(string) # => "a\n b\nc\n" # # @param string [String] the string that will be modified # @return [String] the modified string def self.outdent!(string) string.gsub!(/^ {#{string.index(/[^ ]/)}}/, "") end # Returns a set of Bourne Shell (AKA /bin/sh) compatible helper # functions. This function is usually called inline in a string that # will be executed remotely on a test instance. # # @return [String] a string representation of useful helper functions def self.shell_helpers IO.read(File.join( File.dirname(__FILE__), %w{.. .. support download_helpers.sh} )) end # Lists the contents of the given directory. path will be prepended # to the list returned. '.' and '..' are never returned. # # @param path [String] the directory to list # @param include_dot [Boolean] if true, dot files will be included # @param recurse [Boolean] if true, listing will be recursive # @return A listing of the specified path # # @note You should prefer this method to using Dir.glob directly. The reason is # because Dir.glob behaves strangely on Windows. It wont accept '\' # and doesn't like fake directories (C:\Documents and Settings) # It also does not do any sort of error checking, so things one would # expect to fail just return an empty list # # @note Dir.chdir is applied to the process, thus it is not thread-safe # and must be synchronized. def self.list_directory(path, include_dot: false, recurse: false) # Things (such as tests) are relying on this to not blow up if # the directory does not exist return [] if !Dir.exist?(path) Kitchen.mutex_chdir.synchronize do Dir.chdir(path) do glob_pattern = if recurse "**/*" else "*" end flags = if include_dot [File::FNM_DOTMATCH] else [] end Dir.glob(glob_pattern, *flags) .reject { |f| [".", ".."].include?(f) } .map { |f| File.join(path, f) } end end end # Similar to Dir.glob. # # The difference is this function forces you to specify where to glob from. # You should glob from the path closest to what you want. The reason for this # is because if you have symlinks on windows of any kind, Dir.glob will not # traverse them. # # @param path [String] the directory to glob from # @param pattern [String] The pattern to match # @param flags [Integer] You can specify flags you would have passed to Dir.glob # @return Files matching the specified pattern in the given path # # @note Dir.chdir is applied to the process, thus it is not thread-safe # and must be synchronized. def self.safe_glob(path, pattern, *flags) return [] if !Dir.exist?(path) Kitchen.mutex_chdir.synchronize do Dir.chdir(path) do Dir.glob(pattern, *flags).map { |f| File.join(path, f) } end end end end end test-kitchen-1.23.2/lib/kitchen/suite.rb0000644000004100000410000000323613377651062020120 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # A logical configuration representing a test case or fixture that will be # executed on a platform. # # @author Fletcher Nichol class Suite # @return [String] logical name of this suite attr_reader :name # @return [Array] Array of names of excluded platforms attr_reader :excludes # @return [Array] Array of names of only included platforms attr_reader :includes # Constructs a new suite. # # @param [Hash] options configuration for a new suite # @option options [String] :name logical name of this suit (**Required**) # @option options [String] :excludes Array of names of excluded platforms # @option options [String] :includes Array of names of only included # platforms def initialize(options = {}) @name = options.fetch(:name) do raise ClientError, "Suite#new requires option :name" end @excludes = options.fetch(:excludes, []) @includes = options.fetch(:includes, []) end end end test-kitchen-1.23.2/lib/kitchen/config.rb0000644000004100000410000003030413377651062020230 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, 2013, 2014, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Kitchen # Base configuration class for Kitchen. This class exposes configuration such # as the location of the Kitchen config file, instances, log_levels, etc. # This object is a factory object, meaning that it is responsible for # consuming the desired testing configuration in and returning Ruby objects # which are used to perfom the work. # # Most internal objects are created with the expectation of being # *immutable*, meaning that internal state cannot be modified after creation. # Any data manipulation or thread-unsafe activity is performed in this object # so that the subsequently created objects (such as Instances, Platforms, # Drivers, etc.) can safely run in concurrent threads of execution. To # prevent the re-creation of duplicate objects, most created objects are # memoized. The consequence of this is that once the Instance Array has # been requested (with the `#instances` message), you will always be returned # the same Instance objects. # # @example fetching all instances # # Kitchen::Config.new.instances # # @example fetching an instance by name # # Kitchen::Config.new.instances.get("default-ubuntu-16.04") # # @example fetching all instances matching a regular expression # # Kitchen::Config.new.instances.get_all(/ubuntu/) # # @author Fletcher Nichol class Config # @return [String] the absolute path to the root of a Test Kitchen project # @api private attr_reader :kitchen_root # @return [String] the absolute path to the directory into which all Test # Kitchen log files will be written # @api private attr_reader :log_root # @return [#read] the data loader that responds to a `#read` message, # returning a Hash data structure # @api private attr_reader :loader # @return [Symbol] the logging verbosity level # @api private attr_accessor :log_level # @return [Boolean] whether to overwrite the log file when # Test Kitchen runs # @api private attr_accessor :log_overwrite # @return [String] an absolute path to the directory containing test suites # @api private attr_accessor :test_base_path # @return [Boolean] whether to force color output or not in logger # @api private attr_accessor :colorize # @return [Boolean] whether to enable debugging in the provisioner/verifier plugin or not # @api private attr_accessor :debug # Creates a new configuration, representing a particular testing # configuration for a project. # # @param [Hash] options configuration # @option options [#read] :loader an object that responds to `#read` with # a Hash structure suitable for manipulating # (default: `Kitchen::Loader::YAML.new`) # @option options [String] :kitchen_root an absolute path to the root of a # Test Kitchen project, usually containing a `.kitchen.yml` file # (default `Dir.pwd`) # @option options [String] :log_root an absolute path to the directory # into which all Test Kitchen log files will be written # (default: `"#{kitchen_root}/.kitchen/logs"`) # @option options [String] :test_base_path an absolute path to the # directory containing test suites and other testing-related files and # directories (default: `"#{kitchen_root}/test/integration"`) # @option options [Symbol] :log_level the log level verbosity that the # loggers will use when outputing information (default: `:info`) def initialize(options = {}) @loader = options.fetch(:loader) { Kitchen::Loader::YAML.new } @kitchen_root = options.fetch(:kitchen_root) { Dir.pwd } @log_level = options.fetch(:log_level) { Kitchen::DEFAULT_LOG_LEVEL } @log_overwrite = options.fetch(:log_overwrite) { Kitchen::DEFAULT_LOG_OVERWRITE } @colorize = options.fetch(:colorize) { Kitchen.tty? } @log_root = options.fetch(:log_root) { default_log_root } @test_base_path = options.fetch(:test_base_path) { default_test_base_path } @debug = options.fetch(:debug) { false } end # @return [Collection] all instances, resulting from all # platform and suite combinations def instances @instances ||= Collection.new(build_instances) end # @return [Collection] all defined platforms which will be used # in convergence integration def platforms @platforms ||= Collection.new( data.platform_data.map { |pdata| Platform.new(pdata) }) end # @return [Collection] all defined suites which will be used in # convergence integration def suites @suites ||= Collection.new( data.suite_data.map { |sdata| Suite.new(sdata) }) end private # Builds the filtered list of Instance objects. # # @return [Array>] an Array of Suite/Platform # tuples # @api private def filter_instances suites.product(platforms).select do |suite, platform| if !suite.includes.empty? suite.includes.include?(platform.name) elsif !suite.excludes.empty? !suite.excludes.include?(platform.name) else true end end end # Determines the String name for an Instance, given a Suite and a Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [String] an Instance name # @api private def instance_name(suite, platform) Instance.name_for(suite, platform) end # Generates the immutable Test Kitchen configuration and reasonable # defaults for Drivers, Provisioners and Transports. # # @return [Hash] a configuration Hash # @api private def kitchen_config @kitchen_config ||= { defaults: { driver: Driver::DEFAULT_PLUGIN, provisioner: Provisioner::DEFAULT_PLUGIN, verifier: Verifier::DEFAULT_PLUGIN, transport: lambda do |_suite, platform| platform =~ /^win/i ? "winrm" : Transport::DEFAULT_PLUGIN end, }, kitchen_root: kitchen_root, test_base_path: test_base_path, log_level: log_level, log_overwrite: log_overwrite, debug: debug, } end # Builds a newly configured Driver object, for a given Suite and Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [Driver] a new Driver object # @api private def new_driver(suite, platform) ddata = data.driver_data_for(suite.name, platform.name) Driver.for_plugin(ddata[:name], ddata) end # Builds a newly configured Instance object, for a given Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @param index [Integer] an index used for colorizing output # @return [Instance] a new Instance object # @api private def new_instance(suite, platform, index) Instance.new( driver: new_driver(suite, platform), lifecycle_hooks: new_lifecycle_hooks(suite, platform), logger: new_instance_logger(suite, platform, index), suite: suite, platform: platform, provisioner: new_provisioner(suite, platform), transport: new_transport(suite, platform), verifier: new_verifier(suite, platform), state_file: new_state_file(suite, platform) ) end # Builds a newly configured Logger object, for a given Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @param index [Integer] an index used for colorizing output # @return [Logger] a new Logger object # @api private def new_instance_logger(suite, platform, index) name = instance_name(suite, platform) log_location = File.join(log_root, "#{name}.log").to_s Logger.new( stdout: STDOUT, color: Color::COLORS[index % Color::COLORS.size].to_sym, logdev: log_location, level: Util.to_logger_level(log_level), log_overwrite: log_overwrite, progname: name, colorize: @colorize ) end # Builds a newly configured LifecycleHooks object, for a given a Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [LifecycleHooks] a new LifecycleHooks object # @api private def new_lifecycle_hooks(suite, platform) lhdata = data.lifecycle_hooks_data_for(suite.name, platform.name) LifecycleHooks.new(lhdata) end # Builds a newly configured Provisioner object, for a given Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [Provisioner] a new Provisioner object # @api private def new_provisioner(suite, platform) pdata = data.provisioner_data_for(suite.name, platform.name) Provisioner.for_plugin(pdata[:name], pdata) end # Builds a newly configured StateFile object, for a given Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [StateFile] a new StateFile object # @api private def new_state_file(suite, platform) StateFile.new(kitchen_root, instance_name(suite, platform)) end # Builds a newly configured Transport object, for a given Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [Transport] a new Transport object # @api private def new_transport(suite, platform) tdata = data.transport_data_for(suite.name, platform.name) Transport.for_plugin(tdata[:name], tdata) end # Builds a newly configured Verifier object, for a given a Suite and # Platform. # # @param suite [Suite,#name] a Suite # @param platform [Platform,#name] a Platform # @return [Verifier] a new Verifier object # @api private def new_verifier(suite, platform) vdata = data.verifier_data_for(suite.name, platform.name) Verifier.for_plugin(vdata[:name], vdata) end end end test-kitchen-1.23.2/lib/kitchen/collection.rb0000644000004100000410000000334213377651062021120 0ustar www-datawww-data# -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2012, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "delegate" module Kitchen # Delegate class which adds the ability to find single and multiple # objects by their #name in an Array. Hey, it's better than monkey-patching # Array, right? # # @author Fletcher Nichol class Collection < SimpleDelegator # Returns a single object by its name, or nil if none are found. # # @param name [String] name of object # @return [Object] first match by name, or nil if none are found def get(name) __getobj__.find { |i| i.name == name } end # Returns a Collection of all objects whose #name is matched by the # regular expression. # # @param regexp [Regexp] a regular expression pattern # @return [Kitchen::Config::Collection] a new collection of # matched objects def get_all(regexp) Kitchen::Collection.new(__getobj__.select { |i| i.name =~ regexp }) end # Returns an Array of names from the collection as strings. # # @return [Array] array of name strings def as_names __getobj__.map(&:name) end end end test-kitchen-1.23.2/CONTRIBUTING.md0000644000004100000410000000174713377651062016505 0ustar www-datawww-data# Release Process This release process applies to all Test Kitchen projects, but each project may have additional requirements. 1. Perform a Github diff between master and the last released version. Determine whether included PRs justify a patch, minor or major version release. 2. Check out the master branch of the project being prepared for release. 3. Branch into a release-branch of the form `150_release_prep`. 4. Modify the `version.rb` file to specify the version for releasing. 5. Run `rake changelog` to regenerate the changelog. 6. `git commit` the `version.rb` and `CHANGELOG.md` changes to the branch and setup a PR for them. Allow the PR to run any automated tests and review the CHANGELOG for accuracy. 7. Merge the PR to master after review. 8. Switch your local copy to the master branch and `git pull` to pull in the release preparation changes. 9. Run `rake release` on the master branch. 10. Modify the `version.rb` file and bump the patch or minor version, and commit/push. test-kitchen-1.23.2/Guardfile0000644000004100000410000000205113377651062016066 0ustar www-datawww-data# -*- encoding: utf-8 -*- ignore %r{^\.gem/} def rubocop_opts { all_on_start: false, keep_failed: false, cli: "-r finstyle" } end def cucumber_opts cucumber_cli = "--no-profile --color --format progress --strict" cucumber_cli += " --tags ~@spawn" if RUBY_PLATFORM =~ /mswin|mingw|windows/ { all_on_start: false, cli: cucumber_cli } end def yard_opts { port: "8808" } end group :red_green_refactor, halt_on_fail: true do guard :minitest do watch(%r{^spec/(.*)_spec\.rb}) watch(%r{^lib/(.*)([^/]+)\.rb}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } watch(%r{^spec/spec_helper\.rb}) { "spec" } end guard :rubocop, rubocop_opts do watch(/.+\.rb$/) watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } end end guard :cucumber, cucumber_opts do watch(%r{^features/.+\.feature$}) watch(%r{^features/support/.+$}) { "features" } watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |m| Dir[File.join("**/#{m[1]}.feature")][0] || "features" end end guard :yard, yard_opts do watch(%r{lib/.+\.rb}) end test-kitchen-1.23.2/.yardopts0000644000004100000410000000007613377651062016114 0ustar www-datawww-data--readme README.md --markup markdown --markup-provider maruku test-kitchen-1.23.2/.kitchen.appveyor.yml0000644000004100000410000000063413377651062020340 0ustar www-datawww-data--- driver: name: proxy host: localhost reset_command: "exit 0" port: <%= ENV["machine_port"] %> username: <%= ENV["machine_user"] %> password: <%= ENV["machine_pass"] %> provisioner: name: chef_zero platforms: - name: windows-2012R2 transport: name: winrm elevated: true verifier: name: inspec suites: - name: default run_list: - recipe[test_cookbook::default] test-kitchen-1.23.2/Gemfile0000644000004100000410000000157213377651062015543 0ustar www-datawww-data# -*- encoding: utf-8 -*- source "https://rubygems.org" gemspec gem "train", "~> 0.22" group :integration do gem "berkshelf" gem "kitchen-inspec" gem "kitchen-dokken" gem "kitchen-vagrant" end group :changelog do gem "github_changelog_generator", "1.11.3" end group :debug do gem "pry" gem "pry-byebug" gem "pry-stack_explorer" end group :chefstyle do gem "chefstyle" end group :docs do gem "yard" end instance_eval(ENV["GEMFILE_MOD"]) if ENV["GEMFILE_MOD"] # To avoid bringing in development or test dependencies that are not # absolutely needed, if you want to load tools not present in the gemspec # or this Gemfile, add those additional dependencies into a Gemfile.local # file which is ignored by this repository. # rubocop:disable Security/Eval eval(IO.read(__FILE__ + ".local"), binding) if File.exist?(__FILE__ + ".local") # rubocop:enable Security/Eval test-kitchen-1.23.2/features/0000755000004100000410000000000013377651062016061 5ustar www-datawww-datatest-kitchen-1.23.2/features/kitchen_list_command.feature0000644000004100000410000000753413377651062023625 0ustar www-datawww-dataFeature: Listing Test Kitchen instances In order to understand how the .kitchen.yml is consumed As a user of Test Kitchen I want to run a command to see the state of my instances Background: Given a file named ".kitchen.yml" with: """ --- driver: dummy provisioner: chef_solo platforms: - name: ubuntu-13.04 - name: centos-6.4 - name: centos-6.4-with-small-mem suites: - name: foobar """ Scenario: Listing instances When I run `kitchen list` Then the exit status should be 0 And the output should match /^foobar-ubuntu-1304\s+Dummy\s+ChefSolo\s+Busser\s+Ssh\s+\\s+\$/ And the output should match /^foobar-centos-64\s+Dummy\s+ChefSolo\s+Busser\s+Ssh\s+\\s+\$/ Scenario: Listing a single instance with the --json option When I run `kitchen list --json` Then the exit status should be 0 And the output should contain exactly: """ [ { "instance": "foobar-ubuntu-1304", "driver": "Dummy", "provisioner": "ChefSolo", "verifier": "Busser", "transport": "Ssh", "last_action": null, "last_error": null }, { "instance": "foobar-centos-64", "driver": "Dummy", "provisioner": "ChefSolo", "verifier": "Busser", "transport": "Ssh", "last_action": null, "last_error": null }, { "instance": "foobar-centos-64-with-small-mem", "driver": "Dummy", "provisioner": "ChefSolo", "verifier": "Busser", "transport": "Ssh", "last_action": null, "last_error": null } ] """ Scenario: Listing instances with the --bare option When I run `kitchen list --bare` Then the exit status should be 0 And the output should contain exactly: """ foobar-ubuntu-1304 foobar-centos-64 foobar-centos-64-with-small-mem """ Scenario: Listing instances with a simple regular expression glob When I successfully run `kitchen list ubun --bare` Then the output should contain exactly: """ foobar-ubuntu-1304 """ Scenario: Listing instances with a Ruby regular expression glob, requiring signle quoting on the command line When I successfully run `kitchen list '^foo.*\-(10|13)04$' --bare` Then the output should contain exactly: """ foobar-ubuntu-1304 """ @spawn Scenario: Listing instances with a regular expression yielding no results When I run `kitchen list freebsd --bare` Then the exit status should not be 0 And the output should contain "No instances for regex `freebsd', try running `kitchen list'" @spawn Scenario: Listing instances with a bad regular expression When I run `kitchen list *centos* --bare` Then the exit status should not be 0 And the output should contain "Invalid Ruby regular expression" Scenario: Listing a full instance name returns an exact match, not fuzzy matches When I successfully run `kitchen list foobar-centos-64 --bare` Then the output should contain exactly: """ foobar-centos-64 """ Scenario: Listing a full instance name returns an exact match, not fuzzy matches at start Given a file named ".kitchen.yml" with: """ --- driver: dummy provisioner: chef_solo platforms: - name: ubuntu-16.04 suites: - name: gdb01-master - name: logdb01-master """ When I successfully run `kitchen list gdb01-master-ubuntu-1604 --bare` Then the output should contain exactly: """ gdb01-master-ubuntu-1604 """ Scenario: Listing a full instance with regex returns all regex matches When I successfully run `kitchen list 'foobar-centos-64.*' --bare` Then the output should contain exactly: """ foobar-centos-64 foobar-centos-64-with-small-mem """ test-kitchen-1.23.2/features/kitchen_help_command.feature0000644000004100000410000000101313377651062023564 0ustar www-datawww-dataFeature: Using Test Kitchen CLI help In order to access the self describing documentation As a user of Test Kitchen I want to run a command help help for kitchen commands @spawn Scenario: Printing help When I run `kitchen help` Then the exit status should be 0 And the output should contain "kitchen help [COMMAND]" @spawn Scenario: Bad arugments should exit nonzero When I run `kitchen help -d always -c` Then the exit status should not be 0 And the output should contain "Usage: " test-kitchen-1.23.2/features/kitchen_diagnose_command.feature0000644000004100000410000000452113377651062024434 0ustar www-datawww-dataFeature: Running a diagnosis command In order to understand how Kitchen is wired together As an operator and configuration sleuth I want to run a command to get configuration information Background: Given a file named ".kitchen.yml" with: """ --- driver: name: dummy provisioner: name: dummy transport: name: dummy verifier: name: dummy platforms: - name: cool - name: beans suites: - name: client - name: server """ @spawn Scenario: Displaying help When I run `kitchen help diagnose` Then the output should contain: """ Usage: kitchen diagnose """ And the exit status should be 0 @spawn Scenario: Showing all instances When I run `kitchen diagnose` Then the output should contain "timestamp: " Then the output should contain "kitchen_version: " Then the output should contain " client-cool:" Then the output should contain " client-beans:" Then the output should contain " server-cool:" Then the output should contain " server-beans:" And the exit status should be 0 @spawn Scenario: Showing all instances with loader configuration When I run `kitchen diagnose --loader` Then the output should contain: """ loader: process_erb: true process_local: true process_global: true """ And the exit status should be 0 @spawn Scenario: Showing all instances with plugin configuration When I run `kitchen diagnose --plugins` Then the output should contain "plugins:" Then the output should contain " class: Kitchen::Driver::Dummy" Then the output should contain " class: Kitchen::Provisioner::Dummy" Then the output should contain " class: Kitchen::Transport::Dummy" Then the output should contain " class: Kitchen::Verifier::Dummy" And the exit status should be 0 @spawn Scenario: Coping with loading failure Given a file named ".kitchen.local.yml" with: """ I'm a little teapot """ When I run `kitchen diagnose --all` Then the output should contain: """ raw_data: error: """ And the output should contain: """ instances: error: """ And the output should contain: """ plugins: error: """ And the exit status should be 0 test-kitchen-1.23.2/features/kitchen_action_commands.feature0000644000004100000410000001427613377651062024313 0ustar www-datawww-dataFeature: Running instance actions In order to trigger discrete instance lifecyle actions As an operator I want to run a action commands Background: Given a file named ".kitchen.yml" with: """ --- driver: name: dummy provisioner: name: dummy verifier: name: dummy platforms: - name: cool - name: beans suites: - name: client - name: server """ @spawn Scenario: Creating a single instance When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+\\s+\\Z/ When I run `kitchen create client-beans` Then the output should contain "Finished creating " And the exit status should be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+\\Z/ @spawn Scenario: Creating a single instance that fails Given a file named ".kitchen.local.yml" with: """ --- driver: fail_create: true """ When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+\\s+\\Z/ When I run `kitchen create client-beans` Then the output should contain "Create failed on instance " And the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+\\s+Kitchen::ActionFailed\Z/ @spawn Scenario: Converging a single instance When I successfully run `kitchen create client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+\\Z/ When I run `kitchen converge client-beans` Then the output should contain "Finished converging " And the exit status should be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Converged\s+\\Z/ @spawn Scenario: Converging a single instance that fails Given a file named ".kitchen.local.yml" with: """ --- provisioner: fail: true """ When I successfully run `kitchen create client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+\\Z/ When I run `kitchen converge client-beans` Then the output should contain "Converge failed on instance " And the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+Kitchen::ActionFailed\Z/ @spawn Scenario: Setting up a single instance When I successfully run `kitchen converge client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Converged\s+\\Z/ When I run `kitchen setup client-beans` Then the output should contain "Finished setting up " And the exit status should be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Set Up\s+\\Z/ @spawn Scenario: Setting up a single instance that fails Given a file named ".kitchen.local.yml" with: """ --- verifier: fail: true """ When I successfully run `kitchen converge client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Converged\s+\\Z/ When I run `kitchen verify client-beans` Then the output should contain "Verify failed on instance " And the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Set Up\s+Kitchen::ActionFailed\Z/ @spawn Scenario: Verifying a single instance When I successfully run `kitchen setup client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Set Up\s+\\Z/ When I run `kitchen verify client-beans` Then the output should contain "Finished verifying " And the exit status should be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Verified\s+\\Z/ @spawn Scenario: Verifying a single instance that fails Given a file named ".kitchen.local.yml" with: """ --- verifier: fail: true """ When I successfully run `kitchen setup client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Set Up\s+\\Z/ When I run `kitchen verify client-beans` Then the output should contain "Verify failed on instance " And the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Set Up\s+Kitchen::ActionFailed\Z/ @spawn Scenario: Destroying a single instance When I successfully run `kitchen create client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+\\Z/ When I run `kitchen destroy client-beans` Then the output should contain "Finished destroying " And the exit status should be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+\\s+\\Z/ @spawn Scenario: Destroying a single instance that fails Given a file named ".kitchen.local.yml" with: """ --- driver: fail_destroy: true """ When I successfully run `kitchen create client-beans` And I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+\\Z/ When I run `kitchen destroy client-beans` Then the output should contain "Destroy failed on instance " And the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the stdout should match /^client-beans\s+.+\s+Created\s+Kitchen::ActionFailed\Z/ test-kitchen-1.23.2/features/kitchen_command.feature0000644000004100000410000000113313377651062022557 0ustar www-datawww-dataFeature: A command line interface for Test Kitchen In order to provide a quick and response development workflow As a Test Kitchen user I want a command line interface that has sane defaults and built in help @spawn Scenario: Displaying help When I run `kitchen help` Then the exit status should be 0 And the output should contain "kitchen console" And the output should contain "kitchen version" Scenario: Displaying the version of Test Kitchen When I run `kitchen version` Then the exit status should be 0 And the output should contain "Test Kitchen version" test-kitchen-1.23.2/features/kitchen_test_command.feature0000644000004100000410000000627113377651062023626 0ustar www-datawww-dataFeature: Running a full test instance test In order to "fire-and-forget" or run help setup a CI job As an operator or CI script I want to run a command that will fully test one or more instances Background: Given a file named ".kitchen.yml" with: """ --- driver: name: dummy provisioner: name: dummy platforms: - name: cool - name: beans suites: - name: client - name: server """ @spawn Scenario: Running a single instance When I run `kitchen test client-beans` Then the output should contain "Starting Kitchen" Then the output should contain "Cleaning up any prior instances of " Then the output should contain "Testing " Then the output should contain "Finished testing " Then the output should contain "Kitchen is finished." And the exit status should be 0 @spawn Scenario: Running a single instance never destroying an instance When I successfully run `kitchen test client-beans --destroy=never` And I successfully run `kitchen list client-beans` Then the output should match /^client-beans\s+.+\s+Verified\s+\$/ @spawn Scenario: Running a single instance always destroying an instance Given a file named ".kitchen.local.yml" with: """ --- provisioner: fail: true """ When I run `kitchen test client-beans --destroy=always` Then the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the output should match /^client-beans\s+.+\s+\\s+\$/ @spawn Scenario: Running a single instance not destroying an instance on failure Given a file named ".kitchen.local.yml" with: """ --- provisioner: fail: true """ When I run `kitchen test client-beans --destroy=passing` Then the exit status should not be 0 When I successfully run `kitchen list client-beans` Then the output should match /^client-beans\s+.+\s+Created\s+Kitchen::ActionFailed$/ @spawn Scenario: Running a single instance destroying an instance on success When I run `kitchen test client-beans --destroy=passing` Then the exit status should be 0 When I successfully run `kitchen list client-beans` Then the output should match /^client-beans\s+.+\s+\\s+\$/ @spawn Scenario: Running all instances When I run `kitchen test` Then the output should contain "Starting Kitchen" Then the output should contain "Finished testing " Then the output should contain "Finished testing " Then the output should contain "Finished testing " Then the output should contain "Finished testing " Then the output should contain "Kitchen is finished." And the exit status should be 0 When I successfully run `kitchen list` Then the output should match /^client-cool\s+.+\s+\\s+\$/ Then the output should match /^client-beans\s+.+\s+\\s+\$/ Then the output should match /^server-cool\s+.+\s+\\s+\$/ Then the output should match /^server-beans\s+.+\s+\\s+\$/ test-kitchen-1.23.2/features/step_definitions/0000755000004100000410000000000013377651062021427 5ustar www-datawww-datatest-kitchen-1.23.2/features/step_definitions/output_steps.rb0000644000004100000410000000025213377651062024531 0ustar www-datawww-data# -*- encoding: utf-8 -*- Then(/^the stdout should match \/([^\/]*)\/$/) do |expected| expect(last_command_started).to have_output_on_stdout(Regexp.new(expected)) end test-kitchen-1.23.2/features/step_definitions/gem_steps.rb0000644000004100000410000000100013377651062023731 0ustar www-datawww-data# -*- encoding: utf-8 -*- require "tmpdir" require "pathname" Then(/^a gem named "(.*?)" is installed with version "(.*?)"$/) do |name, version| unbundlerize do run_simple( sanitize_text("gem list #{name} --version #{version} -i"), fail_on_error: true, exit_timeout: nil ) end end Then(/^a gem named "(.*?)" is installed$/) do |name| unbundlerize do run_simple( sanitize_text("gem list #{name} -i"), fail_on_error: true, exit_timeout: nil ) end end test-kitchen-1.23.2/features/step_definitions/git_steps.rb0000644000004100000410000000013613377651062023755 0ustar www-datawww-data# -*- encoding: utf-8 -*- Given(/I have a git repository/) do create_directory(".git") end test-kitchen-1.23.2/features/kitchen_init_command.feature0000644000004100000410000001522613377651062023612 0ustar www-datawww-dataFeature: Add Test Kitchen support to an existing project In order to add Test Kitchen to a project with minimal effort As an operator I want to run a command to initialize my project @spawn Scenario: Displaying help When I run `kitchen help init` Then the output should contain: """ Usage: kitchen init """ And the exit status should be 0 @spawn Scenario: Running init with default values Given I have a git repository When I run `kitchen init` Then the exit status should be 0 And a directory named "test/integration/default" should exist And the file ".gitignore" should contain ".kitchen/" And the file ".gitignore" should contain "kitchen.local.yml" And the file ".gitignore" should contain ".kitchen.local.yml" And the file "kitchen.yml" should exist: And the file "kitchen.yml" should contain: """ driver: name: vagrant """ And a file named "Gemfile" should not exist And a file named "Rakefile" should not exist And a file named "Thorfile" should not exist And a gem named "kitchen-vagrant" is installed And a file named "chefignore" should exist And the file "chefignore" should contain ".kitchen" Scenario: Running init that creates a Gemfile When I successfully run `kitchen init --create-gemfile` Then the file "Gemfile" should contain "https://rubygems.org" And the file "Gemfile" should contain: """ gem "test-kitchen" """ And the output should contain "You must run `bundle install'" Scenario: Running init with an existing Gemfile appends to the Gemfile Given a file named "Gemfile" with: """ source "https://rubygems.org" """ When I successfully run `kitchen init` Then the file "Gemfile" should contain exactly: """ source "https://rubygems.org" gem "test-kitchen" """ And the output should contain "You must run `bundle install'" Scenario: Running init with a Gemfile containing test-kitchen does not re-append Given a file named "Gemfile" with: """ source "https://rubygems.org" gem 'test-kitchen' """ When I successfully run `kitchen init` Then the file "Gemfile" should contain exactly: """ source "https://rubygems.org" gem 'test-kitchen' """ Scenario: Running init with a Gemfile containing the driver gem does not re-append Given a file named "Gemfile" with: """ source "https://rubygems.org" gem 'test-kitchen' gem 'kitchen-ec2' """ When I successfully run `kitchen init --driver=kitchen-ec2` Then the file "Gemfile" should contain exactly: """ source "https://rubygems.org" gem 'test-kitchen' gem 'kitchen-ec2' """ And the output should not contain "You must run `bundle install'" Scenario: Running init with multiple driver sets the plugin_driver to the first driver given Given an empty file named "Gemfile" When I successfully run `kitchen init --driver=kitchen-bluebox kitchen-wakka` Then the file "kitchen.yml" should exist Then the file "kitchen.yml" should contain: """ driver: name: bluebox """ Scenario: Running init with no drivers sets the plugin_driver to the dummy driver Given an empty file named "Gemfile" When I successfully run `kitchen init --no-driver` Then the file "kitchen.yml" should contain: """ driver: name: dummy """ Scenario: Running init without a provisioner sets the default provisioner to chef_solo in kitchen.yml Given an empty file named "Gemfile" When I successfully run `kitchen init --no-driver` Then the file "kitchen.yml" should contain: """ provisioner: name: chef_solo """ Scenario: Running init with a provisioner sets the provisioner in kitchen.yml Given an empty file named "Gemfile" When I successfully run `kitchen init --no-driver --provisioner=chef_zero` Then the file "kitchen.yml" should contain: """ provisioner: name: chef_zero """ Scenario: Running with a Rakefile file appends Kitchen tasks Given an empty file named "Gemfile" And an empty file named "Rakefile" When I successfully run `kitchen init` Then the file "Rakefile" should contain: """ begin require 'kitchen/rake_tasks' Kitchen::RakeTasks.new rescue LoadError puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] end """ Scenario: Running without git doesn't make a .gitignore When I successfully run `kitchen init --no-driver` Then the exit status should be 0 And a file named ".gitignore" should not exist Scenario: Running with a Thorfile file appends Kitchen tasks Given an empty file named "Gemfile" Given an empty file named "Thorfile" When I successfully run `kitchen init` Then the file "Thorfile" should contain: """ begin require 'kitchen/thor_tasks' Kitchen::ThorTasks.new rescue LoadError puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] end """ Scenario: Running init with a name in metadata.rb sets a run list Given an empty file named "Gemfile" Given a file named "metadata.rb" with: """ name "ntp" license "Apache 2.0" description "Installs and configures ntp as a client or server" version "0.1.0" support "ubuntu" support "centos" """ When I successfully run `kitchen init` Then the file "kitchen.yml" should contain exactly: """ --- driver: name: vagrant provisioner: name: chef_solo platforms: - name: ubuntu-16.04 - name: centos-7 suites: - name: default run_list: - recipe[ntp::default] attributes: """ Scenario: Running init with an empty file metadata.rb sets an empty run list Given an empty file named "metadata.rb" When I successfully run `kitchen init` Then the file "kitchen.yml" should contain exactly: """ --- driver: name: vagrant provisioner: name: chef_solo platforms: - name: ubuntu-16.04 - name: centos-7 suites: - name: default run_list: attributes: """ Scenario: Running init with no metadata.rb file sets an empty run list Given a file named "metadata.rb" does not exist When I successfully run `kitchen init` Then the file "kitchen.yml" should contain exactly: """ --- driver: name: vagrant provisioner: name: chef_solo platforms: - name: ubuntu-16.04 - name: centos-7 suites: - name: default run_list: attributes: """ test-kitchen-1.23.2/features/kitchen_sink_command.feature0000644000004100000410000000173013377651062023606 0ustar www-datawww-dataFeature: The `kitchen sink` command In order to have fun and provide an Easter Egg for users As a Test Kitchen user I want a `sink` command that displays ASCII art Scenario: Displaying help When I run `kitchen help` Then the exit status should be 0 And the output should not contain "kitchen sink" Scenario: Displaying the sink When I run `kitchen sink` Then the exit status should be 0 And the output should contain: """ ___ ' _ '. / /` `\ \ | | [__] | | {{ | | }} _ | | _ {{ ___________<_>_| |_<_>}}________ .=======^=(___)=^={{====. / .----------------}}---. \ / / {{ \ \ / / }} \ \ ( '=========================' ) '-----------------------------' """ test-kitchen-1.23.2/features/support/0000755000004100000410000000000013377651062017575 5ustar www-datawww-datatest-kitchen-1.23.2/features/support/env.rb0000644000004100000410000000337513377651062020722 0ustar www-datawww-data# -*- encoding: utf-8 -*- # Set up the environment for testing require "aruba/cucumber" require "aruba/in_process" require "aruba/spawn_process" require "kitchen" require "kitchen/cli" class ArubaHelper def initialize(argv, stdin = STDIN, stdout = STDOUT, stderr = STDERR, kernel = Kernel) @argv = argv @stdin = stdin @stdout = stdout @stderr = stderr @kernel = kernel end def execute! $stdout = @stdout $stdin = @stdin kitchen_cli = Kitchen::CLI kitchen_cli.start(@argv) @kernel.exit(0) end end Before do @aruba_timeout_seconds = 15 @cleanup_dirs = [] aruba.config.command_launcher = :in_process aruba.config.main_class = ArubaHelper end Before("@spawn") do aruba.config.command_launcher = :spawn end After do |s| # Tell Cucumber to quit after this scenario is done - if it failed. # This is useful to inspect the 'tmp/aruba' directory before any other # steps are executed and clear it out. Cucumber.wants_to_quit = true if s.failed? # Restore environment variables to their original settings, if they have # been saved off env = aruba.environment env.to_h.keys.select { |key| key =~ /^_CUKE_/ } .each do |backup_key| env[backup_key.sub(/^_CUKE_/, "")] = env[backup_key] env.delete(backup_key) end @cleanup_dirs.each { |dir| FileUtils.rm_rf(dir) } end def backup_envvar(key) aruba.environment["_CUKE_#{key}"] = aruba.environment[key] end def restore_envvar(key) aruba.environment[key] = aruba.environment["_CUKE_#{key}"] aruba.environment.delete("_CUKE_#{key}") end def unbundlerize keys = %w{BUNDLER_EDITOR BUNDLE_BIN_PATH BUNDLE_GEMFILE RUBYOPT} keys.each { |key| backup_envvar(key); aruba.environment.delete(key) } yield keys.each { |key| restore_envvar(key) } end test-kitchen-1.23.2/features/kitchen_console_command.feature0000644000004100000410000000141413377651062024303 0ustar www-datawww-data@ignore Feature: Running a console command In order to interactively explore Kitchen's internals and wiring As an opterator I want to run a command to launch an interactive console session Background: Given a file named ".kitchen.yml" with: """ --- driver: name: dummy provisioner: name: dummy platforms: - name: flebian suites: - name: default - name: full """ @spawn Scenario: Launching a session When I run `kitchen console` interactively And I type "instances.map { |i| i.name }" And I type "exit" Then the output should contain "kc(Kitchen::Config)> " Then the output should contain: """ ["default-flebian", "full-flebian"] """ And the exit status should be 0 test-kitchen-1.23.2/features/kitchen_defaults.feature0000644000004100000410000000200713377651062022751 0ustar www-datawww-dataFeature: Test Kitchen defaults In order to have a more pleasant out of the box experience As a user of Test Kitchen I want to have some common defaults handled for me Scenario: Windows platforms get the Winrm Transport by default Given a file named ".kitchen.yml" with: """ --- driver: dummy provisioner: dummy verifier: dummy platforms: - name: win-8.1 suites: - name: default """ When I successfully run `kitchen list` Then the output should match /^default-win-81\s+Dummy\s+Dummy\s+Dummy\s+Winrm\s+\\s+\$/ Scenario: Non-Windows platforms get the Ssh Transport by default Given a file named ".kitchen.yml" with: """ --- driver: dummy provisioner: dummy verifier: dummy platforms: - name: ubuntu-16.04 suites: - name: default """ When I successfully run `kitchen list` Then the output should match /^default-ubuntu-1604\s+Dummy\s+Dummy\s+Dummy\s+Ssh\s+\\s+\$/ test-kitchen-1.23.2/features/kitchen_login_command.feature0000644000004100000410000000301113377651062023744 0ustar www-datawww-dataFeature: Logging into a Kitchen instance In order to iterate, explore, and debug As a crafty developer I want to run a command that will give me a terminal session Background: Given a file named ".kitchen.yml" with: """ --- driver: name: dummy transport: name: dummy provisioner: name: dummy platforms: - name: flebian suites: - name: default - name: full """ And I successfully run `kitchen create default-flebian` @spawn Scenario: Logging in to an instance When I run `kitchen login default-flebian` Then the output should contain: """ Remote login not supported in Kitchen::Transport::Dummy::Connection. """ And the exit status should not be 0 @spawn Scenario: Attempting to log into a non-created instance When I run `kitchen login full-flebian` Then the output should contain: """ Instance has not yet been created """ And the exit status should not be 0 @spawn Scenario: Attempting to log into a non-existent instance When I run `kitchen login nope` Then the output should contain "No instances for regex `nope'" And the exit status should not be 0 @spawn Scenario: Attempting to log into an instance with an overly fuzzy match When I run `kitchen login flebian` Then the output should contain: """ Argument `flebian' returned multiple results: * default-flebian * full-flebian """ And the exit status should not be 0 test-kitchen-1.23.2/appveyor.yml0000644000004100000410000000225013377651062016632 0ustar www-datawww-dataversion: "master-{build}" os: Visual Studio 2017 platform: - x64 environment: machine_user: test_user machine_pass: Pass@word1 machine_port: 5985 KITCHEN_YAML: .kitchen.appveyor.yml SSL_CERT_FILE: c:\projects\test_kitchen\certs.pem matrix: - ruby_version: "24-x64" clone_folder: c:\projects\test_kitchen clone_depth: 1 skip_tags: true branches: only: - master install: - ps: net user /add $env:machine_user $env:machine_pass - ps: net localgroup administrators $env:machine_user /add - ps: $env:PATH="C:\Ruby$env:ruby_version\bin;$env:PATH" - ps: Write-Host $env:path - appveyor DownloadFile http://curl.haxx.se/ca/cacert.pem -FileName c:\projects\test_kitchen\certs.pem - set SSL_CERT_FILE=c:\projects\test_kitchen\certs.pem - gem update --system || gem update --system || gem update --system - update_rubygems - gem install bundler --force - ruby --version - gem --version - bundle --version build_script: - bundle install --without guard integration test_script: - SET SPEC_OPTS=--format progress - bundle exec rake unit - bundle exec rake quality - bundle install --with integration - bundle exec kitchen verify windows test-kitchen-1.23.2/MAINTAINERS.md0000644000004100000410000000200113377651062016330 0ustar www-datawww-data# Maintainers This file lists how the Test Kitchen project is maintained. When making changes to the system, this file tells you who needs to review your patch. You need a :+1: vote from at least 2 members of the project team listed below. Additionally, you need to not receive a veto from the Lieutenant or the Project Lead. ## Project Lead * [Fletcher Nichol](https://github.com/fnichol) ## Lieutenants * [Seth Thomas](https://github.com/cheeseplus) ## Maintainers * [Adam Leff](https://github.com/adamleff) * [Chris Hartmann](https://github.com/chris-rock) * [Jennifer Davis](https://github.com/iennae) * [Kimball Johnson](https://github.com/drrk) * [Lamont Granquist](https://github.com/lamont-granquist) * [Matt Wrock](https://github.com/mwrock) * [Noah Kantrowitz](https://github.com/coderanger) * [Robb Kidd](https://github.com/robbkidd) * [Sean Omeara](http://github.com/someara) * [Steven Murawski](http://github.com/smurawski) * [Salim Afiune](http://github.com/afiune) * [Tim Smith](http://github.com/tas50) test-kitchen-1.23.2/.github/0000755000004100000410000000000013377651062015603 5ustar www-datawww-datatest-kitchen-1.23.2/.github/lock.yml0000644000004100000410000000002213377651062017250 0ustar www-datawww-datadaysUntilLock: 60 test-kitchen-1.23.2/.github/ISSUE_TEMPLATE.md0000644000004100000410000000310213377651062020304 0ustar www-datawww-data## Description Briefly describe the issue ## Kitchen Version Tell us which version of test-kitchen you are using (`kitchen --version`). ```ruby # Copy-paste your results here ``` ## ChefDK Version If you are running test-kitchen via ChefDK, `chef --version` will provide additional relevent version details. ```ruby # Copy-paste your results here ``` ## Ruby Version If you are not using test-kitchen via ChefDK, please provide the output of ruby --version. ```ruby # Copy-paste your results here ``` ## Platform Version Tell us which Operating System distribution and version test-kitchen is running on. ## Replication Case Tell us what steps to take to replicate your problem. See [How to create a Minimal, Complete, and Verifiable example](https://stackoverflow.com/help/mcve) for information on how to create a good replication case. ## Kitchen Output The relevant output of the test-kitchen run or a link to a gist of the entire run, if there is one. The debug output (kitchen -l debug) may be useful, but please link to a gist, or truncate it. Please do not paste the full run out put here. If the full output would be useful, link to it in a gist. ```ruby # Copy-paste your results here ``` ## Kitchen Diagnose Please include the output of `kitchen diagnose --all` or `kitchen diagnose --loader` (if the first failed). This can be helpful in troubleshooting, so please include it in a gist. ## NOTE: This repository is for core issues with the Test-Kitchen framework, so if the issue is with a plugin you may be referred to file the issue with the appropriate plugin.test-kitchen-1.23.2/support/0000755000004100000410000000000013377651062015757 5ustar www-datawww-datatest-kitchen-1.23.2/support/chef-client-zero.rb0000755000004100000410000000364413377651062021454 0ustar www-datawww-data#!/usr/bin/env ruby # -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "rubygems" require "chef/config" require "chef_zero/server" require "chef/chef_fs/chef_fs_data_store" require "chef/chef_fs/config" require "English" require "fileutils" # Bust out of our self-imposed sandbox before running chef-client so # gems installed via gem_package land in Chef's GEM_HOME. # # https://github.com/opscode/test-kitchen/issues/240 # ENV["GEM_HOME"] = ENV["GEM_PATH"] = ENV["GEM_CACHE"] = nil class ChefClientZero def self.start new.run end def run create_chef_zero_server run_chef_client end private def create_chef_zero_server Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(repo_path) chef_fs = Chef::ChefFS::Config.new.local_fs chef_fs.write_pretty_json = true @server = ChefZero::Server.new( generate_real_keys: false, data_store: Chef::ChefFS::ChefFSDataStore.new(chef_fs) ) puts "-----> Starting Chef Zero server in #{chef_fs.fs_description}" @server.start_background at_exit do puts "-----> Shutting down Chef Zero server" @server.stop end end def repo_path ENV.fetch("CHEF_REPO_PATH", Dir.pwd) end def run_chef_client system("chef-client", *ARGV) raise if $CHILD_STATUS != 0 end end ChefClientZero.start test-kitchen-1.23.2/support/busser_install_command.sh0000644000004100000410000000105313377651062023041 0ustar www-datawww-data$gem list --no-versions | grep "^busser" 2>&1 >/dev/null if test $? -ne 0; then echo "-----> Installing Busser ($version)" $gem install $gem_install_args else echo "-----> Busser installation detected ($version)" fi if test ! -f "$BUSSER_ROOT/bin/busser"; then $busser setup fi for plugin in $plugins; do $gem list --no-versions | grep "^$plugin$" 2>&1 >/dev/null if test $? -ne 0; then echo "-----> Installing Busser plugin: $plugin" $busser plugin install $plugin else echo "-----> Busser plugin detected: $plugin" fi done test-kitchen-1.23.2/support/busser_install_command.ps10000644000004100000410000000100413377651062023126 0ustar www-datawww-dataif ((& "$ruby" "$gem" list busser -i) -ne "true") { Write-Host "-----> Installing Busser ($version)`n" & "$ruby" "$gem" install $gem_install_args.Split() 2>&1 } else { Write-Host "-----> Busser installation detected ($version)`n" } if (-Not (Test-Path "$busser")) { $gem_bindir = & "$ruby" -rrubygems -e "puts Gem.bindir.dup.gsub('/', '\\')" & "$ruby" "$gem_bindir\busser" setup --type bat 2>&1 } Write-Host " Installing Busser plugins: $plugins`n" & "$busser" plugin install $plugins.Split() 2>&1 test-kitchen-1.23.2/support/chef_base_init_command.ps10000644000004100000410000000075513377651062023033 0ustar www-datawww-dataFunction Delete-AllDirs($dirs) { $dirs | ForEach-Object { if (Test-Path ($path = Unresolve-Path $_)) { Remove-Item $path -Recurse -Force } } } Function Unresolve-Path($p) { if ($p -eq $null) { return $null } else { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p) } } Function Make-RootPath($p) { $p = Unresolve-Path $p if (-Not (Test-Path $p)) { New-Item $p -ItemType directory | Out-Null } } Delete-AllDirs $dirs Make-RootPath $root_path test-kitchen-1.23.2/support/chef_zero_prepare_command_legacy.sh0000644000004100000410000000073113377651062025020 0ustar www-datawww-data# we are installing the first version of chef that bundled chef-zero in order # to get chef-zero and Chef::ChefFS only. The version of Chef that gets run # will be the installed omnibus package. Yep, this is funky :) $gem list chef-zero -i 2>&1 >/dev/null if test $? -ne 0 ; then echo ">>>>>> Attempting to use chef-zero with old version of Chef" echo "-----> Installing chef zero dependencies" $gem install chef --version 11.8.0 --no-ri --no-rdoc --conservative fi test-kitchen-1.23.2/support/chef_base_install_command.ps10000644000004100000410000000534113377651062023532 0ustar www-datawww-data$ErrorActionPreference = "stop" Function Check-UpdateChef($root, $version) { if (-Not (Test-Path $root)) { return $true } elseif ("$version" -eq "true") { return $false } elseif ("$version" -eq "latest") { return $true } Try { $chef_version = (Get-Content $root\version-manifest.txt | select-object -first 1) } Catch { Try { $chef_version = (& $root\bin\chef-solo.bat -v) } Catch { $chef_version = " " } } if ($chef_version.split(" ", 2)[1].StartsWith($version)) { return $false } else { return $true } } Function Get-ChefMetadata($url) { Try { $response = ($c = Make-WebClient).DownloadString($url) } Finally { if ($c -ne $null) { $c.Dispose() } } $md = ConvertFrom-StringData $response.Replace("`t", "=") return @($md.url, $md.md5) } Function Get-MD5Sum($src) { Try { $c = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider $bytes = $c.ComputeHash(($in = (Get-Item $src).OpenRead())) return ([System.BitConverter]::ToString($bytes)).Replace("-", "").ToLower() } Finally { if (($c -ne $null) -and ($c.GetType().GetMethod("Dispose") -ne $null)) { $c.Dispose() }; if ($in -ne $null) { $in.Dispose() } } } Function Download-Chef($md_url, $dst) { $url, $md5 = Get-ChefMetadata $md_url Try { Log "Downloading package from $url" ($c = Make-WebClient).DownloadFile($url, $dst) Log "Download complete." } Finally { if ($c -ne $null) { $c.Dispose() } } if (($dmd5 = Get-MD5Sum $dst) -eq $md5) { Log "Successfully verified $dst" } else { throw "MD5 for $dst $dmd5 does not match $md5" } } Function Install-Chef($msi) { Log "Installing Chef package $msi" $p = Start-Process -FilePath "msiexec.exe" -ArgumentList "/qn /i $msi" -Passthru -Wait if ($p.ExitCode -ne 0) { throw "msiexec was not successful. Received exit code $($p.ExitCode)" } Remove-Item $msi -Force Log "Installation complete" } Function Log($m) { Write-Host " $m`n" } Function Make-WebClient { $proxy = New-Object -TypeName System.Net.WebProxy $proxy.Address = $env:http_proxy $client = New-Object -TypeName System.Net.WebClient $client.Proxy = $proxy return $client } Function Unresolve-Path($p) { if ($p -eq $null) { return $null } else { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p) } } Try { $chef_omnibus_root = Unresolve-Path $chef_omnibus_root $msi = Unresolve-Path $msi if (Check-UpdateChef $chef_omnibus_root $version) { Write-Host "-----> Installing Chef package ($pretty_version)`n" Download-Chef "$chef_metadata_url" $msi Install-Chef $msi } else { Write-Host "-----> Chef package installation detected ($pretty_version)`n" } Catch { Write-Error ($_ | ft -Property * | out-string) -ErrorAction Continue exit 1 } test-kitchen-1.23.2/support/chef_base_install_command.sh0000644000004100000410000001173313377651062023443 0ustar www-datawww-datatmp_stderr="/tmp/stderr"; # capture_tmp_stderr SOURCE capture_tmp_stderr() { # spool up $tmp_stderr from all the commands we called if test -f "$tmp_stderr"; then output="`cat $tmp_stderr`"; stderr_results="${stderr_results}\nSTDERR from $1:\n\n${output}\n"; rm $tmp_stderr; fi } # do_curl URL FILENAME do_curl() { echo "Trying curl..."; curl -sL -D "$tmp_stderr" "$1" > "$2"; ec=$?; # check for 404 grep "404 Not Found" "$tmp_stderr" 2>&1 >/dev/null; if test $? -eq 0; then http_404_error "$1"; fi # check for bad return status or empty output if test $ec -ne 0 || test ! -s "$2"; then capture_tmp_stderr "curl"; return 1; else echo "Download complete."; return 0; fi } # do_download URL FILENAME do_download() { echo "Downloading ${1} to file ${2}"; exists wget; if test $? -eq 0; then do_wget "$1" "$2" && return 0; fi exists curl; if test $? -eq 0; then do_curl "$1" "$2" && return 0; fi exists fetch; if test $? -eq 0; then do_fetch "$1" "$2" && return 0; fi exists python; if test $? -eq 0; then do_python "$1" "$2" && return 0; fi exists perl; if test $? -eq 0; then do_perl "$1" "$2" && return 0; fi unable_to_download "$1" "$2"; } # do_fetch URL FILENAME do_fetch() { echo "Trying fetch..."; fetch -o "$2" "$1" 2>"$tmp_stderr"; ec=$?; # check for 404 grep "Not Found" "$tmp_stderr" 2>&1 >/dev/null; if test $? -eq 0; then http_404_error "$1"; fi # check for bad return status or empty output if test $ec -ne 0 || test ! -s "$2"; then capture_tmp_stderr "fetch"; return 1; else echo "Download complete."; return 0; fi } # do_perl URL FILENAME do_perl() { echo "Trying perl..."; perl -e "use LWP::Simple; getprint(\$ARGV[0]);" "$1" > "$2" 2>"$tmp_stderr"; ec=$?; # check for 404 grep "404 Not Found" "$tmp_stderr" 2>&1 >/dev/null; if test $? -eq 0; then http_404_error "$1"; fi # check for bad return status or empty output if test $ec -ne 0 || test ! -s "$2"; then capture_tmp_stderr "perl"; return 1; else echo "Download complete."; return 0; fi } # do_python URL FILENAME do_python() { echo "Trying python..."; python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" 2>"$tmp_stderr"; ec=$?; # check for 404 grep "HTTP Error 404" "$tmp_stderr" 2>&1 >/dev/null; if test $? -eq 0; then http_404_error "$1"; fi # check for bad return status or empty output if test $ec -ne 0 || test ! -s "$2"; then capture_tmp_stderr "python"; return 1; else echo "Download complete."; return 0; fi } # do_wget URL FILENAME do_wget() { echo "Trying wget..."; wget -O "$2" "$1" 2>"$tmp_stderr"; ec=$?; # check for 404 grep "ERROR 404" "$tmp_stderr" 2>&1 >/dev/null; if test $? -eq 0; then http_404_error "$1"; fi # check for bad return status or empty output if test $ec -ne 0 || test ! -s "$2"; then capture_tmp_stderr "wget"; return 1; else echo "Download complete."; return 0; fi } # exists COMMAND exists() { if command -v "$1" >/dev/null 2>&1; then return 0; else return 1; fi } # http_404_error URL http_404_error() { echo ">>>>>> Downloading ${1} resulted in an HTTP/404, aborting"; exit 40; } # should_update_chef ROOT VERSION should_update_chef() { if test ! -d "$1"; then return 0; elif test "$2" = "true"; then return 1; elif test "$2" = "latest"; then return 0; fi if test -f "${1}/version-manifest.txt"; then chef_version="`head -n 1 ${1}/version-manifest.txt | cut -d \" \" -f 2`"; else chef_version="`${1}/bin/chef-solo -v | cut -d \" \" -f 2`"; fi echo "$chef_version" | grep "^${2}" 2>&1 >/dev/null; if test $? -eq 0; then return 1; else echo "${2}" | grep "^$chef_version" 2>&1 >/dev/null; if test $? -eq 0; then return 1; else return 0; fi fi } # unable_to_download URL FILE unable_to_download() { echo "Unable to download $1 to $2, aborting"; if test "x${stderr_results}" != "x"; then echo "\nDEBUG OUTPUT FOLLOWS:\n${stderr_results}"; fi exit 10; } # main main() { should_update_chef "$chef_omnibus_root" "$version" if test $? -eq 0; then echo "-----> Installing Chef package (${pretty_version})"; # solaris 10 lacks recent enough credentials, so http url is used platform="`/usr/bin/uname -s 2>/dev/null`"; platform_version="`/usr/bin/uname -r 2>/dev/null`"; if test "x${platform}" = "xSunOS" && test "x${platform_version}" = "x5.10"; then chef_omnibus_url=`echo "$chef_omnibus_url" | sed -e "s/https/http/"`; fi do_download "$chef_omnibus_url" /tmp/install.sh; $sudo_sh /tmp/install.sh $install_flags; else echo "-----> Chef package installation detected (${pretty_version})"; fi } # augment path in an attempt to find a download program PATH="${PATH}:/opt/local/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/sfw/bin"; export PATH; main test-kitchen-1.23.2/support/download_helpers.sh0000644000004100000410000000465613377651062021657 0ustar www-datawww-data# Check whether a command exists - returns 0 if it does, 1 if it does not exists() { if command -v $1 >/dev/null 2>&1 then return 0 else return 1 fi } # do_wget URL FILENAME do_wget() { echo "trying wget..." wget -O "$2" "$1" 2>/tmp/stderr # check for bad return status test $? -ne 0 && return 1 # check for 404 or empty file grep "ERROR 404" /tmp/stderr 2>&1 >/dev/null if test $? -eq 0 || test ! -s "$2"; then return 1 fi return 0 } # do_curl URL FILENAME do_curl() { echo "trying curl..." curl -L "$1" > "$2" # check for bad return status [ $? -ne 0 ] && return 1 # check for bad output or empty file grep "The specified key does not exist." "$2" 2>&1 >/dev/null if test $? -eq 0 || test ! -s "$2"; then return 1 fi return 0 } # do_fetch URL FILENAME do_fetch() { echo "trying fetch..." fetch -o "$2" "$1" 2>/tmp/stderr # check for bad return status test $? -ne 0 && return 1 return 0 } # do_perl URL FILENAME do_perl() { echo "trying perl..." perl -e "use LWP::Simple; getprint($ARGV[0]);" "$1" > "$2" # check for bad return status test $? -ne 0 && return 1 # check for bad output or empty file # grep "The specified key does not exist." "$2" 2>&1 >/dev/null # if test $? -eq 0 || test ! -s "$2"; then # unable_to_retrieve_package # fi return 0 } # do_python URL FILENAME do_python() { echo "trying python..." python -c "import sys,urllib2 ; sys.stdout.write(urllib2.urlopen(sys.argv[1]).read())" "$1" > "$2" # check for bad return status test $? -ne 0 && return 1 # check for bad output or empty file #grep "The specified key does not exist." "$2" 2>&1 >/dev/null #if test $? -eq 0 || test ! -s "$2"; then # unable_to_retrieve_package #fi return 0 } # do_download URL FILENAME do_download() { PATH=/opt/local/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin export PATH echo "downloading $1" echo " to file $2" # we try all of these until we get success. # perl, in particular may be present but LWP::Simple may not be installed if exists wget; then do_wget $1 $2 && return 0 fi if exists curl; then do_curl $1 $2 && return 0 fi if exists fetch; then do_fetch $1 $2 && return 0 fi if exists perl; then do_perl $1 $2 && return 0 fi if exists python; then do_python $1 $2 && return 0 fi echo ">>>>>> wget, curl, fetch, perl or python not found on this instance." return 16 } test-kitchen-1.23.2/support/chef-client-fail-if-update-handler.rb0000644000004100000410000000077713377651062024700 0ustar www-datawww-data# Handler to kill the run if any resource is updated class UpdatedResources < ::Chef::Handler def report if updated_resources.size > 0 puts "First chef run should have reached a converged state." puts "Resources updated in a second chef-client run:" updated_resources.each do |r| puts "- #{r}" end # exit 203 # chef handler catch Exception instead of StandardException Process.kill("KILL", Process.pid) end end end report_handlers << UpdatedResources.new test-kitchen-1.23.2/support/chef_zero_prepare_command_legacy.ps10000644000004100000410000000073413377651062025114 0ustar www-datawww-data# we are installing the first version of chef that bundled chef-zero in order # to get chef-zero and Chef::ChefFS only. The version of Chef that gets run # will be the installed omnibus package. Yep, this is funky :) if ((& "$gem" list chef-zero -i) -ne "true") { Write-Host ">>>>>> Attempting to use chef-zero with old version of Chef`n" Write-Host "-----> Installing chef zero dependencies`n" & "$gem" install chef --version 11.8.0 --no-ri --no-rdoc --conservative } test-kitchen-1.23.2/support/chef_base_init_command.sh0000644000004100000410000000004713377651062022734 0ustar www-datawww-data$sudo_rm -rf $dirs mkdir -p $root_path test-kitchen-1.23.2/support/dummy-validation.pem0000644000004100000410000000321713377651062021750 0ustar www-datawww-data-----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA0sOY9tHvVtLZ6xmVmH8d8LrRrNcWOXbrvvCrai+T3GtRvRSL hksLrpOpD0L9EHM6NdThNF/eGA9Oq+UKAe6yXR0hwsKuxKXqQ8SEmlhZZ9GiuggD B/zYD3ItB6SGpdkRe7kQqTChQyrIXqbRkJqxoTXLyeJDF0sCyTdp3L8IZCUWodM8 oV9TlQBJHYtG1gLUwIi8kcMVEoCn2Q8ltCj0/ftnwhTtwO52RkWA0uYOLGVayHsL SCFfx+ACWPU/oWCwW5/KBqb3veTv0aEg/nh0QsFzRLoTx6SRFI5dT2Nf8iiJe4WC UG8WKEB2G8QPnxsxfOPYDBdTJ4CXEi2e+z41VQIDAQABAoIBAALhqbW2KQ+G0nPk ZacwFbi01SkHx8YBWjfCEpXhEKRy0ytCnKW5YO+CFU2gHNWcva7+uhV9OgwaKXkw KHLeUJH1VADVqI4Htqw2g5mYm6BPvWnNsjzpuAp+BR+VoEGkNhj67r9hatMAQr0I itTvSH5rvd2EumYXIHKfz1K1SegUk1u1EL1RcMzRmZe4gDb6eNBs9Sg4im4ybTG6 pPIytA8vBQVWhjuAR2Tm+wZHiy0Az6Vu7c2mS07FSX6FO4E8SxWf8idaK9ijMGSq FvIS04mrY6XCPUPUC4qm1qNnhDPpOr7CpI2OO98SqGanStS5NFlSFXeXPpM280/u fZUA0AECgYEA+x7QUnffDrt7LK2cX6wbvn4mRnFxet7bJjrfWIHf+Rm0URikaNma h0/wNKpKBwIH+eHK/LslgzcplrqPytGGHLOG97Gyo5tGAzyLHUWBmsNkRksY2sPL uHq6pYWJNkqhnWGnIbmqCr0EWih82x/y4qxbJYpYqXMrit0wVf7yAgkCgYEA1twI gFaXqesetTPoEHSQSgC8S4D5/NkdriUXCYb06REcvo9IpFMuiOkVUYNN5d3MDNTP IdBicfmvfNELvBtXDomEUD8ls1UuoTIXRNGZ0VsZXu7OErXCK0JKNNyqRmOwcvYL JRqLfnlei5Ndo1lu286yL74c5rdTLs/nI2p4e+0CgYB079ZmcLeILrmfBoFI8+Y/ gJLmPrFvXBOE6+lRV7kqUFPtZ6I3yQzyccETZTDvrnx0WjaiFavUPH27WMjY01S2 TMtO0Iq1MPsbSrglO1as8MvjB9ldFcvp7gy4Q0Sv6XT0yqJ/S+vo8Df0m+H4UBpU f5o6EwBSd/UQxwtZIE0lsQKBgQCswfjX8Eg8KL/lJNpIOOE3j4XXE9ptksmJl2sB jxDnQYoiMqVO808saHVquC/vTrpd6tKtNpehWwjeTFuqITWLi8jmmQ+gNTKsC9Gn 1Pxf2Gb67PqnEpwQGln+TRtgQ5HBrdHiQIi+5am+gnw89pDrjjO5rZwhanAo6KPJ 1zcPNQKBgQDxFu8v4frDmRNCVaZS4f1B6wTrcMrnibIDlnzrK9GG6Hz1U7dDv8s8 Nf4UmeMzDXjlPWZVOvS5+9HKJPdPj7/onv8B2m18+lcgTTDJBkza7R1mjL1Cje/Z KcVGsryKN6cjE7yCDasnA7R2rVBV/7NWeJV77bmzT5O//rW4yIfUIg== -----END RSA PRIVATE KEY----- test-kitchen-1.23.2/.kitchen.proxy.yml0000644000004100000410000000117113377651062017651 0ustar www-datawww-data--- driver: name: proxy host: localhost reset_command: "echo hello" port: <%= ENV["machine_port"] %> username: <%= ENV["machine_user"] %> password: <%= ENV["machine_pass"] %> provisioner: name: chef_zero platforms: - name: ubuntu-16.04 verifier: name: busser suites: # We run a different test based upon the configuration - basically each TK # test asserts that the expected configuration is transfered to the target # machine - name: default run_list: - recipe[test::tk_<%= ENV['TK_SUITE_NAME'] %>_test] # TODO a machine which doesn't setup squid and doesn't copy across # proxy settings test-kitchen-1.23.2/Berksfile0000644000004100000410000000014713377651062016076 0ustar www-datawww-datasource "https://supermarket.chef.io" cookbook "test_cookbook", path: "./test/cookbooks/test_cookbook" test-kitchen-1.23.2/ECOSYSTEM.md0000644000004100000410000003242113377651062016142 0ustar www-datawww-data# Community and Ecosystem plugins Here is collection of possible extensions to the base test-kitchen application: The following are in the test-kitchen organization [kitchen-ec2][ec2] | [kitchen-digitalocean][do] | [kitchen-openstack][open] | [kitchen-rackspace][rs] | [kitchen-google][google] | [kitchen-vagrant][vagrant] ---- | ---- | ---- | ---- | ---- | ---- [![Status](https://travis-ci.org/test-kitchen/kitchen-ec2.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-ec2)| [![Status](https://travis-ci.org/test-kitchen/kitchen-digitalocean.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-digitalocean) | [![Status](https://travis-ci.org/test-kitchen/kitchen-openstack.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-openstack) | [![Status](https://travis-ci.org/test-kitchen/kitchen-rackspace.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-rackspace) | [![Status](https://travis-ci.org/test-kitchen/kitchen-google.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-google) | [![Status](https://travis-ci.org/test-kitchen/kitchen-vagrant.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-vagrant) [![Gem Version](https://badge.fury.io/rb/kitchen-ec2.svg)](http://badge.fury.io/rb/kitchen-ec2) | [![Gem Version](https://badge.fury.io/rb/kitchen-digitalocean.svg)](http://badge.fury.io/rb/kitchen-digitalocean) | [![Gem Version](https://badge.fury.io/rb/kitchen-openstack.svg)](http://badge.fury.io/rb/kitchen-openstack) | [![Gem Version](https://badge.fury.io/rb/kitchen-rackspace.svg)](http://badge.fury.io/rb/kitchen-rackspace) | [![Gem Version](https://badge.fury.io/rb/kitchen-google.svg)](http://badge.fury.io/rb/kitchen-google) | [![Gem Version](https://badge.fury.io/rb/kitchen-vagrant.svg)](http://badge.fury.io/rb/kitchen-vagrant) [kitchen-dsc][dsc] | [kitchen-pester][pester] | [kitchen-joyent][joyent] | [kitchen-opennebula][opennebula] | [kitchen-hyperv][hyperv] | [kitchen-cloudstack][cloudstack] | ---- | ---- | ---- | ---- | ---- | ---- | [![Status](https://travis-ci.org/test-kitchen/kitchen-dsc.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-dsc)| [![Status](https://travis-ci.org/test-kitchen/kitchen-pester.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-pester) | [![Status](https://travis-ci.org/test-kitchen/kitchen-joyent.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-joyent) | [![Status](https://travis-ci.org/test-kitchen/kitchen-opennebula.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-opennebula) | [![Status](https://travis-ci.org/test-kitchen/kitchen-hyperv.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-hyperv) | [![Status](https://travis-ci.org/test-kitchen/kitchen-cloudstack.svg?branch=master)](https://travis-ci.org/test-kitchen/kitchen-cloudstack) [![Gem Version](https://badge.fury.io/rb/kitchen-dsc.svg)](http://badge.fury.io/rb/kitchen-dsc) | [![Gem Version](https://badge.fury.io/rb/kitchen-pester.svg)](http://badge.fury.io/rb/kitchen-pester) | [![Gem Version](https://badge.fury.io/rb/kitchen-joyent.svg)](http://badge.fury.io/rb/kitchen-joyent) | [![Gem Version](https://badge.fury.io/rb/kitchen-opennebula.svg)](http://badge.fury.io/rb/kitchen-opennebula) | [![Gem Version](https://badge.fury.io/rb/kitchen-hyperv.svg)](http://badge.fury.io/rb/kitchen-hyperv) | [![Gem Version](https://badge.fury.io/rb/kitchen-cloudstack.svg)](http://badge.fury.io/rb/kitchen-cloudstack) The following are written by the [chef][chef] company. [kitchen-appbundle-updater][appbundle-updater] | [kitchen-inspec][inspec] ---- | ---- | [![Status](https://travis-ci.org/chef/kitchen-appbundle-updater.svg?branch=master)](https://travis-ci.org/chef/kitchen-appbundle-updater)| [![Status](https://travis-ci.org/chef/kitchen-inspec.svg?branch=master)](https://travis-ci.org/chef/kitchen-inspec) | [![Gem Version](https://badge.fury.io/rb/kitchen-appbundle-updater.svg)](http://badge.fury.io/rb/kitchen-appbundle-updater) | [![Gem Version](https://badge.fury.io/rb/kitchen-inspec.svg)](http://badge.fury.io/rb/kitchen-inspec) | [kitchen-vra][vra] | [kitchen-vro][vro] | [kitchen-vcair][vcair] | [kitchen-oraclecloud][oracle] | [kitchen-ssh-cisco][sadpanda] | [kitchen-vcenter][vcenter] ---- | ---- | ---- | ---- | ---- | ---- | [![Status](https://travis-ci.org/chef-partners/kitchen-vra.svg?branch=master)](https://travis-ci.org/chef-partners/kitchen-vra)| [![Status](https://travis-ci.org/chef-partners/kitchen-vro.svg?branch=master)](https://travis-ci.org/chef-partners/kitchen-vro) | [![Status](https://travis-ci.org/chef-partners/kitchen-vcair.svg?branch=master)](https://travis-ci.org/chef-partners/kitchen-vcair) | [![Status](https://travis-ci.org/chef-partners/kitchen-oraclecloud.svg?branch=master)](https://travis-ci.org/chef-partners/kitchen-oraclecloud) | [![Status](https://travis-ci.org/chef-partners/kitchen-ssh-cisco.svg?branch=master)](https://travis-ci.org/chef-partners/kitchen-ssh-cisco) | [![Status](https://travis-ci.org/chef/kitchen-vcenter.svg?branch=master)](https://travis-ci.org/chef/kitchen-vcenter) [![Gem Version](https://badge.fury.io/rb/kitchen-vra.svg)](http://badge.fury.io/rb/kitchen-vra) | [![Gem Version](https://badge.fury.io/rb/kitchen-vro.svg)](http://badge.fury.io/rb/kitchen-vro) | [![Gem Version](https://badge.fury.io/rb/kitchen-vcair.svg)](http://badge.fury.io/rb/kitchen-vcair) | [![Gem Version](https://badge.fury.io/rb/kitchen-oraclecloud.svg)](http://badge.fury.io/rb/kitchen-oraclecloud) | [![Gem Version](https://badge.fury.io/rb/kitchen-ssh-cisco.svg)](http://badge.fury.io/rb/kitchen-ssh-cisco) | [![Gem Version](https://badge.fury.io/rb/kitchen-vcenter.svg)](http://badge.fury.io/rb/kitchen-vcenter) The following are community driven plugins. [kitchen-sync][sync] | [kitchen-ansible][ansible] | [kitchen-puppet][puppet] | [kitchen-salt][salt] | [kitchen-dokken][dokken] | [kitchen-lxc][lxc] ---- | ---- | ---- | ---- | ---- | ---- [![Status](https://travis-ci.org/coderanger/kitchen-sync.svg?branch=master)](https://travis-ci.org/coderanger/kitchen-sync)| [![Status](https://travis-ci.org/neillturner/kitchen-ansible.svg?branch=master)](https://travis-ci.org/neillturner/kitchen-ansible) | [![Status](https://travis-ci.org/neillturner/kitchen-puppet.svg?branch=master)](https://travis-ci.org/neillturner/kitchen-puppet) | [![Status](https://travis-ci.org/kitchen-salt/kitchen-salt.svg?branch=master)](https://travis-ci.org/kitchen-salt/kitchen-salt) | [![Status](https://travis-ci.org/someara/kitchen-dokken.svg?branch=master)](https://travis-ci.org/someara/kitchen-dokken) | [![Status](https://travis-ci.org/chrisroberts/kitchen-lxc.svg?branch=master)](https://travis-ci.org/chrisroberts/kitchen-lxc) [![Gem Version](https://badge.fury.io/rb/kitchen-sync.svg)](http://badge.fury.io/rb/kitchen-sync) | [![Gem Version](https://badge.fury.io/rb/kitchen-ansible.svg)](http://badge.fury.io/rb/kitchen-ansible) | [![Gem Version](https://badge.fury.io/rb/kitchen-puppet.svg)](http://badge.fury.io/rb/kitchen-puppet) | [![Gem Version](https://badge.fury.io/rb/kitchen-salt.svg)](http://badge.fury.io/rb/kitchen-salt) | [![Gem Version](https://badge.fury.io/rb/kitchen-dokken.svg)](http://badge.fury.io/rb/kitchen-dokken) | [![Gem Version](https://badge.fury.io/rb/kitchen-lxc.svg)](http://badge.fury.io/rb/kitchen-lxc) [kitchen-nodes][nodes] | [kitchen-zcloudjp][zcloudjp] | [kitchen-bluebox][bluebox] | [kitchen-softlayer][softlayer] | [kitchen-linode][linode] | [kitchen-docker][docker] ---- | ---- | ---- | ---- | ---- | ---- [![Status](https://travis-ci.org/mwrock/kitchen-nodes.svg?branch=master)](https://travis-ci.org/mwwrock/kitchen-nodes) | [![Status](https://travis-ci.org/higanworks/kitchen-zcloudjp.svg?branch=master)](https://travis-ci.org/higanworks/kitchen-zcloudjp) | [![Status](https://travis-ci.org/blueboxgroup/kitchen-bluebox.svg?branch=master)](https://travis-ci.org/blueboxgroup/kitchen-bluebox) | [![Status](https://travis-ci.org/neillturner/kitchen-softlayer.svg?branch=master)](https://travis-ci.org/neillturner/kitchen-softlayer) | [![Status](https://travis-ci.org/ssplatt/kitchen-linode.svg?branch=master)](https://travis-ci.org/ssplatt/kitchen-linode) | [![Status](https://travis-ci.org/ssplatt/kitchen-docker.svg?branch=master)](https://travis-ci.org/portertech/kitchen-docker) [![Gem Version](https://badge.fury.io/rb/kitchen-nodes.svg)](http://badge.fury.io/rb/kitchen-nodes) | [![Gem Version](https://badge.fury.io/rb/kitchen-zcloudjp.svg)](http://badge.fury.io/rb/kitchen-zcloudjp) | [![Gem Version](https://badge.fury.io/rb/kitchen-bluebox.svg)](http://badge.fury.io/rb/kitchen-bluebox) | [![Gem Version](https://badge.fury.io/rb/kitchen-softlayer.svg)](http://badge.fury.io/rb/kitchen-softlayer) | [![Gem Version](https://badge.fury.io/rb/kitchen-linode.svg)](http://badge.fury.io/rb/kitchen-linode) | [![Gem Version](https://badge.fury.io/rb/kitchen-docker.svg)](http://badge.fury.io/rb/kitchen-docker) [kitchen-qemu][qemu] | [kitchen-cfengine][cfengine] | [kitchen-cloudformation][cloudformation] | [kitchen-wpar][wpar] | [kitchen-powervc][powervc] | [kitchen-terraform][terraform] ---- | ---- | ---- | ---- | ---- | ---- [![Status](https://travis-ci.org/esmil/kitchen-qemu.svg?branch=master)](https://travis-ci.org/esmil/kitchen-qemu)| [![Status](https://travis-ci.org/nmische/kitchen-cfengine.svg?branch=master)](https://travis-ci.org/nmische/kitchen-cfengine) | [![Status](https://travis-ci.org/neillturner/kitchen-cloudformation.svg?branch=master)](https://travis-ci.org/neillturner/kitchen-cloudformation) | [![Status](https://travis-ci.org/adejoux/kitchen-wpar.svg?branch=master)](https://travis-ci.org/adejoux/kitchen-wpar) | [![Status](https://travis-ci.org/chmod666org/kitchen-powervc.svg?branch=master)](https://travis-ci.org/chmod666org/kitchen-powervc) | [![Status](https://travis-ci.org/newcontext-oss/kitchen-terraform.svg?branch=master)](https://travis-ci.org/newcontext-oss/kitchen-terraform) [![Gem Version](https://badge.fury.io/rb/kitchen-qemu.svg)](http://badge.fury.io/rb/kitchen-qemu) | [![Gem Version](https://badge.fury.io/rb/kitchen-cfengine.svg)](http://badge.fury.io/rb/kitchen-cfengine) | [![Gem Version](https://badge.fury.io/rb/kitchen-cloudformation.svg)](http://badge.fury.io/rb/kitchen-cloudformation) | [![Gem Version](https://badge.fury.io/rb/kitchen-wpar.svg)](http://badge.fury.io/rb/kitchen-wpar) | [![Gem Version](https://badge.fury.io/rb/kitchen-powervc.svg)](http://badge.fury.io/rb/kitchen-powervc) | [![Gem Version](https://badge.fury.io/rb/kitchen-terraform.svg)](http://badge.fury.io/rb/kitchen-terraform) [kitchen-azurerm][azurerm] | [kitchen-sparkleformation][sparkleformation] ---- | ---- [![Status](https://travis-ci.org/pendrica/kitchen-azurerm.svg?branch=master)](https://travis-ci.org/pendrica/kitchen-azurerm) | [![Status](https://travis-ci.org/devkid/kitchen-sparkleformation.svg?branch=master)](https://travis-ci.org/devkid/kitchen-sparkleformation) [![Gem Version](https://badge.fury.io/rb/kitchen-azurerm.svg)](http://badge.fury.io/rb/kitchen-azurerm) | [![Gem Version](https://badge.fury.io/rb/kitchen-sparkleformation.svg)](http://badge.fury.io/rb/kitchen-sparkleformation) [chefpartners]: https://github.com/chef-partners/ [ec2]: https://github.com/test-kitchen/kitchen-ec2 [do]: https://github.com/test-kitchen/kitchen-digitalocean [open]: https://github.com/test-kitchen/kitchen-openstack [rs]: https://github.com/test-kitchen/kitchen-rackspace [google]: https://github.com/test-kitchen/kitchen-google [vagrant]: https://github.com/test-kitchen/kitchen-vagrant [dsc]: https://github.com/test-kitchen/kitchen-dsc [pester]: https://github.com/test-kitchen/kitchen-pester [joyent]: https://github.com/test-kitchen/kitchen-joyent [opennebula]: https://github.com/test-kitchen/kitchen-opennebula [hyperv]: https://github.com/test-kitchen/kitchen-hyperv [cloudstack]: https://github.com/test-kitchen/kitchen-cloudstack [vra]: https://github.com/chef-partners/kitchen-vra [vro]: https://github.com/chef-partners/kitchen-vro [vcenter]: https://github.com/chef/kitchen-vcenter [vcair]: https://github.com/chef-partners/kitchen-vcair [oracle]: https://github.com/chef-partners/kitchen-oraclecloud [sadpanda]: https://github.com/chef-partners/kitchen-ssh-cisco [sync]: https://github.com/coderanger/kitchen-sync [ansible]: https://github.com/neillturner/kitchen-ansible [puppet]: https://github.com/neillturner/kitchen-puppet [salt]: https://github.com/kitchen-salt/kitchen-salt [dokken]: https://github.com/someara/kitchen-dokken [lxc]: https://github.com/chrisroberts/kitchen-lxc [nodes]: https://github.com/mwrock/kitchen-nodes [zcloudjp]: https://github.com/higanworks/kitchen-zcloudjp [bluebox]: https://github.com/blueboxgroup/kitchen-bluebox [softlayer]: https://github.com/neillturner/kitchen-softlayer [linode]: https://github.com/ssplatt/kitchen-linode [qemu]: https://github.com/esmil/kitchen-qemu [cfengine]: https://github.com/nmische/kitchen-cfengine [cloudformation]: https://github.com/neillturner/kitchen-cloudformation [sparkleformation]: https://github.com/devkid/kitchen-sparkleformation [wpar]: https://github.com/adejoux/kitchen-wpar [powervc]: https://github.com/chmod666org/kitchen-powervc [appbundle-updater]: https://github.com/chef/kitchen-appbundle-updater [inspec]: https://github.com/chef/kitchen-inspec [chef]: https://chef.io [azurerm]: http://github.com/pendrica/kitchen-azurerm [docker]: https://github.com/portertech/kitchen-docker [terraform]: https://github.com/newcontext-oss/kitchen-terraform