From 302ddfc3f09ba9427a208b55309eef70e2646287 Mon Sep 17 00:00:00 2001 From: Fletcher Nichol Date: Fri, 22 Mar 2013 13:18:05 -0600 Subject: [PATCH 1/3] [Breaking] Support Vagrant 1.1+ and remove vagrant gem dependency. A new strategy is used here when dealing with Vagrant: each Kitchen Instance gets its own dedicated Vagrantfile which is rendered out just before any Vagrant CLI commands are invoked. In this way we can avoid writing a vagrant plugin and we return any Vagrantfile in the project root back to the user to fully control. An isolated Vagrant sandbox is created for each Instance in a an internal directory under .kitchen/. For example, given an Instance name of "default-ubuntu-1204", a Vagrantfile will be created in: .kitchen/kitchen-vagrant/default-ubuntu-1204/Vagrantfile --- Rakefile | 6 +- kitchen-vagrant.gemspec | 1 - lib/kitchen/driver/vagrant.rb | 70 ++++++++----- lib/kitchen/vagrant.rb | 101 ------------------- lib/kitchen/vagrant/vagrantfile_creator.rb | 110 +++++++++++++++++++++ 5 files changed, 157 insertions(+), 131 deletions(-) delete mode 100644 lib/kitchen/vagrant.rb create mode 100644 lib/kitchen/vagrant/vagrantfile_creator.rb diff --git a/Rakefile b/Rakefile index 896e807f..d6f66226 100644 --- a/Rakefile +++ b/Rakefile @@ -3,11 +3,7 @@ require 'cane/rake_task' require 'tailor/rake_task' desc "Run cane to check quality metrics" -Cane::RakeTask.new do |cane| - cane.abc_exclude = %w( - Kitchen::Vagrant.define_vagrant_vm - ) -end +Cane::RakeTask.new Tailor::RakeTask.new diff --git a/kitchen-vagrant.gemspec b/kitchen-vagrant.gemspec index 36b178c5..fcdc6e2b 100644 --- a/kitchen-vagrant.gemspec +++ b/kitchen-vagrant.gemspec @@ -18,7 +18,6 @@ Gem::Specification.new do |gem| gem.require_paths = ["lib"] gem.add_dependency 'test-kitchen', '~> 1.0.0.alpha.0' - gem.add_dependency 'vagrant', '~> 1.0' gem.add_development_dependency 'cane' gem.add_development_dependency 'tailor' diff --git a/lib/kitchen/driver/vagrant.rb b/lib/kitchen/driver/vagrant.rb index c6667d42..2e5e585d 100644 --- a/lib/kitchen/driver/vagrant.rb +++ b/lib/kitchen/driver/vagrant.rb @@ -16,7 +16,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +require 'fileutils' + require 'kitchen' +require 'kitchen/vagrant/vagrantfile_creator' module Kitchen @@ -25,6 +28,9 @@ module Driver # Vagrant driver for Kitchen. It communicates to Vagrant via the CLI. # # @author Fletcher Nichol + # + # @todo Vagrant installation check and version will be placed into any + # dependency hook checks when feature is released class Vagrant < Kitchen::Driver::SSHBase default_config :customize, {:memory => '256'} @@ -32,60 +38,76 @@ class Vagrant < Kitchen::Driver::SSHBase no_parallel_for :create, :destroy def create(state) - # @todo Vagrantfile setup will be placed in any dependency hook - # checks when feature is released - vagrantfile = File.join(config[:kitchen_root], "Vagrantfile") - create_vagrantfile(vagrantfile) unless File.exists?(vagrantfile) - state[:hostname] = instance.name - run "vagrant up #{state[:hostname]} --no-provision" + create_vagrantfile(state) + run "vagrant up --no-provision" info("Vagrant instance <#{state[:hostname]}> created.") end def converge(state) + create_vagrantfile(state) ssh_args = build_ssh_args(state) install_omnibus(ssh_args) if config[:require_chef_omnibus] - run "vagrant provision #{state[:hostname]}" + run "vagrant provision" + end + + def setup(state) + create_vagrantfile(state) + super + end + + def verify(state) + create_vagrantfile(state) + super end def destroy(state) return if state[:hostname].nil? - run "vagrant destroy #{state[:hostname]} -f" + create_vagrantfile(state) + run "vagrant destroy -f" + FileUtils.rm_rf(vagrant_root) info("Vagrant instance <#{state[:hostname]}> destroyed.") state.delete(:hostname) end def login_command(state) - %W{vagrant ssh #{state[:hostname]}} + create_vagrantfile(state) + FileUtils.cd(vagrant_root, :verbose => logger.debug? ? true : false) + %W{vagrant ssh} end protected def ssh(ssh_args, cmd) - run %{vagrant ssh #{ssh_args.first} --command '#{cmd}'} + run %{vagrant ssh --command '#{cmd}'} end def run(cmd) cmd = "echo #{cmd}" if config[:dry_run] - run_command(cmd) + FileUtils.cd(vagrant_root, :verbose => logger.debug? ? true : false) do + run_command(cmd) + end end - def create_vagrantfile(vagrantfile) - File.open(vagrantfile, "wb") { |f| f.write(vagrantfile_contents) } + def vagrant_root + @vagrant_root ||= File.join( + config[:kitchen_root], %w{.kitchen kitchen-vagrant}, instance.name + ) end - def vagrantfile_contents - arr = [] - arr << %{require 'kitchen/vagrant'} - if File.exists?(File.join(config[:kitchen_root], "Berksfile")) - arr << %{require 'berkshelf/vagrant'} - end - arr << %{} - arr << %{Vagrant::Config.run do |config|} - arr << %{ Kitchen::Vagrant.define_vms(config)} - arr << %{end\n} - arr.join("\n") + def create_vagrantfile(state) + return if @vagrantfile_created + + vagrantfile = File.join(vagrant_root, "Vagrantfile") + debug("Creating Vagrantfile for <#{state[:hostname]}> (#{vagrantfile})") + FileUtils.mkdir_p(vagrant_root) + File.open(vagrantfile, "wb") { |f| f.write(creator.render) } + @vagrantfile_created = true + end + + def creator + Kitchen::Vagrant::VagrantfileCreator.new(instance, config) end end end diff --git a/lib/kitchen/vagrant.rb b/lib/kitchen/vagrant.rb deleted file mode 100644 index 9feef474..00000000 --- a/lib/kitchen/vagrant.rb +++ /dev/null @@ -1,101 +0,0 @@ -# -*- encoding: utf-8 -*- -# -# Author:: Fletcher Nichol () -# -# Copyright (C) 2012, Fletcher Nichol -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -require 'forwardable' -require 'vagrant' - -require 'kitchen' - -module Kitchen - - module Vagrant - - # A Vagrant confiuration class which wraps a Kitchen::Config instance. - # - # @author Fletcher Nichol - class Config < ::Vagrant::Config::Base - extend Forwardable - - def_delegators :@config, :suites, :suites=, :platforms, :platforms=, - :instances, :yaml_file, :yaml_file=, :log_level, :log_level=, - :test_base_path, :test_base_path=, :yaml_data - - def initialize - @config = Kitchen::Config.new - @config.yaml_file = ENV['KITCHEN_YAML'] if ENV['KITCHEN_YAML'] - end - - # Override default implementation to prevent serializing the config - # instance variable, which may contain circular references. - # - # @return [Hash] an empty Hash - def instance_variables_hash - {} - end - end - - # Defines all Vagrant virtual machines, one for each instance. - # - # @param config [Vagrant::Config::Top] Vagrant top level config object - def self.define_vms(config) - config.kitchen.instances.each do |instance| - define_vagrant_vm(config, instance) - end - end - - private - - def self.define_vagrant_vm(config, instance) - driver = instance.driver - - config.vm.define instance.name do |c| - c.vm.box = driver[:box] - c.vm.box_url = driver[:box_url] if driver[:box_url] - c.vm.host_name = "#{instance.name}.vagrantup.com" - - Array(driver[:forward_port]).each do |ports| - if ports.length != 2 - raise ArgumentError, - "Vagrant config.vm.forward_port only accepts two arguments" - end - c.vm.forward_port(*ports) - end - - Array(driver[:network]).each do |network_options| - options = Array(network_options) - type = options.shift - c.vm.network(type.to_sym, *options) - end - - driver[:customize].each do |key, value| - c.vm.customize ["modifyvm", :id, "--#{key}", value] - end - - c.vm.provision :chef_solo do |chef| - chef.log_level = config.kitchen.log_level - chef.run_list = instance.run_list - chef.json = instance.attributes - chef.data_bags_path = instance.suite.data_bags_path - chef.roles_path = instance.suite.roles_path - end - end - end - end -end - -Vagrant.config_keys.register(:kitchen) { Kitchen::Vagrant::Config } diff --git a/lib/kitchen/vagrant/vagrantfile_creator.rb b/lib/kitchen/vagrant/vagrantfile_creator.rb new file mode 100644 index 00000000..3e89ae79 --- /dev/null +++ b/lib/kitchen/vagrant/vagrantfile_creator.rb @@ -0,0 +1,110 @@ +# -*- encoding: utf-8 -*- +# +# Author:: Fletcher Nichol () +# +# Copyright (C) 2013, Fletcher Nichol +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Kitchen + + module Vagrant + + # Class to render Vagrantfiles to be used by the Kitchen Vagrant driver. + # + # @author Fletcher Nichol + class VagrantfileCreator + + def initialize(instance, config) + @instance = instance + @config = config + end + + def render + vagrantfile_contents_v2 + end + + private + + attr_reader :instance, :config + + def vagrantfile_contents_v2 + arr = [] + arr << %{Vagrant.configure("2") do |c|} + arr << %{ c.vm.box = "#{config[:box]}"} + arr << %{ c.vm.box_url = "#{config[:box_url]}"} if config[:box_url] + arr << %{ c.vm.hostname = "#{instance.name}.vagrantup.com"} + network_block(arr) + provider_block(arr) + chef_block(arr) + berkshelf_block(arr) + arr << %{end} + arr.join("\n") + end + + def network_block(arr) + Array(config[:network]).each do |network_options| + options = Array(network_options.dup) + type = options.shift + arr << %{ c.vm.network(:#{type}, #{options.join(", ")})} + end + end + + def provider_block(arr) + arr << %{ c.vm.provider :virtualbox do |p|} + config[:customize].each do |key, value| + arr << %{ p.customize ["modifyvm", :id, "--#{key}", #{value}]} + end + arr << %{ end} + end + + def chef_block(arr) + arr << %{ c.vm.provision :chef_solo do |chef|} + arr << %{ chef.log_level = #{vagrant_logger_level}} + arr << %{ chef.run_list = #{instance.run_list.inspect}} + arr << %{ chef.json = #{instance.attributes.to_s}} + if instance.suite.data_bags_path + arr << %{ chef.data_bags_path = #{instance.suite.data_bags_path}} + end + if instance.suite.roles_path + arr << %{ chef.roles_path = #{instance.suite.roles_path}} + end + arr << %{ end} + end + + def berkshelf_block(arr) + if File.exists?(berksfile) + arr << %{ c.berkshelf.berksfile_path = "#{berksfile}"} + end + end + + def vagrant_logger_level + if instance.logger.debug? + ":debug" + elsif instance.logger.info? + ":info" + elsif instance.logger.error? + ":error" + elsif instance.logger.fatal? + ":fatal" + else + ":info" + end + end + + def berksfile + File.join(config[:kitchen_root], "Berksfile") + end + end + end +end From 6ffd3863c26deea4abe1bde05714fab8c8af8135 Mon Sep 17 00:00:00 2001 From: Fletcher Nichol Date: Fri, 22 Mar 2013 16:21:25 -0600 Subject: [PATCH 2/3] Apply simplifying assumption of only supporting Vagrant 1.1 and up. --- lib/kitchen/vagrant/vagrantfile_creator.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/kitchen/vagrant/vagrantfile_creator.rb b/lib/kitchen/vagrant/vagrantfile_creator.rb index 3e89ae79..741735de 100644 --- a/lib/kitchen/vagrant/vagrantfile_creator.rb +++ b/lib/kitchen/vagrant/vagrantfile_creator.rb @@ -31,25 +31,25 @@ def initialize(instance, config) end def render - vagrantfile_contents_v2 + arr = [] + arr << %{Vagrant.configure("2") do |c|} + common_block(arr) + network_block(arr) + provider_block(arr) + chef_block(arr) + berkshelf_block(arr) + arr << %{end} + arr.join("\n") end private attr_reader :instance, :config - def vagrantfile_contents_v2 - arr = [] - arr << %{Vagrant.configure("2") do |c|} + def common_block(arr) arr << %{ c.vm.box = "#{config[:box]}"} arr << %{ c.vm.box_url = "#{config[:box_url]}"} if config[:box_url] arr << %{ c.vm.hostname = "#{instance.name}.vagrantup.com"} - network_block(arr) - provider_block(arr) - chef_block(arr) - berkshelf_block(arr) - arr << %{end} - arr.join("\n") end def network_block(arr) From 45809dfd412e7ed23ee6fecb65cde226cfb1fdc7 Mon Sep 17 00:00:00 2001 From: Fletcher Nichol Date: Fri, 22 Mar 2013 20:54:22 -0600 Subject: [PATCH 3/3] Update Vagrant#login_command to return a LoginCommand object. This is the upstream API update needed to change directory before issuing the SSH command to connect to a Test Kitchen controlled Vagrant VM. Coolbeans. --- lib/kitchen/driver/vagrant.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/kitchen/driver/vagrant.rb b/lib/kitchen/driver/vagrant.rb index 2e5e585d..b51b066c 100644 --- a/lib/kitchen/driver/vagrant.rb +++ b/lib/kitchen/driver/vagrant.rb @@ -73,8 +73,7 @@ def destroy(state) def login_command(state) create_vagrantfile(state) - FileUtils.cd(vagrant_root, :verbose => logger.debug? ? true : false) - %W{vagrant ssh} + LoginCommand.new(%W{vagrant ssh}, :chdir => vagrant_root) end protected @@ -85,9 +84,7 @@ def ssh(ssh_args, cmd) def run(cmd) cmd = "echo #{cmd}" if config[:dry_run] - FileUtils.cd(vagrant_root, :verbose => logger.debug? ? true : false) do - run_command(cmd) - end + run_command(cmd, :cwd => vagrant_root) end def vagrant_root