knife-cloudstack 0.0.12 → 0.0.13

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES.rdoc CHANGED
@@ -1,4 +1,9 @@
1
1
  = Changes
2
+ == 2012-11-29 (0.0.13)
3
+ * Windows support
4
+ https://github.com/CloudStack-extras/knife-cloudstack/issues/31
5
+ * Support for Projects
6
+ * HTTPS support
2
7
 
3
8
  == 2012-05-17 (0.0.12)
4
9
  * Adding option to disable public IP allocation on server create
data/README.rdoc CHANGED
@@ -39,6 +39,8 @@ Additionally the following options may be set in your <tt>knife.rb</tt>:
39
39
  * knife[:distro]
40
40
  * knife[:template_file]
41
41
 
42
+ == Public Clouds (Tata InstaCompute, Ninefold etc):
43
+ To get this plugin to work in public clouds, it is essential that the virtual network (and router) be allocated to the account. Cloudstack clouds automatically creates a virtual network when the first VM is requested to be created. Hence, it is essential to create the first VM (of a newly created account) manually(which can be terminated immediately if not required) to ensure the virtual network is created.
42
44
 
43
45
  == SUBCOMMANDS:
44
46
 
@@ -195,7 +197,7 @@ Reboots the specified virtual machines(s).
195
197
  == LICENSE:
196
198
 
197
199
  Author:: Ryan Holmes <rholmes@edmunds.com>
198
- Author:: KC Braunschweig <kbraunschweig@edmunds.com>
200
+ Author:: KC Braunschweig <kcbraunschweig@gmail.com>
199
201
  Author:: John E. Vincent <lusis.org+github.com@gmail.com>
200
202
 
201
203
  Copyright:: Copyright (c) 2011 Edmunds, Inc.
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Ryan Holmes (<rholmes@edmunds.com>)
3
- # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
3
+ # Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
4
4
  # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
@@ -18,18 +18,31 @@
18
18
 
19
19
  require 'chef/knife'
20
20
  require 'json'
21
+ require 'chef/knife/winrm_base'
22
+ require 'winrm'
23
+ require 'httpclient'
24
+ require 'em-winrm'
25
+
21
26
 
22
27
  module KnifeCloudstack
23
28
  class CsServerCreate < Chef::Knife
24
29
 
30
+ include Chef::Knife::WinrmBase
31
+
25
32
  # Seconds to delay between detecting ssh and initiating the bootstrap
26
- BOOTSTRAP_DELAY = 3
33
+ BOOTSTRAP_DELAY = 20
34
+ #The machine will reboot once so we need to handle that
35
+ WINRM_BOOTSTRAP_DELAY = 200
27
36
 
28
37
  # Seconds to wait between ssh pings
29
- SSH_POLL_INTERVAL = 2
38
+ SSH_POLL_INTERVAL = 10
30
39
 
31
40
  deps do
32
41
  require 'chef/knife/bootstrap'
42
+ require 'chef/knife/bootstrap_windows_winrm'
43
+ require 'chef/knife/bootstrap_windows_ssh'
44
+ require 'chef/knife/core/windows_bootstrap_context'
45
+ require 'chef/knife/winrm'
33
46
  Chef::Knife::Bootstrap.load_deps
34
47
  require 'socket'
35
48
  require 'net/ssh/multi'
@@ -86,6 +99,12 @@ module KnifeCloudstack
86
99
  :long => "--ssh-password PASSWORD",
87
100
  :description => "The ssh password"
88
101
 
102
+
103
+ option :ssh_port,
104
+ :long => "--ssh-port PORT",
105
+ :description => "The ssh port",
106
+ :default => "22"
107
+
89
108
  option :identity_file,
90
109
  :short => "-i IDENTITY_FILE",
91
110
  :long => "--identity-file IDENTITY_FILE",
@@ -157,10 +176,47 @@ module KnifeCloudstack
157
176
  :proc => lambda { |o| o.split(/[\s,]+/) },
158
177
  :default => []
159
178
 
179
+ option :cloudstack_project,
180
+ :short => "-P PROJECT_NAME",
181
+ :long => '--cloudstack-project PROJECT_NAME',
182
+ :description => "Cloudstack Project in which to create server",
183
+ :proc => Proc.new { |v| Chef::Config[:knife][:cloudstack_project] = v },
184
+ :default => nil
185
+
186
+ option :static_nat,
187
+ :long => '--static-nat',
188
+ :description => 'Support Static NAT',
189
+ :boolean => true,
190
+ :default => false
191
+
192
+ option :use_http_ssl,
193
+ :long => '--[no-]use-http-ssl',
194
+ :description => 'Support HTTPS',
195
+ :boolean => true,
196
+ :default => true
197
+
198
+ option :bootstrap_protocol,
199
+ :long => "--bootstrap-protocol protocol",
200
+ :description => "Protocol to bootstrap windows servers. options: winrm/ssh",
201
+ :default => "ssh"
202
+
203
+ option :fqdn,
204
+ :long => '--fqdn',
205
+ :description => "FQDN which Kerberos Understands (only for Windows Servers)"
206
+
207
+
208
+ def connection
209
+ @connection ||= CloudstackClient::Connection.new(
210
+ locate_config_value(:cloudstack_url),
211
+ locate_config_value(:cloudstack_api_key),
212
+ locate_config_value(:cloudstack_secret_key),
213
+ locate_config_value(:cloudstack_project),
214
+ locate_config_value(:use_http_ssl)
215
+ )
216
+ end
160
217
 
