kitchen-vagrant 0.15.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,21 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  require "bundler/gem_tasks"
2
- require 'cane/rake_task'
3
- require 'tailor/rake_task'
4
4
 
5
+ require "rspec/core/rake_task"
6
+ desc "Run all specs in spec directory"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.pattern = "spec/**/*_spec.rb"
9
+ end
10
+
11
+ desc "Run all test suites"
12
+ task :test => [:spec]
13
+
14
+ require "cane/rake_task"
5
15
  desc "Run cane to check quality metrics"
6
- Cane::RakeTask.new
16
+ Cane::RakeTask.new do |cane|
17
+ cane.canefile = "./.cane"
18
+ end
7
19
 
8
- Tailor::RakeTask.new do |task|
9
- task.file_set('lib/**/*.rb', 'code')
20
+ require "finstyle"
21
+ require "rubocop/rake_task"
22
+ RuboCop::RakeTask.new(:style) do |task|
23
+ task.options << "--display-cop-names"
10
24
  end
11
25
 
12
26
  desc "Display LOC stats"
13
27
  task :stats do
14
28
  puts "\n## Production Code Stats"
15
29
  sh "countloc -r lib/kitchen"
30
+ puts "\n## Test Code Stats"
31
+ sh "countloc -r spec"
16
32
  end
17
33
 
18
34
  desc "Run all quality tasks"
19
- task :quality => [:cane, :tailor, :stats]
35
+ task :quality => [:cane, :style, :stats]
20
36
 
21
- task :default => [ :quality ]
37
+ task :default => [:test, :quality]
@@ -1,26 +1,34 @@
1
1
  # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path("../lib", __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'kitchen/driver/vagrant_version.rb'
4
+ require "kitchen/driver/vagrant_version.rb"
5
+ require "English"
5
6
 
6
7
  Gem::Specification.new do |gem|
7
8
  gem.name = "kitchen-vagrant"
8
9
  gem.version = Kitchen::Driver::VAGRANT_VERSION
9
- gem.license = 'Apache 2.0'
10
+ gem.license = "Apache 2.0"
10
11
  gem.authors = ["Fletcher Nichol"]
11
12
  gem.email = ["fnichol@nichol.ca"]
12
13
  gem.description = "Kitchen::Driver::Vagrant - A Vagrant Driver for Test Kitchen."
13
14
  gem.summary = gem.description
14
15
  gem.homepage = "https://github.com/test-kitchen/kitchen-vagrant/"
15
16
 
16
- gem.files = `git ls-files`.split($/)
17
+ gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
18
  gem.executables = []
18
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
20
  gem.require_paths = ["lib"]
20
21
 
21
- gem.add_dependency 'test-kitchen', '~> 1.0'
22
+ gem.add_dependency "test-kitchen", "~> 1.0"
22
23
 
23
- gem.add_development_dependency 'cane'
24
- gem.add_development_dependency 'tailor'
25
- gem.add_development_dependency 'countloc'
24
+ gem.add_development_dependency "countloc", "~> 0.4"
25
+ gem.add_development_dependency "rake"
26
+ gem.add_development_dependency "rspec", "~> 3.2"
27
+ gem.add_development_dependency "simplecov", "~> 0.9"
28
+
29
+ # style and complexity libraries are tightly version pinned as newer releases
30
+ # may introduce new and undesireable style choices which would be immediately
31
+ # enforced in CI
32
+ gem.add_development_dependency "finstyle", "1.4.0"
33
+ gem.add_development_dependency "cane", "2.6.2"
26
34
  end
@@ -16,10 +16,11 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
- require 'fileutils'
20
- require 'rubygems/version'
19
+ require "erb"
20
+ require "fileutils"
21
+ require "rubygems/version"
21
22
 
22
- require 'kitchen'
23
+ require "kitchen"
23
24
 
24
25
  module Kitchen
25
26
 
@@ -28,123 +29,153 @@ module Kitchen
28
29
  # Vagrant driver for Kitchen. It communicates to Vagrant via the CLI.
29
30
  #
30
31
  # @author Fletcher Nichol <fnichol@nichol.ca>
