test-kitchen 0.6.0 → 0.7.0.beta.1

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.
data/config/Cheffile CHANGED
@@ -21,13 +21,6 @@ require 'test-kitchen'
21
21
  site 'http://community.opscode.com/api/v1'
22
22
 
23
23
  # base test-kitchen cookbooks
24
- cookbook 'apt', '1.4.6'
25
- cookbook 'yum', '0.8.0'
26
- cookbook 'build-essential', '1.1.0'
27
- cookbook 'git', '1.0.0'
28
- cookbook 'rvm',
29
- :git => 'https://github.com/fnichol/chef-rvm.git',
30
- :ref => 'c27a8942ed311ccce2c63c2e863201b049c70a0d'
31
24
  cookbook 'test-kitchen',
32
25
  :path => TestKitchen.source_root.join('cookbooks', 'test-kitchen').to_s
33
26
 
@@ -27,12 +27,6 @@ module TestKitchen
27
27
  banner "kitchen destroy (options)"
28
28
 
29
29
  def run
30
- options = {
31
- :platform => config[:platform],
32
- :configuration => config[:configuration]
33
- }
34
-
35
- runner = TestKitchen::Runner.targets['vagrant'].new(env, options)
36
30
  runner.destroy
37
31
  end
38
32
 
@@ -54,6 +54,14 @@ module TestKitchen
54
54
  :show_options => true,
55
55
  :exit => 0
56
56
 
57
+ option :version,
58
+ :short => "-v",
59
+ :long => "--version",
60
+ :description => "Show Test Kitchen version",
61
+ :boolean => true,
62
+ :proc => lambda {|v| puts "test-kitchen: #{::TestKitchen::VERSION}"},
63
+ :exit => 0
64
+
57
65
  attr_accessor :runner
58
66
  attr_accessor :env
59
67
  attr_accessor :ui
@@ -91,7 +99,7 @@ module TestKitchen
91
99
  def runner
92
100
  @runner ||= begin
93
101
  # CLI option takes precedence, then project
94
- runner_name = config[:runner] || env.project.runner || 'vagrant'
102
+ runner_name = config[:runner] || env.project.runner || env.default_runner
95
103
  runner_class = TestKitchen::Runner.targets[runner_name]
96
104
  runner = runner_class.new(env, config)
97
105
  end
@@ -257,6 +265,12 @@ module TestKitchen
257
265
 
258
266
  def print_help_and_exit(exitcode=1, fatal_message=nil)
259
267
  $stderr.puts(fatal_message) if fatal_message
268
+
269
+ begin
270
+ self.parse_options
271
+ rescue OptionParser::InvalidOption => e
272
+ puts "#{e}\n"
273
+ end
260
274
  puts self.opt_parser
261
275
  puts
262
276
  TestKitchen::CLI::Kitchen.list_commands
@@ -15,10 +15,10 @@
15
15
  # See the License for the specific language governing permissions and
16
16
  # limitations under the License.
17
17
  #
18
-
19
18
  require 'hashr'
20
19
  require 'test-kitchen/project'
21
20
  require 'test-kitchen/platform'
21
+ require 'test-kitchen/runner/openstack/dsl'
22
22
 
23
23
  module TestKitchen
24
24
  module DSL
@@ -31,6 +31,10 @@ module TestKitchen
31
31
  def platform(name, &block)
32
32
  env.platforms[name.to_s] = Platform.new(name, &block)
33
33
  end
34
+
35
+ def default_runner(name)
36
+ env.default_runner = name
37
+ end
34
38
  end
35
39
  module CookbookDSL
36
40
  def cookbook(name, &block)
@@ -32,6 +32,7 @@ module TestKitchen
32
32
  attr_reader :ui
33
33
  attr_reader :kitchenfile_name
34
34
  attr_accessor :project
35
+ attr_accessor :default_runner
35
36
 
36
37
  KITCHEN_SUBDIRS = [".", "kitchen", "test/kitchen"]