161
218
  def run
162
-
163
- # validate hostname and options
219
+ Chef::Log.debug("Validate hostname and options")
164
220
  hostname = @name_args.first
165
221
  unless /^[a-zA-Z0-9][a-zA-Z0-9-]*$/.match hostname then
166
222
  ui.error "Invalid hostname. Please specify a short hostname, not an fqdn (e.g. 'myhost' instead of 'myhost.domain.com')."
@@ -168,15 +224,26 @@ module KnifeCloudstack
168
224
  end
169
225
  validate_options
170
226
 
227
+ if @windows_image and locate_config_value(:kerberos_realm)
228
+ Chef::Log.debug("Load additional gems for AD/Kerberos Authentication")
229
+ if @windows_platform
230
+ require 'em-winrs'
231
+ else
232
+ require 'gssapi'
233
+ end
234
+ end
235
+
171
236
  $stdout.sync = true
172
237
 
173
- connection = CloudstackClient::Connection.new(
174
- locate_config_value(:cloudstack_url),
175
- locate_config_value(:cloudstack_api_key),
176
- locate_config_value(:cloudstack_secret_key)
177
- )
238
+ Chef::Log.info("Creating instance with
239
+ service : #{locate_config_value(:cloudstack_service)}
240
+ template : #{locate_config_value(:cloudstack_template)}
241
+ zone : #{locate_config_value(:cloudstack_zone)}
242
+ project: #{locate_config_value(:cloudstack_project)}
243
+ network: #{locate_config_value(:cloudstack_networks)}")
244
+
245
+ print "\n#{ui.color("Waiting for Server to be created", :magenta)}"
178
246
 
179
- print "#{ui.color("Waiting for server", :magenta)}"
180
247
  server = connection.create_server(
181
248
  hostname,
182
249
  locate_config_value(:cloudstack_service),
@@ -188,82 +255,160 @@ module KnifeCloudstack
188
255
  public_ip = find_or_create_public_ip(server, connection)
189
256
 
190
257
  puts "\n\n"
191
- puts "#{ui.color("Name", :cyan)}: #{server['name']}"
192
- puts "#{ui.color("Public IP", :cyan)}: #{public_ip}"
258
+ puts "#{ui.color('Name', :cyan)}: #{server['name']}"
259
+ puts "#{ui.color('Public IP', :cyan)}: #{public_ip}"
193
260
 
194
261
  return if config[:no_bootstrap]
195
262
 
196
- print "\n#{ui.color("Waiting for sshd", :magenta)}"
263
+ if @bootstrap_protocol == 'ssh'
264
+ print "\n#{ui.color("Waiting for sshd", :magenta)}"
197
265
 
198
- print(".") until is_ssh_open?(public_ip) {
199
- sleep BOOTSTRAP_DELAY
200
- puts "\n"
201
- }
202
-
203
- bootstrap_for_node(public_ip).run
266
+ print(".") until is_ssh_open?(public_ip) {
267
+ sleep BOOTSTRAP_DELAY
268
+ puts "\n"
269
+ }
270
+ else
271
+ print "\n#{ui.color("Waiting for winrm to be active", :magenta)}"
272
+ print(".") until tcp_test_winrm(public_ip,locate_config_value(:winrm_port)) {
273
+ sleep WINRM_BOOTSTRAP_DELAY
274
+ puts("\n")
275
+ }
276
+ end
204
277
 
205
278
  puts "\n"
206
279
  puts "#{ui.color("Name", :cyan)}: #{server['name']}"
207
280
  puts "#{ui.color("Public IP", :cyan)}: #{public_ip}"
208
281
  puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
209
282
  puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
283
+ bootstrap(server, public_ip).run
284
+ end
210
285
 
286
+ def fetch_server_fqdn(ip_addr)
287
+ require 'resolv'
288
+ Resolv.getname(ip_addr)
211
289
  end
212
290
 
213
- def validate_options
291
+ def is_image_windows?
292
+ template = connection.get_template(locate_config_value(:cloudstack_template))
293
+ if !template
294
+ ui.error("Template: #{template} does not exist")
295
+ exit 1
296
+ end
297
+ return template['ostypename'].scan('Windows').length > 0
298
+ end
214
299
 
300
+ def validate_options
215
301
  unless locate_config_value :cloudstack_template
216
302
  ui.error "Cloudstack template not specified"
217
303
  exit 1
218
304
  end
305
+ @windows_image = is_image_windows?
306
+ @windows_platform = is_platform_windows?
219
307
 
220
308
  unless locate_config_value :cloudstack_service
221
309
  ui.error "Cloudstack service offering not specified"
222
310
  exit 1
223
311
  end
224
-
225
- identity_file = locate_config_value :identity_file
226
- ssh_user = locate_config_value :ssh_user
227
- ssh_password = locate_config_value :ssh_password
228
- unless identity_file || (ssh_user && ssh_password)
229
- ui.error("You must specify either an ssh identity file or an ssh user and password")
230
- exit 1
312
+ if locate_config_value(:bootstrap_protocol) == 'ssh'
313
+ identity_file = locate_config_value :identity_file
314
+ ssh_user = locate_config_value :ssh_user
315
+ ssh_password = locate_config_value :ssh_password
316
+ unless identity_file || (ssh_user && ssh_password)
317
+ ui.error("You must specify either an ssh identity file or an ssh user and password")
318
+ exit 1
319
+ end
320
+ @bootstrap_protocol = 'ssh'
321
+ elsif locate_config_value(:bootstrap_protocol) == 'winrm'
322
+ if not @windows_image
323
+ ui.error("Only Windows Images support WinRM protocol for bootstrapping.")
324
+ exit 1
325
+ end
326
+ winrm_user = locate_config_value :winrm_user
327
+ winrm_password = locate_config_value :winrm_password
328
+ winrm_transport = locate_config_value :winrm_transport
329
+ winrm_port = locate_config_value :winrm_port
330
+ unless winrm_user && winrm_password && winrm_transport && winrm_port
331
+ ui.error("WinRM User, Password, Transport and Port are compulsory parameters")
332
+ exit 1
333
+ end
334
+ @bootstrap_protocol = 'winrm'
231
335
  end
232
336
  end
233
337
 
234
-
235
338
  def find_or_create_public_ip(server, connection)
236
339
  nic = connection.get_server_default_nic(server) || {}
237
340
  #puts "#{ui.color("Not allocating public IP for server", :red)}" unless config[:public_ip]
238
- if (config[:public_ip] == false) || (nic['type'] != 'Virtual') then
341
+ if (config[:public_ip] == false)
239
342
  nic['ipaddress']
240
343
  else
241
- # create ip address, ssh forwarding rule and optional forwarding rules
242
- ip_address = connection.associate_ip_address(server['zoneid'])
243
- ssh_rule = connection.create_port_forwarding_rule(ip_address['id'], "22", "TCP", "22", server['id'])
244
- create_port_forwarding_rules(ip_address['id'], server['id'], connection)
245
- ssh_rule['ipaddress']
344
+ puts("\nAllocate ip address, create forwarding rules")
345
+ ip_address = connection.associate_ip_address(server['zoneid'], locate_config_value(:cloudstack_networks))
346
+ #ip_address = connection.get_public_ip_address('202.2.94.158')
347
+ puts("\nAllocated IP Address: #{ip_address['ipaddress']}")
348
+ Chef::Log.debug("IP Address Info: #{ip_address}")
349
+
350
+ if locate_config_value :static_nat
351
+ Chef::Log.debug("Enabling static NAT for IP Address : #{ip_address['ipaddress']}")
352
+ connection.enable_static_nat(ip_address['id'], server['id'])
353
+ end
354
+ create_port_forwarding_rules(ip_address, server['id'], connection)
355
+ ip_address['ipaddress']
246
356
  end
247
357
  end
248
358
 
249
- def create_port_forwarding_rules(ip_address_id, server_id, connection)
359
+ def create_port_forwarding_rules(ip_address, server_id, connection)
250
360
  rules = locate_config_value(:port_rules)
251
- return unless rules
252
-
361
+ if @bootstrap_protocol == 'ssh'
362
+ rules += ["#{locate_config_value(:ssh_port)}"] #SSH Port
363
+ elsif @bootstrap_protocol == 'winrm'
364
+ rules +=[locate_config_value(:winrm_port)]
365
+ else
366
+ puts("\nUnsupported bootstrap protocol : #{@bootstrap_protocol}")
367
+ exit 1
368
+ end
369
+ return unless rules
253
370
  rules.each do |rule|
254
371
  args = rule.split(':')
255
372
  public_port = args[0]
256
373
  private_port = args[1] || args[0]
257
374
  protocol = args[2] || "TCP"
258
- connection.create_port_forwarding_rule(ip_address_id, private_port, protocol, public_port, server_id)
375
+ if locate_config_value :static_nat
376
+ Chef::Log.debug("Creating IP Forwarding Rule for
377
+ #{ip_address['ipaddress']} with protocol: #{protocol}, public port: #{public_port}")
378
+ connection.create_ip_fwd_rule(ip_address['id'], protocol, public_port, public_port)
379
+ else
380
+ Chef::Log.debug("Creating Port Forwarding Rule for #{ip_address['id']} with protocol: #{protocol},
381
+ public port: #{public_port} and private port: #{private_port} and server: #{server_id}")
382
+ connection.create_port_forwarding_rule(ip_address['id'], private_port, protocol, public_port, server_id)
383
+ end
259
384
  end
385
+ end
260
386
 
387
+ def tcp_test_winrm(hostname, port)
388
+ TCPSocket.new(hostname, port)
389
+ return true
390
+ rescue SocketError
391
+ sleep 2
392
+ false
393
+ rescue Errno::ETIMEDOUT
394
+ false
395
+ rescue Errno::EPERM
396
+ false
397
+ rescue Errno::ECONNREFUSED
398
+ sleep 2
399
+ false
400
+ rescue Errno::EHOSTUNREACH
401
+ sleep 2
402
+ false
403
+ rescue Errno::ENETUNREACH
404
+ sleep 2
405
+ false
261
406
  end
262
407
 
263
408
  #noinspection RubyArgCount,RubyResolve
264
409
  def is_ssh_open?(ip)
265
410
  s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
266
- sa = Socket.sockaddr_in(22, ip)
411
+ sa = Socket.sockaddr_in(locate_config_value(:ssh_port), ip)
267
412
 
268
413
  begin
269
414
  s.connect_nonblock(sa)
@@ -288,31 +433,83 @@ module KnifeCloudstack
288
433
  s && s.close
289
434
  end
290
435
  end
436
+ def is_platform_windows?
437
+ return RUBY_PLATFORM.scan('w32').size > 0
438
+ end
291
439
 
440
+ def bootstrap(server, public_ip)
441
+ if @windows_image
442
+ Chef::Log.debug("Windows Bootstrapping")
443
+ bootstrap_for_windows_node(server, public_ip)
444
+ else
445
+ Chef::Log.debug("Linux Bootstrapping")
446
+ bootstrap_for_node(server, public_ip)
447
+ end
448
+ end
449
+ def bootstrap_for_windows_node(server, fqdn)
450
+ if locate_config_value(:bootstrap_protocol) == 'winrm'
451
+ bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
452
+ if locate_config_value(:kerberos_realm)
453
+ #Fetch AD/WINS based fqdn if any for Kerberos-based Auth
454
+ private_ip_address = connection.get_server_default_nic(server)["ipaddress"]
455
+ fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(private_ip_address)
456
+ end
457
+ bootstrap.name_args = [fqdn]
458
+ bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) || 'Administrator'
459
+ bootstrap.config[:winrm_password] = locate_config_value(:winrm_password)
460
+ bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
461
+ bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
462
+
463
+ elsif locate_config_value(:bootstrap_protocol) == 'ssh'
464
+ bootstrap = Chef::Knife::BootstrapWindowsSsh.new
465
+ bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
466
+ bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
467
+ bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
468
+ bootstrap.config[:identity_file] = locate_config_value(:identity_file)
469
+ bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify)
470
+ else
471
+ ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh")
472
+ exit 1
473
+ end
474
+ bootstrap.config[:chef_node_name] = config[:chef_node_name] || server['id']
475
+ bootstrap.config[:encrypted_data_bag_secret] = config[:encrypted_data_bag_secret]
476
+ bootstrap.config[:encrypted_data_bag_secret_file] = config[:encrypted_data_bag_secret_file]
477
+ bootstrap_common_params(bootstrap)
478
+ end
479
+ def bootstrap_common_params(bootstrap)
292
480
 
293
- def bootstrap_for_node(host)
294
- bootstrap = Chef::Knife::Bootstrap.new
295
- bootstrap.name_args = [host]
296
481
  bootstrap.config[:run_list] = config[:run_list]
297
- bootstrap.config[:ssh_user] = config[:ssh_user]
298
- bootstrap.config[:ssh_password] = config[:ssh_password]
299
- bootstrap.config[:identity_file] = config[:identity_file]
300
- bootstrap.config[:chef_node_name] = config[:chef_node_name] if config[:chef_node_name]
301
482
  bootstrap.config[:prerelease] = config[:prerelease]
302
483
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
303
484
  bootstrap.config[:distro] = locate_config_value(:distro)
304
- bootstrap.config[:use_sudo] = true
305
485
  bootstrap.config[:template_file] = locate_config_value(:template_file)
306
- bootstrap.config[:environment] = config[:environment]
307
- # may be needed for vpc_mode
308
- bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
309
486
  bootstrap
310
487
  end
311
488
 
489
+
490
+ def bootstrap_for_node(server,fqdn)
491
+ bootstrap = Chef::Knife::Bootstrap.new
492
+ bootstrap.name_args = [fqdn]
493
+ # bootstrap.config[:run_list] = config[:run_list]
494
+ bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
495
+ bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
496
+ bootstrap.config[:ssh_port] = locate_config_value(:ssh_port) || 22
497
+ bootstrap.config[:identity_file] = locate_config_value(:identity_file)
498
+ bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server.name
499
+ # bootstrap.config[:prerelease] = locate_config_value(:prerelease)
500
+ # bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
501
+ # bootstrap.config[:distro] = locate_config_value(:distro)
502
+ bootstrap.config[:use_sudo] = true unless locate_config_value(:ssh_user) == 'root'
503
+ # bootstrap.config[:template_file] = config[:template_file]
504
+ bootstrap.config[:environment] = locate_config_value(:environment)
505
+ # may be needed for vpc_mode
506
+ bootstrap.config[:host_key_verify] = config[:host_key_verify]
507
+ bootstrap_common_params(bootstrap)
508
+ end
509
+
312
510
  def locate_config_value(key)
313
511
  key = key.to_sym
314
512
  Chef::Config[:knife][key] || config[key]
315
513
  end
316
-
317
- end # class
514
+ end
318
515
  end
@@ -46,6 +46,19 @@ module KnifeCloudstack
46
46
  :description => "Your CloudStack secret key",
47
47
  :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
48
48
 
49
+ option :cloudstack_project,
50
+ :short => "-P PROJECT_NAME",
51
+ :long => '--cloudstack-project PROJECT_NAME',
52
+ :description => "Cloudstack Project in which to create server",
53
+ :proc => Proc.new { |v| Chef::Config[:knife][:cloudstack_project] = v },
54
+ :default => nil
55
+
56
+ option :use_http_ssl,
57
+ :long => '--[no-]use-http-ssl',
58
+ :description => 'Support HTTPS',
59
+ :boolean => true,
60
+ :default => true
61
+
49
62
  def run
50
63
 
51
64
  @name_args.each do |hostname|
@@ -89,24 +102,15 @@ module KnifeCloudstack
89
102
  end
90
103
 
91
104
  def disassociate_virtual_ip_address(server)
92
- nic = server['nic'].first || {}
93
- return unless nic['type'] == 'Virtual'
94
-
95
- # get the ssh rule for this server
96
- ssh_rule = connection.get_ssh_port_forwarding_rule(server)
97
- return unless ssh_rule
98
-
99
- # get all rules for the same ip address
100
- rules = connection.list_port_forwarding_rules(ssh_rule['ipaddressid'])
101
- return unless rules
102
-
103
- # ensure ip address has rules only for this server
104
- rules.each { |r|
105
- return if r['virtualmachineid'] != server['id']
106
- }
107
-
108
- # dissassociate the ip address if all tests passed
109
- connection.disassociate_ip_address(ssh_rule['ipaddressid'])
105
+ ip_addr = connection.get_server_public_ip(server)
106
+ return unless ip_addr
107
+ ip_addr_info = connection.get_public_ip_address(ip_addr)
108
+ #Check if Public IP has been allocated and is not Source NAT
109
+ if ip_addr_info
110
+ if not ip_addr_info['issourcenat']
111
+ connection.disassociate_ip_address(ip_addr_info['id'])
112
+ end
113
+ end
110
114
  end
111
115
 
112
116
  def delete_client(name)
@@ -136,7 +140,9 @@ module KnifeCloudstack
136
140
  @connection = CloudstackClient::Connection.new(
137
141
  locate_config_value(:cloudstack_url),
138
142
  locate_config_value(:cloudstack_api_key),
139
- locate_config_value(:cloudstack_secret_key)
143
+ locate_config_value(:cloudstack_secret_key),
144
+ locate_config_value(:cloudstack_project),
145
+ locate_config_value(:use_http_ssl)
140
146
  )
141
147
  end
142
148
  @connection
@@ -45,6 +45,18 @@ module KnifeCloudstack
45
45
  :description => "Your CloudStack secret key",
46
46
  :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
47
47
 
48
+ option :cloudstack_project,
49
+ :short => "-P PROJECT_NAME",
50
+ :long => '--cloudstack-project PROJECT_NAME',
51
+ :description => "Cloudstack Project in which to create server",
52
+ :proc => Proc.new { |v| Chef::Config[:knife][:cloudstack_project] = v },
53
+ :default => nil
54
+
55
+ option :use_http_ssl,
56
+ :long => '--[no-]use-http-ssl',
57
+ :description => 'Support HTTPS',
58
+ :boolean => true,
59
+ :default => true
48
60
  def run
49
61
 
50
62
  $stdout.sync = true
@@ -52,7 +64,9 @@ module KnifeCloudstack
52
64
  connection = CloudstackClient::Connection.new(
53
65
  locate_config_value(:cloudstack_url),
54
66
  locate_config_value(:cloudstack_api_key),
55
- locate_config_value(:cloudstack_secret_key)
67
+ locate_config_value(:cloudstack_secret_key),
68
+ locate_config_value(:cloudstack_project),
69
+ locate_config_value(:use_http_ssl)
56
70
  )
57
71
 
58
72
  server_list = [
@@ -60,7 +74,8 @@ module KnifeCloudstack
60
74
  ui.color('Public IP', :bold),
61
75
  ui.color('Service', :bold),
62
76
  ui.color('Template', :bold),
63
- ui.color('State', :bold)
77
+ ui.color('State', :bold),
78
+ ui.color('Hypervisor', :bold)
64
79
  ]
65
80
 
66
81
  servers = connection.list_servers
@@ -78,8 +93,9 @@ module KnifeCloudstack
78
93
  server_list << server['serviceofferingname']
79
94
  server_list << server['templatename']
80
95
  server_list << server['state']
96
+ server_list << (server['hostname'] || 'N/A')
81
97
  end
82
- puts ui.list(server_list, :columns_across, 5)
98
+ puts ui.list(server_list, :columns_across, 6)
83
99
 
84
100
  end
85
101
 
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Ryan Holmes (<rholmes@edmunds.com>)
3
- # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
3
+ # Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
4
4
  # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Ryan Holmes (<rholmes@edmunds.com>)
3
- # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
3
+ # Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
4
4
  # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Ryan Holmes (<rholmes@edmunds.com>)
3
- # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
3
+ # Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
4
4
  # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
@@ -171,7 +171,7 @@ module KnifeCloudstack
171
171
  query = "(#{query})" + " AND chef_environment:#{get_environment}"
172
172
  end
173
173
 
174
- Chef::Log.debug("Searching for nodes: #{query}")
174
+ Chef::Chef::Log.debug("Searching for nodes: #{query}")
175
175
 
176
176
  q = Chef::Search::Query.new
177
177
  nodes = Array(q.search(:node, query))
@@ -53,12 +53,27 @@ module KnifeCloudstack
53
53
  :description => "Your CloudStack secret key",
54
54
  :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
55
55
 
56
+ option :cloudstack_project,
57
+ :short => "-P PROJECT_NAME",
58
+ :long => '--cloudstack-project PROJECT_NAME',
59
+ :description => "Cloudstack Project in which to create server",
60
+ :proc => Proc.new { |v| Chef::Config[:knife][:cloudstack_project] = v },
61
+ :default => nil
62
+
63
+ option :use_http_ssl,
64
+ :long => '--[no-]use-http-ssl',
65
+ :description => 'Support HTTPS',
66
+ :boolean => true,
67
+ :default => true
68
+
56
69
  def run
57
70
 
58
71
  connection = CloudstackClient::Connection.new(
59
72
  locate_config_value(:cloudstack_url),
60
73
  locate_config_value(:cloudstack_api_key),
61
- locate_config_value(:cloudstack_secret_key)
74
+ locate_config_value(:cloudstack_secret_key),
75
+ locate_config_value(:cloudstack_project),
76
+ locate_config_value(:use_http_ssl)
62
77
  )
63
78
 
64
79
  template_list = [
@@ -73,7 +88,7 @@ module KnifeCloudstack
73
88
  templates = connection.list_templates(filter)
74
89
  templates.each do |t|
75
90
  template_list << t['name']
76
- template_list << (human_file_size(t['size']) || 'Unknown')
91
+ #template_list << (human_file_size(t['size']) || 'Unknown')
77
92
  template_list << t['zonename']
78
93
  template_list << t['ispublic'].to_s
79
94
  template_list << t['created']
@@ -1,6 +1,6 @@
1
1
  #
2
2
  # Author:: Ryan Holmes (<rholmes@edmunds.com>)
3
- # Author:: KC Braunschweig (<kbraunschweig@edmunds.com>)
3
+ # Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
4
4
  # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
5
  # License:: Apache License, Version 2.0
6
6
  #
@@ -28,13 +28,24 @@ require 'json'
28
28
  module CloudstackClient
29
29
  class Connection
30
30
 
31
- ASYNC_POLL_INTERVAL = 2.0
32
- ASYNC_TIMEOUT = 300
31
+ ASYNC_POLL_INTERVAL = 5.0
32
+ ASYNC_TIMEOUT = 600
33
33
 
34
- def initialize(api_url, api_key, secret_key)
34
+ def initialize(api_url, api_key, secret_key, project_name=nil, use_ssl=true)
35
35
  @api_url = api_url
36
36
  @api_key = api_key
37
37
  @secret_key = secret_key
38
+ @project_id = nil
39
+ @use_ssl = use_ssl
40
+ if project_name
41
+ project = get_project(project_name)
42
+ if !project then
43
+ puts "Project #{project_name} does not exist"
44
+ exit 1
45
+ end
46
+ @project_id = project['id']
47
+ end
48
+
38
49
  end
39
50
 
40
51
  ##
@@ -45,6 +56,9 @@ module CloudstackClient
45
56
  'command' => 'listVirtualMachines',
46
57
  'name' => name
47
58
  }
59
+ # if @project_id
60
+ # params['projectId'] = @project_id
61
+ # end
48
62
  json = send_request(params)
49
63
  machines = json['virtualmachine']
50
64
 
@@ -60,15 +74,18 @@ module CloudstackClient
60
74
 
61
75
  def get_server_public_ip(server, cached_rules=nil)
62
76
  return nil unless server
63
-
64
77
  # find the public ip
65
- nic = get_server_default_nic(server) || {}
66
- if nic['type'] == 'Virtual' then
67
- ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
68
- ssh_rule ? ssh_rule['ipaddress'] : nil
69
- else
70
- nic['ipaddress']
78
+ nic = get_server_default_nic(server)
79
+ ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
80
+ if ssh_rule
81
+ return ssh_rule['ipaddress']
82
+ end
83
+ #check for static NAT
84
+ ip_addr = list_public_ip_addresses.find {|v| v['virtualmachineid'] == server['id']}
85
+ if ip_addr
86
+ return ip_addr['ipaddress']
71
87
  end
88
+ nic['ipaddress']
72
89
  end
73
90
 
74
91
  ##
@@ -102,6 +119,9 @@ module CloudstackClient
102
119
  params = {
103
120
  'command' => 'listVirtualMachines'
104
121
  }
122
+ # if @project_id
123
+ # params['projectId'] = @project_id
124
+ # end
105
125
  json = send_request(params)
106
126
  json['virtualmachine'] || []
107
127
  end
@@ -147,7 +167,7 @@ module CloudstackClient
147
167
  networks << get_network(name)
148
168
  end
149
169
  if networks.empty? then
150
- networks << get_default_network
170
+ networks << get_default_network(zone['id'])
151
171
  end
152
172
  if networks.empty? then
153
173
  puts "No default network found"
@@ -164,6 +184,10 @@ module CloudstackClient
164
184
  'zoneId' => zone['id'],
165
185
  'networkids' => network_ids.join(',')
166
186
  }
187
+ # if @project_id
188
+ # params['projectId'] = @project_id
189
+ # end
190
+
167
191
  params['name'] = host_name if host_name
168
192
 
169
193
  json = send_async_request(params)
@@ -338,6 +362,25 @@ module CloudstackClient
338
362
  json['template'] || []
339
363
  end
340
364
 
365
+ #Fetch project with the specified name
366
+ def get_project(name)
367
+ params = {
368
+ 'command' => 'listProjects'
369
+ }
370
+
371
+ json = send_request(params)
372
+ projects = json['project']
373
+ return nil unless projects
374
+ projects.each { |n|
375
+ if n['name'] == name then
376
+ return n
377
+ end
378
+ }
379
+
380
+ nil
381
+ end
382
+
383
+
341
384
  ##
342
385
  # Finds the network with the specified name.
343
386
 
@@ -345,6 +388,9 @@ module CloudstackClient
345
388
  params = {
346
389
  'command' => 'listNetworks'
347
390
  }
391
+ # if @project_id
392
+ # params['projectId'] = @project_id
393
+ # end
348
394
  json = send_request(params)
349
395
 
350
396
  networks = json['network']
@@ -362,11 +408,15 @@ module CloudstackClient
362
408
  ##
363
409
  # Finds the default network.
364
410
 
365
- def get_default_network
411
+ def get_default_network(zone)
366
412
  params = {
367
413
  'command' => 'listNetworks',
368
- 'isDefault' => true
414
+ 'isDefault' => true,
415
+ 'zoneid' => zone
369
416
  }
417
+ # if @project_id
418
+ # params['projectId'] = @project_id
419
+ # end
370
420
  json = send_request(params)
371
421
 
372
422
  networks = json['network']
@@ -455,23 +505,66 @@ module CloudstackClient
455
505
  'ipaddress' => ip_address
456
506
  }
