test-kitchen 1.0.0.alpha.7 → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -67,6 +67,7 @@ module Kitchen
67
67
  @logger = logger.is_a?(Proc) ? logger.call(name) : logger
68
68
 
69
69
  @driver.instance = self
70
+ @driver.validate_config!
70
71
  setup_driver_mutex
71
72
  end
72
73
 
@@ -79,26 +80,6 @@ module Kitchen
79
80
  "<#{name}>"
80
81
  end
81
82
 
82
- # Returns a combined run_list starting with the platform's run_list
83
- # followed by the suite's run_list.
84
- #
85
- # @return [Array] combined run_list from suite and platform
86
- def run_list
87
- Array(platform.run_list) + Array(suite.run_list)
88
- end
89
-
90
- # Returns a merged hash of Chef node attributes with values from the
91
- # suite overriding values from the platform.
92
- #
93
- # @return [Hash] merged hash of Chef node attributes
94
- def attributes
95
- platform.attributes.rmerge(suite.attributes)
96
- end
97
-
98
- def dna
99
- attributes.rmerge({ :run_list => run_list })
100
- end
101
-
102
83
  # Creates this instance.
103
84
  #
104
85
  # @see Driver::Base#create
@@ -199,6 +180,39 @@ module Kitchen
199
180
  state_file.read[:last_action]
200
181
  end
201
182
 
183
+ # Extra instance methods used for accessing Puppet data such as a combined
184
+ # run list, node attributes, etc.
185
+ module Cheflike
186
+
187
+ # Returns a combined run_list starting with the platform's run_list
188
+ # followed by the suite's run_list.
189
+ #
190
+ # @return [Array] combined run_list from suite and platform
191
+ def run_list
192
+ Array(platform.run_list) + Array(suite.run_list)
193
+ end
194
+
195
+ # Returns a merged hash of Chef node attributes with values from the
196
+ # suite overriding values from the platform.
197
+ #
198
+ # @return [Hash] merged hash of Chef node attributes
199
+ def attributes
200
+ platform.attributes.rmerge(suite.attributes)
201
+ end
202
+
203
+ def dna
204
+ attributes.rmerge({ :run_list => run_list })
205
+ end
206
+ end
207
+
208
+ # Extra instances methods used for accessing Puppet data such as a run,
209
+ # node attributes, etc.
210
+ module Puppetlike
211
+
212
+ def manifest
213
+ end
214
+ end
215
+
202
216
  private
203
217
 
204
218
  def validate_options(opts)
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, 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
+ module Kitchen
20
+
21
+ # Value object to track a shell command that will be passed to Kernel.exec
22
+ # for execution.
23
+ #
24
+ # @author Fletcher Nichol <fnichol@nichol.ca>
25
+ class LoginCommand
26
+
27
+ attr_reader :cmd_array, :options
28
+
29
+ def initialize(cmd_array, options = {})
30
+ @cmd_array = cmd_array
31
+ @options = options
32
+ end
33
+ end
34
+ end
@@ -28,30 +28,40 @@ module Kitchen
28
28
  # @return [String] logical name of this platform
29
29
  attr_reader :name
30
30
 
31
- # @return [Array] Array of Chef run_list items
32
- attr_reader :run_list
33
-
34
- # @return [Hash] Hash of Chef node attributes
35
- attr_reader :attributes
36
-
37
31
  # Constructs a new platform.
38
32
  #
39
33
  # @param [Hash] options configuration for a new platform
40
34
  # @option options [String] :name logical name of this platform
41
35
  # (**Required**)
42
- # @option options [Array<String>] :run_list Array of Chef run_list
43
- # items
44
- # @option options [Hash] :attributes Hash of Chef node attributes
45
36
  def initialize(options = {})
37
+ options = options.dup
46
38
  validate_options(options)
47
39
 
48
- @name = options[:name]
49
- @run_list = Array(options[:run_list])
50
- @attributes = options[:attributes] || Hash.new
40
+ @name = options.delete(:name)
41
+ @data = options.reject do |key, value|
42
+ [:driver_config, :driver_plugin, :provisioner].include?(key)
43
+ end
44
+ end
45
+
46
+ # Extra platform methods used for accessing Chef data such as a run list,
47
+ # node attributes, etc.
48
+ module Cheflike
49
+
50
+ # @return [Array] Array of Chef run_list items
51
+ def run_list
52
+ Array(data[:run_list])
53
+ end
54
+
55
+ # @return [Hash] Hash of Chef node attributes
56
+ def attributes
57
+ data[:attributes] || Hash.new
58
+ end
51
59
  end
