chef-metal-vsphere 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.md +77 -0
- data/Rakefile +30 -0
- data/lib/chef_metal/provisioner_init/vsphere_init.rb +3 -0
- data/lib/chef_metal_vsphere/version.rb +3 -0
- data/lib/chef_metal_vsphere/vsphere_helpers.rb +127 -0
- data/lib/chef_metal_vsphere/vsphere_provisioner.rb +299 -0
- data/lib/chef_metal_vsphere.rb +12 -0
- metadata +125 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Rally Software Development Corp
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
chef-metal-vsphere
|
2
|
+
==================
|
3
|
+
|
4
|
+
This is a [chef-metal](https://github.com/opscode/chef-metal) provisioner for [VMware vSphere](http://www.vmware.com/products/vsphere).
|
5
|
+
|
6
|
+
Currently, chef-metal-vsphere supports provisioning Unix/ssh guest VMs.
|
7
|
+
|
8
|
+
Try It Out
|
9
|
+
----------
|
10
|
+
|
11
|
+
### vSphere VM Template
|
12
|
+
|
13
|
+
Create or obtain a unix/linux VM template. The VM template must:
|
14
|
+
|
15
|
+
- be capable of installing Chef 11.8 or newer
|
16
|
+
- run vmware-tools on system boot (provides visiblity to ip address of the running VM)
|
17
|
+
- provide access via ssh
|
18
|
+
- provide a user account with NOPASSWD sudo
|
19
|
+
|
20
|
+
### Example recipe
|
21
|
+
require 'chef_metal_vsphere'
|
22
|
+
|
23
|
+
with_vsphere_provisioner vsphere_host: 'vcenter-host-name',
|
24
|
+
vsphere_insecure: true,
|
25
|
+
vsphere_user: 'you_user_name',
|
26
|
+
vsphere_password: 'your_mothers_maiden_name' # consider using a chef-vault
|
27
|
+
|
28
|
+
with_provisioner_options('bootstrap_options' => {
|
29
|
+
datacenter: 'datacenter_name',
|
30
|
+
cluster: 'cluster_name',
|
31
|
+
resource_pool: 'resource_pool_name', # often the same as the cluster_name
|
32
|
+
datastore: 'datastore_name',
|
33
|
+
template_name: 'name_of_template_vm', # may be a VM or a VM Template
|
34
|
+
template_folder: 'folder_containing_template_vm',
|
35
|
+
vm_folder: 'folder_to_clone_vms_into',
|
36
|
+
|
37
|
+
ssh: { # net-ssh start() options
|
38
|
+
user: 'username_on_vm', # must have nopasswd sudo
|
39
|
+
password: 'name_of_your_first_pet', # consider using a chef-vault
|
40
|
+
port: 22,
|
41
|
+
auth_methods: ['password'],
|
42
|
+
user_known_hosts_file: '/dev/null', # don't do this in production
|
43
|
+
paranoid: false, # don't do this in production, either
|
44
|
+
keys: [ ], # consider using a chef-vault
|
45
|
+
keys_only: false
|
46
|
+
}
|
47
|
+
})
|
48
|
+
|
49
|
+
1.upto 2 do |n|
|
50
|
+
machine "metal_#{n}" do
|
51
|
+
action [:create]
|
52
|
+
end
|
53
|
+
|
54
|
+
machine_file "/tmp/metal_#{n}.txt" do
|
55
|
+
machine "metal_#{n}"
|
56
|
+
content "Hello machine #{n}!"
|
57
|
+
end
|
58
|
+
|
59
|
+
machine "metal_#{n}" do
|
60
|
+
action [:stop]
|
61
|
+
end
|
62
|
+
|
63
|
+
machine "metal_#{n}" do
|
64
|
+
# note: no need to :stop before :delete
|
65
|
+
action [:delete]
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
This will clone your VM template to create two VMware Virtual Machines, "metal_1" and "metal_2", in the vSphere Folder specified by vm_folder, bootstrapped to an empty runlist. It will then stop (guest shutdown) and delete the vms.
|
71
|
+
|
72
|
+
Bugs and Contact
|
73
|
+
----------------
|
74
|
+
|
75
|
+
Please submit bugs at [https://github.com/RallySoftware-cookbooks/chef-metal-vsphere], contact Brian Dupras on Twitter at @briandupras, email at rallysoftware-cookbooks@rallydev.com.
|
76
|
+
|
77
|
+
*Warning* if you get an rbvmomi error regarding VMODL::AnyType, add `gem 'nokogiri', '1.5.5'` to your dependencies.
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
require 'bundler/gem_tasks'
|
3
|
+
|
4
|
+
def gem_server
|
5
|
+
@gem_server ||= ENV['GEM_SERVER'] || 'rubygems.org'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Bundler
|
9
|
+
class GemHelper
|
10
|
+
unless gem_server == 'rubygems.org'
|
11
|
+
unless method_defined?(:rubygem_push)
|
12
|
+
raise NoMethodError, "Monkey patching Bundler::GemHelper#rubygem_push failed: did the Bundler API change???"
|
13
|
+
end
|
14
|
+
|
15
|
+
def rubygem_push(path)
|
16
|
+
print "Username: "
|
17
|
+
username = STDIN.gets.chomp
|
18
|
+
print "Password: "
|
19
|
+
password = STDIN.gets.chomp
|
20
|
+
|
21
|
+
gem_server_url = "https://#{username}:#{password}@#{gem_server}/"
|
22
|
+
sh %{gem push #{path} --host #{gem_server_url}}
|
23
|
+
|
24
|
+
Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_server}"
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "Monkey patched Bundler::GemHelper#rubygem_push to push to #{gem_server}."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ChefMetalVsphere
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
def vim
|
5
|
+
# reconnect on every call - connections may silently timeout during long operations (e.g. cloning)
|
6
|
+
conn_opts = {
|
7
|
+
:host => connect_options['vsphere_host'],
|
8
|
+
:port => connect_options['vsphere_port'],
|
9
|
+
:path => connect_options['vshere_path'],
|
10
|
+
:use_ssl => connect_options['vsphere_ssl'],
|
11
|
+
:insecure => connect_options['vsphere_insecure'],
|
12
|
+
:proxyHost => connect_options['proxy_host'],
|
13
|
+
:proxyPort => connect_options['proxy_port'],
|
14
|
+
:user => connect_options['vsphere_user'],
|
15
|
+
:password => connect_options['vsphere_password']
|
16
|
+
}
|
17
|
+
|
18
|
+
vim = RbVmomi::VIM.connect conn_opts
|
19
|
+
return vim
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_vm(dc_name, vm_folder, vm_name)
|
23
|
+
folder = find_folder(dc_name, vm_folder) or raise("vSphere Folder not found [#{vm_folder}] for vm #{vm_name}")
|
24
|
+
vm = folder.find(vm_name, RbVmomi::VIM::VirtualMachine)
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_vm(vm, wait_on_port = 22)
|
28
|
+
state = vm.runtime.powerState
|
29
|
+
unless state == 'poweredOn'
|
30
|
+
vm.PowerOnVM_Task.wait_for_completion
|
31
|
+
end
|
32
|
+
|
33
|
+
sleep 1 until port_ready?(vm, wait_on_port)
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_vm(vm)
|
37
|
+
begin
|
38
|
+
vm.ShutdownGuest
|
39
|
+
sleep 2 until vm.runtime.powerState == 'poweredOff'
|
40
|
+
rescue
|
41
|
+
vm.PowerOffVM_Task.wait_for_completion
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def port_ready?(vm, port)
|
46
|
+
vm_ip = vm.guest.ipAddress
|
47
|
+
return false if vm_ip.nil?
|
48
|
+
|
49
|
+
begin
|
50
|
+
tcp_socket = TCPSocket.new(vm_ip, port)
|
51
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
52
|
+
if readable
|
53
|
+
true
|
54
|
+
else
|
55
|
+
false
|
56
|
+
end
|
57
|
+
rescue Errno::ETIMEDOUT
|
58
|
+
false
|
59
|
+
rescue Errno::EPERM
|
60
|
+
false
|
61
|
+
rescue Errno::ECONNREFUSED
|
62
|
+
false
|
63
|
+
rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
|
64
|
+
false
|
65
|
+
ensure
|
66
|
+
tcp_socket && tcp_socket.close
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#folder could be like: /Level1/Level2/folder_name
|
71
|
+
def find_folder(dc_name, folder_name)
|
72
|
+
#dc(dc_name).vmFolder.childEntity.grep(RbVmomi::VIM::Folder).find { |x| x.name == folder_name }
|
73
|
+
baseEntity = dc(dc_name).vmFolder
|
74
|
+
entityArray = folder_name.split('/')
|
75
|
+
entityArray.each do |entityArrItem|
|
76
|
+
if entityArrItem != ''
|
77
|
+
baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::Folder).find { |f| f.name == entityArrItem }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
baseEntity
|
81
|
+
end
|
82
|
+
|
83
|
+
def dc(dc_name)
|
84
|
+
vim.serviceInstance.find_datacenter(dc_name) or raise("vSphere Datacenter not found [#{datacenter}]")
|
85
|
+
end
|
86
|
+
|
87
|
+
def do_vm_clone(dc_name, vm_template, vm_name, options)
|
88
|
+
pool = options['resource_pool'] ? find_pool(dc(dc_name), options['resource_pool']) : vm_template.resourcePool
|
89
|
+
raise ':resource_pool must be specified when cloning from a VM Template' if pool.nil?
|
90
|
+
|
91
|
+
clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(
|
92
|
+
location: RbVmomi::VIM.VirtualMachineRelocateSpec(pool: pool),
|
93
|
+
powerOn: false,
|
94
|
+
template: false
|
95
|
+
)
|
96
|
+
|
97
|
+
vm_template.CloneVM_Task(
|
98
|
+
name: vm_name,
|
99
|
+
folder: find_folder(dc_name, options['vm_folder']),
|
100
|
+
spec: clone_spec
|
101
|
+
).wait_for_completion
|
102
|
+
end
|
103
|
+
|
104
|
+
def find_pool(dc, pool_name)
|
105
|
+
baseEntity = dc.hostFolder
|
106
|
+
entityArray = pool_name.split('/')
|
107
|
+
entityArray.each do |entityArrItem|
|
108
|
+
if entityArrItem != ''
|
109
|
+
if baseEntity.is_a? RbVmomi::VIM::Folder
|
110
|
+
baseEntity = baseEntity.childEntity.find { |f| f.name == entityArrItem } or nil
|
111
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource or baseEntity.is_a? RbVmomi::VIM::ComputeResource
|
112
|
+
baseEntity = baseEntity.resourcePool.resourcePool.find { |f| f.name == entityArrItem } or nil
|
113
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
|
114
|
+
baseEntity = baseEntity.resourcePool.find { |f| f.name == entityArrItem } or nil
|
115
|
+
else
|
116
|
+
baseEntity = nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
raise "vSphere ResourcePool not found [#{pool_name}]" if baseEntity.nil?
|
122
|
+
|
123
|
+
baseEntity = baseEntity.resourcePool if not baseEntity.is_a?(RbVmomi::VIM::ResourcePool) and baseEntity.respond_to?(:resourcePool)
|
124
|
+
baseEntity
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'chef_metal/provisioner'
|
2
|
+
require 'chef_metal/machine/windows_machine'
|
3
|
+
require 'chef_metal/machine/unix_machine'
|
4
|
+
require 'chef_metal/convergence_strategy/install_msi'
|
5
|
+
require 'chef_metal/convergence_strategy/install_cached'
|
6
|
+
require 'chef_metal/transport/ssh'
|
7
|
+
require 'chef_metal_vsphere/version'
|
8
|
+
require 'rbvmomi'
|
9
|
+
require 'chef_metal_vsphere/vsphere_helpers'
|
10
|
+
|
11
|
+
module ChefMetalVsphere
|
12
|
+
# Provisions machines in vSphere.
|
13
|
+
class VsphereProvisioner < ChefMetal::Provisioner
|
14
|
+
|
15
|
+
include Chef::Mixin::ShellOut
|
16
|
+
include ChefMetalVsphere::Helpers
|
17
|
+
|
18
|
+
def self.inflate(node)
|
19
|
+
url = node['normal']['provisioner_output']['provisioner_url']
|
20
|
+
scheme, provider, id = url.split(':', 3)
|
21
|
+
VsphereProvisioner.new({ :provider => provider }, id)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create a new Vsphere provisioner.
|
25
|
+
#
|
26
|
+
# ## Parameters
|
27
|
+
# connect_options - hash of options to be passed to RbVmomi::VIM.connect
|
28
|
+
# :vsphere_host - required - hostname of the vSphere API server
|
29
|
+
# :vsphere_port - optional - port on the vSphere API server (default: 443)
|
30
|
+
# :vshere_path - optional - path on the vSphere API server (default: /sdk)
|
31
|
+
# :vsphere_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
|
32
|
+
# :vsphere_insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
|
33
|
+
# :vsphere_user - required - user name to use in connection to vSphere API server
|
34
|
+
# :vsphere_password - required - password to use in connection to vSphere API server
|
35
|
+
# :proxy_host - optional - http proxy host to use in connection to vSphere API server (default: none)
|
36
|
+
# :proxy_port - optional - http proxy port to use in connection to vSphere API server (default: none)
|
37
|
+
def initialize(connect_options)
|
38
|
+
connect_options = stringify_keys(connect_options)
|
39
|
+
default_connect_options = {
|
40
|
+
'vsphere_port' => 443,
|
41
|
+
'vsphere_ssl' => true,
|
42
|
+
'vsphere_insecure' => false,
|
43
|
+
'vsphere_path' => '/sdk'
|
44
|
+
}
|
45
|
+
|
46
|
+
@connect_options = default_connect_options.merge(connect_options)
|
47
|
+
|
48
|
+
required_options = %w( vsphere_host vsphere_user vsphere_password )
|
49
|
+
missing_options = []
|
50
|
+
required_options.each do |opt|
|
51
|
+
missing_options << opt unless @connect_options.has_key?(opt)
|
52
|
+
end
|
53
|
+
unless missing_options.empty?
|
54
|
+
raise "missing required options: #{missing_options.join(', ')}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# test vim connection
|
58
|
+
vim || raise("cannot connect to [#{provisioner_url}]")
|
59
|
+
|
60
|
+
@connect_options
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :connect_options
|
64
|
+
|
65
|
+
# Acquire a machine, generally by provisioning it. Returns a Machine
|
66
|
+
# object pointing at the machine, allowing useful actions like setup,
|
67
|
+
# converge, execute, file and directory. The Machine object will have a
|
68
|
+
# "node" property which must be saved to the server (if it is any
|
69
|
+
# different from the original node object).
|
70
|
+
#
|
71
|
+
# ## Parameters
|
72
|
+
# action_handler - the action_handler object that is calling this method; this
|
73
|
+
# is generally a action_handler, but could be anything that can support the
|
74
|
+
# ChefMetal::ActionHandler interface (i.e., in the case of the test
|
75
|
+
# kitchen metal driver for acquiring and destroying VMs; see the base
|
76
|
+
# class for what needs providing).
|
77
|
+
# node - node object (deserialized json) representing this machine. If
|
78
|
+
# the node has a provisioner_options hash in it, these will be used
|
79
|
+
# instead of options provided by the provisioner. TODO compare and
|
80
|
+
# fail if different?
|
81
|
+
# node will have node['normal']['provisioner_options'] in it with any options.
|
82
|
+
# It is a hash with this format:
|
83
|
+
#
|
84
|
+
# -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
|
85
|
+
# -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
|
86
|
+
# :datacenter
|
87
|
+
# :resource_pool
|
88
|
+
# :cluster
|
89
|
+
# :datastore
|
90
|
+
# :template_name
|
91
|
+
# :template_folder
|
92
|
+
# :vm_folder
|
93
|
+
# :winrm {...} (not yet implemented)
|
94
|
+
# :ssh {...}
|
95
|
+
#
|
96
|
+
# Example bootstrap_options for vSphere:
|
97
|
+
# TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
|
98
|
+
# 'bootstrap_options' => {
|
99
|
+
# 'template_name' =>'centos6.small',
|
100
|
+
# 'template_folder' =>'Templates',
|
101
|
+
# 'vm_folder' => 'MyApp'
|
102
|
+
# }
|
103
|
+
#
|
104
|
+
# node['normal']['provisioner_output'] will be populated with information
|
105
|
+
# about the created machine. For vSphere, it is a hash with this
|
106
|
+
# format:
|
107
|
+
#
|
108
|
+
# -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
|
109
|
+
# -- vm_folder: name of the vSphere folder containing the VM
|
110
|
+
#
|
111
|
+
def acquire_machine(action_handler, node)
|
112
|
+
# Set up the provisioner output
|
113
|
+
provisioner_options = stringify_keys(node['normal']['provisioner_options'])
|
114
|
+
|
115
|
+
vm_name = node['name']
|
116
|
+
old_provisioner_output = node['normal']['provisioner_output']
|
117
|
+
node['normal']['provisioner_output'] = provisioner_output = {
|
118
|
+
'provisioner_url' => provisioner_url,
|
119
|
+
'vm_name' => vm_name,
|
120
|
+
'bootstrap_options' => provisioner_options['bootstrap_options']
|
121
|
+
}
|
122
|
+
|
123
|
+
bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
|
124
|
+
vm_folder = bootstrap_options['vm_folder']
|
125
|
+
|
126
|
+
if bootstrap_options['ssh']
|
127
|
+
wait_on_port = bootstrap_options['ssh']['port']
|
128
|
+
raise "Must specify bootstrap_options[:ssh][:port]" if wait_on_port.nil?
|
129
|
+
else
|
130
|
+
raise 'bootstrapping is currently supported for ssh only'
|
131
|
+
# wait_on_port = bootstrap_options['winrm']['port']
|
132
|
+
end
|
133
|
+
|
134
|
+
# TODO compare new options to existing and fail if we cannot change it
|
135
|
+
# over (perhaps introduce a boolean that will force a delete and recreate
|
136
|
+
# in such a case)
|
137
|
+
|
138
|
+
vm = vm_instance(action_handler, node)
|
139
|
+
|
140
|
+
action_handler.perform_action "Start VM and wait for ssh [#{vm_folder}/#{vm_name}]" do
|
141
|
+
start_vm(vm, wait_on_port)
|
142
|
+
end
|
143
|
+
|
144
|
+
machine = machine_for(node)
|
145
|
+
|
146
|
+
machine
|
147
|
+
end
|
148
|
+
|
149
|
+
# Connect to machine without acquiring it
|
150
|
+
def connect_to_machine(node)
|
151
|
+
machine_for(node)
|
152
|
+
end
|
153
|
+
|
154
|
+
def delete_machine(action_handler, node)
|
155
|
+
if node['normal'] && node['normal']['provisioner_output']
|
156
|
+
provisioner_output = node['normal']['provisioner_output']
|
157
|
+
else
|
158
|
+
provisioner_output = {}
|
159
|
+
end
|
160
|
+
vm_name = provisioner_output['vm_name'] || node['name']
|
161
|
+
vm_folder = provisioner_output['bootstrap_options']['vm_folder']
|
162
|
+
vm = vm_for(node)
|
163
|
+
|
164
|
+
unless vm.nil?
|
165
|
+
action_handler.perform_action "Delete VM [#{vm_folder}/#{vm_name}]" do
|
166
|
+
vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == 'poweredOff'
|
167
|
+
vm.Destroy_Task.wait_for_completion
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def stop_machine(action_handler, node)
|
173
|
+
if node['normal'] && node['normal']['provisioner_output']
|
174
|
+
provisioner_output = node['normal']['provisioner_output']
|
175
|
+
else
|
176
|
+
provisioner_output = {}
|
177
|
+
end
|
178
|
+
vm_name = provisioner_output['vm_name'] || node['name']
|
179
|
+
vm_folder = provisioner_output['bootstrap_options']['vm_folder']
|
180
|
+
action_handler.perform_action "Guest shutdown and power off VM [#{vm_folder}/#{vm_name}]" do
|
181
|
+
stop_vm(vm_for(node))
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
protected
|
186
|
+
|
187
|
+
def provisioner_url
|
188
|
+
"vsphere://#{connect_options['vsphere_host']}:#{connect_options['vsphere_port']}#{connect_options['vsphere_path']}?ssl=#{connect_options['vsphere_ssl']}&insecure=#{connect_options['vsphere_insecure']}"
|
189
|
+
end
|
190
|
+
|
191
|
+
def vm_instance(action_handler, node)
|
192
|
+
bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
|
193
|
+
|
194
|
+
datacenter = bootstrap_options['datacenter']
|
195
|
+
vm_name = node['normal']['provisioner_output']['vm_name']
|
196
|
+
vm_folder = bootstrap_options['vm_folder']
|
197
|
+
|
198
|
+
vm = find_vm(datacenter, vm_folder, vm_name)
|
199
|
+
return vm unless vm.nil?
|
200
|
+
|
201
|
+
action_handler.perform_action "Clone a new VM instance for [#{vm_folder}/#{vm_name}]" do
|
202
|
+
vm = clone_vm(vm_name, bootstrap_options)
|
203
|
+
end
|
204
|
+
|
205
|
+
vm
|
206
|
+
end
|
207
|
+
|
208
|
+
def clone_vm(vm_name, bootstrap_options)
|
209
|
+
datacenter = bootstrap_options['datacenter']
|
210
|
+
template_folder = bootstrap_options['template_folder']
|
211
|
+
template_name = bootstrap_options['template_name']
|
212
|
+
vm_template = find_vm(datacenter, template_folder, template_name) or raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
|
213
|
+
|
214
|
+
vm = do_vm_clone(datacenter, vm_template, vm_name, bootstrap_options)
|
215
|
+
end
|
216
|
+
|
217
|
+
def machine_for(node)
|
218
|
+
if is_windows?(vm_for(node))
|
219
|
+
ChefMetal::Machine::WindowsMachine.new(node, transport_for(node), convergence_strategy_for(node))
|
220
|
+
else
|
221
|
+
ChefMetal::Machine::UnixMachine.new(node, transport_for(node), convergence_strategy_for(node))
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def vm_for(node)
|
227
|
+
bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
|
228
|
+
datacenter = bootstrap_options['datacenter']
|
229
|
+
vm_folder = bootstrap_options['vm_folder']
|
230
|
+
vm_name = node['normal']['provisioner_output']['vm_name']
|
231
|
+
vm = find_vm(datacenter, vm_folder, vm_name)
|
232
|
+
end
|
233
|
+
|
234
|
+
def is_windows?(vm)
|
235
|
+
vm.guest.guestFamily == 'windowsGuest'
|
236
|
+
end
|
237
|
+
|
238
|
+
def convergence_strategy_for(node)
|
239
|
+
if is_windows?(vm_for(node))
|
240
|
+
@windows_convergence_strategy ||= begin
|
241
|
+
options = {}
|
242
|
+
provisioner_options = node['normal']['provisioner_options'] || {}
|
243
|
+
options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
|
244
|
+
ChefMetal::ConvergenceStrategy::InstallMsi.new(options)
|
245
|
+
end
|
246
|
+
else
|
247
|
+
@unix_convergence_strategy ||= begin
|
248
|
+
options = {}
|
249
|
+
provisioner_options = node['normal']['provisioner_options'] || {}
|
250
|
+
options[:chef_client_timeout] = provisioner_options['chef_client_timeout'] if provisioner_options.has_key?('chef_client_timeout')
|
251
|
+
ChefMetal::ConvergenceStrategy::InstallCached.new(options)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def transport_for(node)
|
257
|
+
if is_windows?(vm_for(node))
|
258
|
+
create_winrm_transport(node)
|
259
|
+
else
|
260
|
+
create_ssh_transport(node)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def create_winrm_transport(node)
|
265
|
+
raise 'Windows guest VMs are not yet supported'
|
266
|
+
end
|
267
|
+
|
268
|
+
def create_ssh_transport(node)
|
269
|
+
bootstrap_options = node['normal']['provisioner_output']['bootstrap_options']
|
270
|
+
|
271
|
+
hostname = vm_for(node).guest.ipAddress
|
272
|
+
ssh_user = bootstrap_options['ssh']['user']
|
273
|
+
ssh_options = symbolize_keys(bootstrap_options['ssh'])
|
274
|
+
transport_options = {
|
275
|
+
:prefix => 'sudo '
|
276
|
+
}
|
277
|
+
ChefMetal::Transport::SSH.new(hostname, ssh_user, ssh_options, transport_options)
|
278
|
+
end
|
279
|
+
|
280
|
+
def stringify_keys(h)
|
281
|
+
Hash === h ?
|
282
|
+
Hash[
|
283
|
+
h.map do |k, v|
|
284
|
+
[k.respond_to?(:to_s) ? k.to_s : k, stringify_keys(v)]
|
285
|
+
end
|
286
|
+
] : h
|
287
|
+
end
|
288
|
+
|
289
|
+
def symbolize_keys(h)
|
290
|
+
Hash === h ?
|
291
|
+
Hash[
|
292
|
+
h.map do |k, v|
|
293
|
+
[k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
|
294
|
+
end
|
295
|
+
] : h
|
296
|
+
end
|
297
|
+
|
298
|
+
end
|
299
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'chef_metal'
|
2
|
+
require 'chef_metal_vsphere/vsphere_provisioner'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
module DSL
|
6
|
+
module Recipe
|
7
|
+
def with_vsphere_provisioner(options = {}, &block)
|
8
|
+
run_context.chef_metal.with_provisioner(ChefMetalVsphere::VsphereProvisioner.new(options), &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chef-metal-vsphere
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Rally Software Development Corp
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-05-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chef
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rbvmomi
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: Provisioner for creating vSphere VM instances in Chef Metal.
|
79
|
+
email: rallysoftware-cookbooks@rallydev.com
|
80
|
+
executables: []
|
81
|
+
extensions: []
|
82
|
+
extra_rdoc_files:
|
83
|
+
- README.md
|
84
|
+
- LICENSE
|
85
|
+
files:
|
86
|
+
- Rakefile
|
87
|
+
- LICENSE
|
88
|
+
- README.md
|
89
|
+
- lib/chef_metal/provisioner_init/vsphere_init.rb
|
90
|
+
- lib/chef_metal_vsphere/version.rb
|
91
|
+
- lib/chef_metal_vsphere/vsphere_helpers.rb
|
92
|
+
- lib/chef_metal_vsphere/vsphere_provisioner.rb
|
93
|
+
- lib/chef_metal_vsphere.rb
|
94
|
+
homepage: https://github.com/RallySoftware-cookbooks/chef-metal-vsphere
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
103
|
+
requirements:
|
104
|
+
- - ! '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
hash: -2191173413187633211
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
segments:
|
117
|
+
- 0
|
118
|
+
hash: -2191173413187633211
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 1.8.28
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Provisioner for creating vSphere VM instances in Chef Metal.
|
125
|
+
test_files: []
|