457
507
  json = send_request(params)
508
+ return nil unless json['publicipaddress']
458
509
  json['publicipaddress'].first
459
510
  end
460
511
 
512
+ def list_public_ip_addresses()
513
+ params = { 'command' => 'listPublicIpAddresses'}
461
514
 
515
+ json = send_request(params)
516
+ return json['publicipaddress']
517
+ end
462
518
  ##
463
519
  # Acquires and associates a public IP to an account.
464
520
 
465
- def associate_ip_address(zone_id)
521
+ def associate_ip_address(zone_id, networks)
466
522
  params = {
467
523
  'command' => 'associateIpAddress',
468
524
  'zoneId' => zone_id
469
525
  }
470
-
526
+ #Choose the first network from the list
527
+ if networks.size > 0
528
+ params['networkId'] = get_network(networks.first)['id']
529
+ else
530
+ default_network = get_default_network(zone_id)
531
+ params['networkId'] = default_network['id']
532
+ end
533
+ print "params: #{params}"
471
534
  json = send_async_request(params)
472
535
  json['ipaddress']
473
536
  end
474
537
 
538
+ def enable_static_nat(ipaddress_id, virtualmachine_id)
539
+ params = {
540
+ 'command' => 'enableStaticNat',
541
+ 'ipAddressId' => ipaddress_id,
542
+ 'virtualmachineId' => virtualmachine_id
543
+ }
544
+ send_request(params)
545
+ end
546
+
547
+ def disable_static_nat(ipaddress)
548
+ params = {
549
+ 'command' => 'disableStaticNat',
550
+ 'ipAddressId' => ipaddress['id']
551
+ }
552
+ send_async_request(params)
553
+ end
554
+
555
+ def create_ip_fwd_rule(ipaddress_id, protocol, start_port, end_port)
556
+ params = {
557
+ 'command' => 'createIpForwardingRule',
558
+ 'ipaddressId' => ipaddress_id,
559
+ 'protocol' => protocol,
560
+ 'startport' => start_port,
561
+ 'endport' => end_port
562
+ }
563
+
564
+ send_async_request(params)
565
+ end
566
+
567
+
475
568
  ##
