knife-vsphere 0.1.5 → 0.1.6
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/lib/chef/knife/BaseVsphereCommand.rb +167 -111
- data/lib/chef/knife/vsphere_customization_list.rb +29 -29
- data/lib/chef/knife/vsphere_template_list.rb +32 -35
- data/lib/chef/knife/vsphere_vm_clone.rb +360 -176
- data/lib/chef/knife/vsphere_vm_delete.rb +39 -41
- data/lib/chef/knife/vsphere_vm_list.rb +28 -42
- data/lib/chef/knife/vsphere_vm_state.rb +85 -89
- data/lib/knife-vsphere/version.rb +4 -4
- metadata +2 -28
@@ -1,111 +1,167 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
require '
|
8
|
-
|
9
|
-
|
10
|
-
class
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
end
|
1
|
+
#
|
2
|
+
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
+
# Contributor:: Jesse Campbell (<hikeit@gmail.com>)
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'chef/knife'
|
8
|
+
require 'rbvmomi'
|
9
|
+
|
10
|
+
# Base class for vsphere knife commands
|
11
|
+
class Chef
|
12
|
+
class Knife
|
13
|
+
class BaseVsphereCommand < Knife
|
14
|
+
|
15
|
+
deps do
|
16
|
+
require 'chef/knife/bootstrap'
|
17
|
+
Chef::Knife::Bootstrap.load_deps
|
18
|
+
require 'fog'
|
19
|
+
require 'socket'
|
20
|
+
require 'net/ssh/multi'
|
21
|
+
require 'readline'
|
22
|
+
require 'chef/json_compat'
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def self.get_common_options
|
27
|
+
|
28
|
+
option :vsphere_user,
|
29
|
+
:short => "-u USERNAME",
|
30
|
+
:long => "--user USERNAME",
|
31
|
+
:description => "The username for the host"
|
32
|
+
|
33
|
+
option :vsphere_pass,
|
34
|
+
:short => "-p PASSWORD",
|
35
|
+
:long => "--password PASSWORD",
|
36
|
+
:description => "The password for the host"
|
37
|
+
|
38
|
+
option :datacenter,
|
39
|
+
:short => "-d DATACENTER",
|
40
|
+
:long => "--datacenter DATACENTER",
|
41
|
+
:description => "The Datacenter to create the VM in"
|
42
|
+
|
43
|
+
option :path,
|
44
|
+
:long => "--path SOAP_PATH",
|
45
|
+
:description => "The SOAP endpoint path",
|
46
|
+
:proc => Proc.new { |p| Chef::Config[:knife][:path] = p },
|
47
|
+
:default => "/sdk"
|
48
|
+
|
49
|
+
option :port,
|
50
|
+
:long => "--port PORT",
|
51
|
+
:description => "The VI SDK port number to use",
|
52
|
+
:proc => Proc.new { |p| Chef::Config[:knife][:port] = p },
|
53
|
+
:default => 443
|
54
|
+
|
55
|
+
option :use_ssl,
|
56
|
+
:long => "--ssl USE_SSL",
|
57
|
+
:description => "Whether to use SSL connection",
|
58
|
+
:default => true
|
59
|
+
|
60
|
+
option :insecure,
|
61
|
+
:short => "-i USE_INSECURE_SSL",
|
62
|
+
:long => "--insecure USE_INSECURE_SSL",
|
63
|
+
:description => "Determines whether SSL certificate verification is skipped",
|
64
|
+
:default => true
|
65
|
+
|
66
|
+
option :folder,
|
67
|
+
:short => "-f FOLDER",
|
68
|
+
:long => "--folder FOLDER",
|
69
|
+
:description => "The folder to get VMs from",
|
70
|
+
:default => ''
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def locate_config_value(key)
|
75
|
+
key = key.to_sym
|
76
|
+
Chef::Config[:knife][key] || config[key]
|
77
|
+
end
|
78
|
+
|
79
|
+
def get_vim_connection
|
80
|
+
|
81
|
+
conn_opts = {
|
82
|
+
:host => locate_config_value(:vsphere_host),
|
83
|
+
:path => config[:path],
|
84
|
+
:port => config[:port],
|
85
|
+
:use_ssl => config[:ssl],
|
86
|
+
:user => locate_config_value(:vsphere_user),
|
87
|
+
:password => locate_config_value(:vsphere_pass),
|
88
|
+
:insecure => config[:insecure]
|
89
|
+
}
|
90
|
+
|
91
|
+
# opt :insecure, "don't verify ssl certificate", :short => 'k', :default => (ENV['RBVMOMI_INSECURE'] == '1')
|
92
|
+
# opt :debug, "Log SOAP messages", :short => 'd', :default => (ENV['RBVMOMI_DEBUG'] || false)
|
93
|
+
|
94
|
+
vim = RbVmomi::VIM.connect conn_opts
|
95
|
+
config[:vim] = vim
|
96
|
+
return vim
|
97
|
+
end
|
98
|
+
|
99
|
+
def find_folder(folderName)
|
100
|
+
dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
|
101
|
+
dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
|
102
|
+
baseEntity = dc.vmFolder
|
103
|
+
entityArray = folderName.split('/')
|
104
|
+
entityArray.each do |entityArrItem|
|
105
|
+
if entityArrItem != ''
|
106
|
+
baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::Folder).find { |f| f.name == entityArrItem } or
|
107
|
+
abort "no such folder #{folderName} while looking for #{entityArrItem}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
baseEntity
|
111
|
+
end
|
112
|
+
|
113
|
+
def find_network(networkName)
|
114
|
+
dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
|
115
|
+
dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
|
116
|
+
baseEntity = dc.network
|
117
|
+
baseEntity.find { |f| f.name == networkName } or abort "no such network #{networkName}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def find_pool(poolName)
|
121
|
+
dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
|
122
|
+
dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
|
123
|
+
baseEntity = dc.hostFolder
|
124
|
+
entityArray = poolName.split('/')
|
125
|
+
entityArray.each do |entityArrItem|
|
126
|
+
if entityArrItem != ''
|
127
|
+
if baseEntity.is_a? RbVmomi::VIM::Folder
|
128
|
+
baseEntity = baseEntity.childEntity.find { |f| f.name == entityArrItem } or
|
129
|
+
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
130
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ClusterComputeResource
|
131
|
+
baseEntity = baseEntity.resourcePool.resourcePool.find { |f| f.name == entityArrItem } or
|
132
|
+
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
133
|
+
elsif baseEntity.is_a? RbVmomi::VIM::ResourcePool
|
134
|
+
baseEntity = baseEntity.resourcePool.find { |f| f.name == entityArrItem } or
|
135
|
+
abort "no such pool #{poolName} while looking for #{entityArrItem}"
|
136
|
+
else
|
137
|
+
abort "Unexpected Object type encountered #{baseEntity.type} while finding resourcePool"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
baseEntity
|
142
|
+
end
|
143
|
+
|
144
|
+
def find_datastore(dsName)
|
145
|
+
dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
|
146
|
+
dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
|
147
|
+
baseEntity = dc.datastore
|
148
|
+
baseEntity.find { |f| f.info.name == dsName } or abort "no such datastore #{dsName}"
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
def find_all_in_folder(folder, type)
|
153
|
+
folder.childEntity.grep(type)
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_in_folder(folder, type, name)
|
157
|
+
folder.childEntity.grep(type).find { |o| o.name == name }
|
158
|
+
end
|
159
|
+
|
160
|
+
def fatal_exit(msg)
|
161
|
+
ui.fatal(msg)
|
162
|
+
exit 1
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -1,29 +1,29 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
-
# License:: Apache License, Version 2.0
|
4
|
-
#
|
5
|
-
require 'chef/knife'
|
6
|
-
require 'chef/knife/BaseVsphereCommand'
|
7
|
-
|
8
|
-
# Lists all customization specifications in the configured datacenter
|
9
|
-
class Chef::Knife::VsphereCustomizationList < Chef::Knife::BaseVsphereCommand
|
10
|
-
|
11
|
-
banner "knife vsphere customization list"
|
12
|
-
|
13
|
-
get_common_options
|
14
|
-
|
15
|
-
def run
|
16
|
-
|
17
|
-
$stdout.sync = true
|
18
|
-
|
19
|
-
vim = get_vim_connection
|
20
|
-
|
21
|
-
csm = vim.serviceContent.customizationSpecManager
|
22
|
-
csm.info.each do |c|
|
23
|
-
puts "#{ui.color("Customization Name", :cyan)}: #{c.name}"
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
1
|
+
#
|
2
|
+
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
require 'chef/knife'
|
6
|
+
require 'chef/knife/BaseVsphereCommand'
|
7
|
+
|
8
|
+
# Lists all customization specifications in the configured datacenter
|
9
|
+
class Chef::Knife::VsphereCustomizationList < Chef::Knife::BaseVsphereCommand
|
10
|
+
|
11
|
+
banner "knife vsphere customization list"
|
12
|
+
|
13
|
+
get_common_options
|
14
|
+
|
15
|
+
def run
|
16
|
+
|
17
|
+
$stdout.sync = true
|
18
|
+
|
19
|
+
vim = get_vim_connection
|
20
|
+
|
21
|
+
csm = vim.serviceContent.customizationSpecManager
|
22
|
+
csm.info.each do |c|
|
23
|
+
puts "#{ui.color("Customization Name", :cyan)}: #{c.name}"
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -1,35 +1,32 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
-
# License:: Apache License, Version 2.0
|
4
|
-
#
|
5
|
-
|
6
|
-
require 'chef/knife'
|
7
|
-
require 'chef/knife/BaseVsphereCommand'
|
8
|
-
|
9
|
-
# Lists all known VM templates in the configured datacenter
|
10
|
-
class Chef::Knife::VsphereTemplateList < Chef::Knife::BaseVsphereCommand
|
11
|
-
|
12
|
-
banner "knife vsphere template list"
|
13
|
-
|
14
|
-
get_common_options
|
15
|
-
|
16
|
-
def run
|
17
|
-
|
18
|
-
$stdout.sync = true
|
19
|
-
$stderr.sync = true
|
20
|
-
|
21
|
-
vim = get_vim_connection
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
vms
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
1
|
+
#
|
2
|
+
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'chef/knife'
|
7
|
+
require 'chef/knife/BaseVsphereCommand'
|
8
|
+
|
9
|
+
# Lists all known VM templates in the configured datacenter
|
10
|
+
class Chef::Knife::VsphereTemplateList < Chef::Knife::BaseVsphereCommand
|
11
|
+
|
12
|
+
banner "knife vsphere template list"
|
13
|
+
|
14
|
+
get_common_options
|
15
|
+
|
16
|
+
def run
|
17
|
+
|
18
|
+
$stdout.sync = true
|
19
|
+
$stderr.sync = true
|
20
|
+
|
21
|
+
vim = get_vim_connection
|
22
|
+
|
23
|
+
baseFolder = find_folder(config[:folder]);
|
24
|
+
|
25
|
+
vms = find_all_in_folder(baseFolder, RbVmomi::VIM::VirtualMachine).
|
26
|
+
select {|v| !v.config.nil? && v.config.template == true }
|
27
|
+
|
28
|
+
vms.each do |vm|
|
29
|
+
puts "#{ui.color("Template Name", :cyan)}: #{vm.name}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,176 +1,360 @@
|
|
1
|
-
#
|
2
|
-
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
-
#
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
require 'chef/knife
|
8
|
-
require '
|
9
|
-
require '
|
10
|
-
|
11
|
-
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
# --
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
1
|
+
#
|
2
|
+
# Author:: Ezra Pagel (<ezra@cpan.org>)
|
3
|
+
# Contributor:: Jesse Campbell (<hikeit@gmail.com>)
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'chef/knife'
|
8
|
+
require 'chef/knife/BaseVsphereCommand'
|
9
|
+
require 'rbvmomi'
|
10
|
+
require 'netaddr'
|
11
|
+
|
12
|
+
# Clone an existing template into a new VM, optionally applying a customization specification.
|
13
|
+
#
|
14
|
+
# usage:
|
15
|
+
# knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
|
16
|
+
# --cips 192.168.0.99/24,192.168.1.99/24 \
|
17
|
+
# --chostname NODENAME --cdomain NODEDOMAIN
|
18
|
+
class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
|
19
|
+
|
20
|
+
banner "knife vsphere vm clone VMNAME (options)"
|
21
|
+
|
22
|
+
get_common_options
|
23
|
+
|
24
|
+
option :dest_folder,
|
25
|
+
:long => "--dest-folder FOLDER",
|
26
|
+
:description => "The folder into which to put the cloned VM"
|
27
|
+
|
28
|
+
option :datastore,
|
29
|
+
:long => "--datastore STORE",
|
30
|
+
:description => "The datastore into which to put the cloned VM"
|
31
|
+
|
32
|
+
option :resource_pool,
|
33
|
+
:long => "--resource-pool POOL",
|
34
|
+
:description => "The resource pool into which to put the cloned VM",
|
35
|
+
:default => ''
|
36
|
+
|
37
|
+
option :source_vm,
|
38
|
+
:long => "--template TEMPLATE",
|
39
|
+
:description => "The source VM / Template to clone from",
|
40
|
+
:required => true
|
41
|
+
|
42
|
+
option :customization_spec,
|
43
|
+
:long => "--cspec CUST_SPEC",
|
44
|
+
:description => "The name of any customization specification to apply"
|
45
|
+
|
46
|
+
option :customization_vlan,
|
47
|
+
:long => "--cvlan CUST_VLAN",
|
48
|
+
:description => "VLAN name for network adapter to join"
|
49
|
+
|
50
|
+
option :customization_ips,
|
51
|
+
:long => "--cips CUST_IPS",
|
52
|
+
:description => "Comma-delimited list of CIDR IPs for customization"
|
53
|
+
|
54
|
+
option :customization_gw,
|
55
|
+
:long => "--cgw CUST_GW",
|
56
|
+
:description => "CIDR IP of gateway for customization"
|
57
|
+
|
58
|
+
option :customization_hostname,
|
59
|
+
:long => "--chostname CUST_HOSTNAME",
|
60
|
+
:description => "Unqualified hostname for customization"
|
61
|
+
|
62
|
+
option :customization_domain,
|
63
|
+
:long => "--cdomain CUST_DOMAIN",
|
64
|
+
:description => "Domain name for customization"
|
65
|
+
|
66
|
+
option :customization_tz,
|
67
|
+
:long => "--ctz CUST_TIMEZONE",
|
68
|
+
:description => "Timezone invalid 'Area/Location' format"
|
69
|
+
|
70
|
+
option :customization_cpucount,
|
71
|
+
:long => "--ccpu CUST_CPU_COUNT",
|
72
|
+
:description => "Number of CPUs"
|
73
|
+
|
74
|
+
option :customization_memory,
|
75
|
+
:long => "--cram CUST_MEMORY_GB",
|
76
|
+
:description => "Gigabytes of RAM"
|
77
|
+
|
78
|
+
option :power,
|
79
|
+
:long => "--start STARTVM",
|
80
|
+
:description => "Indicates whether to start the VM after a successful clone",
|
81
|
+
:default => true
|
82
|
+
|
83
|
+
option :bootstrap,
|
84
|
+
:long => "--bootstrap FALSE",
|
85
|
+
:description => "Indicates whether to bootstrap the VM",
|
86
|
+
:default => true
|
87
|
+
|
88
|
+
option :fqdn,
|
89
|
+
:long => "--fqdn SERVER_FQDN",
|
90
|
+
:description => "Fully qualified hostname for bootstrapping"
|
91
|
+
|
92
|
+
option :ssh_user,
|
93
|
+
:short => "-x USERNAME",
|
94
|
+
:long => "--ssh-user USERNAME",
|
95
|
+
:description => "The ssh username",
|
96
|
+
:default => "root"
|
97
|
+
|
98
|
+
option :ssh_password,
|
99
|
+
:short => "-P PASSWORD",
|
100
|
+
:long => "--ssh-password PASSWORD",
|
101
|
+
:description => "The ssh password"
|
102
|
+
|
103
|
+
option :ssh_port,
|
104
|
+
:short => "-p PORT",
|
105
|
+
:long => "--ssh-port PORT",
|
106
|
+
:description => "The ssh port",
|
107
|
+
:default => "22",
|
108
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
|
109
|
+
|
110
|
+
option :identity_file,
|
111
|
+
:short => "-i IDENTITY_FILE",
|
112
|
+
:long => "--identity-file IDENTITY_FILE",
|
113
|
+
:description => "The SSH identity file used for authentication"
|
114
|
+
|
115
|
+
option :chef_node_name,
|
116
|
+
:short => "-N NAME",
|
117
|
+
:long => "--node-name NAME",
|
118
|
+
:description => "The Chef node name for your new node"
|
119
|
+
|
120
|
+
option :prerelease,
|
121
|
+
:long => "--prerelease",
|
122
|
+
:description => "Install the pre-release chef gems"
|
123
|
+
|
124
|
+
option :bootstrap_version,
|
125
|
+
:long => "--bootstrap-version VERSION",
|
126
|
+
:description => "The version of Chef to install",
|
127
|
+
:proc => lambda { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
128
|
+
|
129
|
+
option :bootstrap_proxy,
|
130
|
+
:long => "--bootstrap-proxy PROXY_URL",
|
131
|
+
:description => "The proxy server for the node being bootstrapped",
|
132
|
+
:proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
|
133
|
+
|
134
|
+
option :distro,
|
135
|
+
:short => "-d DISTRO",
|
136
|
+
:long => "--distro DISTRO",
|
137
|
+
:description => "Bootstrap a distro using a template",
|
138
|
+
:default => "ubuntu10.04-gems"
|
139
|
+
|
140
|
+
option :template_file,
|
141
|
+
:long => "--template-file TEMPLATE",
|
142
|
+
:description => "Full path to location of template to use",
|
143
|
+
:default => false
|
144
|
+
|
145
|
+
option :run_list,
|
146
|
+
:short => "-r RUN_LIST",
|
147
|
+
:long => "--run-list RUN_LIST",
|
148
|
+
:description => "Comma separated list of roles/recipes to apply",
|
149
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
150
|
+
:default => []
|
151
|
+
|
152
|
+
option :no_host_key_verify,
|
153
|
+
:long => "--no-host-key-verify",
|
154
|
+
:description => "Disable host key verification",
|
155
|
+
:boolean => true,
|
156
|
+
:default => false
|
157
|
+
|
158
|
+
def run
|
159
|
+
|
160
|
+
$stdout.sync = true
|
161
|
+
|
162
|
+
vmname = @name_args[0]
|
163
|
+
if vmname.nil?
|
164
|
+
show_usage
|
165
|
+
fatal_exit("You must specify a virtual machine name")
|
166
|
+
end
|
167
|
+
config[:fqdn] = vmname unless config[:fqdn]
|
168
|
+
config[:chef_node_name] = vmname unless config[:chef_node_name]
|
169
|
+
|
170
|
+
get_vim_connection
|
171
|
+
|
172
|
+
src_folder = find_folder(config[:folder])
|
173
|
+
|
174
|
+
src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
|
175
|
+
abort "VM/Template not found"
|
176
|
+
|
177
|
+
clone_spec = generate_clone_spec(src_vm.config)
|
178
|
+
|
179
|
+
dest_folder = find_folder(config[:dest_folder] || config[:folder]);
|
180
|
+
|
181
|
+
task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
|
182
|
+
puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
|
183
|
+
task.wait_for_completion
|
184
|
+
puts "Finished creating virtual machine #{vmname}"
|
185
|
+
|
186
|
+
if config[:power] || config[:bootstrap]
|
187
|
+
vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
|
188
|
+
fatal_exit("VM #{vmname} not found")
|
189
|
+
vm.PowerOnVM_Task.wait_for_completion
|
190
|
+
puts "Powered on virtual machine #{vmname}"
|
191
|
+
end
|
192
|
+
|
193
|
+
if config[:bootstrap]
|
194
|
+
print "Waiting for sshd..."
|
195
|
+
print "." until tcp_test_ssh(config[:fqdn])
|
196
|
+
puts "done"
|
197
|
+
|
198
|
+
bootstrap_for_node.run
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# Builds a CloneSpec
|
203
|
+
def generate_clone_spec (src_config)
|
204
|
+
rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(config[:resource_pool]))
|
205
|
+
|
206
|
+
if config[:datastore]
|
207
|
+
rspec.datastore = find_datastore(config[:datastore])
|
208
|
+
end
|
209
|
+
|
210
|
+
clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
|
211
|
+
:powerOn => false,
|
212
|
+
:template => false)
|
213
|
+
|
214
|
+
clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
|
215
|
+
|
216
|
+
if config[:customization_cpucount]
|
217
|
+
clone_spec.config.numCPUs = config[:customization_cpucount]
|
218
|
+
end
|
219
|
+
|
220
|
+
if config[:customization_memory]
|
221
|
+
clone_spec.config.memoryMB = Integer(config[:customization_memory]) * 1024
|
222
|
+
end
|
223
|
+
|
224
|
+
if config[:customization_vlan]
|
225
|
+
network = find_network(config[:customization_vlan])
|
226
|
+
switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid ,:portgroupKey => network.key)
|
227
|
+
card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" } or
|
228
|
+
abort "Can't find source network card to customize"
|
229
|
+
card.backing.port = switch_port
|
230
|
+
dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
|
231
|
+
clone_spec.config.deviceChange.push dev_spec
|
232
|
+
end
|
233
|
+
|
234
|
+
if config[:customization_spec]
|
235
|
+
csi = find_customization(config[:customization_spec]) or
|
236
|
+
fatal_exit("failed to find customization specification named #{config[:customization_spec]}")
|
237
|
+
|
238
|
+
if csi.info.type != "Linux"
|
239
|
+
fatal_exit("Only Linux customization specifications are currently supported")
|
240
|
+
end
|
241
|
+
|
242
|
+
if config[:customization_ips]
|
243
|
+
if config[:customization_gw]
|
244
|
+
csi.spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i,config[:customization_gw]) }
|
245
|
+
else
|
246
|
+
csi.spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
use_ident = !config[:customization_hostname].nil? || !config[:customization_domain].nil?
|
251
|
+
|
252
|
+
if use_ident
|
253
|
+
# TODO - verify that we're deploying a linux spec, at least warn
|
254
|
+
ident = RbVmomi::VIM.CustomizationLinuxPrep
|
255
|
+
|
256
|
+
if config[:customization_hostname]
|
257
|
+
ident.hostName = RbVmomi::VIM.CustomizationFixedName
|
258
|
+
ident.hostName.name = config[:customization_hostname]
|
259
|
+
else
|
260
|
+
ident.hostName = RbVmomi::VIM.CustomizationFixedName
|
261
|
+
ident.hostName.name = config[:customization_domain]
|
262
|
+
end
|
263
|
+
|
264
|
+
if config[:customization_domain]
|
265
|
+
ident.domain = config[:customization_domain]
|
266
|
+
end
|
267
|
+
|
268
|
+
csi.spec.identity = ident
|
269
|
+
end
|
270
|
+
|
271
|
+
clone_spec.customization = csi.spec
|
272
|
+
end
|
273
|
+
clone_spec
|
274
|
+
end
|
275
|
+
|
276
|
+
# Retrieves a CustomizationSpecItem that matches the supplied name
|
277
|
+
# @param vim [Connection] VI Connection to use
|
278
|
+
# @param name [String] name of customization
|
279
|
+
# @return [RbVmomi::VIM::CustomizationSpecItem]
|
280
|
+
def find_customization(name)
|
281
|
+
csm = config[:vim].serviceContent.customizationSpecManager
|
282
|
+
csm.GetCustomizationSpec(:name => name)
|
283
|
+
end
|
284
|
+
|
285
|
+
# Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
|
286
|
+
# @param ip [String] Any static IP address to use, otherwise DHCP
|
287
|
+
# @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
|
288
|
+
# @return [RbVmomi::VIM::CustomizationIPSettings]
|
289
|
+
def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
|
290
|
+
|
291
|
+
settings = RbVmomi::VIM.CustomizationIPSettings
|
292
|
+
|
293
|
+
if ip.nil?
|
294
|
+
settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
|
295
|
+
else
|
296
|
+
cidr_ip = NetAddr::CIDR.create(ip)
|
297
|
+
settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
|
298
|
+
settings.subnetMask = cidr_ip.netmask_ext
|
299
|
+
|
300
|
+
# TODO - want to confirm gw/ip are in same subnet?
|
301
|
+
# Only set gateway on first IP.
|
302
|
+
if config[:customization_ips].split(',').first == ip
|
303
|
+
if gw.nil?
|
304
|
+
settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
|
305
|
+
else
|
306
|
+
gw_cidr = NetAddr::CIDR.create(gw)
|
307
|
+
settings.gateway = [gw_cidr.ip]
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
|
313
|
+
adapter_map.adapter = settings
|
314
|
+
adapter_map
|
315
|
+
end
|
316
|
+
|
317
|
+
def bootstrap_for_node()
|
318
|
+
Chef::Knife::Bootstrap.load_deps
|
319
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
320
|
+
bootstrap.name_args = [config[:fqdn]]
|
321
|
+
bootstrap.config[:run_list] = config[:run_list]
|
322
|
+
bootstrap.config[:ssh_user] = config[:ssh_user]
|
323
|
+
bootstrap.config[:ssh_password] = config[:ssh_password]
|
324
|
+
bootstrap.config[:ssh_port] = config[:ssh_port]
|
325
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
326
|
+
bootstrap.config[:chef_node_name] = config[:chef_node_name]
|
327
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
328
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
329
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
330
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
331
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
332
|
+
bootstrap.config[:environment] = config[:environment]
|
333
|
+
# may be needed for vpc_mode
|
334
|
+
bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
|
335
|
+
bootstrap
|
336
|
+
end
|
337
|
+
|
338
|
+
def tcp_test_ssh(hostname)
|
339
|
+
tcp_socket = TCPSocket.new(hostname, 22)
|
340
|
+
readable = IO.select([tcp_socket], nil, nil, 5)
|
341
|
+
if readable
|
342
|
+
Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
|
343
|
+
true
|
344
|
+
else
|
345
|
+
false
|
346
|
+
end
|
347
|
+
rescue Errno::ETIMEDOUT
|
348
|
+
false
|
349
|
+
rescue Errno::EPERM
|
350
|
+
false
|
351
|
+
rescue Errno::ECONNREFUSED
|
352
|
+
sleep 2
|
353
|
+
false
|
354
|
+
rescue Errno::EHOSTUNREACH
|
355
|
+
sleep 2
|
356
|
+
false
|
357
|
+
ensure
|
358
|
+
tcp_socket && tcp_socket.close
|
359
|
+
end
|
360
|
+
end
|