knife-ovh-cloud 1.0.0.pre.1

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.
@@ -0,0 +1,49 @@
1
+ # Copyright (C) 2012, SCM Ventures AB
2
+ # Author: Ian Delahorne <ian@scmventures.se>
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for
5
+ # any purpose with or without fee is hereby granted, provided that the
6
+ # above copyright notice and this permission notice appear in all
7
+ # copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
14
+ # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ # PERFORMANCE OF THIS SOFTWARE
17
+
18
+ require 'chef/knife'
19
+ require 'chef/knife/base_ovh_cloud_command'
20
+
21
+ # Gets the data store with the most free space in datacenter
22
+ class Chef::Knife::OvhCloudDatastoreMaxfree < Chef::Knife::BaseOvhCloudCommand
23
+
24
+ banner "knife ovh cloud datastore maxfree"
25
+
26
+ option :regex,
27
+ :short => "-r REGEX",
28
+ :long => "--regex REGEX",
29
+ :description => "Regex to match the datastore name"
30
+
31
+ get_common_options
32
+ $default[:regex] = ''
33
+
34
+ def run
35
+ $stdout.sync = true
36
+
37
+ vim = get_vim_connection
38
+ dcname = get_config(:vsphere_dc)
39
+ regex = /#{Regexp.escape( get_config(:regex))}/
40
+ dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
41
+ max = nil
42
+ dc.datastore.each do |store|
43
+ if regex.match(store.name) and (max == nil or max.summary[:freeSpace] < store.summary[:freeSpace])
44
+ max = store
45
+ end
46
+ end
47
+ puts max ? max.name : ""
48
+ end
49
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (C) 2012, SCM Ventures AB
2
+ # Author: Ian Delahorne <ian@scmventures.se>
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for
5
+ # any purpose with or without fee is hereby granted, provided that the
6
+ # above copyright notice and this permission notice appear in all
7
+ # copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
14
+ # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ # PERFORMANCE OF THIS SOFTWARE
17
+
18
+ require 'chef/knife'
19
+ require 'chef/knife/base_ovh_cloud_command'
20
+
21
+ def number_to_human_size(number)
22
+ number = number.to_f
23
+ storage_units_fmt = ["byte", "kB", "MB", "GB", "TB"]
24
+ base = 1024
25
+ if number.to_i < base
26
+ unit = storage_units_fmt[0]
27
+ else
28
+ max_exp = storage_units_fmt.size - 1
29
+ exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
30
+ exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
31
+ number /= base ** exponent
32
+ unit = storage_units_fmt[exponent]
33
+ end
34
+
35
+ return sprintf("%0.2f %s", number, unit)
36
+ end
37
+
38
+
39
+ # Lists all known data store cluster in datacenter with sizes
40
+ class Chef::Knife::OvhCloudDatastoreclusterList < Chef::Knife::BaseOvhCloudCommand
41
+
42
+ banner "knife ovh cloud datastorecluster list"
43
+
44
+ get_common_options
45
+
46
+ def run
47
+ $stdout.sync = true
48
+
49
+ vim = get_vim_connection
50
+ dc = get_datacenter
51
+ dc.datastoreFolder.childEntity.each do |store|
52
+ if store.class.to_s == "StoragePod"
53
+ avail = number_to_human_size(store.summary[:freeSpace])
54
+ cap = number_to_human_size(store.summary[:capacity])
55
+ puts "#{ui.color("DatastoreCluster", :cyan)}: #{store.name} (#{avail} / #{cap})"
56
+ end
57
+ end
58
+ end
59
+ end
60
+
@@ -0,0 +1,48 @@
1
+ # Copyright (C) 2012, SCM Ventures AB
2
+ # Author: Ian Delahorne <ian@scmventures.se>
3
+ #
4
+ # Permission to use, copy, modify, and/or distribute this software for
5
+ # any purpose with or without fee is hereby granted, provided that the
6
+ # above copyright notice and this permission notice appear in all
7
+ # copies.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
13
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
14
+ # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
15
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ # PERFORMANCE OF THIS SOFTWARE
17
+
18
+ require 'chef/knife'
19
+ require 'chef/knife/base_ovh_cloud_command'
20
+
21
+ # Gets the data store cluster with the most free space in datacenter
22
+ class Chef::Knife::OvhCloudDatastoreclusterMaxfree < Chef::Knife::BaseOvhCloudCommand
23
+
24
+ banner "knife ovh cloud datastorecluster maxfree"
25
+
26
+ option :regex,
27
+ :short => "-r REGEX",
28
+ :long => "--regex REGEX",
29
+ :description => "Regex to match the datastore cluster name"
30
+ get_common_options
31
+ $default[:regex] = ''
32
+
33
+ def run
34
+ $stdout.sync = true
35
+
36
+ vim = get_vim_connection
37
+ dcname = get_config(:vsphere_dc)
38
+ regex = /#{Regexp.escape( get_config(:regex))}/
39
+ dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
40
+ max = nil
41
+ dc.datastoreFolder.childEntity.each do |store|
42
+ if regex.match(store.name) and (store.class.to_s == "StoragePod") and (max == nil or max.summary[:freeSpace] < store.summary[:freeSpace])
43
+ max = store
44
+ end
45
+ end
46
+ puts max ? max.name : ""
47
+ end
48
+ end
@@ -0,0 +1,49 @@
1
+ require 'chef/knife'
2
+ require 'chef/knife/base_ovh_cloud_command'
3
+ require 'rbvmomi'
4
+ require 'netaddr'
5
+ #list hosts belonging to pool
6
+ class Chef::Knife::OvhCloudHostsList < Chef::Knife::BaseOvhCloudCommand
7
+ banner "knife ovh cloud hosts list"
8
+
9
+ get_common_options
10
+ option :pool,
11
+ :long => "--pool pool",
12
+ :short => "-h",
13
+ :description => "Target pool"
14
+
15
+ def traverse_folders_for_pool(folder, poolname)
16
+ children = folder.children.find_all
17
+ children.each do |child|
18
+ if child.class == RbVmomi::VIM::ClusterComputeResource || child.class == RbVmomi::VIM::ComputeResource || child.class == RbVmomi::VIM::ResourcePool
19
+ if child.name == poolname then return child end
20
+ elsif child.class == RbVmomi::VIM::Folder
21
+ pool = traverse_folders_for_pool(child, poolname)
22
+ if pool then return pool end
23
+ end
24
+ end
25
+ return false
26
+ end
27
+
28
+ def run
29
+ poolname = config[:pool]
30
+ if poolname.nil?
31
+ show_usage
32
+ fatal_exit("You must specify a resource pool or cluster name (see knife vsphere pool list)")
33
+ end
34
+
35
+
36
+ vim = get_vim_connection
37
+ dc = get_datacenter
38
+ folder = dc.hostFolder
39
+
40
+ pool = traverse_folders_for_pool(folder, poolname) or abort "Pool #{poolname} not found"
41
+
42
+ hosts=pool.host
43
+ unless hosts.nil?
44
+ hosts.each do |hostc|
45
+ puts "#{ui.color("Host", :cyan)}: #{hostc.name}"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # Author:: Jesse Campbell (<hikeit@gmail.com>)
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ require 'chef/knife'
6
+ require 'chef/knife/base_ovh_cloud_command'
7
+
8
+ # Lists all known pools in the configured datacenter
9
+ class Chef::Knife::OvhCloudPoolList < Chef::Knife::BaseOvhCloudCommand
10
+
11
+ banner "knife ovh cloud pool list"
12
+
13
+ get_common_options
14
+
15
+ def traverse_folders(folder)
16
+ puts "#{ui.color("#{folder.class}", :cyan)}: "+(folder.path[3..-1].map { |x| x[1] }.* '/')
17
+ folders = find_all_in_folder(folder, RbVmomi::VIM::ManagedObject)
18
+ unless folders.nil?
19
+ folders.each do |child|
20
+ traverse_folders(child)
21
+ end
22
+ end
23
+ end
24
+
25
+ def find_pool_folder(folderName)
26
+ dc = get_datacenter
27
+ baseEntity = dc.hostFolder
28
+ entityArray = folderName.split('/')
29
+ entityArray.each do |entityArrItem|
30
+ if entityArrItem != ''
31
+ baseEntity = baseEntity.childEntity.grep(RbVmomi::VIM::ManagedObject).find { |f| f.name == entityArrItem } or
32
+ abort "no such folder #{folderName} while looking for #{entityArrItem}"
33
+ end
34
+ end
35
+ baseEntity
36
+ end
37
+
38
+ def run
39
+ $stdout.sync = true
40
+ vim = get_vim_connection
41
+ baseFolder = find_pool_folder(get_config(:folder));
42
+ traverse_folders(baseFolder)
43
+ end
44
+ end
@@ -0,0 +1,58 @@
1
+ require 'chef/knife'
2
+ require 'chef/knife/base_ovh_cloud_command'
3
+ require 'rbvmomi'
4
+ require 'netaddr'
5
+
6
+ class Chef::Knife::OvhCloudPoolQuery < Chef::Knife::BaseOvhCloudCommand
7
+ banner "knife ovh cloud pool query POOLNAME QUERY. See \"http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.ComputeResource.html\" for allowed QUERY values."
8
+
9
+ get_common_options
10
+
11
+ def traverse_folders_for_pool(folder, poolname)
12
+ children = folder.children.find_all
13
+ children.each do |child|
14
+ if child.class == RbVmomi::VIM::ClusterComputeResource || child.class == RbVmomi::VIM::ComputeResource || child.class == RbVmomi::VIM::ResourcePool
15
+ if child.name == poolname then return child end
16
+ elsif child.class == RbVmomi::VIM::Folder
17
+ pool = traverse_folders_for_pool(child, poolname)
18
+ if pool then return pool end
19
+ end
20
+ end
21
+ return false
22
+ end
23
+
24
+ def run
25
+ $stdout.sync = true
26
+ poolname = @name_args[0]
27
+ if poolname.nil?
28
+ show_usage
29
+ fatal_exit("You must specify a resource poor or cluster name (see knife vsphere pool list)")
30
+ end
31
+
32
+ query_string = @name_args[1]
33
+ if query_string.nil?
34
+ show_usage
35
+ fatal_exit("You must specify a QUERY value (e.g. summary.overallStatus )")
36
+ end
37
+
38
+ vim = get_vim_connection
39
+
40
+ dc = get_datacenter
41
+ folder = dc.hostFolder
42
+
43
+ pool = traverse_folders_for_pool(folder, poolname) or abort "Pool #{poolname} not found"
44
+
45
+ # split QUERY by dots, and walk the object model
46
+ query = query_string.split '.'
47
+ result = pool
48
+ query.each do |part|
49
+ message, index = part.split(/[\[\]]/)
50
+ unless result.respond_to? message.to_sym
51
+ fatal_exit("\"#{query_string}\" not recognized.")
52
+ end
53
+
54
+ result = index ? result.send(message)[index.to_i] : result.send(message)
55
+ end
56
+ puts result
57
+ end
58
+ end
@@ -0,0 +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/base_ovh_cloud_command'
8
+
9
+ # Lists all known VM templates in the configured datacenter
10
+ class Chef::Knife::OvhCloudTemplateList < Chef::Knife::BaseOvhCloudCommand
11
+
12
+ banner "knife ovh cloud 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(get_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
@@ -0,0 +1,37 @@
1
+ # Author: Jesse Campbell
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for
4
+ # any purpose with or without fee is hereby granted, provided that the
5
+ # above copyright notice and this permission notice appear in all
6
+ # copies.
7
+ #
8
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9
+ # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
11
+ # AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
12
+ # DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
13
+ # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
14
+ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ # PERFORMANCE OF THIS SOFTWARE
16
+
17
+ require 'chef/knife'
18
+ require 'chef/knife/base_ovh_cloud_command'
19
+
20
+ # Lists all known data stores in datacenter with sizes
21
+ class Chef::Knife::OvhCloudVlanList < Chef::Knife::BaseOvhCloudCommand
22
+
23
+ banner "knife ovh cloud vlan list"
24
+
25
+ get_common_options
26
+
27
+ def run
28
+ $stdout.sync = true
29
+
30
+ vim = get_vim_connection
31
+ dc = get_datacenter
32
+ dc.network.each do |network|
33
+ puts "#{ui.color("VLAN", :cyan)}: #{network.name}"
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,567 @@
1
+ #
2
+ # Author:: Ezra Pagel (<ezra@cpan.org>)
3
+ # Contributor:: Jesse Campbell (<hikeit@gmail.com>)
4
+ # Contributor:: Bethany Erskine (<bethany@paperlesspost.com>)
5
+ # Contributor:: Adrian Stanila (https://github.com/sacx)
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+
9
+ require 'chef/knife'
10
+ require 'chef/knife/base_ovh_cloud_command'
11
+ require 'rbvmomi'
12
+ require 'netaddr'
13
+
14
+ # Clone an existing template into a new VM, optionally applying a customization specification.
15
+ # usage:
16
+ # knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
17
+ # --cips 192.168.0.99/24,192.168.1.99/24 \
18
+ # --chostname NODENAME --cdomain NODEDOMAIN
19
+ class Chef::Knife::OvhCloudVmClone < Chef::Knife::BaseOvhCloudCommand
20
+
21
+ banner "knife ovh cloud vm clone VMNAME (options)"
22
+
23
+ get_common_options
24
+
25
+ option :dest_folder,
26
+ :long => "--dest-folder FOLDER",
27
+ :description => "The folder into which to put the cloned VM"
28
+
29
+ option :datastore,
30
+ :long => "--datastore STORE",
31
+ :description => "The datastore into which to put the cloned VM"
32
+
33
+ option :datastorecluster,
34
+ :long => "--datastorecluster STORE",
35
+ :description => "The datastorecluster into which to put the cloned VM"
36
+
37
+ option :resource_pool,
38
+ :long => "--resource-pool POOL",
39
+ :description => "The resource pool into which to put the cloned VM"
40
+
41
+ option :source_vm,
42
+ :long => "--template TEMPLATE",
43
+ :description => "The source VM / Template to clone from"
44
+
45
+ option :linked_clone,
46
+ :long => "--linked-clone",
47
+ :description => "Indicates whether to use linked clones.",
48
+ :boolean => false
49
+
50
+ option :annotation,
51
+ :long => "--annotation TEXT",
52
+ :description => "Add TEXT in Notes field from annotation"
53
+
54
+ option :customization_spec,
55
+ :long => "--cspec CUST_SPEC",
56
+ :description => "The name of any customization specification to apply"
57
+
58
+ option :customization_plugin,
59
+ :long => "--cplugin CUST_PLUGIN_PATH",
60
+ :description => "Path to plugin that implements KnifeOvhCloudPlugin.customize_clone_spec and/or KnifeOvhCloudPlugin.reconfig_vm"
61
+
62
+ option :customization_plugin_data,
63
+ :long => "--cplugin-data CUST_PLUGIN_DATA",
64
+ :description => "String of data to pass to the plugin. Use any format you wish."
65
+
66
+ option :customization_vlan,
67
+ :long => "--cvlan CUST_VLAN",
68
+ :description => "VLAN name for network adapter to join"
69
+
70
+ option :customization_ips,
71
+ :long => "--cips CUST_IPS",
72
+ :description => "Comma-delimited list of CIDR IPs for customization"
73
+
74
+ option :customization_dns_ips,
75
+ :long => "--cdnsips CUST_DNS_IPS",
76
+ :description => "Comma-delimited list of DNS IP addresses"
77
+
78
+ option :customization_dns_suffixes,
79
+ :long => "--cdnssuffix CUST_DNS_SUFFIXES",
80
+ :description => "Comma-delimited list of DNS search suffixes"
81
+
82
+ option :customization_gw,
83
+ :long => "--cgw CUST_GW",
84
+ :description => "CIDR IP of gateway for customization"
85
+
86
+ option :customization_hostname,
87
+ :long => "--chostname CUST_HOSTNAME",
88
+ :description => "Unqualified hostname for customization"
89
+
90
+ option :customization_domain,
91
+ :long => "--cdomain CUST_DOMAIN",
92
+ :description => "Domain name for customization"
93
+
94
+ option :customization_tz,
95
+ :long => "--ctz CUST_TIMEZONE",
96
+ :description => "Timezone invalid 'Area/Location' format"
97
+
98
+ option :customization_cpucount,
99
+ :long => "--ccpu CUST_CPU_COUNT",
100
+ :description => "Number of CPUs"
101
+
102
+ option :customization_memory,
103
+ :long => "--cram CUST_MEMORY_GB",
104
+ :description => "Gigabytes of RAM"
105
+
106
+ option :power,
107
+ :long => "--start",
108
+ :description => "Indicates whether to start the VM after a successful clone",
109
+ :boolean => false
110
+
111
+ option :bootstrap,
112
+ :long => "--bootstrap",
113
+ :description => "Indicates whether to bootstrap the VM",
114
+ :boolean => false
115
+
116
+ option :fqdn,
117
+ :long => "--fqdn SERVER_FQDN",
118
+ :description => "Fully qualified hostname for bootstrapping"
119
+
120
+ option :ssh_user,
121
+ :short => "-x USERNAME",
122
+ :long => "--ssh-user USERNAME",
123
+ :description => "The ssh username"
124
+ $default[:ssh_user] = "root"
125
+
126
+ option :ssh_password,
127
+ :short => "-P PASSWORD",
128
+ :long => "--ssh-password PASSWORD",
129
+ :description => "The ssh password"
130
+
131
+ option :ssh_port,
132
+ :short => "-p PORT",
133
+ :long => "--ssh-port PORT",
134
+ :description => "The ssh port"
135
+ $default[:ssh_port] = 22
136
+
137
+ option :identity_file,
138
+ :short => "-i IDENTITY_FILE",
139
+ :long => "--identity-file IDENTITY_FILE",
140
+ :description => "The SSH identity file used for authentication"
141
+
142
+ option :chef_node_name,
143
+ :short => "-N NAME",
144
+ :long => "--node-name NAME",
145
+ :description => "The Chef node name for your new node"
146
+
147
+ option :prerelease,
148
+ :long => "--prerelease",
149
+ :description => "Install the pre-release chef gems",
150
+ :boolean => false
151
+
152
+ option :bootstrap_version,
153
+ :long => "--bootstrap-version VERSION",
154
+ :description => "The version of Chef to install",
155
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
156
+
157
+ option :bootstrap_proxy,
158
+ :long => "--bootstrap-proxy PROXY_URL",
159
+ :description => "The proxy server for the node being bootstrapped",
160
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
161
+
162
+ option :distro,
163
+ :short => "-d DISTRO",
164
+ :long => "--distro DISTRO",
165
+ :description => "Bootstrap a distro using a template"
166
+
167
+ option :template_file,
168
+ :long => "--template-file TEMPLATE",
169
+ :description => "Full path to location of template to use"
170
+
171
+ option :run_list,
172
+ :short => "-r RUN_LIST",
173
+ :long => "--run-list RUN_LIST",
174
+ :description => "Comma separated list of roles/recipes to apply"
175
+ $default[:run_list] = ''
176
+
177
+ option :secret_file,
178
+ :long => "--secret-file SECRET_FILE",
179
+ :description => "A file containing the secret key to use to encrypt data bag item values"
180
+ $default[:secret_file] = ''
181
+
182
+ option :hint,
183
+ :long => "--hint HINT_NAME[=HINT_FILE]",
184
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
185
+ :proc => Proc.new { |h|
186
+ Chef::Config[:knife][:hints] ||= Hash.new
187
+ name, path = h.split("=")
188
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new }
189
+ $default[:hint] = ''
190
+
191
+ option :no_host_key_verify,
192
+ :long => "--no-host-key-verify",
193
+ :description => "Disable host key verification",
194
+ :boolean => true
195
+
196
+ option :first_boot_attributes,
197
+ :short => "-j JSON_ATTRIBS",
198
+ :long => "--json-attributes",
199
+ :description => "A JSON string to be added to the first run of chef-client",
200
+ :proc => lambda { |o| JSON.parse(o) },
201
+ :default => {}
202
+
203
+ option :disable_customization,
204
+ :long => "--disable-customization",
205
+ :description => "Disable default customization",
206
+ :boolean => true,
207
+ :default => false
208
+
209
+ option :log_level,
210
+ :short => "-l LEVEL",
211
+ :long => "--log_level",
212
+ :description => "Set the log level (debug, info, warn, error, fatal) for chef-client",
213
+ :proc => lambda { |l| l.to_sym }
214
+
215
+ option :mark_as_template,
216
+ :long => "--mark_as_template",
217
+ :description => "Indicates whether to mark the new vm as a template",
218
+ :boolean => false
219
+
220
+ def run
221
+ $stdout.sync = true
222
+
223
+ vmname = @name_args[0]
224
+ if vmname.nil?
225
+ show_usage
226
+ fatal_exit("You must specify a virtual machine name")
227
+ end
228
+ config[:chef_node_name] = vmname unless config[:chef_node_name]
229
+ config[:vmname] = vmname
230
+
231
+ if get_config(:bootstrap) && get_config(:distro) && !@@chef_config_dir
232
+ fatal_exit("Can't find .chef for bootstrap files. chdir to a location with a .chef directory and try again")
233
+ end
234
+
235
+ vim = get_vim_connection
236
+
237
+ dc = get_datacenter
238
+
239
+ src_folder = find_folder(get_config(:folder)) || dc.vmFolder
240
+
241
+ abort "--template or knife[:source_vm] must be specified" unless config[:source_vm]
242
+
243
+ src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
244
+ abort "VM/Template not found"
245
+
246
+ if get_config(:linked_clone)
247
+ create_delta_disk(src_vm)
248
+ end
249
+
250
+ clone_spec = generate_clone_spec(src_vm.config)
251
+
252
+ cust_folder = config[:dest_folder] || get_config(:folder)
253
+
254
+ dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
255
+
256
+ task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
257
+ puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
258
+ task.wait_for_completion
259
+ puts "Finished creating virtual machine #{vmname}"
260
+
261
+ if customization_plugin && customization_plugin.respond_to?(:reconfig_vm)
262
+ target_vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or abort "VM could not be found in #{dest_folder}"
263
+ customization_plugin.reconfig_vm(target_vm)
264
+ end
265
+
266
+ if !get_config(:mark_as_template)
267
+ if get_config(:power) || get_config(:bootstrap)
268
+ vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
269
+ fatal_exit("VM #{vmname} not found")
270
+ vm.PowerOnVM_Task.wait_for_completion
271
+ puts "Powered on virtual machine #{vmname}"
272
+ end
273
+
274
+
275
+ if get_config(:bootstrap)
276
+ sleep 2 until vm.guest.ipAddress
277
+ config[:fqdn] = vm.guest.ipAddress unless config[:fqdn]
278
+ print "Waiting for sshd..."
279
+ print "." until tcp_test_ssh(config[:fqdn])
280
+ puts "done"
281
+
282
+ bootstrap_for_node.run
283
+ end
284
+ end
285
+ end
286
+
287
+ def create_delta_disk(src_vm)
288
+ disks = src_vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
289
+ disks.select { |disk| disk.backing.parent == nil }.each do |disk|
290
+ spec = {
291
+ :deviceChange => [
292
+ {
293
+ :operation => :remove,
294
+ :device => disk
295
+ },
296
+ {
297
+ :operation => :add,
298
+ :fileOperation => :create,
299
+ :device => disk.dup.tap { |new_disk|
300
+ new_disk.backing = new_disk.backing.dup
301
+ new_disk.backing.fileName = "[#{disk.backing.datastore.name}]"
302
+ new_disk.backing.parent = disk.backing
303
+ },
304
+ }
305
+ ]
306
+ }
307
+ src_vm.ReconfigVM_Task(:spec => spec).wait_for_completion
308
+ end
309
+ end
310
+
311
+ # Builds a CloneSpec
312
+ def generate_clone_spec (src_config)
313
+
314
+ rspec = nil
315
+ if get_config(:resource_pool)
316
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
317
+ else
318
+ dc = get_datacenter
319
+ hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
320
+ rp = hosts.first.resourcePool
321
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
322
+ end
323
+
324
+ if get_config(:linked_clone)
325
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:diskMoveType => :moveChildMostDiskBacking)
326
+ end
327
+
328
+ if get_config(:datastore) && get_config(:datastorecluster)
329
+ abort "Please select either datastore or datastorecluster"
330
+ end
331
+
332
+ if get_config(:datastore)
333
+ rspec.datastore = find_datastore(get_config(:datastore))
334
+ end
335
+
336
+ if get_config(:datastorecluster)
337
+ dsc = find_datastorecluster(get_config(:datastorecluster))
338
+
339
+ dsc.childEntity.each do |store|
340
+ if (rspec.datastore == nil or rspec.datastore.summary[:freeSpace] < store.summary[:freeSpace])
341
+ rspec.datastore = store
342
+ end
343
+ end
344
+ end
345
+
346
+ if get_config(:mark_as_template)
347
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
348
+ :powerOn => false,
349
+ :template => true)
350
+ else
351
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
352
+ :powerOn => false,
353
+ :template => false)
354
+ end
355
+ clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
356
+
357
+ if get_config(:annotation)
358
+ clone_spec.config.annotation = get_config(:annotation)
359
+ end
360
+
361
+ if get_config(:customization_cpucount)
362
+ clone_spec.config.numCPUs = get_config(:customization_cpucount)
363
+ end
364
+
365
+ if get_config(:customization_memory)
366
+ clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
367
+ end
368
+
369
+ if get_config(:customization_vlan)
370
+ network = find_network(get_config(:customization_vlan))
371
+ card = src_config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first or
372
+ abort "Can't find source network card to customize"
373
+ begin
374
+ switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid, :portgroupKey => network.key)
375
+ card.backing.port = switch_port
376
+ rescue
377
+ # not connected to a distibuted switch?
378
+ card.backing.deviceName = network.name
379
+ end
380
+ dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
381
+ clone_spec.config.deviceChange.push dev_spec
382
+ end
383
+
384
+ if get_config(:customization_spec)
385
+ csi = find_customization(get_config(:customization_spec)) or
386
+ fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
387
+
388
+ cust_spec = csi.spec
389
+ else
390
+ global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
391
+ cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
392
+ end
393
+
394
+ if get_config(:customization_dns_ips)
395
+ cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
396
+ end
397
+
398
+ if get_config(:customization_dns_suffixes)
399
+ cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
400
+ end
401
+
402
+ if config[:customization_ips]
403
+ if get_config(:customization_gw)
404
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i, get_config(:customization_gw)) }
405
+ else
406
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
407
+ end
408
+ end
409
+
410
+ unless get_config(:disable_customization)
411
+ use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
412
+
413
+ if use_ident
414
+ hostname = if config[:customization_hostname]
415
+ config[:customization_hostname]
416
+ else
417
+ config[:vmname]
418
+ end
419
+
420
+ if src_config.guestId.downcase.include?("linux")
421
+ ident = RbVmomi::VIM.CustomizationLinuxPrep
422
+
423
+ ident.hostName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
424
+
425
+ if get_config(:customization_domain)
426
+ ident.domain = get_config(:customization_domain)
427
+ else
428
+ ident.domain = ''
429
+ end
430
+
431
+ cust_spec.identity = ident
432
+ elsif src_config.guestId.downcase.include?("windows")
433
+ if cust_spec.identity.nil?
434
+ fatal_exit("Please provide Windows Guest Customization")
435
+ else
436
+ cust_spec.identity.userData.computerName = RbVmomi::VIM.CustomizationFixedName(:name => hostname)
437
+ end
438
+ end
439
+ end
440
+
441
+ clone_spec.customization = cust_spec
442
+
443
+ if customization_plugin && customization_plugin.respond_to?(:customize_clone_spec)
444
+ clone_spec = customization_plugin.customize_clone_spec(src_config, clone_spec)
445
+ end
446
+ end
447
+ clone_spec
448
+ end
449
+
450
+ # Loads the customization plugin if one was specified
451
+ # @return [KnifeVspherePlugin] the loaded and initialized plugin or nil
452
+ def customization_plugin
453
+ if @customization_plugin.nil?
454
+ if cplugin_path = get_config(:customization_plugin)
455
+ if File.exists? cplugin_path
456
+ require cplugin_path
457
+ else
458
+ abort "Customization plugin could not be found at #{cplugin_path}"
459
+ end
460
+
461
+ if Object.const_defined? 'OvhCloudPlugin'
462
+ @customization_plugin = Object.const_get('OvhCloudPlugin').new
463
+ if cplugin_data = get_config(:customization_plugin_data)
464
+ if @customization_plugin.respond_to?(:data=)
465
+ @customization_plugin.data = cplugin_data
466
+ else
467
+ abort "Customization plugin has no :data= accessor to receive the --cplugin-data argument. Define both or neither."
468
+ end
469
+ end
470
+ else
471
+ abort "OvhCloudPlugin class is not defined in #{cplugin_path}"
472
+ end
473
+ end
474
+ end
475
+
476
+ @customization_plugin
477
+ end
478
+
479
+ # Retrieves a CustomizationSpecItem that matches the supplied name
480
+ # @param vim [Connection] VI Connection to use
481
+ # @param name [String] name of customization
482
+ # @return [RbVmomi::VIM::CustomizationSpecItem]
483
+ def find_customization(name)
484
+ csm = config[:vim].serviceContent.customizationSpecManager
485
+ csm.GetCustomizationSpec(:name => name)
486
+ end
487
+
488
+ # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
489
+ # @param ip [String] Any static IP address to use, otherwise DHCP
490
+ # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
491
+ # @return [RbVmomi::VIM::CustomizationIPSettings]
492
+ def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
493
+
494
+ settings = RbVmomi::VIM.CustomizationIPSettings
495
+
496
+ if ip.nil?
497
+ settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
498
+ else
499
+ cidr_ip = NetAddr::CIDR.create(ip)
500
+ settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
501
+ settings.subnetMask = cidr_ip.netmask_ext
502
+
503
+ # TODO - want to confirm gw/ip are in same subnet?
504
+ # Only set gateway on first IP.
505
+ if config[:customization_ips].split(',').first == ip
506
+ if gw.nil?
507
+ settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
508
+ else
509
+ gw_cidr = NetAddr::CIDR.create(gw)
510
+ settings.gateway = [gw_cidr.ip]
511
+ end
512
+ end
513
+ end
514
+
515
+ adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
516
+ adapter_map.adapter = settings
517
+ adapter_map
518
+ end
519
+
520
+ def bootstrap_for_node()
521
+ Chef::Knife::Bootstrap.load_deps
522
+ bootstrap = Chef::Knife::Bootstrap.new
523
+ bootstrap.name_args = [config[:fqdn]]
524
+ bootstrap.config[:run_list] = get_config(:run_list).split(/[\s,]+/)
525
+ bootstrap.config[:secret_file] = get_config(:secret_file)
526
+ bootstrap.config[:hint] = get_config(:hint)
527
+ bootstrap.config[:ssh_user] = get_config(:ssh_user)
528
+ bootstrap.config[:ssh_password] = get_config(:ssh_password)
529
+ bootstrap.config[:ssh_port] = get_config(:ssh_port)
530
+ bootstrap.config[:identity_file] = get_config(:identity_file)
531
+ bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
532
+ bootstrap.config[:prerelease] = get_config(:prerelease)
533
+ bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
534
+ bootstrap.config[:distro] = get_config(:distro)
535
+ bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
536
+ bootstrap.config[:template_file] = get_config(:template_file)
537
+ bootstrap.config[:environment] = get_config(:environment)
538
+ bootstrap.config[:first_boot_attributes] = get_config(:first_boot_attributes)
539
+ bootstrap.config[:log_level] = get_config(:log_level)
540
+ # may be needed for vpc_mode
541
+ bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
542
+ bootstrap
543
+ end
544
+
545
+ def tcp_test_ssh(hostname)
546
+ tcp_socket = TCPSocket.new(hostname, get_config(:ssh_port))
547
+ readable = IO.select([tcp_socket], nil, nil, 5)
548
+ if readable
549
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
550
+ true
551
+ else
552
+ false
553
+ end
554
+ rescue Errno::ETIMEDOUT
555
+ false
556
+ rescue Errno::EPERM
557
+ false
558
+ rescue Errno::ECONNREFUSED
559
+ sleep 2
560
+ false
561
+ rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
562
+ sleep 2
563
+ false
564
+ ensure
565
+ tcp_socket && tcp_socket.close
566
+ end
567
+ end