knife-esx 0.1.5 → 0.2

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.
@@ -1,4 +1,63 @@
1
- # Sat Feb 25 12:16:01 CET 2012 - 0.1.5
1
+ # 0.2 - 2012/02/28
2
+
3
+ * Added --batch and --async options
4
+
5
+ Inspired by spiceweasel from Matt Ray (https://github.com/mattray/spiceweasel), I've added a batch mode where a YAML file describes the VMs you want to bootstrap.
6
+
7
+ knife esx vm create --batch batch.yml
8
+
9
+ Sample batch.yml file:
10
+
11
+ ---
12
+ :test1:
13
+ 'extra-args': --no-host-key-verify
14
+ 'vm-memory': 128
15
+ 'esx-host': esx-server-1
16
+ 'esx-password': secret
17
+ 'ssh-user': ubuntu
18
+ 'ssh-password': ubuntu
19
+ 'vm-disk': /home/rubiojr/mnt/mirror/virtual_appliances/ubuntu1110-x64-vmware-tools.vmdk
20
+ 'datastore': datastore2
21
+ :test2:
22
+ 'extra-args': --no-host-key-verify
23
+ 'vm-memory': 128
24
+ 'esx-host': esx-server-1
25
+ 'esx-password': secret
26
+ 'ssh-user': ubuntu
27
+ 'ssh-password': ubuntu
28
+ 'vm-disk': /home/rubiojr/mnt/mirror/virtual_appliances/ubuntu1110-x64-vmware-tools.vmdk
29
+ 'datastore': datastore2
30
+ :test3:
31
+ 'extra-args': --no-host-key-verify
32
+ 'vm-memory': 256
33
+ 'esx-host': esx-server-1
34
+ 'esx-password': secret
35
+ 'ssh-user': ubuntu
36
+ 'ssh-password': ubuntu
37
+ 'vm-disk': /home/rubiojr/mnt/mirror/virtual_appliances/ubuntu1110-x64-vmware-tools.vmdk
38
+ 'datastore': datastore2
39
+
40
+ This will try to create three VMs (testvm1, testvm2 and testvm3) sequentially. VM definitions inside the batch file accept all the parameters that can be used with knife-esx.
41
+
42
+ If you want to bootstrap the VMs asynchronously, use the --async flag.
43
+
44
+ knife esx vm create --batch batch.yml --async
45
+
46
+ Standard output and error log is redirected to /tmp/knife_esx_vm_create_VMNAME.log, so if we use the deploy script from above, three log files will be created:
47
+
48
+ /tmp/knife_esx_vm_create_test1.log
49
+ /tmp/knife_esx_vm_create_test2.log
50
+ /tmp/knife_esx_vm_create_test3.log
51
+
52
+ * Added --skip-bootstrap flag. If the flag is used the VM will be created but
53
+ the bootstrap template/script won't be executed (it also means that Chef won't be installed).
54
+
55
+ * Fixed bug preventing knife-esx to create a VM when the hypervisor has an empty root password.
56
+
57
+ KNOWN ISSUES
58
+ * To use --batch without --skip-bootstrap, the ssh user (--ssh-user) needs to be able to sudo without asking for a password (i.e. adding something like 'ubuntu ALL=(ALL) NOPASSWD: ALL' to /etc/sudoers in the appliance template) otherwise the bootstraping process won't work if more than one VM is being deployed.
59
+
60
+ # 0.1.5 - 2012/02/25
2
61
 
3
62
  * Patch from @pperezrubio adding multiple networks support
4
63
 
@@ -8,7 +67,27 @@
8
67
  --ssh-password ubuntu \
9
68
  --vm-network "VLAN-Integration,VLAN-Test"
10
69
 
11
-
12
70
  This will create a VM with two NICs, attaching them to the VLAN-Integration and VLAN-Test networks respectively.
13
71
 
72
+ Fixed MAC addresses can also be assigned to each NIC using the --mac-address parameter:
73
+
74
+ knife esx vm create --vm-disk ubuntu-oneiric.vmdk \
75
+ --vm-name testvm --datastore datastore1 \
76
+ --esx-host 192.168.88.1 --ssh-user ubuntu \
77
+ --ssh-password ubuntu \
78
+ --vm-network "VLAN-Integration,VLAN-Test" \
79
+ --mac-address "00:01:02:03:04:05,00:01:02:03:04:06"
80
+
81
+ MAC address 00:01:02:03:04:05 will be assigned to VLAN-Integration NIC and 00:01:02:03:04:06 to the VLAN-Test NIC.
82
+
83
+ If a MAC address is omitted it will be dynamically generated:
84
+
85
+ knife esx vm create --vm-disk ubuntu-oneiric.vmdk \
86
+ --vm-name testvm --datastore datastore1 \
87
+ --esx-host 192.168.88.1 --ssh-user ubuntu \
88
+ --ssh-password ubuntu \
89
+ --vm-network "VLAN-Integration,VLAN-Test" \
90
+ --mac-address ",00:01:02:03:04:06"
91
+
92
+ Note that I did not specify the first MAC address, so VLAN-Integration NIC will get a random MAC.
14
93
 
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.add_dependency "esx", ">= 0.3.2"
20
20
  s.add_dependency "terminal-table"
