knife-cloudstack 0.0.12 → 0.0.13

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.
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
-