37
38
 
@@ -47,6 +48,7 @@ module TestKitchen
47
48
  @project = Project::Cookbook.new(File.basename(Dir.pwd))
48
49
  end
49
50
 
51
+ @default_runner = "vagrant"
50
52
  @tmp_path = root_path.join('.kitchen')
51
53
  @cache_path = tmp_path.join('.cache')
52
54
  @ui = options[:ui]
@@ -41,6 +41,8 @@ module TestKitchen
41
41
  include Chef::Mixin::ParamsValidate
42
42
 
43
43
  attr_reader :name
44
+
45
+ # Virtual Box Specific Attributes
44
46
  attr_writer :box, :box_url
45
47
 
46
48
  def initialize(name, &block)
@@ -49,6 +51,20 @@ module TestKitchen
49
51
  instance_eval(&block) if block_given?
50
52
  end
51
53
 
54
+ OPENSTACK_OPTIONS = {
55
+ :image_id => nil, :flavor_id => nil, :install_chef => false,
56
+ :install_chef_cmd => "curl -L http://www.opscode.com/chef/install.sh | sudo bash",
57
+ :keyname => nil, :instance_name => nil, :ssh_user => "root", :ssh_key => nil}
58
+
59
+ OPENSTACK_OPTIONS.each do |option, default|
60
+ attr_writer option
61
+ define_method(option) do |*args|
62
+ arg = args.first
63
+ set_or_return(option, arg, default.nil? ? {} : {:default => default})
64
+ end
65
+ end
66
+
67
+
52
68
  def box(arg=nil)
53
69
  set_or_return(:box, arg, {})
54
70
  end
@@ -78,7 +78,7 @@ module TestKitchen
78
78
  end
79
79
 
80
80
  def runner(arg=nil)
81
- set_or_return(:runner, arg, :default => 'vagrant')
81
+ set_or_return(:runner, arg, nil)
82
82
  end
83
83
 
84
84
  def language(arg=nil)
@@ -143,6 +143,8 @@ module TestKitchen
143
143
  desired_platform = env.all_platforms[options[:platform]]
144
144
  if desired_platform.box_url
145
145
  TestKitchen::Runner.targets['vagrant'].new(env, options)
146
+ elsif desired_platform.image_id
147
+ TestKitchen::Runner.targets['openstack'].new(env, options)
146
148
  else
147
149
  raise ArgumentError,
148
150
  "No runner available for platform: #{desired_platform.name}"
