knife-xapi 0.1.5 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -28,6 +28,7 @@ end
28
28
 
29
29
  require 'chef/knife'
30
30
  require 'units/standard'
31
+ require 'xenapi'
31
32
 
32
33
  class Chef::Knife
33
34
  module XapiBase
@@ -84,6 +85,11 @@ class Chef::Knife
84
85
  end
85
86
  end
86
87
 
88
+ def locate_config_value(key)
89
+ key = key.to_sym
90
+ Chef::Config[:knife][key] || config[key]
91
+ end
92
+
87
93
  # get template by name_label
88
94
  def get_template(template)
89
95
  xapi.VM.get_by_name_label(template).first
@@ -25,14 +25,14 @@ class Chef
25
25
  class Knife
26
26
  class XapiGuestCreate < Knife
27
27
  include Chef::Knife::XapiBase
28
+ require 'timeout'
28
29
 
29
- banner "knife xapi guest create NAME [NETWORKS] (options)"
30
+ deps do
31
+ require 'chef/knife/bootstrap'
32
+ Chef::Knife::Bootstrap.load_deps
33
+ end
30
34
 
31
- option :bootstrap,
32
- :short => "-B BOOTSTRAP",
33
- :long => "--xapi-bootstrap BOOTSTRAP",
34
- :description => "bootstrap template to push to the server",
35
- :proc => Proc.new { |bootstrap| Chef::Config[:knife][:xapi_bootstrap] = bootstrap }
35
+ banner "knife xapi guest create NAME [NETWORKS] (options)"
36
36
 
37
37
  option :vm_template,
38
38
  :short => "-T Template Name Label",
@@ -40,6 +40,12 @@ class Chef
40
40
  :description => "xapi template name to create from. accepts an string or regex",
41
41
  :proc => Proc.new { |template| Chef::Config[:knife][:xapi_vm_template] = template }
42
42
 
43
+ option :domain,
44
+ :short => "-f Name",
45
+ :long => "--domain Name",
46
+ :description => "the domain name for the guest",
47
+ :proc => Proc.new { |domain| Chef::Config[:knife][:xapi_domainname] = domain }
48
+
43
49
  option :install_repo,
44
50
  :short => "-R If you're using a builtin template you will need to specify a repo url",
45
51
  :long => "--xapi-install-repo",
@@ -76,13 +82,131 @@ class Chef
76
82
  :description => "Ammount of memory the VM should have specify with m g etc 512m, 2g if no unit spcified it assumes gigabytes",
77
83
  :proc => Proc.new {|mem| Chef::Config[:knife][:xapi_mem] = mem }
78
84
 