476
569
  # Disassociates an ip address from the account.
477
570
  #
@@ -529,10 +622,13 @@ module CloudstackClient
529
622
  ##
530
623
  # Sends a synchronous request to the CloudStack API and returns the response as a Hash.
531
624
  #
532
- # The wrapper element of the response (e.g. mycommandresponse) is discarded and the
625
+ # The wrapper element of the response (e.g. mycommandresponse) is discarded and the
533
626
  # contents of that element are returned.
534
627
 
535
628
  def send_request(params)
629
+ if @project_id
630
+ params['projectId'] = @project_id
631
+ end
536
632
  params['response'] = 'json'
537
633
  params['apiKey'] = @api_key
538
634
 
@@ -547,8 +643,12 @@ module CloudstackClient
547
643
  signature = CGI.escape(signature)
548
644
 
549
645
  url = "#{@api_url}?#{data}&signature=#{signature}"
550
-
551
- response = Net::HTTP.get_response(URI.parse(url))
646
+ uri = URI.parse(url)
647
+ http = Net::HTTP.new(uri.host, uri.port)
648
+ http.use_ssl = @use_ssl
649
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
650
+ request = Net::HTTP::Get.new(uri.request_uri)
651
+ response = http.request(request)
552
652
 
553
653
  if !response.is_a?(Net::HTTPOK) then
