test-kitchen 0.6.0 → 0.7.0.beta.1

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