test-kitchen 0.7.0 → 1.0.0.alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. data/.gitignore +20 -0
  2. data/.travis.yml +11 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +13 -0
  5. data/Guardfile +11 -0
  6. data/LICENSE +15 -0
  7. data/README.md +131 -0
  8. data/Rakefile +69 -0
  9. data/bin/kitchen +9 -4
  10. data/features/cli.feature +17 -0
  11. data/features/cli_init.feature +156 -0
  12. data/features/support/env.rb +14 -0
  13. data/lib/kitchen/busser.rb +166 -0
  14. data/lib/kitchen/chef_data_uploader.rb +156 -0
  15. data/lib/kitchen/cli.rb +540 -0
  16. data/lib/kitchen/collection.rb +55 -0
  17. data/lib/kitchen/color.rb +46 -0
  18. data/lib/kitchen/config.rb +223 -0
  19. data/lib/kitchen/driver/base.rb +180 -0
  20. data/lib/kitchen/driver/dummy.rb +81 -0
  21. data/lib/kitchen/driver/ssh_base.rb +192 -0
  22. data/lib/kitchen/driver.rb +42 -0
  23. data/lib/kitchen/errors.rb +52 -0
  24. data/lib/kitchen/instance.rb +327 -0
  25. data/lib/kitchen/instance_actor.rb +42 -0
  26. data/lib/kitchen/loader/yaml.rb +105 -0
  27. data/lib/kitchen/logger.rb +145 -0
  28. data/{cookbooks/test-kitchen/libraries/helpers.rb → lib/kitchen/logging.rb} +13 -9
  29. data/lib/kitchen/manager.rb +45 -0
  30. data/lib/kitchen/metadata_chopper.rb +52 -0
  31. data/lib/kitchen/platform.rb +61 -0
  32. data/lib/kitchen/rake_tasks.rb +59 -0
  33. data/lib/kitchen/shell_out.rb +65 -0
  34. data/lib/kitchen/state_file.rb +88 -0
  35. data/lib/kitchen/suite.rb +76 -0
  36. data/lib/kitchen/thor_tasks.rb +62 -0
  37. data/lib/kitchen/util.rb +79 -0
  38. data/{cookbooks/test-kitchen/recipes/erlang.rb → lib/kitchen/version.rb} +9 -6
  39. data/lib/kitchen.rb +98 -0
  40. data/lib/vendor/hash_recursive_merge.rb +74 -0
  41. data/spec/kitchen/collection_spec.rb +80 -0
  42. data/spec/kitchen/color_spec.rb +54 -0
  43. data/spec/kitchen/config_spec.rb +201 -0
  44. data/spec/kitchen/driver/dummy_spec.rb +191 -0
  45. data/spec/kitchen/instance_spec.rb +162 -0
  46. data/spec/kitchen/loader/yaml_spec.rb +243 -0
  47. data/spec/kitchen/platform_spec.rb +48 -0
  48. data/spec/kitchen/state_file_spec.rb +122 -0
  49. data/spec/kitchen/suite_spec.rb +64 -0
  50. data/spec/spec_helper.rb +47 -0
  51. data/templates/plugin/driver.rb.erb +23 -0
  52. data/templates/plugin/license_apachev2.erb +15 -0
  53. data/templates/plugin/license_gplv2.erb +18 -0
  54. data/templates/plugin/license_gplv3.erb +16 -0
  55. data/templates/plugin/license_mit.erb +22 -0
  56. data/templates/plugin/license_reserved.erb +5 -0
  57. data/templates/plugin/version.rb.erb +12 -0
  58. data/test-kitchen.gemspec +44 -0
  59. metadata +290 -82
  60. data/config/Cheffile +0 -47
  61. data/config/Kitchenfile +0 -39
  62. data/config/Vagrantfile +0 -114
  63. data/cookbooks/test-kitchen/attributes/default.rb +0 -25
  64. data/cookbooks/test-kitchen/metadata.rb +0 -27
  65. data/cookbooks/test-kitchen/recipes/chef.rb +0 -19
  66. data/cookbooks/test-kitchen/recipes/compat.rb +0 -39
  67. data/cookbooks/test-kitchen/recipes/default.rb +0 -51
  68. data/cookbooks/test-kitchen/recipes/ruby.rb +0 -29
  69. data/lib/test-kitchen/cli/destroy.rb +0 -36
  70. data/lib/test-kitchen/cli/init.rb +0 -37
  71. data/lib/test-kitchen/cli/platform_list.rb +0 -37
  72. data/lib/test-kitchen/cli/project_info.rb +0 -44
  73. data/lib/test-kitchen/cli/ssh.rb +0 -36
  74. data/lib/test-kitchen/cli/status.rb +0 -36
  75. data/lib/test-kitchen/cli/test.rb +0 -68
  76. data/lib/test-kitchen/cli.rb +0 -282
  77. data/lib/test-kitchen/dsl.rb +0 -63
  78. data/lib/test-kitchen/environment.rb +0 -166
  79. data/lib/test-kitchen/platform.rb +0 -79
  80. data/lib/test-kitchen/project/base.rb +0 -159
  81. data/lib/test-kitchen/project/cookbook.rb +0 -97
  82. data/lib/test-kitchen/project/cookbook_copy.rb +0 -58
  83. data/lib/test-kitchen/project/ruby.rb +0 -37
  84. data/lib/test-kitchen/project/supported_platforms.rb +0 -75
  85. data/lib/test-kitchen/project.rb +0 -23
  86. data/lib/test-kitchen/runner/base.rb +0 -154
  87. data/lib/test-kitchen/runner/openstack/dsl.rb +0 -39
  88. data/lib/test-kitchen/runner/openstack/environment.rb +0 -141
  89. data/lib/test-kitchen/runner/openstack.rb +0 -147
  90. data/lib/test-kitchen/runner/vagrant.rb +0 -95
  91. data/lib/test-kitchen/runner.rb +0 -21
  92. data/lib/test-kitchen/scaffold.rb +0 -88
  93. data/lib/test-kitchen/ui.rb +0 -73
  94. data/lib/test-kitchen/version.rb +0 -21
  95. data/lib/test-kitchen.rb +0 -34
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rvmrc
19
+ .rbenv-version
20
+ .ruby-version
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - ruby-head
7
+
8
+ matrix:
9
+ allow_failures:
10
+ - rvm: ruby-head
11
+ - rvm: jruby-19mode
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --readme README.md
2
+ --markup markdown
3
+ --markup-provider maruku
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rb-inotify', :require => false
7
+ gem 'rb-fsevent', :require => false
8
+ gem 'rb-fchange', :require => false
9
+ end
10
+
11
+ group :test do
12
+ gem 'rake', '~> 0.9'
13
+ end
data/Guardfile ADDED
@@ -0,0 +1,11 @@
1
+ guard 'minitest' do
2
+ watch(%r|^spec/(.*)_spec\.rb|)
3
+ watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
4
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
5
+ end
6
+
7
+ guard 'cucumber' do
8
+ watch(%r{^features/.+\.feature$})
9
+ watch(%r{^features/support/.+$}) { 'features' }
10
+ watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Author:: Fletcher Nichol (<fnichol@nichol.ca>)
2
+
3
+ Copyright 2012 Fletcher Nichol
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Test Kitchen
2
+
3
+ [![Build Status](https://secure.travis-ci.org/opscode/test-kitchen.png?branch=1.0)](https://travis-ci.org/opscode/test-kitchen)
4
+ [![Code Climate](https://codeclimate.com/github/opscode/test-kitchen.png)](https://codeclimate.com/github/opscode/test-kitchen)
5
+
6
+ A convergence integration test harness for configuration management systems.
7
+
8
+ # Getting started
9
+
10
+
11
+ Project Setup
12
+ -------------
13
+
14
+ In your `Gemfile`, add `test-kitchen` as a
15
+ dependency:
16
+
17
+ ```ruby
18
+ gem 'test-kitchen', git: 'git://github.com/opscode/test-kitchen.git', branch: '1.0'
19
+ ```
20
+
21
+ and run the `bundle` command to install:
22
+
23
+ $ bundle install
24
+
25
+ This will expose the `test-kitchen` CLI. Run `bundle exec kitchen init` to get started:
26
+
27
+ $ kitchen init
28
+
29
+ You will be prompted with a series of questions. In this guide, we
30
+ will be using the [kitchen vagrant driver](https://github.com/opscode/kitchen-vagrant).
31
+
32
+ ```text
33
+ $ bundle exec kitchen init
34
+ create .kitchen.yml
35
+ append Rakefile
36
+ create test/integration/default
37
+ append .gitignore
38
+ append .gitignore
39
+ Add a Driver plugin to your Gemfile?
40
+ (y/n)> y
41
+ Enter gem name, `list', or `skip'>
42
+ kitchen-vagrant
43
+ append Gemfile
44
+ You must run `bundle install' to fetch any new gems.
45
+ ```
46
+
47
+ Run the `bundle` command again to install the new vagrant driver:
48
+
49
+ $ bundle install
50
+
51
+ Open up the `.kitchen.yml` file created in the root of your
52
+ repository.
53
+
54
+ Now, it is time to get testing. Use the `--parallel` option to run
55
+ your tests in parallel. Trust us, it's faster!
56
+
57
+ $ bundle exec kitchen test
58
+
59
+ ## The Kitchen YAML format
60
+
61
+ Test-Kitchen reads its configuration from the .kitchen.yml
62
+ configuration file at the root of your cookbook. It closely resembles
63
+ the format of .travis.yml which is intentional.
64
+
65
+ There are 4 stanzas in .kitchen.yml, driver_plugin, driver_config,
66
+ platforms, and suites. driver_plugin, platforms, and suites are
67
+ currently required. driver_config can optionally be used to set values
68
+ for all platforms defined.
69
+
70
+ The driver_plugin stanza is only one line long and defines which
71
+ driver is used by test-kitchen.
72
+
73
+ The platforms stanza defines individual virtual machines. Additional
74
+ driver_config, node attributes, and run_list can be defined in this stanza
75
+
76
+ The suites stanza defines sets of tests that you intend to be run on
77
+ each platform. A run_list and node attributes can be defined for each
78
+ suite. The run_list and node attributes will be merged with that of
79
+ each platform. In case of the conflict, the attributes defined on the
80
+ suite will triumph.
81
+
82
+ ```yaml
83
+
84
+ ---
85
+ driver_plugin: vagrant
86
+
87
+ platforms:
88
+ - name: ubuntu-12.04
89
+ driver_config:
90
+ box: opscode-ubuntu-12.04
91
+ box_url: https://opscode-vm.s3.amazonaws.com/vagrant/boxes/opscode-ubuntu-12.04.box
92
+
93
+ - name: centos-6.3
94
+ driver_config:
95
+ box: opscode-centos-6.3
96
+ box_url: https://opscode-vm.s3.amazonaws.com/vagrant/boxes/opscode-centos-6.3.box
97
+ run_list:
98
+ - recipe[yum::epel]
99
+
100
+ suites:
101
+ - name: stock_system_and_user
102
+ run_list:
103
+ - recipe[user::data_bag]
104
+ - recipe[rvm::system]
105
+ - recipe[rvm::user]
106
+ attributes:
107
+ users:
108
+ - wigglebottom
109
+ rvm:
110
+ user_installs:
111
+ - user: wigglebottom
112
+ default_ruby: 1.8.7
113
+ ```
114
+
115
+ ## Overriding .kitchen.yaml with .kitchen.<driver>.local.yml
116
+
117
+ TODO
118
+
119
+ ## A Note
120
+
121
+ This project is currently in rapid development which means frequent releases,
122
+ potential for massive refactorings (that could be API breaking), and minimal
123
+ to no documentation. This will change as the project transitions to be used in
124
+ production environments.
125
+
126
+ Despite the warnings above, if you are still interested, please get in touch
127
+ via freenode/IRC (#chef-hacking),
128
+ Twitter ([@fnichol](https://twitter.com/fnichol)),
129
+ or Email ([fnichol@nichol.ca](mailto:fnichol@nichol.ca)).
130
+
131
+ For everyone else, watch [this space](https://github.com/opscode/test-kitchen).
data/Rakefile ADDED
@@ -0,0 +1,69 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push "lib"
6
+ t.test_files = FileList['spec/**/*_spec.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => [:test]
11
+
12
+ unless RUBY_ENGINE == 'jruby'
13
+ require 'cane/rake_task'
14
+ require 'tailor/rake_task'
15
+ require 'cucumber'
16
+ require 'cucumber/rake/task'
17
+
18
+ desc "Run cane to check quality metrics"
19
+ Cane::RakeTask.new do |cane|
20
+ cane.abc_exclude = %w(
21
+ Kitchen::RakeTasks#define
22
+ Kitchen::ThorTasks#define
23
+ Kitchen::CLI#pry_prompts
24
+ Kitchen::Instance#synchronize_or_call
25
+ )
26
+ cane.style_exclude = %w(
27
+ lib/vendor/hash_recursive_merge.rb
28
+ )
29
+ cane.doc_exclude = %w(
30
+ lib/vendor/hash_recursive_merge.rb
31
+ )
32
+ cane.style_measure = 160
33
+ end
34
+
35
+ Tailor::RakeTask.new do |task|
36
+ task.file_set('bin/*', 'binaries')
37
+ task.file_set('lib/**/*.rb', 'code') do |style|
38
+ # TODO: Tailor is confused thinking `module Kitchen` is a class. Until
39
+ # the classes are split in seperate files, let's punt on this
40
+ style.max_code_lines_in_class 1550, level: :warn
41
+ # NOTE: Kitchen::InitGenerator.default_yaml is over the default 30 lines
42
+ # and produces a warning. Since most of it is increasing readability of
43
+ # the data structure, allowing it here to prevent it from growing
44
+ style.max_code_lines_in_method 34
45
+ style.max_line_length 80, level: :warn
46
+ style.max_line_length 160, level: :error
47
+ end
48
+ task.file_set('spec/**/*.rb', 'tests') do |style|
49
+ # allow vertical alignment of `let(:foo) { block }` blocks
50
+ style.spaces_before_lbrace 1, level: :off
51
+ end
52
+ end
53
+
54
+ Cucumber::Rake::Task.new(:features) do |t|
55
+ t.cucumber_opts = ['features', '-x', '--format progress']
56
+ end
57
+
58
+ Rake::Task[:default].enhance [:cane, :features, :tailor]
59
+ end
60
+
61
+ desc "Display LOC stats"
62
+ task :stats do
63
+ puts "\n## Production Code Stats"
64
+ sh "countloc -r lib/kitchen lib/kitchen.rb"
65
+ puts "\n## Test Code Stats"
66
+ sh "countloc -r spec features"
67
+ end
68
+
69
+ Rake::Task[:default].enhance [:stats]
data/bin/kitchen CHANGED
@@ -1,7 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
+ # -*- encoding: utf-8 -*-
2
3
 
3
- $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
4
- #require 'bundler/setup'
5
- require 'test-kitchen'
4
+ # Trap interrupts to quit cleanly. See
5
+ # https://twitter.com/mitchellh/status/283014103189053442
6
+ Signal.trap("INT") { exit 1 }
6
7
 
7
- TestKitchen::CLI::Kitchen.new.run
8
+ $:.unshift File.join(File.dirname(__FILE__), %w{.. lib})
9
+ require 'rubygems'
10
+ require 'kitchen/cli'
11
+
12
+ Kitchen::CLI.start
@@ -0,0 +1,17 @@
1
+ Feature: Ensure that the Command Line Interface works as designed
2
+ In order to test code via CLI
3
+ As an Operator
4
+ I want to run the CLI with different arguments
5
+
6
+ Scenario: Running the help command exits cleanly
7
+ When I successfully run `kitchen help`
8
+ Then the exit status should be 0
9
+ And the output should contain "kitchen console"
10
+ And a file named ".kitchen/logs/kitchen.log" should exist
11
+
12
+ Scenario: Show the version number
13
+ When I successfully run `kitchen version`
14
+ Then the exit status should be 0
15
+
16
+
17
+
@@ -0,0 +1,156 @@
1
+ Feature: Ensure that the Command Line Interface init creates the correct files
2
+ In order to initialize an un-Kitchenified cookbook
3
+ As an Operator
4
+ I want to initialize a cookbook
5
+
6
+
7
+ @ok
8
+ Scenario: Basic init with no extras succeeds
9
+ When I run `kitchen init` interactively
10
+ And I type "n"
11
+ Then the exit status should be 0
12
+ And a directory named ".kitchen" should exist
13
+ And a directory named "test/integration/default" should exist
14
+ And the file ".gitignore" should contain:
15
+ """
16
+ .kitchen/
17
+ .kitchen.local.yml
18
+ """
19
+ And the file ".kitchen.yml" should contain:
20
+ """
21
+ ---
22
+ driver_plugin: vagrant
23
+ platforms:
24
+ - name: ubuntu-12.04
25
+ driver_config:
26
+ box: opscode-ubuntu-12.04
27
+ box_url: https://opscode-vm.s3.amazonaws.com/vagrant/boxes/opscode-ubuntu-12.04.box
28
+ run_list:
29
+ - recipe[apt]
30
+ - name: ubuntu-10.04
31
+ driver_config:
32
+ box: opscode-ubuntu-10.04
33
+ box_url: https://opscode-vm.s3.amazonaws.com/vagrant/boxes/opscode-ubuntu-10.04.box
34
+ run_list:
35
+ - recipe[apt]
36
+ - name: centos-6.3
37
+ driver_config:
38
+ box: opscode-centos-6.3
39
+ box_url: https://opscode-vm.s3.amazonaws.com/vagrant/boxes/opscode-centos-6.3.box
40
+ run_list:
41
+ - recipe[yum::epel]
42
+ - name: centos-5.8
43
+ driver_config:
44
+ box: opscode-centos-5.8
45
+ box_url: https://opscode-vm.s3.amazonaws.com/vagrant/boxes/opscode-centos-5.8.box
46
+ run_list:
47
+ - recipe[yum::epel]
48
+ suites:
49
+ - name: default
50
+ run_list: []
51
+ attributes: {}
52
+ """
53
+ And a file named "Gemfile" should not exist
54
+ And a file named "Rakefile" should not exist
55
+ And a file named "Thorfile" should not exist
56
+
57
+
58
+
59
+ @ok
60
+ Scenario: Running with a Rakefile file appends Kitchen tasks
61
+ Given an empty file named "Rakefile"
62
+ When I run `kitchen init` interactively
63
+ And I type "n"
64
+ Then the exit status should be 0
65
+ And the file "Rakefile" should contain exactly:
66
+ """
67
+
68
+ begin
69
+ require 'kitchen/rake_tasks'
70
+ Kitchen::RakeTasks.new
71
+ rescue LoadError
72
+ puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV['CI']
73
+ end
74
+
75
+ """
76
+
77
+
78
+ @ok
79
+ Scenario: Running with a Thorfile file appends Kitchen tasks
80
+ Given an empty file named "Thorfile"
81
+ When I run `kitchen init` interactively
82
+ And I type "n"
83
+ Then the exit status should be 0
84
+ And the file "Thorfile" should contain exactly:
85
+ """
86
+
87
+ begin
88
+ require 'kitchen/thor_tasks'
89
+ Kitchen::ThorTasks.new
90
+ rescue LoadError
91
+ puts ">>>>> Kitchen gem not loaded, omitting tasks" unless ENV['CI']
92
+ end
93
+
94
+ """
95
+
96
+
97
+ @ok
98
+ Scenario: Listing the drivers provides correct output, does not write Gemfile
99
+ When I run `kitchen init` interactively
100
+ And I type "y"
101
+ And I type "list"
102
+ And I type "skip"
103
+ Then the exit status should be 0
104
+ And a file named ".kitchen.yml" should exist
105
+ And a directory named ".kitchen" should exist
106
+ And a file named "Gemfile" should not exist
107
+
108
+
109
+ @ok
110
+ Scenario: Running the init command without a Gemfile provides warning and fails
111
+ When I run `kitchen init` interactively
112
+ And I type "y"
113
+ And I type "kitchen-vagrant"
114
+ And the output should contain "You do not have an existing Gemfile"
115
+ Then the exit status should be 1
116
+
117
+
118
+ @ok
119
+ Scenario: Running the init command succeeds
120
+ Given an empty file named "Gemfile"
121
+ When I run `kitchen init` interactively
122
+ And I type "y"
123
+ And I type "kitchen-vagrant"
124
+ Then the exit status should be 0
125
+ And the output should contain "You must run `bundle install' to fetch any new gems."
126
+ And a file named ".kitchen.yml" should exist
127
+ And a file named ".gitignore" should exist
128
+ And the file "Gemfile" should contain "gem 'kitchen-vagrant', :group => :integration"
129
+
130
+
131
+ @ok
132
+ Scenario: Running init with a correct metadata.rb works
133
+ Given a file named "metadata.rb" with:
134
+ """
135
+ name "ntp"
136
+ license "Apache 2.0"
137
+ description "Installs and configures ntp as a client or server"
138
+ version "0.1.0"
139
+ recipe "ntp", "Installs and configures ntp either as a server or client"
140
+
141
+ %w{ ubuntu debian redhat centos fedora scientific amazon oracle freebsd }.each do |os|
142
+ supports os
143
+ end
144
+ """
145
+ When I run `kitchen init` interactively
146
+ And I type "n"
147
+ Then the exit status should be 0
148
+ And the file ".kitchen.yml" should contain:
149
+ """
150
+ suites:
151
+ - name: default
152
+ run_list:
153
+ - recipe[ntp]
154
+ attributes: {}
155
+ """
156
+
@@ -0,0 +1,14 @@
1
+ # Set up the environment for testing
2
+ require 'aruba/cucumber'
3
+ require 'kitchen'
4
+
5
+ Before do
6
+ @aruba_timeout_seconds = 5
7
+ end
8
+
9
+ After do |s|
10
+ # Tell Cucumber to quit after this scenario is done - if it failed.
11
+ # This is useful to inspect the 'tmp/aruba' directory before any other
12
+ # steps are executed and clear it out.
13
+ Cucumber.wants_to_quit = true if s.failed?
14
+ end
@@ -0,0 +1,166 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2012, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'base64'
20
+ require 'digest'
21
+ require 'net/https'
22
+
23
+ module Kitchen
24
+
25
+ # Command string generator to interface with Kitchen Busser (kb). The
26
+ # commands that are generated are safe to pass to an SSH command or as an
27
+ # unix command argument (escaped in single quotes).
28
+ #
29
+ # @author Fletcher Nichol <fnichol@nichol.ca>
30
+ class Busser
31
+
32
+ # Constructs a new busser command generator, given a suite name.
33
+ #
34
+ # @param [String] suite_name name of suite on which to operate
35
+ # (**Required**)
36
+ # @param [Hash] opts optional configuration
37
+ # @option opts [TrueClass, FalseClass] :use_sudo whether or not to invoke
38
+ # sudo before commands requiring root access (default: `true`)
39
+ def initialize(suite_name, opts = { :use_sudo => true })
40
+ validate_options(suite_name)
41
+
42
+ @suite_name = suite_name
43
+ @use_sudo = opts[:use_sudo]
44
+ end
45
+
46
+ # Returns a command string which installs the Kitchen Busser (kb), installs
47
+ # all required kb plugins for the suite.
48
+ #
49
+ # If no work needs to be performed, for example if there are no tests for
50
+ # the given suite, then `nil` will be returned.
51
+ #
52
+ # @return [String] a command string to setup the test suite, or nil if no
53
+ # work needs to be performed
54
+ def setup_cmd
55
+ @setup_cmd ||= if local_suite_files.empty?
56
+ nil
57
+ else
58
+ <<-INSTALL_CMD.gsub(/^ {10}/, '')
59
+ #{sudo}#{ruby_bin} -e "$(cat <<"EOF"
60
+ #{install_script}
61
+ EOF
62
+ )"
63
+ #{sudo}#{kb_bin} install #{plugins.join(' ')}
64
+ INSTALL_CMD
65
+ end
66
+ end
67
+
68
+ # Returns a command string which transfers all suite test files to the
69
+ # instance.
70
+ #
71
+ # If no work needs to be performed, for example if there are no tests for
72
+ # the given suite, then `nil` will be returned.
73
+ #
74
+ # @return [String] a command string to transfer all suite test files, or
75
+ # nil if no work needs to be performed.
76
+ def sync_cmd
77
+ @sync_cmd ||= if local_suite_files.empty?
78
+ nil
79
+ else
80
+ <<-INSTALL_CMD.gsub(/^ {10}/, '')
81
+ #{sudo}#{kb_bin} cleanup-suites
82
+ #{local_suite_files.map { |f| stream_file(f, remote_file(f)) }.join}
83
+ INSTALL_CMD
84
+ end
85
+ end
86
+
87
+ # Returns a command string which runs all kb suite tests for the suite.
88
+ #
89
+ # If no work needs to be performed, for example if there are no tests for
90
+ # the given suite, then `nil` will be returned.
91
+ #
92
+ # @return [String] a command string to run the test suites, or nil if no
93
+ # work needs to be performed
94
+ def run_cmd
95
+ @run_cmd ||= local_suite_files.empty? ? nil : "#{sudo}#{kb_bin} test"
96
+ end
97
+
98
+ private
99
+
100
+ INSTALL_URL = "https://raw.github.com/opscode/kb/go".freeze
101
+ DEFAULT_RUBY_BINPATH = "/opt/chef/embedded/bin".freeze
102
+ DEFAULT_KB_ROOT = "/opt/kb".freeze
103
+ DEFAULT_TEST_ROOT = File.join(Dir.pwd, "test/integration").freeze
104
+
105
+ def validate_options(suite_name)
106
+ raise ClientError, "Busser#new requires a suite_name" if suite_name.nil?
107
+ end
108
+
109
+ def install_script
110
+ @install_script ||= begin
111
+ uri = URI.parse(INSTALL_URL)
112
+ http = Net::HTTP.new(uri.host, 443)
113
+ http.use_ssl = true
114
+ response = http.request(Net::HTTP::Get.new(uri.path))
115
+ response.body
116
+ end
117
+ end
118
+
119
+ def plugins
120
+ Dir.glob(File.join(test_root, @suite_name, "*")).select { |d|
121
+ File.directory?(d) && File.basename(d) != "data_bags"
122
+ }.map { |d| File.basename(d) }.sort.uniq
123
+ end
124
+
125
+ def local_suite_files
126
+ Dir.glob(File.join(test_root, @suite_name, "*/**/*")).reject do |f|
127
+ f["data_bags"] || File.directory?(f)
128
+ end
129
+ end
130
+
131
+ def remote_file(file)
132
+ local_prefix = File.join(test_root, @suite_name)
133
+ "$(#{kb_bin} suitepath)/".concat(file.sub(%r{^#{local_prefix}/}, ''))
134
+ end
135
+
136
+ def stream_file(local_path, remote_path)
137
+ local_file = IO.read(local_path)
138
+ md5 = Digest::MD5.hexdigest(local_file)
139
+ perms = sprintf("%o", File.stat(local_path).mode)[3, 3]
140
+ kb_stream_file = "#{kb_bin} stream-file #{remote_path} #{md5} #{perms}"
141
+
142
+ <<-STREAMFILE.gsub(/^ {8}/, '')
143
+ echo "Uploading #{remote_path} (mode=#{perms})"
144
+ cat <<"__EOFSTREAM__" | #{sudo}#{kb_stream_file}
145
+ #{Base64.encode64(local_file)}
146
+ __EOFSTREAM__
147
+ STREAMFILE
148
+ end
149
+
150
+ def sudo
151
+ @use_sudo ? "sudo " : ""
152
+ end
153
+
154
+ def ruby_bin
155
+ File.join(DEFAULT_RUBY_BINPATH, "ruby")
156
+ end
157
+
158
+ def kb_bin
159
+ File.join(DEFAULT_KB_ROOT, "bin/kb")
160
+ end
161
+
162
+ def test_root
163
+ DEFAULT_TEST_ROOT
164
+ end
165
+ end
166
+ end