554
654
  puts "Error #{response.code}: #{response.message}"
metadata CHANGED
@@ -1,103 +1,103 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: knife-cloudstack
3
- version: !ruby/object:Gem::Version
4
- hash: 7
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.13
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 0
9
- - 12
10
- version: 0.0.12
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Ryan Holmes
14
9
  - KC Braunschweig
15
10
  - John E. Vincent
11
+ - Chirag Jog
16
12
  autorequire:
17
13
  bindir: bin
18
14
  cert_chain: []
19
-
20
- date: 2012-05-17 00:00:00 Z
21
- dependencies:
22
- - !ruby/object:Gem::Dependency
15
+ date: 2012-11-28 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
23
18
  name: chef
19
+ requirement: !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ! '>='
23
+ - !ruby/object:Gem::Version
24
+ version: 0.10.0
25
+ type: :runtime
24
26
  prerelease: false
25
- requirement: &id001 !ruby/object:Gem::Requirement
27
+ version_requirements: !ruby/object:Gem::Requirement
26
28
  none: false
27
- requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- hash: 55
31
- segments:
32
- - 0
33
- - 10
34
- - 0
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
35
32
  version: 0.10.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: knife-windows
35
+ requirement: !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
36
41
  type: :runtime
37
- version_requirements: *id001
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ none: false
45
+ requirements:
46
+ - - ! '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
38
49
  description: A Knife plugin to create, list and manage CloudStack servers