85
+ option :chef_node_name,
86
+ :short => "-N NAME",
87
+ :long => "--node-name NAME",
88
+ :description => "The Chef node name for your new node"
89
+
90
+ option :ssh_key_name,
91
+ :short => "-S KEY",
92
+ :long => "--ssh-key KEY",
93
+ :description => "The SSH key id",
94
+ :proc => Proc.new { |key| Chef::Config[:knife][:xapi_ssh_key_id] = key }
95
+
96
+ option :ssh_user,
97
+ :short => "-x USERNAME",
98
+ :long => "--ssh-user USERNAME",
99
+ :description => "The ssh username",
100
+ :default => "root"
101
+
102
+ option :ssh_password,
103
+ :short => "-P PASSWORD",
104
+ :long => "--ssh-password PASSWORD",
105
+ :description => "The ssh password"
106
+
107
+ option :ssh_port,
108
+ :short => "-p PORT",
109
+ :long => "--ssh-port PORT",
110
+ :description => "The ssh port",
111
+ :default => "22",
112
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
113
+
114
+ option :bootstrap_version,
115
+ :long => "--bootstrap-version VERSION",
116
+ :description => "The version of Chef to install",
117
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
118
+
119
+ option :distro,
120
+ :short => "-d DISTRO",
121
+ :long => "--distro DISTRO",
122
+ :description => "Bootstrap a distro using a template",
123
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
124
+ :default => "ubuntu10.04-gems"
125
+
126
+ option :template_file,
127
+ :short => "-F FILEPATH",
128
+ :long => "--template-file TEMPLATE",
129
+ :description => "Full path to location of template to use",
130
+ :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
131
+ :default => false
132
+
133
+ option :run_list,
134
+ :short => "-r RUN_LIST",
135
+ :long => "--run-list RUN_LIST",
136
+ :description => "Comma separated list of roles/recipes to apply",
137
+ :proc => lambda { |o| o.split(/[\s,]+/) },
138
+ :default => []
139
+
140
+ def tcp_test_ssh(hostname)
141
+ tcp_socket = TCPSocket.new(hostname, config[:ssh_port])
142
+ readable = IO.select([tcp_socket], nil, nil, 5)
143
+ if readable
144
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
145
+ yield
146
+ true
147
+ else
148
+ false
149
+ end
150
+ rescue SocketError
151
+ sleep 2
152
+ false
153
+ rescue Errno::ETIMEDOUT
154
+ false
155
+ rescue Errno::EPERM
156
+ false
157
+ rescue Errno::ECONNREFUSED
158
+ sleep 2
159
+ false
160
+ # This happens on EC2 quite often
161
+ rescue Errno::EHOSTUNREACH
162
+ sleep 2
163
+ false
164
+ ensure
165
+ tcp_socket && tcp_socket.close
166
+ end
167
+
168
+ def get_guest_ip(vm_ref)
169
+ begin
170
+ timeout(480) do
171
+ ui.msg "Waiting for guest ip address"
172
+ guest_ip = ""
173
+ while guest_ip.empty?
174
+ print(".")
175
+ sleep @initial_sleep_delay ||= 10
176
+ vgm = xapi.VM.get_guest_metrics(vm_ref)
177
+ next if "OpaqueRef:NULL" == vgm
178
+ networks = xapi.VM_guest_metrics.get_networks(vgm)
179
+ if networks.has_key?("0/ip")
180
+ guest_ip = networks["0/ip"]
181
+ end
182
+ end
183
+ puts "\n"
184
+ return guest_ip
185
+ end
186
+ rescue Timeout::Error
187
+ ui.msg "Timeout waiting for XAPI to report IP address "
188
+ end
189
+ end
190
+
79
191
  # destroy/remove VM refs and exit
80
192
  def cleanup(vm_ref)
81
- ui.warn "Clenaing up work and exiting"
82
- xapi.VM.destroy(vm_ref)
193
+ ui.warn "Cleaning up work and exiting"
194
+ # shutdown and dest
195
+ unless xapi.VM.get_power_state(vm_ref) == "Halted"
196
+ print "Shutting down Guest"
197
+ task = xapi.Async.VM.hard_shutdown(vm_ref)
198
+ wait_on_task(task)
199
+ print " #{h.color "Done", :green} \n"
200
+ end
201
+
202
+ print "Destroying Guest"
203
+ task = xapi.Async.VM.destroy(vm_ref)
204
+ wait_on_task(task)
205
+ print " #{h.color "Done", :green} \n"
83
206
  exit 1
84
207
  end
85
208
 
209
+
86
210
  def run
87
211
  server_name = @name_args[0]
88
212
  $stdout.sync = true
@@ -115,8 +239,13 @@ class Chef
115
239
  # setup the Boot args
116
240
  #
117
241
  boot_args = Chef::Config[:knife][:xapi_kernel_params] || "graphical utf8"
242
+ domainname = Chef::Config[:knife][:xapi_domainname] || ""
243
+
118
244
  # if no hostname param set hostname to given vm name
119
245
  boot_args << " hostname=#{server_name}" unless boot_args.match(/hostname=.+\s?/)
