test-kitchen 0.7.0 → 1.0.0.alpha.0

Sign up to get free protection for your applications and to get access to all the features.
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