diff --git a/README.md b/README.md index ccff08f3..6d6f8b7e 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,12 @@ platforms: driver: box: opscode-ubuntu-12.04 box_url: https://opscode-vm-bento.s3.amazonaws.com/vagrant/opscode_ubuntu-12.04_provisionerless.box +- name: windows2012r2_cloud + driver: + box: windows2012r2_cloud + box_url: https://s3.amazonaws.com/box-cutter-us-east-1-cloudtrail/windows/virtualbox4.3.12/win2012r2-datacenter-chef11.12.8.box + transport: + name: winrm - name: ubuntu-12.10 driver: box: opscode-ubuntu-12.10 @@ -119,6 +125,17 @@ By default the value is unset, or `nil`. In this case the driver will use the Vagrant [default provider][vagrant_default_provider] which at this current time is `virtualbox` unless set by `VAGRANT_DEFAULT_PROVIDER` environment variable. +### gui + +This is the vm console gui configuration and is used to make the console +visible/hidden following a create action. + +If this value is `nil` or `false` then the console will be hidden, if `true` +the console will be visble. + +This is particularly helpful for debugging local Virtualbox/VMware Fusion/VMware Workstation guests along with +adding `clipboard: bidirectional` to the customize section. + ### customize A **Hash** of customizations to a Vagrant virtual machine. Each key/value @@ -130,6 +147,7 @@ driver: customize: memory: 1024 cpuexecutioncap: 50 + clipboard: bidirectional ``` will generate a Vagrantfile configuration similar to: diff --git a/Rakefile b/Rakefile index 0cb701cf..30d36bc7 100644 --- a/Rakefile +++ b/Rakefile @@ -1,6 +1,9 @@ require "bundler/gem_tasks" require 'cane/rake_task' require 'tailor/rake_task' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:test) desc "Run cane to check quality metrics" Cane::RakeTask.new @@ -16,6 +19,6 @@ task :stats do end desc "Run all quality tasks" -task :quality => [:cane, :tailor, :stats] +task :quality => [:cane, :tailor, :stats, :test] task :default => [ :quality ] diff --git a/kitchen-vagrant.gemspec b/kitchen-vagrant.gemspec index aba61b9e..fd5ffa53 100644 --- a/kitchen-vagrant.gemspec +++ b/kitchen-vagrant.gemspec @@ -23,4 +23,5 @@ Gem::Specification.new do |gem| gem.add_development_dependency 'cane' gem.add_development_dependency 'tailor' gem.add_development_dependency 'countloc' + gem.add_development_dependency 'rspec' end diff --git a/lib/kitchen/driver/vagrant.rb b/lib/kitchen/driver/vagrant.rb index 32c1403e..be277bdf 100644 --- a/lib/kitchen/driver/vagrant.rb +++ b/lib/kitchen/driver/vagrant.rb @@ -21,6 +21,9 @@ require 'kitchen' +# Useful VBox Machine Class +require 'kitchen/provider/machine' + module Kitchen module Driver @@ -31,7 +34,7 @@ module Driver # # @todo Vagrant installation check and version will be placed into any # dependency hook checks when feature is released - class Vagrant < Kitchen::Driver::SSHBase + class Vagrant < Kitchen::Driver::Base default_config :customize, {} default_config :network, [] @@ -44,12 +47,16 @@ class Vagrant < Kitchen::Driver::SSHBase default_config :provider, ENV.fetch('VAGRANT_DEFAULT_PROVIDER', "virtualbox") + default_config :box do |driver| + "opscode-#{driver.instance.platform.name}" + end + default_config :vm_hostname do |driver| - "#{driver.instance.name}.vagrantup.com" + driver.instance.name end - default_config :box do |driver| - "opscode-#{driver.instance.platform.name}" + default_config :communicator do |driver| + driver.instance.transport.class.name.split('::').last.downcase end default_config :box_url do |driver| @@ -66,7 +73,7 @@ def create(state) cmd = "vagrant up --no-provision" cmd += " --provider=#{config[:provider]}" if config[:provider] run cmd - set_ssh_state(state) + set_state(state) info("Vagrant instance #{instance.to_str} created.") end @@ -178,15 +185,6 @@ def template File.expand_path(config[:vagrantfile_erb], config[:kitchen_root]) end - def set_ssh_state(state) - hash = vagrant_ssh_config - - state[:hostname] = hash["HostName"] - state[:username] = hash["User"] - state[:ssh_key] = hash["IdentityFile"] - state[:port] = hash["Port"] - end - def vagrant_ssh_config output = run("vagrant ssh-config", :live_stream => nil) lines = output.split("\n").map do |line| @@ -230,6 +228,48 @@ def check_vagrant_version " Please upgrade to version #{MIN_VER} or higher from #{WEBSITE}." end end + + def set_state(state) + hash = vagrant_ssh_config + + state[:hostname] = hash["HostName"] + state[:username] = config[:username] || hash["User"] + state[:password] = config[:password] || 'vagrant' + state[:ssh_key] = hash["IdentityFile"] + state[:port] = port_from_config + if config[:communicator] == "ssh" + state[:port] = hash["Port"] + end + refresh_forwarded_port(state) + end + + def port_from_config + port = config[:port] + if port.nil? + guest_port = config[:guest_port] || instance.transport.default_port + if !config[:network].empty? + forwards = config[:network].select do |net| + net[0] == "forwarded_port" && net[1][:guest] == guest_port + end + forwards.each do |forward| + port = forward[1][:host] + end + end + end + port || instance.transport.default_port + end + + # Get forwarded_port from Provider + # + # Working with: VirtualBox + def refresh_forwarded_port(state) + case config[:provider] + when "virtualbox" + Provider::VirtualBox::Machine.new(vagrant_root) do |machine| + state[:port] = machine.host_port(default_port) + end + end + end end end end diff --git a/lib/kitchen/driver/vagrant_version.rb b/lib/kitchen/driver/vagrant_version.rb index 37e26fcc..960cb828 100644 --- a/lib/kitchen/driver/vagrant_version.rb +++ b/lib/kitchen/driver/vagrant_version.rb @@ -21,6 +21,6 @@ module Kitchen module Driver # Version string for Vagrant Kitchen driver - VAGRANT_VERSION = "0.15.0" + VAGRANT_VERSION = '0.16.0' end end diff --git a/lib/kitchen/provider/machine.rb b/lib/kitchen/provider/machine.rb new file mode 100644 index 00000000..f2deac0d --- /dev/null +++ b/lib/kitchen/provider/machine.rb @@ -0,0 +1,155 @@ +# -*- encoding: utf-8 -*- +# +# Author:: Salim Afiune () +# +# Copyright (C) 2014, Salim Afiune +# +# 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 'mixlib/shellout' + +module Kitchen + + module Provider + + module VirtualBox + # This class will simulate one Virtual Box Machine + # It will give us the latest Forwarded Ports list + # where we can check if the port has changed + # + # Supported Virtual Box + # => version 4.2.x + # => version 4.3.x + class Machine + # The UUID of the virtual machine + attr_reader :uuid + + # The forwarded_ports of the virtual machine + attr_reader :forwarded_ports + + + def initialize(root_path, uuid=nil) + # Did we get the UUID + @uuid = uuid + + # The VBoxID that Vagrant spun up + id = File.expand_path(File.join(root_path, + %w{.vagrant machines default virtualbox id})) + + # If UUID was not given but the VBoxID exist. + @uuid = File.read(id) if File.exists?(id) && @uuid.nil? + + # Report that the VM is missing. + vm_notfound unless exists? + + yield self if block_given? + end + + def execute(*command) + # Easy shellout method to execute something! :D + command.insert(0, vboxmanage) + c = Mixlib::ShellOut.new(*command.join(' '), :timeout => 10800) + c.run_command + c.error! + c + end + + # Conveniente method for executing a method. + def self.host_port(guest_port, root_path) + new(root_path).host_port(guest_port) + end + + def get_vboxmanage_path + # Set the path to VBoxManage + vboxmanage_path = 'VBoxManage' + + if RUBY_PLATFORM =~ /cygwin|mswin|mingw|bccwin|wince|emx/ + # On Windows, search for VBOX_INSTALL_PATH environmental + # variable to find VBoxManage. + if ENV.has_key?('VBOX_INSTALL_PATH') + # Get the path. + path = ENV['VBOX_INSTALL_PATH'] + + # There can actually be multiple paths in here, so we need to + # split by the separator ";" and see which is a good one. + path.split(";").each do |single| + # Make sure it ends with a \ + single += '\\' if !single.end_with?('\\') + + # If the executable exists, then set it as the main path + # and break out + vboxmanage = "#{path}VBoxManage.exe" + if File.file?(vboxmanage) + vboxmanage_path = "\"#{vboxmanage}\"" + break + end + end + end + end + @vboxmanage = vboxmanage_path + end + + def read_forwarded_ports(uuid=nil) + uuid ||= @uuid + + results = [] + current_nic = nil + info = execute('showvminfo', uuid, '--machinereadable') + info.stdout.split("\n").each do |line| + # This is how we find the nic that a FP is attached to, + # since this comes first. + current_nic = $1.to_i if line =~ /^nic(\d+)=".+?"/ + + # Parse out the forwarded port information + if line =~ /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"/ + result = [current_nic, $1.to_s, $2.to_i, $3.to_i] + results << result + end + end + @forwarded_ports = results + end + + def host_port(guest_port) + port=nil + # Verify forwarded_ports + read_forwarded_ports.each do |forwarded_port| + # Checking that this is the WinRM port + if forwarded_port.include?(guest_port) + # For Virtual Box 4.2 the host port position is (2) + port=forwarded_port[2] + break + end + end + # This is the actual port that the VM is using. + port + end + + def exists?(uuid=nil) + uuid ||= @uuid + execute('showvminfo', uuid).exitstatus == 0 + end + + # Command to manage Virtual Box `vboxmanage` + def vboxmanage + get_vboxmanage_path if @vboxmanage.nil? + @vboxmanage + end + + def vm_notfound + raise 'Missing Virtual Box Machine. Run `kitchen create` and retry.' + end + + end # => Machine + end # => VirtualBox + end # => Provider +end # => Kitchen diff --git a/spec/kitchen/driver/vagrant_spec.rb b/spec/kitchen/driver/vagrant_spec.rb new file mode 100644 index 00000000..0dc171d7 --- /dev/null +++ b/spec/kitchen/driver/vagrant_spec.rb @@ -0,0 +1,173 @@ +require 'kitchen/driver/vagrant' +require 'yaml' + +module Kitchen + + module Driver + + class Testable < Kitchen::Driver::Vagrant + + def initialize(config, ssh_config = {}) + super(config) + @ssh_config = ssh_config + end + + def create(state) + set_state(state) + end + + protected + + def vagrant_ssh_config + @ssh_config + end + + def refresh_forwarded_port(state) + end + end + end +end + +describe Kitchen::Driver::Vagrant do + let(:vagrant_ssh_config) {{ + 'Port' => 22, + 'HostName' => "hostname", + 'User' => "user", + 'IdentityFile' => "key" + }} + + subject do + parsed = Kitchen::Util.symbolized_hash(YAML.load(yaml)) + transport = parsed[:transport][:name] + instance = double( + transport: Kitchen::Transport.for_plugin(transport, parsed), + logger: Logger.new(STDOUT) + ) + driver = Kitchen::Driver::Testable.new(parsed[:driver], vagrant_ssh_config) + driver.instance = instance + driver + end + + context "using ssh" do + let(:vagrant_ssh_config) {{ + 'Port' => 8888, + 'HostName' => "hostname", + 'User' => "user", + 'IdentityFile' => "key" + }} + let(:yaml) do + <<-EOS + transport: + name: ssh + driver: + box: mybox + EOS + end + + it "uses ssh_config from vagrant" do + state = {} + subject.create(state) + + expect(state[:port]).to eq 8888 + end + end + + context "using winrm" do + context "forwarding to default guest port" do + let(:yaml) do + <<-EOS + transport: + name: winrm + driver: + box: mybox + network: + - ["forwarded_port", {guest: 5985, host: 55985}] + EOS + end + + it "sets the port to the forwarded port" do + state = {} + subject.create(state) + + expect(state[:port]).to eq 55985 + end + end + + context "using a custom port" do + let(:yaml) do + <<-EOS + transport: + name: winrm + driver: + box: mybox + port: 9999 + EOS + end + + it "sets the port to the custom port" do + state = {} + subject.create(state) + + expect(state[:port]).to eq 9999 + end + end + + context "forwarding to a custom guest_port" do + let(:yaml) do + <<-EOS + transport: + name: winrm + driver: + box: mybox + guest_port: 7777 + network: + - ["forwarded_port", {guest: 7777, host: 33333}] + EOS + end + + it "sets the port to the forwarded port" do + state = {} + subject.create(state) + + expect(state[:port]).to eq 33333 + end + end + + context "specifying a custom guest_port with no forwarding" do + let(:yaml) do + <<-EOS + transport: + name: winrm + driver: + box: mybox + guest_port: 7777 + EOS + end + + it "sets the port to the default port" do + state = {} + subject.create(state) + + expect(state[:port]).to eq 5985 + end + end + + context "no network config" do + let(:yaml) do + <<-EOS + transport: + name: winrm + driver: + box: mybox + EOS + end + + it "sets the port to the default port" do + state = {} + subject.create(state) + + expect(state[:port]).to eq 5985 + end + end + end +end \ No newline at end of file diff --git a/templates/Vagrantfile.erb b/templates/Vagrantfile.erb index 9c714fc4..54409501 100644 --- a/templates/Vagrantfile.erb +++ b/templates/Vagrantfile.erb @@ -5,11 +5,21 @@ Vagrant.configure("2") do |c| <% if config[:vm_hostname] %> c.vm.hostname = "<%= config[:vm_hostname] %>" <% end %> +c.vm.communicator = "<%= config[:communicator] %>" +<% if config[:port] %> + c.<%= config[:communicator] %>.port = <%= config[:port] %> +<% end %> +<% if config[:guest_port] %> + c.<%= config[:communicator] %>.guest_port = <%= config[:guest_port] %> +<% end %> <% if config[:guest] %> c.vm.guest = <%= config[:guest] %> <% end %> <% if config[:username] %> - c.ssh.username = "<%= config[:username] %>" + c.<%= config[:communicator] %>.username = "<%= config[:username] %>" +<% end %> +<% if config[:password] %> + c.<%= config[:communicator] %>.password = "<%= config[:password] %>" <% end %> <% if config[:ssh_key] %> c.ssh.private_key_path = "<%= config[:ssh_key] %>" @@ -25,22 +35,29 @@ Vagrant.configure("2") do |c| <% end %> c.vm.provider :<%= config[:provider] %> do |p| -<% config[:customize].each do |key, value| %> - <% case config[:provider] - when "virtualbox" %> - p.customize ["modifyvm", :id, "--<%= key %>", "<%= value %>"] - <% when "rackspace", "softlayer" %> - p.<%= key %> = "<%= value%>" - <% when /^vmware_/ %> - <% if key == :memory %> - <% unless config[:customize].include?(:memsize) %> - p.vmx["memsize"] = "<%= value %>" + <% case config[:provider] + when "virtualbox", "vmware_fusion", "vmware_workstation" %> + <% if config[:gui] %> + p.gui = <%= config[:gui] %> + <% end %> + <% end %> + + <% config[:customize].each do |key, value| %> + <% case config[:provider] + when "virtualbox" %> + p.customize ["modifyvm", :id, "--<%= key %>", "<%= value %>"] + <% when "rackspace", "softlayer" %> + p.<%= key %> = "<%= value%>" + <% when /^vmware_/ %> + <% if key == :memory %> + <% unless config[:customize].include?(:memsize) %> + p.vmx["memsize"] = "<%= value %>" + <% end %> + <% else %> + p.vmx["<%= key %>"] = "<%= value %>" <% end %> - <% else %> - p.vmx["<%= key %>"] = "<%= value %>" <% end %> <% end %> -<% end %> end end