246
+ # if domainname is supplied we put that in there as well
247
+ boot_args << " domainname=#{domainname}" unless boot_args.match(/domainname=.+\s?/)
248
+
120
249
  ui.msg "Setting Boot Args: #{h.color boot_args, :cyan}"
121
250
  xapi.VM.set_PV_args( vm_ref, boot_args )
122
251
 
@@ -156,12 +285,13 @@ class Chef
156
285
  ui.msg "Provisioning new Guest: #{h.color(vm_ref, :bold, :cyan )}"
157
286
  provisioned = xapi.VM.provision(vm_ref)
158
287
 
159
- ui.msg "Starting new Guest: #{h.color( provisioned, :cyan)} "
288
+ ui.msg "Starting new Guest #{h.color( provisioned, :cyan)} "
160
289
 
161
290
  task = xapi.Async.VM.start(vm_ref, false, true)
162
291
  wait_on_task(task)
163
292
  ui.msg( "#{ h.color "Done!", :green}" )
164
293
 
294
+ exit 0 unless locate_config_value(:run_list)
165
295
  rescue Exception => e
166
296
  ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}"
167
297
  # have to use join here to pass a string to highline
@@ -170,7 +300,52 @@ class Chef
170
300
 
171
301
  cleanup(vm_ref)
172
302
  end
173
- # TODO: bootstrap
303
+
304
+ guest_addr = get_guest_ip(vm_ref)
305
+ if guest_addr.nil? or guest_addr.empty?
306
+ ui.msg("ip seems wrong using host+domain name instead")
307
+ guest_addr = "#{host_name}.#{domainname}"
308
+ end
309
+ ui.msg "Trying to connect to guest @ #{guest_addr} "
310
+
311
+ begin
312
+ timeout(480) do
313
+ print(".") until tcp_test_ssh(guest_addr) {
314
+ sleep @initial_sleep_delay ||= 10
315
+ puts("done")
316
+ }
317
+ end
318
+ rescue Timeout::Error
319
+ ui.msg "Timeout trying to login cleaning up"
320
+ cleanup(vm_ref)
321
+ end
322
+
323
+
324
+ begin
325
+ bootstrap = Chef::Knife::Bootstrap.new
326
+ bootstrap.name_args = [ guest_addr ]
327
+ bootstrap.config[:run_list] = config[:run_list]
328
+ bootstrap.config[:ssh_user] = config[:ssh_user]
329
+ bootstrap.config[:ssh_port] = config[:ssh_port]
330
+ bootstrap.config[:ssh_password] = config[:ssh_password]
331
+ bootstrap.config[:identity_file] = config[:identity_file]
332
+ bootstrap.config[:chef_node_name] = config[:chef_node_name] || server_name
333
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
334
+ bootstrap.config[:distro] = locate_config_value(:distro)
335
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
336
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
337
+ bootstrap.config[:environment] = config[:environment]
338
+ bootstrap.config[:host_key_verify] = false
339
+ bootstrap.config[:run_list] = config[:run_list]
340
+
341
+ bootstrap.run
342
+ rescue Exception => e
343
+ ui.msg "#{h.color 'ERROR:'} #{h.color( e.message, :red )}"
344
+ puts "Nested backtrace:"
345
+ ui.msg "#{h.color( e.backtrace.join("\n"), :yellow)}"
346
+ cleanup(vm_ref)
347
+ end
348
+
174
349
  end
175
350
 
176
351
  end
@@ -1,3 +1,3 @@
1
1
  module KnifeXenserver
2
- VERSION = "0.1.5"
2
+ VERSION = "0.2.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-xapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.2.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-23 00:00:00.000000000 Z
12
+ date: 2012-05-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: chef
@@ -110,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
110
  version: '0'
111
111
  requirements: []
112
112
  rubyforge_project:
113
- rubygems_version: 1.8.19
113
+ rubygems_version: 1.8.24
114
114
  signing_key:
115
115
  specification_version: 3
116
116
  summary: Xen API Support for Chef's Knife Command