@@ -0,0 +1,39 @@
1
+ require 'test-kitchen/runner/openstack/environment'
2
+
3
+ module TestKitchen
4
+ class Openstack
5
+ include Chef::Mixin::ParamsValidate
6
+
7
+ attr_writer :username, :password, :tenant, :auth_url
8
+
9
+ def initialize(&block)
10
+ instance_eval(&block) if block_given?
11
+ end
12
+
13
+ def username(arg=nil)
14
+ set_or_return(:username, arg, {})
15
+ end
16
+
17
+ def password(arg=nil)
18
+ set_or_return(:password, arg, {})
19
+ end
20
+
21
+ def tenant(arg=nil)
22
+ set_or_return(:tenant, arg, {})
23
+ end
24
+
25
+ def auth_url(arg=nil)
26
+ set_or_return(:auth_url, arg, {})
27
+ end
28
+ end
29
+ end
30
+
31
+ module TestKitchen
32
+ module DSL
33
+ module BasicDSL
34
+ def openstack(&block)
35
+ TestKitchen::Environment::Openstack.config = TestKitchen::Openstack.new(&block)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,141 @@
1
+ require 'test-kitchen/environment'
2
+ require 'json'
3
+ require 'fog'
4
+
5
+ module TestKitchen
6
+ class Environment
7
+ class Openstack < TestKitchen::Environment
8
+ attr_reader :username, :password, :tenant, :auth_url
9
+ attr_reader :servers
10
+
11
+ def initialize(conf={})
12
+ super
13
+ @username = conf[:username] || config.username
14
+ @password = conf[:password] || config.password
15
+ @tenant = conf[:tenant] || config.tenant
16
+ @auth_url = conf[:auth_url] || config.auth_url
17
+ @servers = {}
18
+ load
19
+ end
20
+
21
+ def create_server(platform_name, server_def)
22
+ @servers[platform_name] ||=
23
+ begin
24
+ server = connection.servers.create({ :name => server_def[:instance_name],
25
+ :image_ref => server_def[:image_id],
26
+ :flavor_ref => server_def[:flavor_id],
27
+ :key_name => server_def[:keyname]})
28
+ server.wait_for { ready? }
29
+ sleep(2) until tcp_test_ssh(server.public_ip_address['addr'])
30
+ save
31
+ server
32
+ end
33
+
34
+ # These won't persist on the fog objectso we have to set them every
35
+ # time. :(
36
+ @servers[platform_name].username = server_def[:ssh_user]
37
+ if server_def[:ssh_key]
38
+ @servers[platform_name].private_key_path = File.expand_path(server_def[:ssh_key])
39
+ end
40
+ @servers[platform_name]
41
+ end
42
+
43
+ def tcp_test_ssh(hostname)
44
+ tcp_socket = TCPSocket.new(hostname, '22')
45
+ IO.select([tcp_socket], nil, nil, 5)
46
+ rescue SocketError, Errno::ETIMEDOUT, Errno::EPERM,
47
+ Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH
48
+ false
49
+ ensure
50
+ tcp_socket && tcp_socket.close
51
+ end
52
+
53
+ def connection
54
+ @connection ||= Fog::Compute.new(:provider => 'OpenStack',
55
+ :openstack_username => username,
56
+ :openstack_api_key => password,
57
+ :openstack_auth_url => auth_url,
58
+ :openstack_tenant => tenant)
59
+ end
60
+
61
+
62
+ # The following functions all take a name of
63
+ # of a VM in our enironment
64
+
65
+ def status(name)
66
+ if @servers.has_key?(name)
67
+ @servers[name].state.to_s.downcase
68
+ else
69
+ "not created"
70
+ end
71
+ end
72
+
73
+ def destroy(name)
74
+ @servers[name].destroy if @servers.has_key?(name)
75
+ @servers.delete(name)
76
+ save
77
+ end
78
+
79
+
80
+ # Ideally we could use the #ssh and #scp functions on Fog's server '
81
+ # object. But these seem to be broken in the case of Openstack
82
+
83
+ def ssh_options(name)
84
+ if key = @servers[name].private_key_path
85
+ {:keys => [key]}
86
+ else
87
+ {}
88
+ end
89
+ end
90
+
91
+ def ssh(name)
92
+ server = @servers[name]
93
+ Fog::SSH.new(server.public_ip_address['addr'], server.username, ssh_options(name))
94
+ end
95
+
96
+ def scp(name)
97
+ server = @servers[name]
98
+ Fog::SCP.new(server.public_ip_address['addr'], server.username, ssh_options(name))
99
+ end
100
+
101
+ # GROSS: Global config as a class variable
102
+ def config
103
+ @@config
104
+ end
105
+
106
+ def self.config
107
+ @@config
108
+ end
109
+
110
+ def self.config=(config)
111
+ @@config = config
112
+ end
113
+
114
+ private
115
+
116
+ # Store state in .openstack_state file,
117
+ # allowing us to re-use already created VMs
118
+
119
+ def state_file
120
+ File.join(root_path, ".openstack_state")
121
+ end
122
+
123
+ def load
124
+ if File.exists?(state_file)
125
+ state = JSON.parse(File.read(state_file))
126
+ state.each do |platform, id|
127
+ @servers[platform] = connection.servers.get(id)
128
+ end
129
+ end
130
+ end
131
+
132
+ def save
133
+ state = {}
134
+ @servers.each {|k,s| state[k] = s.id}
135
+ File.open(state_file, 'w') do |f|
136
+ f.write(state.to_json)
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,147 @@
1
+ #
2
+ # Author:: Steven Danna (<steve@opscode.com>)
3
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'fog'
20
+ require 'test-kitchen/runner/openstack/environment'
21
+
22
+ module TestKitchen
23
+ module Runner
24
+ class Openstack < Base
25
+
26
+
27
+ # @vm: The TestKitchen::Platform::Version object
28
+ #
29
+ # @platform: From the parent constructor, contains the full name
30
+ # of the relevant platform if we were created by #for_platform
31
+
32
+ attr_accessor :vm
33
+
34
+ def initialize(env, options={})
35
+ super
36
+
37
+ if @platform
38
+ @vm = env.all_platforms[@platform]
39
+ end
40
+
41
+ @os_env = TestKitchen::Environment::Openstack.new()
42
+ end
43
+
44
+ def create
45
+ env.ui.msg "[#{platform}] Provisioning guest on Openstack", :green
46
+ @os_env.create_server(@platform,
47
+ { :instance_name => (vm.instance_name || "cookbook-tester-#{platform}"),
48
+ :image_id => vm.image_id,
49
+ :flavor_id => vm.flavor_id,
50
+ :keyname => vm.keyname,
51
+ :ssh_key => vm.ssh_key,
52
+ :ssh_user => vm.ssh_user})
53
+
54
+ end
55
+
56
+ def converge
57
+ install_chef if vm.install_chef
58
+ move_repo
59
+ move_cookbooks
60
+ run_chef_solo
61
+ end
62
+
63
+ # status and destroy are expected to operate on all
64
+ # vm's in an environment
65
+ def status
66
+ env.all_platforms.each do |name, ver|
67
+ env.ui.msg "#{name}\t#{@os_env.status(name)}"
68
+ end
69
+ end
70
+
71
+ def destroy
72
+ env.all_platforms.each do |name, platform|
73
+ env.ui.msg "[#{name}] Terminating openstack server", :yellow
74
+ @os_env.destroy name
75
+ end
76
+ end
77
+
78
+ def execute_remote_command(platform_name, command, message=nil)
79
+ env.ui.msg("[#{platform_name}] #{message}", :green) if message
80
+ results = @os_env.ssh(platform_name).run(command) do |data|
81
+ stdout, stderr = data.map {|s| s.rstrip}
82
+ stdout.lines.each do |line|
83
+ env.ui.msg "[#{platform_name}] #{line}", :green
84
+ end
85
+ stderr.lines.each do |line|
86
+ env.ui.msg "[#{platform_name}] #{line}", :red
87
+ end
88
+ end
89
+ if results.first.status != 0
90
+ msg = message || "Remote command"
91
+ env.ui.msg "[#{platform_name}] #{msg} failed!", :red
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def install_chef(name=platform)
98
+ execute_remote_command(name, vm.install_chef_cmd, "Installing Chef")
99
+ end
100
+
101
+ def run_chef_solo(name=platform)
102
+ execute_remote_command(name, "echo '#{json_for_node}' > node.json",
103
+ "Creating node configuration JSON")
104
+ execute_remote_command(name, "echo 'cookbook_path [ \"#{remote_cookbook_dir}\" ]' > solo.rb",
105
+ "Creating chef-solo configuration")
106
+ execute_remote_command(name, "sudo chef-solo -j node.json -c solo.rb",
107
+ "Running chef-solo on host")
108
+ end
109
+
110
+ def move_cookbooks(name=platform)
111
+ env.ui.msg("[#{name}] Moving cookbooks via SCP", :green)
112
+ @os_env.scp(name).upload(File.join(env.tmp_path, "cookbooks"),
113
+ remote_cookbook_dir, :recursive => true)
114
+ end
115
+
116
+ def move_repo(name=platform)
117
+ env.ui.msg("[#{name}] Moving repo via SCP", :green)
118
+ execute_remote_command(name, "sudo mkdir -p #{remote_root_dir}")
119
+ execute_remote_command(name, "sudo chown #{vm.ssh_user} #{remote_root_dir}")
120
+ @os_env.scp(name).upload(File.join(env.tmp_path, "cookbook_under_test"),
121
+ configuration.guest_source_root,
122
+ {:recursive => true})
123
+ end
124
+
125
+ def json_for_node
126
+ {
127
+ 'test-kitchen' => {
128
+ 'project' => configuration.to_hash.merge('source_root' => configuration.guest_source_root,
129
+ 'test_root' => configuration.guest_test_root)},
130
+ 'run_list' => run_list
131
+ }.to_json
132
+ end
133
+
134
+ def run_list
135
+ configuration.run_list + [test_recipe_name]
136
+ end
137
+
138
+ def remote_root_dir
139
+ File.dirname(configuration.guest_source_root)
140
+ end
141
+
142
+ def remote_cookbook_dir
143
+ File.join(remote_root_dir, 'cookbooks')
144
+ end
145
+ end
146
+ end
147
+ end
@@ -18,3 +18,4 @@
18
18
 