31
- #
32
- # @todo Vagrant installation check and version will be placed into any
33
- # dependency hook checks when feature is released
34
32
  class Vagrant < Kitchen::Driver::SSHBase
35
33
 
34
+ default_config(:box) { |driver| driver.default_box }
35
+ required_config :box
36
+
37
+ default_config :box_check_update, nil
38
+
39
+ default_config(:box_url) { |driver| driver.default_box_url }
40
+
41
+ default_config :box_version, nil
42
+
36
43
  default_config :customize, {}
44
+
45
+ default_config :gui, nil
46
+
37
47
  default_config :network, []
38
- default_config :synced_folders, []
39
- default_config :pre_create_command, nil
40
48
 
41
- default_config :vagrantfile_erb,
42
- File.join(File.dirname(__FILE__), "../../../templates/Vagrantfile.erb")
49
+ default_config :pre_create_command, nil
43
50
 
44
- default_config :provider,
45
- ENV.fetch('VAGRANT_DEFAULT_PROVIDER', "virtualbox")
51
+ default_config :provision, false
46
52
 
47
- default_config :vm_hostname do |driver|
48
- "#{driver.instance.name}.vagrantup.com"
53
+ default_config :provider do |_|
54
+ ENV.fetch("VAGRANT_DEFAULT_PROVIDER", "virtualbox")
49
55
  end
50
56
 
51
- default_config :box do |driver|
52
- "opscode-#{driver.instance.platform.name}"
53
- end
57
+ default_config :ssh, {}
54
58
 
55
- default_config :box_url do |driver|
56
- driver.default_box_url
57
- end
59
+ default_config :synced_folders, []
58
60
 
59
- required_config :box
61
+ default_config :vagrantfile_erb,
62
+ File.join(File.dirname(__FILE__), "../../../templates/Vagrantfile.erb")
63
+ expand_path_for :vagrantfile_erb
64
+
65
+ default_config :vagrantfiles, []
66
+
67
+ default_config(:vm_hostname) { |driver| driver.instance.name }
60
68
 
61
69
  no_parallel_for :create, :destroy
62
70
 
71
+ # Creates a Vagrant VM instance.
72
+ #
73
+ # @param state [Hash] mutable instance state
74
+ # @raise [ActionFailed] if the action could not be completed
63
75
  def create(state)
64
76
  create_vagrantfile
65
77
  run_pre_create_command
66
- cmd = "vagrant up --no-provision"
67
- cmd += " --provider=#{config[:provider]}" if config[:provider]
68
- run cmd
69
- set_ssh_state(state)
78
+ run_vagrant_up
79
+ update_state(state)
70
80
  info("Vagrant instance #{instance.to_str} created.")
71
81
  end
72
82
 
73
- def converge(state)
74
- create_vagrantfile
75
- super
83
+ # @return [String,nil] the Vagrant box for this Instance
84
+ def default_box
85
+ if bento_boxes.include?(instance.platform.name)
86
+ "opscode-#{instance.platform.name}"
87
+ else
88
+ instance.platform.name
89
+ end
76
90
  end
77
91
 
78
- def setup(state)
79
- create_vagrantfile
80
- super
81
- end
92
+ # @return [String,nil] the Vagrant box URL for this Instance
93
+ def default_box_url
94
+ return unless bento_boxes.include?(instance.platform.name)
82
95
 
83
- def verify(state)
84
- create_vagrantfile
85
- super
96
+ provider = config[:provider]
97
+ provider = "vmware" if config[:provider] =~ /^vmware_(.+)$/
98
+
99
+ if %w[virtualbox vmware].include?(provider)
100
+ "https://opscode-vm-bento.s3.amazonaws.com/vagrant/#{provider}/" \
101
+ "opscode_#{instance.platform.name}_chef-provisionerless.box"
102
+ end
86
103
  end
87
104
 
105
+ # Destroys an instance.
106
+ #
107
+ # @param state [Hash] mutable instance state
108
+ # @raise [ActionFailed] if the action could not be completed
88
109
  def destroy(state)
89
110
  return if state[:hostname].nil?
90
111
 
91
112
  create_vagrantfile
92
113
  @vagrantfile_created = false