21
21
  s.add_dependency "chef", ">= 0.10"
22
+ s.add_dependency "celluloid", ">= 0.9"
23
+ s.add_dependency "open4"
22
24
  s.require_paths = ["lib"]
23
25
 
24
26
  end
@@ -61,7 +61,7 @@ class Chef
61
61
  ui.info "#{ui.color("Connecting to ESX host #{config[:esx_host]}... ", :magenta)}"
62
62
  @connection = ESX::Host.connect(Chef::Config[:knife][:esx_host],
63
63
  Chef::Config[:knife][:esx_username],
64
- Chef::Config[:knife][:esx_password])
64
+ Chef::Config[:knife][:esx_password] || '')
65
65
  else
66
66
  @connection
67
67
  end
@@ -17,9 +17,121 @@
17
17
  #
18
18
 
19
19
  require 'chef/knife/esx_base'
20
+ require 'open4'
21
+ require 'celluloid'
22
+ require 'singleton'
23
+
24
+ module KnifeESX
25
+ class DeployScript
26
+
27
+ attr_reader :job_count
28
+
29
+ # Sample job
30
+ #---
31
+ #:test1:
32
+ # 'vm-memory':
33
+ # 'extra-args':
34
+ # 'esx-host':
35
+ # 'template-file':
36
+ # 'vm-disk':
37
+ # 'ssh-user':
38
+ # 'ssh-password':
39
+ # 'run-list':
40
+ # 'network-interface':
41
+ def initialize(batch_file)
42
+ @batch_file = batch_file
43
+ @jobs = []
44
+ @job_count = 0
45
+ (YAML.load_file batch_file).each do |i|
46
+ @jobs << DeployJob.new(i)
47
+ @job_count += 1
48
+ end
49
+ end
50
+
51
+ def each_job(&block)
52
+ @jobs.each do |j|
53
+ yield j
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ class CLogger
60
+ include Celluloid
61
+ include Singleton
62
+
63
+ def info(msg)
64
+ puts "INFO: #{msg}"
65
+ end
66
+
67
+ def error(msg)
68
+ $stderr.puts "ERROR: #{msg}"
69
+ end
70
+ end
71
+
72
+ class DeployJob
73
+
74
+ include Celluloid
75
+
76
+ attr_reader :name
77
+
78
+ def initialize(options)
79
+ @name, @options = options
80
+ validate
81
+ end
82
+
83
+ def validate
84
+ if @name.nil? or @name.empty?
85
+ raise Exception.new("Invalid job name")
86
+ end
87
+ if not @options['vm-disk'] or !File.exist?(@options['vm-disk'])
88
+ raise Exception.new("Invalid VM disk for job #{@name}.")
89
+ end
90
+ end
91
+
92
+ # returns [status, stdout, stderr]
93
+ def run
94
+ args = ""
95
+ extra_args = ""
96
+ @options.each do |k, v|
97
+ if k == 'extra-args'
98
+ extra_args << v
99
+ else
100
+ args << "--#{k} #{v} " unless k == 'extra-args'
101
+ end
102
+ end
103
+
104
+ @out = ""
105
+ @err = ""
106
+ optstring = []
107
+ @options.each do |k,v|
108
+ optstring << " - #{k}:".ljust(25) + "#{v}\n" unless k =~ /password/
109
+ end
110
+ log_file = "/tmp/knife_esx_vm_create_#{@name.to_s.strip.chomp.gsub(/\s/,'_')}.log"
111
+ CLogger.instance.info! "Bootstrapping VM #{@name} \n#{optstring.join}"
112
+ CLogger.instance.info! "VM #{@name} bootstrap log: #{log_file}"
113
+ @status = Open4.popen4("knife esx vm create --vm-name #{@name} #{args} #{extra_args} > #{log_file} 2>&1") do |pid, stdin, stdout, stderr|
114
+ @out << stdout.read.strip
115
+ @err << stderr.read.strip
116
+ end
117
+ if @status == 0
118
+ CLogger.instance.info! "[#{@name}] deployment finished OK"
119
+ else
120
+ CLogger.instance.error! "[#{@name}] deployment FAILED"
121
+ @err.each_line do |l|
122
+ CLogger.instance.error! "[#{@name}] #{l.chomp}"
123
+ end
124
+ end
125
+ return @status, @out, @err
126
+ end
127
+
128
+ end
129
+
130
+ end
20
131
 
21
132
  class Chef
22
133
  class Knife
134
+
23
135
  class EsxVmCreate < Knife
24
136
 
25
137
  include Knife::ESXBase
@@ -125,7 +237,26 @@ class Chef
125
237
  :long => "--mac-address",
126
238
  :description => "Mac address list",
127
239
  :default => nil
