diff --git a/Gemfile b/Gemfile index d606e011..acea281f 100644 --- a/Gemfile +++ b/Gemfile @@ -12,6 +12,10 @@ group :kvm do gem "ruby-libvirt" end +group :vsphere do + gem "rbvmomi" +end + group :test do gem "rake" gem "em-winrm", :git => 'git://github.com/hh/em-winrm.git', :ref => '31745601d3' diff --git a/README.md b/README.md index 281c0495..44b07b72 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Depending on how you want to use veewee, read through one of the following guide - [guide for Virtualbox](https://github.com/jedi4ever/veewee/tree/master/doc/vbox.md) - [guide for Vmware fusion](https://github.com/jedi4ever/veewee/tree/master/doc/fusion.md) - [guide for KVM](https://github.com/jedi4ever/veewee/tree/master/doc/kvm.md) +- [guide for VMware vSphere](https://github.com/jedi4ever/veewee/tree/master/doc/vsphere.md) You can also look at the more detailed pages on each subject in the [documentation directory](https://github.com/jedi4ever/veewee/tree/master/doc) diff --git a/doc/vsphere.md b/doc/vsphere.md new file mode 100644 index 00000000..cce86991 --- /dev/null +++ b/doc/vsphere.md @@ -0,0 +1,115 @@ +The veewee vSphere provider adds the capability to create base boxes/VMs on VMware ESXi/vSphere and vCenter servers. Because this capability leverages a centralized hypervisor (rather than a local hypervisor on the machien running the veewee command), there are some considerations when building a machine that are unique to this provider. + +vCenter server support is still experimental pending further tests with more advance vCenter features + +## Credentials + +vSphere/vCenter require a users to authenticate actions the remote server in order to perform management actions. User credentials can be specified in 3 ways, in order of precedence: + +1. Use the -h (i.e., vsphere_server), -u (i.e., vsphere_user), and -p (i.e., vsphere_password) options with each `veewee vsphere` command +2. Set the `VEEWEE_VSPHERE_AUTHFILE` environment variable to the path of a YAML files with the vSphere server hostname and credentials +3. The vSphere server and user can be provided by either method above, and veewee vsphere provider will prompt the user for the password. + +Note, a `VEEWEE_VSPHERE_AUTHFILE` YAML file contains credentials to login to a given vSphere/vCenter server. It is the responsibility of the user to ensure this file has appropriate access controls applied to ensure proper security compliance. + +### YAML configuration format + +```yaml +--- +vsphere_server: vcenter.example.com +vsphere_user: vsphere-user +vsphere_password: vsphere-password +``` + +## Datacenter Configuration + +The vSphere datacenter targeted can be specified by using the `:datacenter` vm_option + +Logic should look for the datacenter in the following order: + +* If datacenter is specified as vm option in definition and it exists, use that datacenter +* If only one datacenter exists and no datacenter is specified, use that datacenter + +All other conditions result in errors. + +Note: although ESXi servers do not show a concept of a datacenter in the vSphere Client, the vSphere API creates a default datacenter "ha-datacenter" to provide a consistent API. + +## Compute Resource Configuration + +The compute resource targeted can be specified by using the `:compute_resource` vm_option + +* In the case of vSphere ESXi, there is one compute resource that encapsulates the ESXi server +* In the case of vCenter, there are one or more compute resources that represent a single ESXi server or a cluster + +If multiple Compute Resources exist and a name is not provided, an error is raise + + +## Networking support + +vSphere does not have built in support concepts like a NAT network configuration within the hypervisor itself. This means that when creating a virtual machine, a veewee vSphere user needs to have the appropriate networking capability in place prior to issuing vSphere build commands. In addition, there are special steps that must be completed within definition.rb and kickstart or preseed.cfg files to ensure network support works as expected. + +### Virtual Network + +Each vSphere/vCenter enviornment has a series of virtual networks that can be added to an individual virtual machine. Veewee must specify the virtual network when building a vSphere VM to make sure the remaining commands can be executed as expected. The virtual network can be set in 3 ways, listed below in the order of precedence: + +1. `--net` option provided with the `veewee vsphere build` command +2. Contents of the definition.rb vsphere properties `:vsphere => { :vm_options => { :network => "VM Network" } }` +3. If no name provided and only one network exists, choose that network + +Along with the above virtual network definition, the following assumptions are made about the state of the network: + +* The network includes a DHCP server that can provide a new VM with an IP address to communicate on the network +* The host issuing the veewee command has a route to the VM after its created on the virtual network + +### Veewee Host IP + +Because we cannot assume anything about the network topology between the veewee host and the virtual network attached to a VMware VM build using the vSphere provider, the IP address used to serve the kickstart or preseed.cfg file must be specified as part of the definition.rb file. This can be configured using the following: + + :vsphere => { :kickstart_ip => "192.168.0.1" } + + veewee will raise an error if this configuration is not complete. + +## Data Location + +vSphere defines storage for VMs and files as a series of datastores within a given server or cluster. The vSphere provider must specify a datastore to build a given VM in. The datastore can be set in 3 ways, listed below in the order of precedence: + +1. `--ds` option provided with the `veewee vsphere build` command +2. Contents of the definition.rb vsphere properties `:vsphere => { :vm_options => { :datastore => "datastore" } }` +3. If no name provided and only one datastore exists, choose that datastore + +Note, the datastore location chosen is used for both creating the VM and its associated files, and also for uploading ISO files used to build the VM. + +## VNC Dependencies + +The vSphere provider, like other veewee providers, depends on VNC to configure the guest OS being built. On ESXi 5.x hosts, the internal firewall is disabled by default. The following gist contains the instructions for ESXi 5.1: https://gist.github.com/4670943. + +TODO Embed gist information + +## VMware Tools + +In order to complete postinstall.sh scripts, veewee has to be able to determine the created VMs IP address after OS installation is complete. vSphere/vCenter do not have a method to retrieve this information withou the VMware Tools--the VMware equivalent of VirtualBox Guest Additions--installed on the guest OS. Therefore, the kickstart or preseed.cfg files used to automated the OS installation must be modified in order to install VMware Tools prior to the OS installation completing. This will allow post installation actions to execute as expected. + +### RHEL/CentOS Instructions + +Follow the instructions from VMware for installing VMware tools on a RHEL platform as seen at the following link: http://www.vmware.com/download/packages.html. + +### Ubuntu Instructions + +On Ubuntu distributions, VMware tools can be added by installing the open-vm-tools from the multiverse repository inside the preseed.cfg file. For example: + + d-i pkgsel/include string openssh-server open-vm-tools + + There can be only one instance of this within a preseed.cfg file, so ensure that if you add open-vm-tools to the existing command as provided. + +## External tool pre-requisites + +The following tools are needed to support the execution of the vSphere provider: + +* curl - used to upload files to a vSphere datastore (specifically to support ISO uploads). This will hopefully be corrected in later revisions to replace curl with a native ruby solution +* ovftool - used for the `veewee vsphere export` command to export a VM from the vSphere/vCenter server to an ova file + +The veewee vSphere provider assumes both of these tools are in the user's path. + +## Command Timing + +Working with a remote server increases the likelihood that veewee processes that depend on waits or delays to execute a given command will fail leaving veewee and the guest VM being built in an unstable state. Users should consider increasing timeouts if issues appear where steps are out of sequence. diff --git a/lib/veewee/command.rb b/lib/veewee/command.rb index efe7ee3e..faf3bba7 100644 --- a/lib/veewee/command.rb +++ b/lib/veewee/command.rb @@ -13,3 +13,4 @@ module Command require 'veewee/command/vbox' require 'veewee/command/fusion' require 'veewee/command/parallels' +require 'veewee/command/vsphere' diff --git a/lib/veewee/command/vsphere.rb b/lib/veewee/command/vsphere.rb new file mode 100644 index 00000000..14be3b1d --- /dev/null +++ b/lib/veewee/command/vsphere.rb @@ -0,0 +1,172 @@ +module Veewee + module Command + class Vsphere< Veewee::Command::GroupBase + + register "vsphere", "Subcommand for Vmware vsphere" + desc "build [BOX_NAME]", "Build box" + method_option :vsphere_server,:type => :string , :default => nil, :aliases => "-h", :desc => "VSphere Server" + method_option :vsphere_user,:type => :string , :default => nil, :aliases => "-u", :desc => "VSphere User" + method_option :vsphere_password,:type => :string , :default => nil, :aliases => "-p", :desc => "VSphere Password" + method_option :datastore,:type => :string , :default => nil, :aliases => "--ds", :desc => "VSphere Datastore" + method_option :network,:type => :string , :default => nil, :aliases => "--net", :desc => "VSphere Virtual Network" + method_option :force,:type => :boolean , :default => false, :aliases => "-f", :desc => "force the build" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :nogui,:type => :boolean , :default => false, :aliases => "-n", :desc => "no gui" + method_option :auto,:type => :boolean , :default => false, :aliases => "-a", :desc => "auto answers" + method_option :postinstall_include, :type => :array, :default => [], :aliases => "-i", :desc => "ruby regexp of postinstall filenames to additionally include" + method_option :postinstall_exclude, :type => :array, :default => [], :aliases => "-e", :desc => "ruby regexp of postinstall filenames to exclude" + def build(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).build(options) + end + + method_option :vsphere_server,:type => :string , :default => nil, :aliases => "-h", :desc => "VSphere Server" + method_option :vsphere_user,:type => :string , :default => nil, :aliases => "-u", :desc => "VSphere User" + method_option :vsphere_password,:type => :string , :default => nil, :aliases => "-p", :desc => "VSphere Password" + method_option :force,:type => :boolean , :default => false, :aliases => "-f", :desc => "force the destroy" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :nogui,:type => :boolean , :default => false, :aliases => "-n", :desc => "no gui" + desc "destroy [BOXNAME]", "Destroys the virtualmachine that was built" + def destroy(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).destroy(options) + end + + method_option :vsphere_server,:type => :string , :default => nil, :aliases => "-h", :desc => "VSphere Server" + method_option :vsphere_user,:type => :string , :default => nil, :aliases => "-u", :desc => "VSphere User" + method_option :vsphere_password,:type => :string , :default => nil, :aliases => "-p", :desc => "VSphere Password" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :force,:type => :boolean , :default => false, :aliases => "-f", :desc => "force the shutdown" + desc "halt [BOXNAME]", "Activates a shutdown the virtualmachine" + def halt(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).halt(options) + end + + method_option :vsphere_server,:type => :string , :default => nil, :aliases => "-h", :desc => "VSphere Server" + method_option :vsphere_user,:type => :string , :default => nil, :aliases => "-u", :desc => "VSphere User" + method_option :vsphere_password,:type => :string , :default => nil, :aliases => "-p", :desc => "VSphere Password" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :nogui,:type => :boolean , :default => false, :aliases => "-n", :desc => "no gui" + desc "up [BOXNAME]", "Starts a Box" + def up(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).up(options) + end + + desc "scp [BOXNAME] [LOCAL FILE] [REMOTE FILE]", "SCP to box" + method_option :vsphere_server,:type => :string , :default => nil, :aliases => "-h", :desc => "VSphere Server" + method_option :vsphere_user,:type => :string , :default => nil, :aliases => "-u", :desc => "VSphere User" + method_option :vsphere_password,:type => :string , :default => nil, :aliases => "-p", :desc => "VSphere Password" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def scp(box_name,local_file,remote_file) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).scp(local_file,remote_file) + end + + desc "ssh [BOXNAME] [COMMAND]", "SSH to box" + method_option :vsphere_server,:type => :string , :default => nil, :aliases => "-h", :desc => "VSphere Server" + method_option :vsphere_user,:type => :string , :default => nil, :aliases => "-u", :desc => "VSphere User" + method_option :vsphere_password,:type => :string , :default => nil, :aliases => "-p", :desc => "VSphere Password" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def ssh(box_name,command=nil) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).issh(command) + end + + desc "define [BOXNAME] [TEMPLATE]", "Define a new basebox starting from a template" + method_option :force,:type => :boolean , :default => false, :aliases => "-f", :desc => "overwrite the definition" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def define(definition_name, template_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.definitions.define(definition_name,template_name,options) + env.ui.info "The basebox '#{definition_name}' has been succesfully created from the template '#{template_name}'" + env.ui.info "You can now edit the definition files stored in definitions/#{definition_name} or build the box with:" + env.ui.info "veewee vsphere build '#{definition_name}'" + end + + desc "undefine [BOXNAME]", "Removes the definition of a basebox " + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def undefine(definition_name) + env.ui.info "Removing definition #{definition_name}" , :prefix => false + begin + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.definitions.undefine(definition_name,options) + env.ui.info "Definition #{definition_name} succesfully removed",:prefix => false + rescue Error => ex + env.ui.error "#{ex}" , :prefix => false + exit -1 + end + end + + desc "validate [NAME]", "Validates a box against vsphere compliancy rules" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :tags, :type => :array , :default => %w{vsphere puppet chef}, :aliases => "-t", :desc => "tags to validate" + def validate(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).validate_vsphere(options) + end + + desc "ostypes", "List the available Operating System types" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def ostypes + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.ostypes.each do |name| + env.ui.info "- #{name}" + end + end + + desc "path [NAME]", "Prints the vms path" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :force,:type => :boolean , :default => false, :aliases => "-f", :desc => "overwrite existing file" + def path(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + env.ui.info venv.providers["vsphere"].get_box(box_name).path + end + + desc "export [NAME]", "Exports the basebox to the ova format" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + method_option :force,:type => :boolean , :default => false, :aliases => "-f", :desc => "overwrite existing file" + def export(box_name) + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.providers["vsphere"].get_box(box_name).export_ova(options) + end + + desc "templates", "List the currently available templates" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def templates + env.ui.info "The following templates are available:",:prefix => false + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.templates.each do |name,template| + env.ui.info "veewee vsphere define '' '#{name}'",:prefix => false + end + end + + desc "list", "Lists all defined boxes" + method_option :debug,:type => :boolean , :default => false, :aliases => "-d", :desc => "enable debugging" + def list + env.ui.info "The following local definitions are available:",:prefix => false + venv=Veewee::Environment.new(options) + venv.ui=env.ui + venv.definitions.each do |name,definition| + env.ui.info "- #{name}" + end + end + + end + + end +end diff --git a/lib/veewee/config/ostypes.yml b/lib/veewee/config/ostypes.yml index 0feb5720..e49db09d 100644 --- a/lib/veewee/config/ostypes.yml +++ b/lib/veewee/config/ostypes.yml @@ -1,111 +1,133 @@ --- Windows7_64: :fusion: windows7-64 + :vsphere: windows7_64Guest :kvm: :vbox: Windows7_64 :parallels: win-7 Windows7: :fusion: windows7 + :vsphere: windows7Guest :kvm: :vbox: Windows7 :parallels: win-7 Windows8: :fusion: + :vsphere: :kvm: :vbox: Windows8 :parallels: win-8 Windows8_64: :fusion: + :vsphere: :kvm: :vbox: Windows8_64 :parallels: win-8 WindowsNT: :fusion: winNT + :vsphere: :kvm: :vbox: WindowsNT :parallels: win-net Windows2008: :fusion: longhorn + :vsphere: :kvm: :vbox: Windows2008 :parallels: win-2008 Windows2008_64: :fusion: longhorn-64 + :vsphere: :kvm: :vbox: Windows2008_64 :parallels: win-2008 WindowsVista_64: :fusion: winvista-64 + :vsphere: :kvm: :vbox: WindowsVista_64 :parallels: win-vista WindowsVista: :fusion: winvista + :vsphere: :kvm: :vbox: WindowsVista :parallels: win-vista Windows2003: :fusion: winnetstandard + :vsphere: :kvm: :vbox: Windows2003 :parallels: win-2003 Windows2003_64: :fusion: winnetstandard-64 + :vsphere: :kvm: :vbox: Windows2003_64 :parallels: win-2003 WindowsXP_64: :fusion: winXPPro-64 + :vsphere: :kvm: :vbox: WindowsXP_64 :parallels: win-xp WindowsXP: :fusion: winXP + :vsphere: :kvm: :vbox: WindowsXP :parallels: win-xp Windows2000: :fusion: + :vsphere: :kvm: :vbox: Windows200 :parallels: win-2000 WindowsNT4: :fusion: + :vsphere: :kvm: :vbox: WindowsNT4 :parallels: win-nt WindowsMe: :fusion: + :vsphere: :kvm: :vbox: WindowsMe :parallels: win-me Windows98: :fusion: + :vsphere: :kvm: :vbox: Windows98 :parallels: win-98 Windows95: :fusion: + :vsphere: :kvm: :vbox: Windows95 :parallels: win-95 Windows31: :fusion: + :vsphere: :kvm: :vbox: Windows31 :parallels: win-311 Other: :fusion: other + :vsphere: otherGuest :kvm: :vbox: Other :parallels: other Other_64: :fusion: other-64 + :vsphere: otherGuest64 :kvm: :vbox: Other_64 :parallels: other FreeBSD: :fusion: freeBSD + :vsphere: :kvm: :vbox: FreeBSD :parallels: freebsd @@ -116,11 +138,13 @@ FreeBSD_64: :parallels: freebsd Oracle: :fusion: oraclelinux + :vsphere: oracleLinuxGuest :kvm: :vbox: Oracle :parallels: other Oracle_64: :fusion: oraclelinux-64 + :vsphere: oracleLinux64Guest :kvm: :vbox: Oracle_64 :parallels: other @@ -161,21 +185,25 @@ Linux22: :parallels: other Linux24: :fusion: other24xlinux + :vsphere: other24xLinuxGuest :kvm: :vbox: Linux24 :parallels: linux-2.4 Linux24_64: :fusion: other24xlinux-64 + :vsphere: other24xLinux64Guest :kvm: :vbox: Linux24_64 :parallels: linux-2.4 Linux26: :fusion: other26xlinux + :vsphere: other26xLinuxGuest :kvm: :vbox: Linux26 :parallels: linux-2.6 Linux26_64: :fusion: other26xlinux-64 + :vsphere: other26xLinux64Guest :kvm: :vbox: Linux26_64 :parallels: linux-2.6 @@ -191,21 +219,25 @@ RedHat_64: :parallels: redhat RedHat5: :fusion: rhel5 + :vsphere: rhel5Guest :kvm: :vbox: RedHat :parallels: redhat RedHat5_64: :fusion: rhel5-64 + :vsphere: rhel5_64Guest :kvm: :vbox: RedHat_64 :parallels: redhat RedHat6: :fusion: rhel6 + :vsphere: rhel6Guest :kvm: :vbox: RedHat :parallels: redhat RedHat6_64: :fusion: rhel6-64 + :vsphere: rhel6_64Guest :kvm: :vbox: RedHat_64 :parallels: redhat @@ -251,8 +283,10 @@ SUSE_64: :parallels: suse SLES11: :fusion: sles11 + :vsphere: sles11Guest SLES11_64: :fusion: sles11-64 + :vsphere: sles11_64Guest Fedora: :fusion: fedora :kvm: @@ -265,11 +299,13 @@ Fedora_64: :parallels: fedora-core Ubuntu: :fusion: ubuntu + :vsphere: ubuntuGuest :kvm: :vbox: Ubuntu :parallels: ubuntu Ubuntu_64: :fusion: ubuntu-64 + :vsphere: ubuntu64Guest :kvm: :vbox: Ubuntu_64 :parallels: ubuntu @@ -280,26 +316,31 @@ Linux: :parallels: linux Solaris: :fusion: solaris10 + :vsphere: solaris10Guest :kvm: :vbox: Solaris :parallels: solaris-10 Solaris_64: :fusion: solaris10-64 + :vsphere: solaris10_64Guest :kvm: :vbox: Solaris_64 :parallels: solaris-10 Solaris9: :fusion: solaris + :vsphere: solaris9Guest :kvm: :vbox: Solaris :parallels: solaris-9 Solaris7: :fusion: solaris7 + :vsphere: solaris7Guest :kvm: :vbox: Solaris :parallels: solaris Solaris8: :fusion: solaris8 + :vsphere: solaris8Guest :kvm: :vbox: Solaris :parallels: solaris diff --git a/lib/veewee/definition.rb b/lib/veewee/definition.rb index cd6c4bbc..95649415 100644 --- a/lib/veewee/definition.rb +++ b/lib/veewee/definition.rb @@ -38,6 +38,7 @@ class Definition attr_accessor :virtualbox attr_accessor :vmfusion + attr_accessor :vsphere attr_accessor :kvm attr_accessor :add_shares attr_accessor :vmdk_file @@ -96,6 +97,7 @@ def initialize(name, path, env) @virtualbox = { :vm_options => {} } @vmfusion = { :vm_options => {} } @kvm = { :vm_options => {} } + @vsphere = { :vm_options => {} } end diff --git a/lib/veewee/error.rb b/lib/veewee/error.rb index 7dd41bec..3e2a77ee 100644 --- a/lib/veewee/error.rb +++ b/lib/veewee/error.rb @@ -23,6 +23,9 @@ class SshError < Error class WinrmError < Error end + + class VncError < Error + end end #Usage (from the exceptional ruby book) diff --git a/lib/veewee/provider/vsphere/box.rb b/lib/veewee/provider/vsphere/box.rb new file mode 100644 index 00000000..42845de4 --- /dev/null +++ b/lib/veewee/provider/vsphere/box.rb @@ -0,0 +1,64 @@ +require 'rbvmomi' + +require 'veewee/provider/core/box' +require 'veewee/provider/core/box/vnc' +require 'veewee/provider/core/helper/tcp' + +require 'veewee/provider/vsphere/box/helper/status' +require 'veewee/provider/vsphere/box/helper/ip' +require 'veewee/provider/vsphere/box/helper/ssh_options' +require 'veewee/provider/vsphere/box/helper/vnc' +require 'veewee/provider/vsphere/box/helper/console_type' +require 'veewee/provider/vsphere/box/helper/buildinfo' +require 'veewee/provider/vsphere/box/helper/vsphere' +require 'veewee/provider/vsphere/box/helper/datacenter' +require 'veewee/provider/vsphere/box/helper/compute_resource' +require 'veewee/provider/vsphere/box/helper/datastore' +require 'veewee/provider/vsphere/box/helper/network' + +require 'veewee/provider/vsphere/box/build' +require 'veewee/provider/vsphere/box/up' +require 'veewee/provider/vsphere/box/create' +require 'veewee/provider/vsphere/box/poweroff' +require 'veewee/provider/vsphere/box/halt' +require 'veewee/provider/vsphere/box/destroy' +require 'veewee/provider/vsphere/box/validate_vsphere' +require 'veewee/provider/vsphere/box/ssh' +require 'veewee/provider/vsphere/box/export_ova' + +module Veewee + module Provider + module Vsphere + class Box < Veewee::Provider::Core::Box + + include ::Veewee::Provider::Vsphere::BoxCommand + include ::Veewee::Provider::Core::BoxCommand + + + def initialize(name,env) + super(name,env) + end + + def vim + vsphere_server = provider.vsphere_server + vsphere_user = provider.vsphere_user + vsphere_password = provider.vsphere_password + @vim ||= RbVmomi::VIM.connect host: vsphere_server, user: vsphere_user, password: vsphere_password, insecure: true + end + + def raw + @raw ||= dc.find_vm(name) + end + + def dc + @dc ||= retrieve_datacenter + end + + #Path to the VM relative to the host server (vCenter of vSphere) + def path + path = dc.name + "/" + dc.vmFolder.name + "/" + name + end + end # End Class + end # End Module + end # End Module +end # End Module diff --git a/lib/veewee/provider/vsphere/box/build.rb b/lib/veewee/provider/vsphere/box/build.rb new file mode 100644 index 00000000..b6dba3fa --- /dev/null +++ b/lib/veewee/provider/vsphere/box/build.rb @@ -0,0 +1,64 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def build(options={}) + env.ui.warn "Boot wait is less than 10 seconds...build may fail" if Integer(definition.boot_wait) < 10 + validate_host + + + begin + super(options) + close_vnc + rescue Veewee::VncError + # If VncError is seen, then assume installation will fail and cleanup + # TODO Cleanup retrieval of host, possibly abstract this call + runtime_host = raw.runtime.host + config = runtime_host.config + env.ui.error "Veewee could not reach VM over VNC at #{runtime_host.name} (#{@vnc_host}:#{@vnc_port+5900})" + env.ui.error "* Ensure network firewalls are not blocking the connection" + env.ui.error "* See documentation on configuring ESXi 5.x hosts internal firewall here: https://gist.github.com/4670943" if /^5/ =~ config.product.version + env.ui.error "* Check the #{config.product.fullName} documentation to ensure it is configured properly." + env.ui.error "Destroying partially created VM" + self.destroy(options) + raise Veewee::Error, "VNC Error Occurred" + end + end + + # Validate the host configuration is set before + # building the VM + def validate_host + # Check we have a host IP to use + self.host_ip_as_seen_by_guest + end + + def handle_kickstart(options) + super(options) + + question = nil + until !self.ip_address.nil? || !question.nil? + env.logger.info "wait for Ip address" + sleep 2 + + # Verify there isn't a question blocking execution + env.logger.info "checking for questions" + question = raw.runtime.question + end + + unless question.nil? + # If the question is a cdromdisconnect from the installation, + # answer the question with "Yes" ( value is 0 ) + if question.text =~ /msg\.cdromdisconnect\.locked/ + raw.AnswerVM(:questionId => question.id, :answerChoice => "0") + else + raise Veewee::Error, "Unanswerable VM Question '#{question.id}' encountered\n#{question.text}" + end + end + + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/create.rb b/lib/veewee/provider/vsphere/box/create.rb new file mode 100644 index 00000000..2a34b838 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/create.rb @@ -0,0 +1,117 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # When we create a new box + # We assume the box is not running + def create(options) + network = retrieve_network options + datastore = retrieve_datastore options + compute_resource = retrieve_compute_resource + + # Verify network and datastore are not only accessible from the datacenter + # but also from the specific compute resource (host or cluster) being used + if compute_resource.network.find { |x| x.name == network.name }.nil? + raise Veewee::Error, "Network #{network.name} is not accessible from compute resource #{compute_resource.name}" + elsif compute_resource.datastore.find { |x| x.name == datastore.name }.nil? + raise Veewee::Error, "Datastore #{datastore.name} is not accessible from compute resource #{compute_resource.name}" + end + + load_iso datastore + create_vm datastore, network, compute_resource + create_disk + enable_vnc + end + + # Load ISO to vSphere datastore, assumes same datastore as VM will be built on + def load_iso datastore + filename = definition.iso_file + local_path=File.join(env.config.veewee.iso_dir,filename) + File.exists?(local_path) or fail "ISO does not exist" + + # These checks need to be done in this order + # otherwise the requests to the datastore over + # http will hang. (Observed in ESXi 4.1 update 3) + unless datastore.exists? "isos/" + vim.serviceContent.fileManager.MakeDirectory :name => "[#{datastore.name}] isos", :datacenter => dc + end + unless datastore.exists? "isos/"+filename + env.ui.info "Loading ISO to vSphere Host" + datastore.upload "isos/"+filename, local_path + end + end + + def create_disk + env.ui.info "Creating virtual disk" + add_disk "#{definition.disk_size}M" + end + + def vsphere_os_type(type_id) + env.logger.info "Translating #{type_id} into vsphere type" + vspheretype=env.ostypes[type_id][:vsphere] + env.logger.info "Found vsphere type #{vspheretype}" + return vspheretype + end + + def create_vm datastore, network, compute_resource + # TODO verify that single host compute resources and clusters cannot create name collisions + pool = compute_resource.resourcePool + vmFolder = dc.vmFolder + datastore_path = "[#{datastore.name}]" + config = { + :name => definition.name, + :guestId => vsphere_os_type(definition.os_type_id), + :files => { :vmPathName => datastore_path }, + :numCPUs => definition.cpu_count, + :memoryMB => definition.memory_size, + :deviceChange => [ + { + :operation => :add, + :device => VIM.VirtualCdrom( + :key => -2, + :connectable => { + :allowGuestControl => true, + :connected => true, + :startConnected => true, + }, + :backing => VIM.VirtualCdromIsoBackingInfo( + :fileName => datastore_path + " isos/" + definition.iso_file + ), + :controllerKey => 200, + :unitNumber => 0 + ) + }, + { + :operation => :add, + :device => VIM.VirtualLsiLogicController( + :key => -1, + :busNumber => 0, + :sharedBus => 'noSharing', + :hotAddRemove => nil + ) + }, + { + :operation => :add, + :device => VIM.VirtualE1000( + :key => -1, + :deviceInfo => { + :summary => network.name, + :label => "", + }, + # TODO Verify this works with Distributed vSwitch configurations + :backing => VIM.VirtualEthernetCardNetworkBackingInfo(:deviceName => network.name), + :addressType => 'generated' + ) + } + ], + } + + vmFolder.CreateVM_Task(:config=>config, + :pool=>pool).wait_for_completion + + end + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/destroy.rb b/lib/veewee/provider/vsphere/box/destroy.rb new file mode 100644 index 00000000..86972671 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/destroy.rb @@ -0,0 +1,19 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def destroy(options={}) + unless self.exists? + raise Veewee::Error, "Error:: You tried to destroy a non-existing box '#{name}'" + end + + raw.PowerOffVM_Task.wait_for_completion if raw.summary.runtime.powerState=="poweredOn" + raw.Destroy_Task.wait_for_completion + # remove it from memory + @raw=nil + end + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/export_ova.rb b/lib/veewee/provider/vsphere/box/export_ova.rb new file mode 100644 index 00000000..66d87129 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/export_ova.rb @@ -0,0 +1,68 @@ +require 'tempfile' + +module Veewee + module Provider + module Vsphere + module BoxCommand + # This function 'exports' the box based on the definition + def export_ova(options) + debug="--X:logToConsole=true --X:logLevel=\"verbose\"" + debug="" + flags="--compress=9" + + if File.exists?("#{name}.ova") + if options["force"] + env.logger.debug("#{name}.ova exists, but --force was provided") + env.logger.debug("removing #{name}.ova first") + FileUtils.rm("#{name}.ova") + env.logger.debug("#{name}.ova removed") + else + raise Veewee::Error, "export file #{name}.ova already exists. Use --force option to overwrite." + end + end + + # Need to check binary first + if self.running? + # Wait for the shutdown to complete + begin + Timeout::timeout(20) do + self.halt(options) + status=self.running? + unless status + return + end + sleep 4 + end + rescue TimeoutError::Error => ex + raise Veewee::Error,ex + end + end + + # before exporting the system needs to be shut down + # otherwise the debug log will show - The specified virtual disk needs repair + + # Need to ensure any CDROMS are disconnected, otherwise + # we'll receive an error when importing the OVA + cdroms = raw.config.hardware.device.grep(RbVmomi::VIM::VirtualCdrom) + cdrom_changes = cdroms.map do |dev| + dev = dev.dup + dev.connectable = dev.connectable.dup + dev.connectable.connected = false + dev.connectable.startConnected = false + { :operation => :edit, :device => dev } + end + spec = { :deviceChange => cdrom_changes } + raw.ReconfigVM_Task(:spec => spec) + + host = provider.host + user = provider.user + password = provider.password + + connect_string = "vi://#{user}:#{password}@#{host}/#{path}" + + shell_exec("ovftool #{debug} #{flags} #{connect_string.shellescape} #{name}.ova") + end + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/halt.rb b/lib/veewee/provider/vsphere/box/halt.rb new file mode 100644 index 00000000..eee7e307 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/halt.rb @@ -0,0 +1,13 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def halt(options={}) + super(options) + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/buildinfo.rb b/lib/veewee/provider/vsphere/box/helper/buildinfo.rb new file mode 100644 index 00000000..767dec0c --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/buildinfo.rb @@ -0,0 +1,27 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def build_info + info=super + #output=IO.popen("#{vmrun_cmd.shellescape}").readlines + info << {:filename => ".vsphere_version",:content => vim.serviceContent.about.fullName } + end + + # Transfer information provide by the provider to the box + # + # + def transfer_buildinfo(options) + # Arbitrarily sleep 120 seconds to ensure box is actually up before attempting the transfer + # The core veewee timeouts for ssh availability doesn't work as expected + # TODO Determine a less hackish way of solving this problem + ui.info "Waiting 120 seconds for ssh to be ready" + sleep 120 + super(options) + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/compute_resource.rb b/lib/veewee/provider/vsphere/box/helper/compute_resource.rb new file mode 100644 index 00000000..e4eb3ce0 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/compute_resource.rb @@ -0,0 +1,55 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Retrieve the Compute Resource to install to + # + # In the case of vSphere ESXi, there is one compute resource + # that encapsulates the ESXi server + # + # In the case of vCenter, there are one or more compute resources + # that represent a single ESXi server or a cluster + # + # If multiple Compute Resources exist and a name is not provided, + # an error is raise + # + def retrieve_compute_resource + # Retrieve the name from the vm_options, if available + name ||= definition.vsphere[:vm_options][:compute_resource] + + unless name.nil? + # Retrieve compute resource by name + compute_resource = dc.find_compute_resource name + raise Veewee::Error, "Compute Resource #{name} was not found on the vSphere server\n" + valid_compute_resources if compute_resource.nil? + else + # If no name was provided, and there are multiple resources, raise an error + raise Veewee::Error, "Multiple compute resources found, you must specify a name\n" + valid_compute_resources if dc.hostFolder.children.count > 1 + + # this is a standalone ESXi or single compute resource vCenter + name ||= dc.hostFolder.children[0].name + compute_resource = dc.find_compute_resource name + end + + raise Veewee::Error, "Veewee could not find a compute resource to use, consult your ESXi or vCenter server to see if it is working as expected" if compute_resource.nil? + + env.ui.info "Using Compute Resource #{name}" + + return compute_resource + end + + # Retrieve the valid compute resources to inform the users what selections they can make + def valid_compute_resources + rval = "The following compute resources (i.e., hosts or clusters) are available:\n" + dc.hostFolder.children.each do | compute_resource | + rval += " " + compute_resource.name + "\n" + end + rval += "Modify :vsphere=>{:vm_options=>{:compute_resource=>''}} in your definition.rb to specify a compute resource" + + return rval + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/console_type.rb b/lib/veewee/provider/vsphere/box/helper/console_type.rb new file mode 100644 index 00000000..a62a9c8d --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/console_type.rb @@ -0,0 +1,24 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Type on the console + def console_type(sequence,type_options={}) + enable_vnc unless vnc_enabled? + if vnc_enabled? + begin + vnc_type(sequence,vnc_host,vnc_port) + rescue Errno::ETIMEDOUT + # Raise VncError if connectin times out + raise Veewee::VncError, "Connection to VNC timed out" + end + else + raise Veewee::Error, "VNC is not enabled" + end + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/datacenter.rb b/lib/veewee/provider/vsphere/box/helper/datacenter.rb new file mode 100644 index 00000000..2b731687 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/datacenter.rb @@ -0,0 +1,48 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Retrieve datacenter to use + # + # Logic should look for the datacenter in the following order: + # If datacenter is specified as vm option in definition and it exists, use that datacenter + # If only one datacenter exists and no datacenter is specified, use that datacenter + # All other conditions result in errors + def retrieve_datacenter + # Retrieve the name from the vm_options, if available + name ||= definition.vsphere[:vm_options][:datacenter] + + unless name.nil? + # If provided a name, attempt to retrieve datacenter, if its not found raise an error + @dc ||= vim.serviceInstance.find_datacenter name + raise Veewee::Error, "Datacenter #{name} was not found on the vSphere server\n" + valid_datacenters if @dc.nil? + else + # If multiple datacenters are found, but no name is provided, raise an error + raise Veewee::Error, "Multiple datacenters found, you must specify a name\n" + valid_datacenter if vim.serviceContent.rootFolder.children.grep(RbVmomi::VIM::Datacenter).count > 1 + + # if there's only one datacenter, use it (also works in standalone ESXi cases) + # Call find datacenter to retrieve the single datacenter found + @dc ||= vim.serviceInstance.find_datacenter + end + + raise Veewee::Error, "Veewee could not find a datacenter to use, consult your ESXi or vCenter server to see if it is working as expected" if @dc.nil? + + return @dc + end + + # Retrieve the valid datacenters to inform the users what selections they can make + def valid_datacenters + rval = "The following datacenters are available:\n" + vim.serviceContent.rootFolder.children.grep(RbVmomi::VIM::Datacenter).each do | datacenter | + rval += " " + datacenter.name + "\n" + end + rval += "Modify :vsphere=>{:vm_options=>{:datacenter=>''}} in your definition.rb to specify a datacenter" + + return rval + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/datastore.rb b/lib/veewee/provider/vsphere/box/helper/datastore.rb new file mode 100644 index 00000000..9fb74b7e --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/datastore.rb @@ -0,0 +1,48 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Retrieve datastore to install to + # + # Logic should look for the datastore in the following order: + # If datastore is specified as command line option and it exists, use that datastore + # If datastore is specified as vm option in definition and it exists, use that datastore + # If only one datastore exists and no datastore is specified, use that datastore + # TODO All other use cases should result in an error being thrown + # + def retrieve_datastore options + name ||= options["datastore"] + name ||= definition.vsphere[:vm_options][:datastore] + + unless name.nil? + datastore = dc.find_datastore name + raise Veewee::Error, "Datastore #{name} was not found on the vSphere server in datacenter #{dc.name}\n" + valid_datastores if datastore.nil? + else + raise Veewee::Error, "Multiple datastores found in datacenter #{dc.name}, you must specify a name\n" + valid_datastores if dc.datastore.count > 1 + + # There is only one datastore, choose that datastore + datastore = dc.datastore.first + end + + raise Veewee::Error, "Veewee could not find a datastore to use, consult your ESXi or vCenter server to see if it is working as expected" if datastore.nil? + env.ui.info "Using datastore #{datastore.name}" + + return datastore + end + + # Retrieve the valid datastores to inform the users what selections they can make + def valid_datastores + rval = "The following datastores are available for datacenter #{dc.name}:\n" + dc.datastore.each do | datastore | + rval += " " + datastore.name + "\n" + end + rval += "Modify :vsphere=>{:vm_options=>{:datastore=>''}} in your definition.rb to specify a compute resource" + + return rval + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/ip.rb b/lib/veewee/provider/vsphere/box/helper/ip.rb new file mode 100644 index 00000000..600df406 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/ip.rb @@ -0,0 +1,43 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Retrieve the first mac address for a vm + # This will only retrieve the first auto generate mac address + def mac_address + raise Veewee::Error,"VM #{name} does not exist" unless self.exists? + + return raw.macs.first + end + + # Retrieve the ip address for a vm. + # Requires VMware Tools installed on the target VM + def ip_address + raise Veewee::Error,"VM #{name} does not exist" unless self.exists? + + return raw.guest_ip + end + + # Retrieve host IP as seen by guest, assume this is same + # as kickstart_ip provided in Veewee Configuration + # TODO Figure out if there is a better way to accomplish this + def host_ip_as_seen_by_guest + ip = definition.vsphere[:kickstart_ip] + raise Veewee::Error, "No ip defined for this machine in the definition file.\nThe following IP addresses are available:\n#{valid_host_ips}Please specify an IP address in the definition file using vsphere[:kickstart_ip]" if ip.nil? || '' == ip + + return ip + end + + def valid_host_ips + require 'socket' + + ipv4_addresses = Socket.ip_address_list.find_all { | ip | ip.ipv4? && !ip.ipv4_loopback? } + + return ipv4_addresses.reduce("") { | ip_list, ip | ip_list + " " + ip.ip_address + "\n" } + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/network.rb b/lib/veewee/provider/vsphere/box/helper/network.rb new file mode 100644 index 00000000..07b37dfb --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/network.rb @@ -0,0 +1,48 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Retrieve the specified network to attach the VM to + # + # Logic should look for the network in the following order: + # If network is specified as command line option and it exists, use that network + # If network is specified as vm option in definition and it exists, use that network + # If only one network exists and no network is specified, use that network + # TODO All other use cases should result in an error being thrown + # + def retrieve_network options + name ||= options["network"] + name ||= definition.vsphere[:vm_options][:network] + + unless name.nil? + network = dc.network.find { |x| x.name == name } + raise Veewee::Error, "Network #{name} was not found on the vSphere server in datacenter #{dc.name}\n" + valid_networks if network.nil? + else + raise Veewee::Error, "Multiple networks found in datacenter #{dc.name}, you must specify a name\n" + valid_networks if dc.network.count > 1 + + # There is only one datastore, choose that network + network = dc.network.first + end + + raise Veewee::Error, "Veewee could not find a network to use, consult your ESXi or vCenter server to see if it is working as expected" if network.nil? + env.ui.info "Using network #{network.name}" + + return network + end + + # Retrieve the valid networks to inform the users what selections they can make + def valid_networks + rval = "The following networks are available for datacenter #{dc.name}:\n" + dc.network.each do | network | + rval += " " + network.name + "\n" + end + rval += "Modify :vsphere=>{:vm_options=>{:network=>''}} in your definition.rb to specify a compute resource" + + return rval + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/ssh_options.rb b/lib/veewee/provider/vsphere/box/helper/ssh_options.rb new file mode 100644 index 00000000..f9527bbd --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/ssh_options.rb @@ -0,0 +1,20 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Translate the definition ssh options to ssh options that can be passed to Net::Ssh calls + def ssh_options + ssh_options={ + :user => definition.ssh_user, + :port => 22, + :password => definition.ssh_password, + :timeout => definition.ssh_login_timeout.to_i + } + return ssh_options + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/status.rb b/lib/veewee/provider/vsphere/box/helper/status.rb new file mode 100644 index 00000000..ad1a5864 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/status.rb @@ -0,0 +1,20 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + # Check if box is running + def running? + return false if raw.nil? + return raw.summary.runtime.powerState=="poweredOn" + end + + # Check if the box already exists + def exists? + return (not raw.nil?) + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/vnc.rb b/lib/veewee/provider/vsphere/box/helper/vnc.rb new file mode 100644 index 00000000..660d9605 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/vnc.rb @@ -0,0 +1,71 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + attr_reader :vnc_port + attr_reader :vnc_host + + def enable_vnc + vm = raw + @vnc_host = reachable_ip raw.collect('runtime.host')[0] + extraConfig, = raw.collect('config.extraConfig') + already_enabled = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.enabled' && x.value.downcase == 'true' } + if already_enabled + puts "VNC already enabled" + real_vnc_port = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.port' }.value + #Modify the port to work around the way Veewee's VNC class is implemented + @vnc_port = real_vnc_port - 5900 + else + real_vnc_port = unused_vnc_port vnc_host + #Modify the port to work around the way Veewee's VNC class is implemented + @vnc_port = real_vnc_port - 5900 + vm.ReconfigVM_Task(:spec => { + :extraConfig => [ + { :key => 'RemoteDisplay.vnc.enabled', :value => 'true' }, + { :key => 'RemoteDisplay.vnc.port', :value => real_vnc_port.to_s } + ] + }).wait_for_completion + end + end + + def reachable_ip host + ips = host.collect('config.network.vnic')[0].map { |x| x.spec.ip.ipAddress } + ips.find do |x| + begin + Timeout.timeout(1) { TCPSocket.new(x, 443).close; true } + rescue + false + end + end or err("could not find IP for server #{host.name}") + end + + def unused_vnc_port ip + 10.times do + port = 5901 + rand(64) + unused = (TCPSocket.connect(ip, port).close rescue true) + return port if unused + end + err "no unused port found" + end + + def close_vnc + vm = raw + vm.ReconfigVM_Task(:spec => { + :extraConfig => [ + { :key => 'RemoteDisplay.vnc.enabled', :value => 'false' }, + { :key => 'RemoteDisplay.vnc.password', :value => '' }, + { :key => 'RemoteDisplay.vnc.port', :value => '' } + ] + }).wait_for_completion + end + + def vnc_enabled? + extraConfig, = raw.collect('config.extraConfig') + return extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.enabled' && x.value.downcase == 'true' } + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/helper/vsphere.rb b/lib/veewee/provider/vsphere/box/helper/vsphere.rb new file mode 100644 index 00000000..27027e8b --- /dev/null +++ b/lib/veewee/provider/vsphere/box/helper/vsphere.rb @@ -0,0 +1,157 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + VIM = RbVmomi::VIM + NET_DEVICE_CLASSES = { + 'e1000' => VIM::VirtualE1000, + 'vmxnet3' => VIM::VirtualVmxnet3, + } + + def add_net network, opts + klass = NET_DEVICE_CLASSES[opts[:type]] or err "unknown network adapter type #{opts[:type].inspect}" + + case network + when VIM::DistributedVirtualPortgroup + switch, pg_key = network.collect 'config.distributedVirtualSwitch', 'key' + port = VIM.DistributedVirtualSwitchPortConnection( + :switchUuid => switch.uuid, + :portgroupKey => pg_key) + summary = network.name + backing = VIM.VirtualEthernetCardDistributedVirtualPortBackingInfo(:port => port) + when VIM::Network + summary = network.name + backing = VIM.VirtualEthernetCardNetworkBackingInfo(:deviceName => network.name) + else fail + end + + _add_device raw, nil, klass.new( + :key => -1, + :deviceInfo => { + :summary => summary, + :label => "", + }, + :backing => backing, + :addressType => 'generated' + ) + end + + def add_disk size + vm = raw + controller, unit_number = pick_controller vm, nil, [VIM::VirtualSCSIController] + id = "disk-#{controller.key}-#{unit_number}" + + filename = "#{File.dirname(vm.summary.config.vmPathName)}/#{id}.vmdk" + + _add_device vm, 'create', VIM::VirtualDisk( + :key => -1, + :backing => VIM.VirtualDiskFlatVer2BackingInfo( + :fileName => filename, + :diskMode => :persistent, + :thinProvisioned => true + ), + :capacityInKB => MetricNumber.parse(size).to_i/1000, + :controllerKey => controller.key, + :unitNumber => unit_number + ) + end + + def pick_controller vm, controller, controller_classes + existing_devices, = vm.collect 'config.hardware.device' + + controller ||= existing_devices.find do |dev| + controller_classes.any? { |klass| dev.is_a? klass } && + dev.device.length < 2 + end + err "no suitable controller found" unless controller + + used_unit_numbers = existing_devices.select { |dev| dev.controllerKey == controller.key }.map(&:unitNumber) + unit_number = (used_unit_numbers.max||-1) + 1 + + [controller, unit_number] + end + + def _add_device vm, fileOp, dev + spec = { + :deviceChange => [ + { :operation => :add, :fileOperation => fileOp, :device => dev }, + ] + } + task = vm.ReconfigVM_Task(:spec => spec).wait_for_completion + #result = progress([task])[task] + #result = nil + #if result == nil + #new_device = vm.collect('config.hardware.device')[0].grep(dev.class).last + #puts "Added device #{new_device.name}" + #end + end + + require "delegate" + + class MetricNumber < SimpleDelegator + attr_reader :unit, :binary + + def initialize val, unit, binary=false + @unit = unit + @binary = binary + super val.to_f + end + + def to_s + limit = @binary ? 1024 : 1000 + if self < limit + prefix = '' + multiple = 1 + else + prefixes = @binary ? BINARY_PREFIXES : DECIMAL_PREFIXES + prefixes = prefixes.sort_by { |k,v| v } + prefix, multiple = prefixes.find { |k,v| self/v < limit } + prefix, multiple = prefixes.last unless prefix + end + ("%0.2f %s%s" % [self/multiple, prefix, @unit]).strip + end + + # http://physics.nist.gov/cuu/Units/prefixes.html + DECIMAL_PREFIXES = { + 'k' => 10 ** 3, + 'M' => 10 ** 6, + 'G' => 10 ** 9, + 'T' => 10 ** 12, + 'P' => 10 ** 15, + } + + # http://physics.nist.gov/cuu/Units/binary.html + BINARY_PREFIXES = { + 'Ki' => 2 ** 10, + 'Mi' => 2 ** 20, + 'Gi' => 2 ** 30, + 'Ti' => 2 ** 40, + 'Pi' => 2 ** 50, + } + + CANONICAL_PREFIXES = Hash[(DECIMAL_PREFIXES.keys + BINARY_PREFIXES.keys).map { |x| [x.downcase, x] }] + + def self.parse str + if str =~ /^([0-9,.]+)\s*([kmgtp]i?)?/i + x = $1.delete(',').to_f + binary = false + if $2 + prefix = $2.downcase + binary = prefix[1..1] == 'i' + prefixes = binary ? BINARY_PREFIXES : DECIMAL_PREFIXES + multiple = prefixes[CANONICAL_PREFIXES[prefix]] + else + multiple = 1 + end + units = $' + new x*multiple, units, binary + else + raise "Problem parsing SI number #{str.inspect}" + end + end + end + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/poweroff.rb b/lib/veewee/provider/vsphere/box/poweroff.rb new file mode 100644 index 00000000..be139019 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/poweroff.rb @@ -0,0 +1,13 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def poweroff(options={}) + raw.PowerOffVM_Task unless raw.nil? + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/ssh.rb b/lib/veewee/provider/vsphere/box/ssh.rb new file mode 100644 index 00000000..43af626d --- /dev/null +++ b/lib/veewee/provider/vsphere/box/ssh.rb @@ -0,0 +1,12 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def ssh(command,options) + super(command,options) + end + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/up.rb b/lib/veewee/provider/vsphere/box/up.rb new file mode 100644 index 00000000..33cc29e1 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/up.rb @@ -0,0 +1,15 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def up(options={}) + ui.info "Up called: running #{self.running?} or nil #{raw.nil?}" + ui.info "Starting VM" unless ( raw.nil? or self.running? ) + raw.PowerOnVM_Task unless ( raw.nil? or self.running? ) + end + + end + end + end +end diff --git a/lib/veewee/provider/vsphere/box/validate_vsphere.rb b/lib/veewee/provider/vsphere/box/validate_vsphere.rb new file mode 100644 index 00000000..986bdab2 --- /dev/null +++ b/lib/veewee/provider/vsphere/box/validate_vsphere.rb @@ -0,0 +1,13 @@ +module Veewee + module Provider + module Vsphere + module BoxCommand + + def validate_vsphere(options) + validate_tags( options['tags'],options) + end + end #Module + + end #Module + end #Module +end #Module diff --git a/lib/veewee/provider/vsphere/provider.rb b/lib/veewee/provider/vsphere/provider.rb new file mode 100644 index 00000000..19553f50 --- /dev/null +++ b/lib/veewee/provider/vsphere/provider.rb @@ -0,0 +1,44 @@ +require 'veewee/provider/core/provider' + +module Veewee + module Provider + module Vsphere + class Provider < Veewee::Provider::Core::Provider + + attr_reader :vsphere_server + attr_reader :vsphere_user + attr_reader :vsphere_password + + #include ::Veewee::Provider::Vsphere::ProviderCommand + def initialize(name, options, env) + super(name, options, env) + end + + def check_requirements + #Retrieve credentials for VMware Host + # Credentials are used in this order + # Command Line + # ENV File + @vsphere_server = options['vsphere_server'] + @vsphere_user = options['vsphere_user'] + @vsphere_password = options['vsphere_password'] + + cred_path = ENV["VEEWEE_VSPHERE_AUTHFILE"] + unless cred_path.nil? + env.logger.info "Reading credentials yamlfile #{cred_path}" + credentials=YAML.load_file(cred_path) + + @vsphere_server ||= credentials["vsphere_server"] + @vsphere_user ||= credentials["vsphere_user"] + @vsphere_password ||= credentials["vsphere_password"] + end + + raise Veewee::Error, "Must define vsphere_server" if @vsphere_server.nil? + raise Veewee::Error, "Must define vsphere_user" if @vsphere_user.nil? + + @vsphere_password ||= ask("#{@vsphere_user}@#{@vsphere_server} password: ") {|q| q.echo = false} + end + end #End Class + end # End Module + end # End Module +end # End Module diff --git a/templates/CentOS-5.8-x86_64-netboot-vsphere/definition.rb b/templates/CentOS-5.8-x86_64-netboot-vsphere/definition.rb new file mode 100644 index 00000000..fcae028c --- /dev/null +++ b/templates/CentOS-5.8-x86_64-netboot-vsphere/definition.rb @@ -0,0 +1,17 @@ +Veewee::Session.declare({ + :cpu_count => '1', :memory_size=> '384', + :disk_size => '10140', :disk_format => 'VDI', :hostiocache => 'off', :ioapic => 'on', :pae => 'on', + :os_type_id => 'RedHat5_64', + :iso_file => "CentOS-5.8-x86_64-netinstall.iso", + :iso_src => "http://mirrors.arsc.edu/centos/5.8/isos/x86_64/CentOS-5.8-x86_64-netinstall.iso", + :iso_md5 => "6425035e9adee4b8653a85f59877ac5b", + :iso_download_timeout => 1000, + :boot_wait => "10", :boot_cmd_sequence => [ 'linux cmdline autostep ks=http://%IP%:%PORT%/ks.cfg' ], + :kickstart_port => "7122", :kickstart_timeout => 10000, :kickstart_file => "ks.cfg", + :ssh_login_timeout => "10000000", :ssh_user => "vagrant", :ssh_password => "vagrant", :ssh_key => "", + :ssh_host_port => "7222", :ssh_guest_port => "22", + :sudo_cmd => "echo '%p'|sudo -S sh '%f'", + :shutdown_cmd => "/sbin/halt -h -p", + :postinstall_files => [ "postinstall.sh" ], :postinstall_timeout => 10000, + :vsphere => { :vm_options => { }, :kickstart_ip => "" } +}) diff --git a/templates/CentOS-5.8-x86_64-netboot-vsphere/ks.cfg b/templates/CentOS-5.8-x86_64-netboot-vsphere/ks.cfg new file mode 100644 index 00000000..deac74f3 --- /dev/null +++ b/templates/CentOS-5.8-x86_64-netboot-vsphere/ks.cfg @@ -0,0 +1,62 @@ +# Kickstart file automatically generated by anaconda. + +install +url --url=http://mirrors.arsc.edu/centos/5.8/os/x86_64/ +lang en_US.UTF-8 +langsupport --default=en_US.UTF-8 en_US.UTF-8 +keyboard us +xconfig --card "VMWare" --videoram 16384 --hsync 31.5-37.9 --vsync 50-70 --resolution 800x600 --depth 16 +network --device eth0 --bootproto dhcp +rootpw --iscrypted $1$vSG8FjAu$ekQ0grf16hS4G93HTPcco/ +firewall --enabled --trust eth0 --ssh +selinux --enforcing +authconfig --enableshadow --enablemd5 +timezone America/New_York +bootloader --location=mbr +# The following is the partition information you requested +# Note that any partitions you deleted are not expressed +# here so unless you clear all partitions first, this is +# not guaranteed to work +clearpart --all --drives=sda --initlabel +part /boot --fstype ext3 --size=100 --ondisk=sda +part pv.2 --size=0 --grow --ondisk=sda +volgroup VolGroup00 --pesize=32768 pv.2 +logvol swap --fstype swap --name=LogVol01 --vgname=VolGroup00 --size=528 --grow --maxsize=1056 +logvol / --fstype ext3 --name=LogVol00 --vgname=VolGroup00 --size=1024 --grow +reboot + +%packages +#@ admin-tools +#@ text-internet +#@ dialup +#@ smb-server +#@ web-server +#@ printing +#@ server-cfg +@ core +vim-enhanced +keyutils +kexec-tools +fipscheck + +%post +/usr/sbin/groupadd vagrant +/usr/sbin/useradd vagrant -g vagrant -G wheel +echo "vagrant"|passwd --stdin vagrant +echo "vagrant ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +#Install VMware Tools +mkdir -p /tmp/vmware-tools +cd /tmp/vmware-tools +wget -m -np -nd -E -A pub http://packages.vmware.com/tools/keys/ +for file in ./*;do rpm -import ${file}; done + +cat > /etc/yum.repos.d/vmware-tools.repo < /etc/vsphere_box_build_time + + +yum -y install gcc bzip2 make kernel-devel-`uname -r` +#yum -y update +#yum -y upgrade + +yum -y install gcc-c++ zlib-devel openssl-devel readline-devel sqlite3-devel +yum -y erase wireless-tools gtk2 libX11 hicolor-icon-theme avahi freetype bitstream-vera-fonts +yum -y clean all + +#Installing ruby +cd /tmp +wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.2-p180.tar.gz || fail "Could not download Ruby source" +tar xzvf ruby-1.9.2-p180.tar.gz +cd ruby-1.9.2-p180 +./configure +make && make install +cd /tmp +rm -rf /tmp/ruby-1.9.2-p180 +rm /tmp/ruby-1.9.2-p180.tar.gz +ln -s /usr/local/bin/ruby /usr/bin/ruby # Create a sym link for the same path +ln -s /usr/local/bin/gem /usr/bin/gem # Create a sym link for the same path + +#Installing chef & Puppet +/usr/bin/gem install chef --no-ri --no-rdoc +/usr/bin/gem install puppet --no-ri --no-rdoc + +#Installing vagrant keys +mkdir /home/vagrant/.ssh +chmod 700 /home/vagrant/.ssh +cd /home/vagrant/.ssh +wget --no-check-certificate 'https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub' -O authorized_keys +chown -R vagrant /home/vagrant/.ssh + +sed -i "s/^.*requiretty/#Defaults requiretty/" /etc/sudoers +sed -i "s/^\(.*env_keep = \"\)/\1PATH /" /etc/sudoers + +#poweroff -h + +exit diff --git a/templates/ubuntu-12.10-server-amd64-vsphere/definition.rb b/templates/ubuntu-12.10-server-amd64-vsphere/definition.rb new file mode 100644 index 00000000..24484188 --- /dev/null +++ b/templates/ubuntu-12.10-server-amd64-vsphere/definition.rb @@ -0,0 +1,37 @@ +Veewee::Session.declare({ + :cpu_count => '1', + :memory_size=> '2048', + :disk_size => '10140', + :disk_format => 'VDI', + :hostiocache => 'off', + :os_type_id => 'Ubuntu_64', + :iso_file => "ubuntu-12.10-server-amd64.iso", + :iso_src => "http://releases.ubuntu.com/12.10/ubuntu-12.10-server-amd64.iso", + :iso_md5 => '4bd3270bde86d7e4e017e3847a4af485', + :iso_download_timeout => "1000", + :boot_wait => "10", + :boot_cmd_sequence => [ + '', + '/install/vmlinuz noapic preseed/url=http://%IP%:%PORT%/preseed.cfg ', + 'debian-installer=en_US auto locale=en_US kbd-chooser/method=us ', + 'hostname=%NAME% ', + 'fb=false debconf/frontend=noninteractive ', + #'cdrom-detect/eject=false ', + 'keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=us keyboard-configuration/variant=us console-setup/ask_detect=false ', + 'initrd=/install/initrd.gz -- ' +], + :kickstart_port => "7122", + :kickstart_timeout => "10000", + :kickstart_file => "preseed.cfg", + :ssh_login_timeout => "10000", + :ssh_user => "vagrant", + :ssh_password => "vagrant", + :ssh_key => "", + :ssh_host_port => "7222", + :ssh_guest_port => "22", + :sudo_cmd => "echo '%p'|sudo -S sh '%f'", + :shutdown_cmd => "shutdown -P now", + :postinstall_files => [ "postinstall.sh"], + :postinstall_timeout => "10000", + :vsphere => { :vm_options => { }, :kickstart_ip => "" } +}) diff --git a/templates/ubuntu-12.10-server-amd64-vsphere/postinstall.sh b/templates/ubuntu-12.10-server-amd64-vsphere/postinstall.sh new file mode 100644 index 00000000..58f84f01 --- /dev/null +++ b/templates/ubuntu-12.10-server-amd64-vsphere/postinstall.sh @@ -0,0 +1,97 @@ +# postinstall.sh created from Mitchell's official lucid32/64 baseboxes + +date > /etc/vagrant_box_build_time + +# Apt-install various things necessary for Ruby, guest additions, +# etc., and remove optional things to trim down the machine. +apt-get -y update +apt-get -y upgrade +apt-get -y install linux-headers-$(uname -r) build-essential +apt-get -y install zlib1g-dev libssl-dev libreadline-gplv2-dev libyaml-dev +apt-get -y install vim +apt-get clean + +# Installing the virtualbox guest additions +apt-get -y install dkms +VBOX_VERSION=$(cat /home/vagrant/.vbox_version) +mount -o loop VBoxGuestAdditions_$VBOX_VERSION.iso /mnt +sh /mnt/VBoxLinuxAdditions.run +umount /mnt + +rm VBoxGuestAdditions_$VBOX_VERSION.iso + +cd /tmp + +# Setup sudo to allow no-password sudo for "admin" +groupadd -r admin +usermod -a -G admin vagrant +cp /etc/sudoers /etc/sudoers.orig +sed -i -e '/Defaults\s\+env_reset/a Defaults\texempt_group=admin' /etc/sudoers +sed -i -e 's/%admin ALL=(ALL) ALL/%admin ALL=NOPASSWD:ALL/g' /etc/sudoers + +# Add puppet user and group +adduser --system --group --home /var/lib/puppet puppet + +# Install NFS client +apt-get -y install nfs-common + +# Install Ruby from source in /opt so that users of Vagrant +# can install their own Rubies using packages or however. +wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.3-p286.tar.gz +tar xvzf ruby-1.9.3-p286.tar.gz +cd ruby-1.9.3-p286 +./configure --prefix=/opt/ruby +make +make install +cd .. +rm -rf ruby-1.9.3-p286 +rm ruby-1.9.3-p286.tar.gz + +# Install RubyGems 1.8.24 +wget http://production.cf.rubygems.org/rubygems/rubygems-1.8.24.tgz +tar xzf rubygems-1.8.24.tgz +cd rubygems-1.8.24 +/opt/ruby/bin/ruby setup.rb +cd .. +rm -rf rubygems-1.8.24 +rm rubygems-1.8.24.tgz + +# Installing chef & Puppet +/opt/ruby/bin/gem install chef --no-ri --no-rdoc +/opt/ruby/bin/gem install puppet --no-ri --no-rdoc + +# Add /opt/ruby/bin to the global path as the last resort so +# Ruby, RubyGems, and Chef/Puppet are visible +echo 'PATH=$PATH:/opt/ruby/bin/'> /etc/profile.d/vagrantruby.sh + +# Installing vagrant keys +mkdir /home/vagrant/.ssh +chmod 700 /home/vagrant/.ssh +cd /home/vagrant/.ssh +wget --no-check-certificate 'https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub' -O authorized_keys +chmod 600 /home/vagrant/.ssh/authorized_keys +chown -R vagrant /home/vagrant/.ssh + +# Remove items used for building, since they aren't needed anymore +apt-get -y remove linux-headers-$(uname -r) build-essential +apt-get -y autoremove + +# Zero out the free space to save space in the final image: +dd if=/dev/zero of=/EMPTY bs=1M +rm -f /EMPTY + +# Removing leftover leases and persistent rules +echo "cleaning up dhcp leases" +rm /var/lib/dhcp3/* + +# Make sure Udev doesn't block our network +# http://6.ptmc.org/?p=164 +echo "cleaning up udev rules" +rm /etc/udev/rules.d/70-persistent-net.rules +mkdir /etc/udev/rules.d/70-persistent-net.rules +rm -rf /dev/.udev/ +rm /lib/udev/rules.d/75-persistent-net-generator.rules + +echo "Adding a 2 sec delay to the interface up, to make the dhclient happy" +echo "pre-up sleep 2" >> /etc/network/interfaces +exit diff --git a/templates/ubuntu-12.10-server-amd64-vsphere/preseed.cfg b/templates/ubuntu-12.10-server-amd64-vsphere/preseed.cfg new file mode 100644 index 00000000..ecaee1b1 --- /dev/null +++ b/templates/ubuntu-12.10-server-amd64-vsphere/preseed.cfg @@ -0,0 +1,92 @@ +choose-mirror-bin mirror/http/proxy string + +## Options to set on the command line +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +#d-i netcfg/get_hostname string dummy +d-i netcfg/get_hostname string unassigned-hostname +d-i netcfg/get_domain string unassigned-domain + +# Continue without a default route +# Not working , specify a dummy in the DHCP +#d-i netcfg/no_default_route boolean + +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +d-i kbd-chooser/method select American English + +d-i netcfg/wireless_wep string + +d-i base-installer/kernel/override-image string linux-server +#d-i base-installer/kernel/override-image string linux-image-2.6.32-21-generic + +# Choices: Dialog, Readline, Gnome, Kde, Editor, Noninteractive +d-i debconf debconf/frontend select Noninteractive + +d-i pkgsel/install-language-support boolean false +tasksel tasksel/first multiselect standard, ubuntu-server + +#d-i partman-auto/method string regular +d-i partman-auto/method string lvm +#d-i partman-auto/purge_lvm_from_device boolean true + +d-i partman-lvm/confirm boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-auto/choose_recipe select atomic + +d-i partman/confirm_write_new_label boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true + +#http://ubuntu-virginia.ubuntuforums.org/showthread.php?p=9626883 +#Message: "write the changes to disk and configure lvm preseed" +#http://serverfault.com/questions/189328/ubuntu-kickstart-installation-using-lvm-waits-for-input +#preseed partman-lvm/confirm_nooverwrite boolean true + +# Write the changes to disks and configure LVM? +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-auto-lvm/guided_size string max + +## Default user, we can get away with a recipe to change this +d-i passwd/user-fullname string vagrant +d-i passwd/username string vagrant +d-i passwd/user-password password vagrant +d-i passwd/user-password-again password vagrant +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +## minimum is puppet and ssh and ntp +# Individual additional packages to install +# Setup VMware Tools +d-i pkgsel/include string openssh-server ntp open-vm-tools + +# Whether to upgrade packages after debootstrap. +# Allowed values: none, safe-upgrade, full-upgrade +d-i pkgsel/upgrade select full-upgrade + +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note + +#For the update +d-i pkgsel/update-policy select none + + +# debconf-get-selections --install +#Use mirror +#d-i apt-setup/use_mirror boolean true +#d-i mirror/country string manual +#choose-mirror-bin mirror/protocol string http +#choose-mirror-bin mirror/http/hostname string 192.168.4.150 +#choose-mirror-bin mirror/http/directory string /ubuntu +#choose-mirror-bin mirror/suite select maverick +#d-i debian-installer/allow_unauthenticated string true + + + diff --git a/validation/veewee.feature b/validation/veewee.feature index 9fdc2d83..1ed1472d 100644 --- a/validation/veewee.feature +++ b/validation/veewee.feature @@ -2,25 +2,25 @@ Feature: veewee box validation As a valid veewee box I need to comply to a set of rules - @vmfusion @virtualbox @kvm @parallels + @vmfusion @vsphere @virtualbox @kvm @parallels Scenario: Valid definition Given a veeweebox was build And I run "whoami" over ssh Then I should see the provided username in the output - @vmfusion @virtualbox @kvm @parallels + @vmfusion @vsphere @virtualbox @kvm @parallels Scenario: Checking sudo Given a veeweebox was build And I sudorun "whoami" over ssh Then I should see "root" in the output - @vmfusion @virtualbox @kvm @parallels + @vmfusion @vsphere @virtualbox @kvm @parallels Scenario: Checking ruby Given a veeweebox was build And I run ". /etc/profile ;ruby --version 2> /dev/null 1> /dev/null; echo $?" over ssh Then I should see "0" in the output - @vmfusion @virtualbox @kvm @parallels + @vmfusion @vsphere @virtualbox @kvm @parallels Scenario: Checking gem Given a veeweebox was build And I run ". /etc/profile; gem --version 2> /dev/null 1> /dev/null ; echo $?" over ssh