93
- run "vagrant destroy -f"
114
+ run("vagrant destroy -f")
94
115
  FileUtils.rm_rf(vagrant_root)
95
116
  info("Vagrant instance #{instance.to_str} destroyed.")
96
117
  state.delete(:hostname)
97
118
  end
98
119
 
99
- def verify_dependencies
100
- check_vagrant_version
101
- end
102
-
103
- def instance=(instance)
104
- @instance = instance
105
- resolve_config!
120
+ # A lifecycle method that should be invoked when the object is about
121
+ # ready to be used. A reference to an Instance is required as
122
+ # configuration dependant data may be access through an Instance. This
123
+ # also acts as a hook point where the object may wish to perform other
124
+ # last minute checks, validations, or configuration expansions.
125
+ #
126
+ # @param instance [Instance] an associated instance
127
+ # @return [self] itself, for use in chaining
128
+ # @raise [ClientError] if instance parameter is nil
129
+ def finalize_config!(instance)
130
+ super
131
+ config[:vagrantfiles] = config[:vagrantfiles].map do |path|
132
+ File.expand_path(path, config[:kitchen_root])
133
+ end
134
+ finalize_pre_create_command!
135
+ finalize_synced_folders!
136
+ self
106
137
  end
107
138
 
108
- def default_box_url
109
- bucket = config[:provider]
110
- bucket = 'vmware' if config[:provider] =~ /^vmware_(.+)$/
111
-
112
- "https://opscode-vm-bento.s3.amazonaws.com/vagrant/#{bucket}/" +
113
- "opscode_#{instance.platform.name}_chef-provisionerless.box"
139
+ # Performs whatever tests that may be required to ensure that this driver
140
+ # will be able to function in the current environment. This may involve
141
+ # checking for the presence of certain directories, software installed,
142
+ # etc.
143
+ #
144
+ # @raise [UserError] if the driver will not be able to perform or if a
145
+ # documented dependency is missing from the system
146
+ def verify_dependencies
147
+ super
148
+ if Gem::Version.new(vagrant_version) < Gem::Version.new(MIN_VER.dup)
149
+ raise UserError, "Detected an old version of Vagrant " \
150
+ "(#{vagrant_version})." \
151
+ " Please upgrade to version #{MIN_VER} or higher from #{WEBSITE}."
152
+ end
114
153
  end
115
154
 
116
155
  protected
117
156
 
118
- WEBSITE = "http://downloads.vagrantup.com/"
119
- MIN_VER = "1.1.0"
120
-
121
- def run(cmd, options = {})
122
- cmd = "echo #{cmd}" if config[:dry_run]
123
- run_command(cmd, { :cwd => vagrant_root }.merge(options))
124
- end
125
-
126
- def silently_run(cmd)
127
- run_command(cmd,
128
- :live_stream => nil, :quiet => logger.debug? ? false : true)
129
- end
130
-
131
- def run_pre_create_command
132
- if config[:pre_create_command]
133
- run(config[:pre_create_command], :cwd => config[:kitchen_root])
134
- end
135
- end
136
-
137
- def vagrant_root
138
- @vagrant_root ||= File.join(
139
- config[:kitchen_root], %w{.kitchen kitchen-vagrant}, instance.name
140
- )
157
+ WEBSITE = "http://www.vagrantup.com/downloads.html".freeze
158
+ MIN_VER = "1.1.0".freeze
159
+
160
+ # Retuns a list of Vagrant base boxes produced by the Bento project
161
+ # (https://github.com/chef/bento).
162
+ #
163
+ # @return [Arrau<String>] list of Bento box names
164
+ # @api private
165
+ def bento_boxes
166
+ %W[
167
+ centos-5.11 centos-6.6 centos-7.0 debian-6.0.10 debian-7.8 fedora-20
168
+ fedora-21 freebsd-9.3 freebsd-10.1 opensuse-13.1 ubuntu-10.04
169
+ ubuntu-12.04 ubuntu-14.04 ubuntu-14.10
170
+ ].map { |name| [name, "#{name}-i386"] }.flatten
141
171
  end
142
172
 