19
19
  require 'test-kitchen/runner/base'
20
20
  require 'test-kitchen/runner/vagrant'
21
+ require 'test-kitchen/runner/openstack'
@@ -42,7 +42,7 @@ module TestKitchen
42
42
  scaffold_file 'test/kitchen/Kitchenfile',
43
43
  <<-eos
44
44
  #{project_type(output_dir)} "#{project_name(output_dir)}" do
45
- #{'runtimes []' if project_type(output_dir) == 'cookbook'}
45
+
46
46
  end
47
47
  eos
48
48
  end
@@ -17,5 +17,5 @@
17
17
  #
18
18
 
19
19
  module TestKitchen
20
- VERSION = "0.6.0"
20
+ VERSION = "0.7.0.beta.1"
21
21
  end
metadata CHANGED
@@ -1,32 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test-kitchen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
5
- prerelease:
4
+ version: 0.7.0.beta.1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Seth Chisamore
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-02 00:00:00.000000000 Z
12
+ date: 2012-11-21 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
14
30
  - !ruby/object:Gem::Dependency
15
31
  name: foodcritic
16
32
  requirement: !ruby/object:Gem::Requirement
17
33
  none: false
18
34
  requirements:
19
- - - ~>
35
+ - - ! '>='
20
36
  - !ruby/object:Gem::Version