39
- email:
50
+ email:
40
51
  - rholmes@edmunds.com
41
- - kbraunschweig@edmunds.com
52
+ - kcbraunschweig@gmail.com
42
53
  - lusis.org+github.com@gmail.com
54
+ - chirag.jog@me.com
43
55
  executables: []
44
-
45
56
  extensions: []
46
-
47
- extra_rdoc_files:
57
+ extra_rdoc_files:
48
58
  - README.rdoc
49
59
  - CHANGES.rdoc
50
60
  - LICENSE
51
- files:
61
+ files:
52
62
  - CHANGES.rdoc
53
63
  - README.rdoc
54
64
  - LICENSE
55
65
  - lib/knife-cloudstack/connection.rb
56
- - lib/chef/knife/cs_stack_delete.rb
57
66
  - lib/chef/knife/cs_server_list.rb
58
67
  - lib/chef/knife/cs_network_list.rb
59
68
  - lib/chef/knife/cs_server_delete.rb
60
69
  - lib/chef/knife/cs_template_list.rb
61
- - lib/chef/knife/cs_server_reboot.rb
62
- - lib/chef/knife/cs_server_start.rb
63
- - lib/chef/knife/cs_service_list.rb
64
- - lib/chef/knife/cs_zone_list.rb
65
70
  - lib/chef/knife/cs_server_stop.rb