173
+ # Renders and writes out a Vagrantfile dedicated to this instance.
174
+ #
175
+ # @api private
143
176
  def create_vagrantfile
144
177
  return if @vagrantfile_created
145
178
 
146
- finalize_synced_folder_config
147
-
148
179
  vagrantfile = File.join(vagrant_root, "Vagrantfile")
149
180
  debug("Creating Vagrantfile for #{instance.to_str} (#{vagrantfile})")
150
181
  FileUtils.mkdir_p(vagrant_root)
@@ -153,83 +184,147 @@ module Kitchen
153
184
  @vagrantfile_created = true
154
185
  end
155
186
 
156
- def finalize_synced_folder_config
157
- config[:synced_folders].map! do |source, destination, options|
158
- [
159
- File.expand_path(
160
- source.gsub("%{instance_name}", instance.name),
161
- config[:kitchen_root]
162
- ),
163
- destination.gsub("%{instance_name}", instance.name),
164
- options || "nil"
165
- ]
166
- end
187
+ # Logs the Vagrantfile's contents to the debug log level.
188
+ #
189
+ # @param vagrantfile [String] path to the Vagrantfile
190
+ # @api private
191
+ def debug_vagrantfile(vagrantfile)
192
+ return unless logger.debug?
193
+
194
+ debug("------------")
195
+ IO.read(vagrantfile).each_line { |l| debug("#{l.chomp}") }
196
+ debug("------------")
197
+ end
198
+
199
+ # Replaces any `{{vagrant_root}}` tokens in the pre create command.
200
+ #
201
+ # @api private
202
+ def finalize_pre_create_command!
203
+ return if config[:pre_create_command].nil?
204
+
205
+ config[:pre_create_command] = config[:pre_create_command].
206
+ gsub("{{vagrant_root}}", vagrant_root)
207
+ end
208
+
209
+ # Replaces an `%{instance_name}` tokens in the synced folder items.
210
+ #
211
+ # @api private
212
+ def finalize_synced_folders!
213
+ config[:synced_folders] = config[:synced_folders].
214
+ map do |source, destination, options|
215
+ [
216
+ File.expand_path(
217
+ source.gsub("%{instance_name}", instance.name),
218
+ config[:kitchen_root]
219
+ ),
220
+ destination.gsub("%{instance_name}", instance.name),
221
+ options || "nil"
222
+ ]
223
+ end
167
224
  end
168
225
 
226
+ # Renders the Vagrantfile ERb template.
227
+ #
228
+ # @return [String] the contents for a Vagrantfile
229
+ # @raise [ActionFailed] if the Vagrantfile template was not found
230
+ # @api private
169
231
  def render_template
170
- if File.exists?(template)
171
- ERB.new(IO.read(template)).result(binding).gsub(%r{^\s*$\n}, '')
232
+ template = File.expand_path(
233
+ config[:vagrantfile_erb], config[:kitchen_root])
234
+
235
+ if File.exist?(template)
236
+ ERB.new(IO.read(template)).result(binding).gsub(%r{^\s*$\n}, "")
172
237
  else
173
238
  raise ActionFailed, "Could not find Vagrantfile template #{template}"
174
239
  end
175
240
  end
176
241
 
177
- def template
178
- File.expand_path(config[:vagrantfile_erb], config[:kitchen_root])
242
+ # Convenience method to run a command locally.
243
+ #
244
+ # @param cmd [String] command to run locally
245
+ # @param options [Hash] options hash
246
+ # @see Kitchen::ShellOut.run_command
247
+ # @api private
248
+ def run(cmd, options = {})
249
+ cmd = "echo #{cmd}" if config[:dry_run]
250
+ run_command(cmd, { :cwd => vagrant_root }.merge(options))
179
251
  end
180
252
 
