knife-esx 0.1.5 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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