71
+ - lib/chef/knife/cs_zone_list.rb
72
+ - lib/chef/knife/cs_stack_delete.rb
66
73
  - lib/chef/knife/cs_hosts.rb
67
74
  - lib/chef/knife/cs_stack_create.rb
75
+ - lib/chef/knife/cs_service_list.rb
76
+ - lib/chef/knife/cs_server_start.rb
68
77
  - lib/chef/knife/cs_server_create.rb
78
+ - lib/chef/knife/cs_server_reboot.rb
69
79
  homepage: http://cloudstack.org/
70
80
  licenses: []
71
-
72
81
  post_install_message:
73
82
  rdoc_options: []
74
-
75
- require_paths:
83
+ require_paths:
76
84
  - lib
77
- required_ruby_version: !ruby/object:Gem::Requirement
85
+ required_ruby_version: !ruby/object:Gem::Requirement
78
86
  none: false
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- hash: 3
83
- segments:
84
- - 0
85
- version: "0"
86
- required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
92
  none: false
88
- requirements:
89
- - - ">="
90
- - !ruby/object:Gem::Version
91
- hash: 3
92
- segments:
93
- - 0
94
- version: "0"
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
95
97
  requirements: []
96
-
97
98
  rubyforge_project:
98
- rubygems_version: 1.8.11
99
+ rubygems_version: 1.8.24
99
100
  signing_key:
100
101
  specification_version: 3
101
102
  summary: A knife plugin for the CloudStack API
102
103
  test_files: []
103
-