181
- def set_ssh_state(state)
253
+ # Runs a local command before `vagrant up` has been called.
254
+ #
255
+ # @api private
256
+ def run_pre_create_command
257
+ if config[:pre_create_command]
258
+ run(config[:pre_create_command], :cwd => config[:kitchen_root])
259
+ end
260
+ end
261
+
262
+ # Runs a local command without streaming the stdout to the logger.
263
+ #
264
+ # @param cmd [String] command to run locally
265
+ # @api private
266
+ def run_silently(cmd, options = {})
267
+ merged = {
268
+ :live_stream => nil, :quiet => (logger.debug? ? false : true)
269
+ }.merge(options)
270
+ run(cmd, merged)
271
+ end
272
+
273
+ # Runs the `vagrant up` command locally.
274
+ #
275
+ # @api private
276
+ def run_vagrant_up
277
+ cmd = "vagrant up"
278
+ cmd += " --no-provision" unless config[:provision]
279
+ cmd += " --provider #{config[:provider]}" if config[:provider]
280
+ run(cmd)
281
+ end
282
+
283
+ # Updates any state after creation.
284
+ #
285
+ # @param state [Hash] mutable instance state
286
+ # @api private
287
+ def update_state(state)
182
288
  hash = vagrant_ssh_config
183
289
 
184
290
  state[:hostname] = hash["HostName"]
185
291
  state[:username] = hash["User"]
186
292
  state[:ssh_key] = hash["IdentityFile"]
187
293
  state[:port] = hash["Port"]
294
+ state[:proxy_command] = hash["ProxyCommand"] if hash["ProxyCommand"]
295
+ end
296
+
297
+ # @return [String] full local path to the directory containing the
298
+ # instance's Vagrantfile
299
+ # @api private
300
+ def vagrant_root
301
+ @vagrant_root ||= instance.nil? ? nil : File.join(
302
+ config[:kitchen_root], %w[.kitchen kitchen-vagrant],
303
+ "kitchen-#{File.basename(config[:kitchen_root])}-#{instance.name}"
304
+ )
188
305
  end
189
306
 
307
+ # @return [Hash] key/value pairs resulting from parsing a
308
+ # `vagrant ssh-config` local command invocation
309
+ # @api private
190
310
  def vagrant_ssh_config
191
- output = run("vagrant ssh-config", :live_stream => nil)
192
- lines = output.split("\n").map do |line|
311
+ lines = run_silently("vagrant ssh-config").split("\n").map do |line|
193
312
  tokens = line.strip.partition(" ")
194
- [tokens.first, tokens.last.gsub(/"/, '')]
313
+ [tokens.first, tokens.last.gsub(/"/, "")]
195
314
  end
196
315
  Hash[lines]
197
316
  end
198
317
 
199
- def debug_vagrantfile(vagrantfile)
200
- if logger.debug?
201
- debug("------------")
202
- IO.read(vagrantfile).each_line { |l| debug("#{l.chomp}") }
203
- debug("------------")
204
- end
205
- end
206
-
207
- def resolve_config!
208
- unless config[:vagrantfile_erb].nil?
209
- config[:vagrantfile_erb] =
210
- File.expand_path(config[:vagrantfile_erb], config[:kitchen_root])
211
- end
212
- unless config[:pre_create_command].nil?
213
- config[:pre_create_command] =
214
- config[:pre_create_command].gsub("{{vagrant_root}}", vagrant_root)
215
- end
216
- end
217
-
318
+ # @return [String] version of Vagrant
319
+ # @raise [UserError] if the `vagrant` command can not be found locally
320
+ # @api private
218
321
  def vagrant_version
219
- version_string = silently_run("vagrant --version")
220
- version_string = version_string.chomp.split(" ").last
322
+ @version ||= run_silently("vagrant --version", :cwd => Dir.pwd).
323
+ chomp.split(" ").last
221
324
  rescue Errno::ENOENT
222
- raise UserError, "Vagrant #{MIN_VER} or higher is not installed." +
325
+ raise UserError, "Vagrant #{MIN_VER} or higher is not installed." \
223
326
  " Please download a package from #{WEBSITE}."
224
327
  end
225
-
226
- def check_vagrant_version
227
- version = vagrant_version
228
- if Gem::Version.new(version) < Gem::Version.new(MIN_VER)
229
- raise UserError, "Detected an old version of Vagrant (#{version})." +
230
- " Please upgrade to version #{MIN_VER} or higher from #{WEBSITE}."
231
- end
232
- end
233
328
  end
234
329
  end
235
330
  end