128
-
240
+
241
+ option :skip_bootstrap,
242
+ :long => "--skip-bootstrap",
243
+ :description => "Skip bootstrap process (Deploy only mode)",
244
+ :boolean => true,
245
+ :default => false,
246
+ :proc => Proc.new { true }
247
+
248
+ option :async,
249
+ :long => "--async",
250
+ :description => "Deploy the VMs asynchronously (Ignored unless combined with --batch)",
251
+ :boolean => true,
252
+ :default => false,
253
+ :proc => Proc.new { true }
254
+
255
+ option :batch,
256
+ :long => "--batch script.yml",
257
+ :description => "Use a batch file to deploy multiple VMs",
258
+ :default => nil
259
+
129
260
  def tcp_test_ssh(hostname)
130
261
  tcp_socket = TCPSocket.new(hostname, 22)
131
262
  readable = IO.select([tcp_socket], nil, nil, 5)
@@ -147,6 +278,38 @@ class Chef
147
278
 
148
279
  def run
149
280
  $stdout.sync = true
281
+
282
+ if config[:batch]
283
+ CLogger.instance.info "Running in batch mode. Extra arguments will be ignored."
284
+ if not config[:async]
285
+ counter = 0
286
+ script = KnifeESX::DeployScript.new(config[:batch])
287
+ script.each_job do |job|
288
+ counter += 1
289
+ status, stdout, stderr = job.run
290
+ if status == 0
291
+ CLogger.instance.info 'Ok'
292
+ else
293
+ CLogger.instance.error 'Failed'
294
+ stderr.each_line do |l|
295
+ ui.error l
296
+ end
297
+ end
298
+ end
299
+ else
300
+ CLogger.instance.info! "Asynchronous boostrapping selected"
301
+ CLogger.instance.info! "Now do something productive while I finish my job ;)"
302
+ script = KnifeESX::DeployScript.new(config[:batch])
303
+ futures = []
304
+ script.each_job do |job|
305
+ futures << job.future(:run)
306
+ end
307
+ futures.each do |f|
308
+ f.value
309
+ end
310
+ end
311
+ return
312
+ end
150
313
 
151
314
  unless config[:vm_disk]
152
315
  ui.error("You have not provided a valid VMDK file. (--vm-disk)")
@@ -172,7 +335,7 @@ class Chef
172
335
  destination_path = "/vmfs/volumes/#{datastore}/#{vm_name}"
173
336
 
174
337
  connection.remote_command "mkdir #{destination_path}"
175
- puts "#{ui.color("Creating VM... ", :magenta)}"
338
+ puts "#{ui.color("Creating VM #{vm_name}... ", :magenta)}"
176
339
  puts "#{ui.color("Importing VM disk... ", :magenta)}"
177
340
 
178
341
  connection.import_disk vm_disk, destination_path + "/#{vm_name}.vmdk"
@@ -186,6 +349,8 @@ class Chef
186
349
 
187
350
  puts "#{ui.color("VM Name", :cyan)}: #{vm.name}"
188
351
  puts "#{ui.color("VM Memory", :cyan)}: #{(vm.memory_size.to_f/1024/1024).round} MB"
352
+
353
+ return if config[:skip_bootstrap]
189
354
 
190
355
  # wait for it to be ready to do stuff
191
356
  print "\n#{ui.color("Waiting server... ", :magenta)}"
@@ -220,6 +385,7 @@ class Chef
220
385
  def bootstrap_for_node(vm)
221
386
  bootstrap = Chef::Knife::Bootstrap.new
222
387
  bootstrap.name_args = [vm.ip_address]
388
+ bootstrap.config[:async] = config[:async]
223
389
  bootstrap.config[:run_list] = config[:run_list]
224
390
  bootstrap.config[:ssh_user] = config[:ssh_user]
225
391
  bootstrap.config[:identity_file] = config[:identity_file]
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module ESX
3
- VERSION = "0.1.5"
3
+ VERSION = "0.2"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-esx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: '0.2'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-02-25 00:00:00.000000000 Z
12
+ date: 2012-02-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: esx
16
- requirement: &14262920 !ruby/object:Gem::Requirement
16
+ requirement: &12088940 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 0.3.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *14262920
24
+ version_requirements: *12088940
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: terminal-table
27
- requirement: &14262220 !ruby/object:Gem::Requirement
27
+ requirement: &12088520 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *14262220
35
+ version_requirements: *12088520
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: chef
38
- requirement: &14261380 !ruby/object:Gem::Requirement
38
+ requirement: &12087940 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,29 @@ dependencies:
43
43
  version: '0.10'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *14261380
46
+ version_requirements: *12087940
47
+ - !ruby/object:Gem::Dependency
48
+ name: celluloid
49
+ requirement: &12087280 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *12087280
58
+ - !ruby/object:Gem::Dependency
59
+ name: open4
60
+ requirement: &12086840 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *12086840
47
69
  description: ESX Support for Chef's Knife Command
48
70
  email:
49
71
  - rubiojr@frameos.org