21
- version: '1.4'
37
+ version: 1.4.0
22
38
  type: :runtime
23
39
  prerelease: false
24
40
  version_requirements: !ruby/object:Gem::Requirement
25
41
  none: false
26
42
  requirements:
27
- - - ~>
43
+ - - ! '>='
28
44
  - !ruby/object:Gem::Version
29
- version: '1.4'
45
+ version: 1.4.0
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: hashr
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -161,6 +177,9 @@ files:
161
177
  - lib/test-kitchen/project/supported_platforms.rb
162
178
  - lib/test-kitchen/project.rb
163
179
  - lib/test-kitchen/runner/base.rb
180
+ - lib/test-kitchen/runner/openstack/dsl.rb
181
+ - lib/test-kitchen/runner/openstack/environment.rb
182
+ - lib/test-kitchen/runner/openstack.rb
164
183
  - lib/test-kitchen/runner/vagrant.rb
165
184
  - lib/test-kitchen/runner.rb
166
185
  - lib/test-kitchen/scaffold.rb
@@ -183,9 +202,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
183
202
  required_rubygems_version: !ruby/object:Gem::Requirement
184
203
  none: false
185
204
  requirements:
186
- - - ! '>='
205
+ - - ! '>'
187
206
  - !ruby/object:Gem::Version
188
- version: '0'
207
+ version: 1.3.1
189
208
  requirements: []
190
209
  rubyforge_project:
191
210
  rubygems_version: 1.8.23