chef-metal-vsphere 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|