52
60
 
53
61
  private
54
62
 
63
+ attr_reader :data
64
+
55
65
  def validate_options(opts)
56
66
  [:name].each do |k|
57
67
  raise ClientError, "Platform#new requires option :#{k}" if opts[k].nil?
@@ -0,0 +1,50 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, 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 'thor/util'
20
+
21
+ module Kitchen
22
+
23
+ # A provisioner is responsible for generating the commands necessary to
24
+ # install set up and use a configuration management tool such as Chef and
25
+ # Puppet.
26
+ #
27
+ # @author Fletcher Nichol <fnichol@nichol.ca>
28
+ module Provisioner
29
+
30
+ # Returns an instance of a provisioner given a plugin type string.
31
+ #
32
+ # @param plugin [String] a provisioner plugin type, to be constantized
33
+ # @return [Provisioner::Base] a driver instance
34
+ # @raise [ClientError] if a provisioner instance could not be created
35
+ def self.for_plugin(plugin, instance, config)
36
+ require("kitchen/provisioner/#{plugin}")
37
+
38
+ str_const = Thor::Util.camel_case(plugin)
39
+ klass = self.const_get(str_const)
40
+ klass.new(instance, config)
41
+ rescue UserError
42
+ raise
43
+ rescue LoadError, NameError
44
+ raise ClientError,
45
+ "Could not load the '#{plugin}' provisioner from the load path." +
46
+ " Please ensure that your provisioner is installed as a gem or" +
47
+ " included in your Gemfile if using Bundler."
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,63 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, 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
+ module Kitchen
20
+
21
+ module Provisioner
22
+
23
+ # Base class for a provisioner.
24
+ #
25
+ # @author Fletcher Nichol <fnichol@nichol.ca>
26
+ class Base
27
+
28
+ include Logging
29
+
30
+ def initialize(instance, config)
31
+ @instance = instance
32
+ @config = config
33
+ @logger = instance.logger
34
+ end
35
+
36
+ def install_command ; end
37
+
38
+ def init_command ; end
39
+
40
+ def create_sandbox ; end
41
+
42
+ def prepare_command ; end
43
+
44
+ def run_command ; end
45
+
46
+ def cleanup_sandbox ; end
47
+
48
+ def home_path ; end
49
+
50
+ protected
51
+
52
+ attr_reader :instance, :logger, :config, :tmpdir
53
+
54
+ def sudo(script)
55
+ config[:sudo] ? "sudo -E #{script}" : script
56
+ end
57
+
58
+ def kitchen_root
59
+ config[:kitchen_root]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,290 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2013, 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 'fileutils'
20
+ require 'json'
21
+
22
+ module Kitchen
23
+
24
+ module Provisioner
25
+
26
+ # Common implementation details for Chef-related provisioners.
27
+ #
28
+ # @author Fletcher Nichol <fnichol@nichol.ca>
29
+ class ChefBase < Base
30
+
31
+ def install_command
32
+ return nil unless config[:require_chef_omnibus]
33
+
34
+ url = "https://www.opscode.com/chef/install.sh"
35
+ flag = config[:require_chef_omnibus]
36
+ version = if flag.is_a?(String) && flag != "latest"
37
+ "-s -- -v #{flag.downcase}"
38
+ else
39
+ ""
40
+ end
41
+
42
+ <<-INSTALL.gsub(/^ {10}/, '')
43
+ bash -c '
44
+ should_update_chef() {
45
+ case "#{flag}" in
46
+ true|$(chef-solo -v | cut -d " " -f 2)) return 1 ;;
47
+ latest|*) return 0 ;;
48
+ esac
49
+ }
50
+
51
+ if [ ! -d "/opt/chef" ] || should_update_chef ; then
52
+ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
53
+ export PATH
54
+ echo "-----> Installing Chef Omnibus (#{flag})"
55
+ if command -v wget >/dev/null ; then
56
+ wget #{url} -O - | #{sudo('bash')} #{version}
57
+ elif command -v curl >/dev/null ; then
58
+ curl -sSL #{url} | #{sudo('bash')} #{version}
59
+ else
60
+ echo ">>>>>> Neither wget nor curl found on this instance."
61
+ exit 16
62
+ fi
63
+ fi'
64
+ INSTALL
65
+ end
66
+
67
+ def init_command
68
+ "#{sudo('rm')} -rf #{home_path}"
69
+ end
70
+
71
+ def cleanup_sandbox
72
+ return if tmpdir.nil?
73
+
74
+ debug("Cleaning up local sandbox in #{tmpdir}")
75
+ FileUtils.rmtree(tmpdir)
76
+ end
77
+
78
+ protected
79
+
80
+ def create_chef_sandbox
81
+ @tmpdir = Dir.mktmpdir("#{instance.name}-sandbox-")
82
+ debug("Creating local sandbox in #{tmpdir}")
83
+
84
+ yield if block_given?
85
+ prepare_json
86
+ prepare_data_bags
87
+ prepare_roles
88
+ prepare_nodes
89
+ prepare_secret
90
+ prepare_cache
91
+ prepare_cookbooks
92
+ tmpdir
93
+ end
94
+
95
+ def prepare_json
96
+ File.open(File.join(tmpdir, "dna.json"), "wb") do |file|
97
+ file.write(instance.dna.to_json)
98
+ end
99
+ end
100
+
101
+ def prepare_data_bags
102
+ return unless data_bags
103
+
104
+ info("Preparing data bags")
105
+ debug("Using data bags from #{data_bags}")
106
+
107
+ tmpbags_dir = File.join(tmpdir, "data_bags")
108
+ FileUtils.mkdir_p(tmpbags_dir)
109
+ FileUtils.cp_r(Dir.glob("#{data_bags}/*"), tmpbags_dir)
110
+ end
111
+
112
+ def prepare_roles
113
+ return unless roles
114
+
115
+ info("Preparing roles")
116
+ debug("Using roles from #{roles}")
117
+
118
+ tmproles_dir = File.join(tmpdir, "roles")
119
+ FileUtils.mkdir_p(tmproles_dir)
120
+ FileUtils.cp_r(Dir.glob("#{roles}/*"), tmproles_dir)
121
+ end
122
+
123
+ def prepare_nodes
124
+ return unless nodes
125
+
126
+ info("Preparing nodes")
127
+ debug("Using nodes from #{nodes}")
128
+
129
+ tmpnodes_dir = File.join(tmpdir, "nodes")
130
+ FileUtils.mkdir_p(tmpnodes_dir)
131
+ FileUtils.cp_r(Dir.glob("#{nodes}/*"), tmpnodes_dir)
132
+ end
133
+
134
+ def prepare_secret
135
+ return unless secret
136
+
137
+ info("Preparing encrypted data bag secret")
138
+ debug("Using secret from #{secret}")
139
+
140
+ FileUtils.cp_r(secret, File.join(tmpdir, "encrypted_data_bag_secret"))
141
+ end
142
+
143
+ def prepare_cache
144
+ FileUtils.mkdir_p(File.join(tmpdir, "cache"))
145
+ end
146
+
147
+ def prepare_cookbooks
148
+ if File.exists?(berksfile)
149
+ resolve_with_berkshelf
150
+ elsif File.exists?(cheffile)
151
+ resolve_with_librarian
152
+ elsif File.directory?(cookbooks_dir)
153
+ cp_cookbooks
154
+ elsif File.exists?(metadata_rb)
155
+ cp_this_cookbook
156
+ else
157
+ FileUtils.rmtree(tmpdir)
158
+ fatal("Berksfile, Cheffile, cookbooks/, or metadata.rb" +
159
+ " must exist in #{kitchen_root}")
160
+ raise UserError, "Cookbooks could not be found"
161
+ end
162
+
163
+ filter_only_cookbook_files
164
+ end
165
+
166
+ def filter_only_cookbook_files
167
+ info("Removing non-cookbook files in sandbox")
168
+
169
+ all_files = Dir.glob(File.join(tmpbooks_dir, "**/*")).
170
+ select { |fn| File.file?(fn) }
171
+ cookbook_files = Dir.glob(File.join(tmpbooks_dir, cookbook_files_glob)).
172
+ select { |fn| File.file?(fn) }
173
+
174
+ FileUtils.rm(all_files - cookbook_files)
175
+ end
176
+
177
+ def cookbook_files_glob
178
+ files = %w{README.* metadata.{json,rb}
179
+ attributes/**/* definitions/**/* files/**/* libraries/**/*
180
+ providers/**/* recipes/**/* resources/**/* templates/**/*
181
+ }
182
+
183
+ "*/{#{files.join(',')}}"
184
+ end
185
+
186
+ def berksfile
187
+ File.join(kitchen_root, "Berksfile")
188
+ end
189
+
190
+ def cheffile
191
+ File.join(kitchen_root, "Cheffile")
192
+ end
193
+
194
+ def metadata_rb
195
+ File.join(kitchen_root, "metadata.rb")
196
+ end
197
+
198
+ def cookbooks_dir
199
+ File.join(kitchen_root, "cookbooks")
200
+ end
201
+
202
+ def data_bags
203
+ instance.suite.data_bags_path
204
+ end
205
+
206
+ def roles
207
+ instance.suite.roles_path
208
+ end
209
+
210
+ def nodes
211
+ instance.suite.nodes_path
212
+ end
213
+
214
+ def secret
215
+ instance.suite.encrypted_data_bag_secret_key_path
216
+ end
217
+
218
+ def tmpbooks_dir
219
+ File.join(tmpdir, "cookbooks")
220
+ end
221
+
222
+ def cp_cookbooks
223
+ info("Preparing cookbooks from project directory")
224
+ debug("Using cookbooks from #{cookbooks_dir}")
225
+
226
+ FileUtils.mkdir_p(tmpbooks_dir)
227
+ FileUtils.cp_r(File.join(cookbooks_dir, "."), tmpbooks_dir)
228
+ cp_this_cookbook if File.exists?(metadata_rb)
229
+ end
230
+
231
+ def cp_this_cookbook
232
+ info("Preparing current project directory as a cookbook")
233
+ debug("Using metadata.rb from #{metadata_rb}")
234
+
235
+ cb_name = MetadataChopper.extract(metadata_rb).first or raise(UserError,
236
+ "The metadata.rb does not define the 'name' key." +
237
+ " Please add: `name '<cookbook_name>'` to metadata.rb and retry")
238
+
239
+ cb_path = File.join(tmpbooks_dir, cb_name)
240
+ glob = Dir.glob("#{kitchen_root}/{metadata.rb,README.*," +
241
+ "attributes,files,libraries,providers,recipes,resources,templates}")
242
+
243
+ FileUtils.mkdir_p(cb_path)
244
+ FileUtils.cp_r(glob, cb_path)
245
+ end
246
+
247
+ def resolve_with_berkshelf
248
+ info("Resolving cookbook dependencies with Berkshelf")
249
+ debug("Using Berksfile from #{berksfile}")
250
+
251
+ begin
252
+ require 'berkshelf'
253
+ rescue LoadError
254
+ fatal("The `berkself' gem is missing and must be installed." +
255
+ " Run `gem install berkshelf` or add the following " +
256
+ "to your Gemfile if you are using Bundler: `gem 'berkshelf'`.")
257
+ raise UserError, "Could not load Berkshelf"
258
+ end
259
+
260
+ Kitchen.mutex.synchronize do
261
+ Berkshelf::Berksfile.from_file(berksfile).
262
+ install(:path => tmpbooks_dir)
263
+ end
264
+ end
265
+
266
+ def resolve_with_librarian
267
+ info("Resolving cookbook dependencies with Librarian-Chef")
268
+ debug("Using Cheffile from #{cheffile}")
269
+
270
+ begin
271
+ require 'librarian/chef/environment'
272
+ require 'librarian/action/resolve'
273
+ require 'librarian/action/install'
274
+ rescue LoadError
275
+ fatal("The `librarian-chef' gem is missing and must be installed." +
276
+ " Run `gem install librarian-chef` or add the following " +
277
+ "to your Gemfile if you are using Bundler: `gem 'librarian-chef'`.")
278
+ raise UserError, "Could not load Librarian-Chef"
279
+ end
280
+
281
+ Kitchen.mutex.synchronize do
282
+ env = Librarian::Chef::Environment.new
283
+ env.config_db.local["path"] = tmpbooks_dir
284
+ Librarian::Action::Resolve.new(env).run
285
+ Librarian::Action::Install.new(env).run
286
+ end
287
+ end
288
+ end
289
+ end
290
+ end