Skip to content

Instantly share code, notes, and snippets.

@MattMencel
Last active December 15, 2015 18:19
Show Gist options
  • Save MattMencel/5302820 to your computer and use it in GitHub Desktop.
Save MattMencel/5302820 to your computer and use it in GitHub Desktop.
This is a site specific script I use to bootstrap VMware templates using Chef, the knife-vsphere gem, and rbvmomi gem. It needs lots of work to make it less site specific, but shows how knife-vsphere and rbvmomi can be used to do some cool stuff.
#!/usr/bin/env ruby
# PREREQ: YOU MUST HAVE A TEMPLATE ALREADY BUILT. IF USING UBUNTU,
# MAKE SURE THE LAST STEP BEFORE YOU CREATE THE TEMPLATE IS THIS OR THE NETWORKING STEPS WON'T WORK.
# http://chris.dziemborowicz.com/blog/2010/07/25/fix-missing-eth0-when-cloning-ubuntu-vmware-virtual-machines/
require 'rubygems'
require 'awesome_print'
require 'net/ping'
require 'rbvmomi'
require 'trollop'
include Net
Ping::TCP.service_check = true
opts = Trollop::options do
opt :ccpu, "Number of CPUs", :default => 1
opt :cram, "Amount or RAM (in GB)", :default => 1
opt :cspec, "Customization Spec", :type => :string
opt :cvlan, "VLAN Name", :type => :string
opt :dest_folder, "Destination Folder", :type => :string
opt :name, "VM name", :type => :string, :required => true
opt :pool, "Resource or Cluster Location", :type => :string
opt :template, "Template Name", :type => :string
opt :template_folder, "Template Folder", :type => :string
end
if opts[:dest_folder] == nil
folder_list = `knife vsphere vm list -r --only-folders`.split("\n")
folder_list.map! {|f| f.split(": ")[1]}
folder_list.delete_if {|e| e == nil }
folder_list.each_with_index {|e,i| puts "(#{i+1}) #{e}"}
puts "Select Destination Folder for VM:"
selection = gets.chomp.to_i
if selection > folder_list.length || selection < 1
puts "Invalid Input"
exit
end
opts[:dest_folder] = folder_list.at(selection-1)
end
if opts[:cspec_list] == nil
cspec_list = `knife vsphere customization list`.split("\n")
cspec_list.map! {|f| f.split(": ")[1]}
cspec_list.delete_if {|e| e == nil }
cspec_list.each_with_index {|e,i| puts "(#{i+1}) #{e}"}
puts "Select Customization Spec for VM:"
selection = gets.chomp.to_i
if selection > cspec_list.length || selection < 1
puts "Invalid Input"
exit
end
opts[:cspec] = cspec_list.at(selection-1)
end
if opts[:template_folder] == nil
folder_list = `knife vsphere vm list -r --only-folders`.split("\n")
folder_list.map! {|f| f.split(": ")[1]}
folder_list.each_with_index {|e,i| puts "(#{i+1}) #{e}"}
puts "Select Folder Location of Template:"
selection = gets.chomp.to_i
if selection > folder_list.length || selection < 1
puts "Invalid Input"
exit
end
opts[:template_folder] = folder_list.at(selection-1)
end
if opts[:template] == nil
template_list = `knife vsphere template list -f #{opts[:template_folder]}`.split("\n")
template_list.map! {|f| f.split(": ")[1]}
template_list.delete_if {|e| e == nil }
template_list.each_with_index {|e,i| puts "(#{i+1}) #{e}"}
puts "Select Template for VM:"
selection = gets.chomp.to_i
if selection > template_list.length || selection < 1
puts "Invalid Input"
exit
end
opts[:template] = template_list.at(selection-1)
end
if opts[:cvlan] == nil
cvlan_list = `knife vsphere vlan list`.split("\n")
cvlan_list.map! {|f| f.split(": ")[1]}
cvlan_list.delete_if {|e| e == nil }
cvlan_list.each_with_index {|e,i| puts "(#{i+1}) #{e}"}
puts "Select VLAN for VM:"
selection = gets.chomp.to_i
if selection > cvlan_list.length || selection < 1
puts "Invalid Input"
exit
end
opts[:cvlan] = cvlan_list.at(selection-1)
end
if opts[:pool] == nil
pool_list = `knife vsphere pool list`.split("\n")
pool_list.map! {|f| f.split(": ")[1]}
pool_list.delete_if {|e| e == nil }
pool_list.each_with_index {|e,i| puts "(#{i+1}) #{e}"}
puts "Select Resource Pool or Cluster for VM:"
selection = gets.chomp.to_i
if selection > pool_list.length || selection < 1
puts "Invalid Input"
exit
end
opts[:pool] = pool_list.at(selection-1)
end
## YOU HAVE TO CUSTOMIZE THIS CASE BLOCK WITH YOUR OWN VMWARE NETWORKS
case opts[:cvlan]
when "Main-02-UNIX-pg"
subnet = "10.50.2.0"
when "Main-08-ServerConsolidation-pg"
subnet = "10.50.8.0"
else
puts "No subnet defined for vlan #{opts[:cvlan]}. Update script..."
exit
end
puts "Cloning VM From Template with Knife (Chef)...this will take a few minutes..."
chef_vsphere_command = "knife vsphere vm clone #{opts[:name]} --dest-folder \"#{opts[:dest_folder]}\" -N #{opts[:name]} --cspec #{opts[:cspec]} --ccpu #{opts[:ccpu]} --cram #{opts[:cram]} --ctz \"America/Chicago\" --template #{opts[:template]} -f \"#{opts[:template_folder]}\" --cvlan #{opts[:cvlan]} --resource-pool \"#{opts[:pool]}\""
puts chef_vsphere_command
begin
clone_vm = `#{chef_vsphere_command} 2>&1`
rescue Exception => e
ap e
puts "Clone Failed..."
exit
end
if clone_vm.include?("no such") || clone_vm.include?("ERROR")
puts "Clone Failed!! === #{clone_vm}"
exit
end
puts "Resetting NIC (BUG: Is not connected after cloning)"
vim = RbVmomi::VIM.connect host: 'VCENTERSERVER', user: 'VCENTERUSER', password: 'VCENTERPASS', :insecure => true
dc = vim.serviceInstance.find_datacenter("DATACENTERNAME") or fail "datacenter not found"
vm = dc.find_vm("/#{opts[:dest_folder]}/#{opts[:name]}") or fail "VM not found"
dnic = vm.config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).find{|nic| nic.props}
port = dnic[:backing][:port]
dev_info = dnic[:deviceInfo]
if dnic[:connectable][:startConnected].eql?false
spec = RbVmomi::VIM.VirtualMachineConfigSpec({
:deviceChange => [{
:operation => :remove,
:device => dnic
}]
})
puts "Removing NIC"
vm.ReconfigVM_Task(:spec => spec).wait_for_completion
puts "Adding NIC"
spec = RbVmomi::VIM.VirtualMachineConfigSpec(
:deviceChange => [{
:operation => :add,
:device => RbVmomi::VIM::VirtualVmxnet3(
:key => 0,
:deviceInfo => {
:label => dev_info[:label],
:summary => dev_info[:summary]
},
:backing => RbVmomi::VIM::VirtualEthernetCardDistributedVirtualPortBackingInfo(
:port => port
),
:addressType => 'generated'
)
}]
)
vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end
puts "Creating DHCP Entry For New VM..."
mac_addr = vm.macs
if mac_addr.size > 1
puts "Cannot process VMs with more than one NIC yet...exiting"
exit
end
macaddress = mac_addr.values[0]
comment = "Created by Chef #{Date.today()}"
## THIS IS SITE SPECIFIC CODE TO MANAGE OUR DHCP. YOU"LL HAVE TO CUSTOMIZE FOR YOUR OWN SITE.
#result = `ssh root@someserver ~/dhcp_maintenance/create_static_printer_or_host --add --showip --name #{opts[:name]} --macaddress #{macaddress} --subnet #{subnet} --ticket utech --uid utech --comment "#{comment}"`
#puts result
#if !result.include?("Entry added")
# puts "Failed to add DHCP entry...exiting..."
# exit
#end
ip_addr = result.split(": ")[1].chomp
puts "Starting VM #{opts[:name]}"
puts `knife vsphere vm state #{opts[:name]} -f "#{opts[:dest_folder]}" -s on`
puts "Waiting for SSH on #{ip_addr}..."
x = 0
alive = false
until x==25 || alive
puts "."
alive = Net::Ping::TCP.new(ip_addr, 22).ping?
break if alive
sleep 5
x+=1
end
if !alive
puts "VM is not running. Cannot continue with Chef Bootstrap..."
exit
end
## HAVE TO BOOTSTRAP IN TWO STEPS AT OUR SITE BECAUSE OF THE PROXY
puts "Bootstrapping Chef from Opscode"
`knife bootstrap #{ip_addr} -N #{opts[:name]} -x root -P PASS --bootstrap-proxy http://PROXYHOST:PORT -r 'role[base]'`
puts "Registering VM with Chef Server"
`knife bootstrap #{ip_addr} -N #{opts[:name]} -x root -P PASS -r 'role[base]'`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment