knife-cloudstack 0.0.14 → 0.0.15

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.
@@ -85,7 +85,9 @@ module KnifeCloudstack
85
85
 
86
86
  output_format(connection_result)
87
87
 
88
- rules = connection.list_port_forwarding_rules
88
+ rules = connection.list_port_forwarding_rules(nil, true)
89
+ public_list = connection.list_public_ip_addresses(true)
90
+
89
91
 
90
92
  connection_result.each do |r|
91
93
  name = r['name']
@@ -98,7 +100,7 @@ module KnifeCloudstack
98
100
  locate_config_value(:fields).downcase.split(',').each { |n| object_list << ((r[("#{n}").strip]).to_s || 'N/A') }
99
101
  else
100
102
  object_list << r['name']
101
- object_list << (connection.get_server_public_ip(r, rules) || '')
103
+ r['nic'].empty? ? object_list << "N/A" : object_list << (connection.get_server_public_ip(r, rules, public_list) || '')
102
104
  object_list << r['serviceofferingname']
103
105
  object_list << r['templatename']
104
106
  object_list << r['state']
@@ -16,11 +16,17 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'chef/knife/cs_base'
20
+ require 'chef/knife/cs_baselist'
21
+
19
22
  module KnifeCloudstack
20
23
  class CsStackCreate < Chef::Knife
21
24
 
22
25
  attr_accessor :current_stack
23
26
 
27
+ include Chef::Knife::KnifeCloudstackBase
28
+ include Chef::Knife::KnifeCloudstackBaseList
29
+
24
30
  deps do
25
31
  require 'chef/json_compat'
26
32
  require 'chef/mash'
@@ -29,7 +35,6 @@ module KnifeCloudstack
29
35
  require 'net/ssh'
30
36
  require 'net/ssh/multi'
31
37
  require 'knife-cloudstack/connection'
32
- require 'chef/knife'
33
38
  Chef::Knife.load_deps
34
39
  Chef::Knife::Ssh.load_deps
35
40
  Chef::Knife::NodeRunListRemove.load_deps
@@ -38,24 +43,6 @@ module KnifeCloudstack
38
43
 
39
44
  banner "knife cs stack create JSON_FILE (options)"
40
45
 
41
- option :cloudstack_url,
42
- :short => "-U URL",
43
- :long => "--cloudstack-url URL",
44
- :description => "The CloudStack endpoint URL",
45
- :proc => Proc.new { |url| Chef::Config[:knife][:cloudstack_url] = url }
46
-
47
- option :cloudstack_api_key,
48
- :short => "-A KEY",
49
- :long => "--cloudstack-api-key KEY",
50
- :description => "Your CloudStack API key",
51
- :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_api_key] = key }
52
-
53
- option :cloudstack_secret_key,
54
- :short => "-K SECRET",
55
- :long => "--cloudstack-secret-key SECRET",
56
- :description => "Your CloudStack secret key",
57
- :proc => Proc.new { |key| Chef::Config[:knife][:cloudstack_secret_key] = key }
58
-
59
46
  option :ssh_user,
60
47
  :short => "-x USERNAME",
61
48
  :long => "--ssh-user USERNAME",
@@ -72,6 +59,8 @@ module KnifeCloudstack
72
59
  :description => "The SSH identity file used for authentication"
73
60
 
74
61
  def run
62
+ validate_base_options
63
+
75
64
  file_path = File.expand_path(@name_args.first)
76
65
  unless File.exist?(file_path) then
77
66
  ui.error "Stack file '#{file_path}' not found. Please check the path."
@@ -81,25 +70,12 @@ module KnifeCloudstack
81
70
  data = File.read file_path
82
71
  stack = Chef::JSONCompat.from_json data
83
72
  create_stack stack
84
-
85
- #puts "Stack: #{stack.inspect}"
86
- end
87
-
88
- def connection
89
- if (!@connection) then
90
- url = locate_config_value(:cloudstack_url)
91
- api_key = locate_config_value(:cloudstack_api_key)
92
- secret_key = locate_config_value(:cloudstack_secret_key)
93
- @connection = CloudstackClient::Connection.new(url, api_key, secret_key)
94
- end
95
- @connection
96
73
  end
97
74
 
98
75
  def create_stack(stack)
99
76
  @current_stack = Mash.new(stack)
100
77
  current_stack[:servers].each do |server|
101
78
  if server[:name]
102
-
103
79
  # create server(s)
104
80
  names = server[:name].split(/[\s,]+/)
105
81
  names.each do |n|
@@ -120,16 +96,21 @@ module KnifeCloudstack
120
96
  def create_server(server)
121
97
 
122
98
  cmd = KnifeCloudstack::CsServerCreate.new([server[:name]])
123
-
124
99
  # configure and run command
125
100
  # TODO: validate parameters
126
101
  cmd.config[:ssh_user] = config[:ssh_user]
127
102
  cmd.config[:ssh_password] = config[:ssh_password]
128
- cmd.config[:ssh_port] = Chef::Config[:knife][:ssh_port] || config[:ssh_port]
103
+ cmd.config[:ssh_port] = config[:ssh_port] || "22" # Chef::Config[:knife][:ssh_port]
129
104
  cmd.config[:identity_file] = config[:identity_file]
130
105
  cmd.config[:cloudstack_template] = server[:template] if server[:template]
131
106
  cmd.config[:cloudstack_service] = server[:service] if server[:service]
132
- cmd.config[:cloudstack_zone] = server[:service] if server[:zone]
107
+ cmd.config[:cloudstack_zone] = server[:zone] if server[:zone]
108
+ server.has_key?(:public_ip) ? cmd.config[:public_ip] = server[:public_ip] : cmd.config[:no_public_ip] = true
109
+ cmd.config[:bootstrap] = server[:bootstrap] if server.has_key?(:bootstrap)
110
+ cmd.config[:bootstrap_protocol] = server[:bootstrap_protocol] || "ssh"
111
+ cmd.config[:distro] = server[:distro] || "chef-full"
112
+ cmd.config[:template_file] = server[:template_file] if server.has_key?(:template_file)
113
+ cmd.config[:no_host_key_verify] = server[:no_host_key_verify] if server.has_key?(:no_host_key_verify)
133
114
  cmd.config[:cloudstack_networks] = server[:networks].split(/[\s,]+/) if server[:networks]
134
115
  cmd.config[:run_list] = server[:run_list].split(/[\s,]+/) if server[:run_list]
135
116
  cmd.config[:port_rules] = server[:port_rules].split(/[\s,]+/) if server[:port_rules]
@@ -139,7 +120,6 @@ module KnifeCloudstack
139
120
  end
140
121
 
141
122
  cmd.run_with_pretty_exceptions
142
-
143
123
  end
144
124
 
145
125
  def run_actions(actions)
@@ -171,7 +151,7 @@ module KnifeCloudstack
171
151
  query = "(#{query})" + " AND chef_environment:#{get_environment}"
172
152
  end
173
153
 
174
- Chef::Chef::Log.debug("Searching for nodes: #{query}")
154
+ Chef::Log.debug("Searching for nodes: #{query}")
175
155
 
176
156
  q = Chef::Search::Query.new
177
157
  nodes = Array(q.search(:node, query))
@@ -315,11 +295,5 @@ module KnifeCloudstack
315
295
  puts hosts.join("\n")
316
296
  end
317
297
  end
318
-
319
- def locate_config_value(key)
320
- key = key.to_sym
321
- Chef::Config[:knife][key] || config[key]
322
- end
323
-
324
298
  end
325
299
  end
@@ -78,7 +78,7 @@ module KnifeCloudstack
78
78
  locate_config_value(:listall),
79
79
  nil,
80
80
  nil,
81
- locate_config_value(:templatefilter)
81
+ params = { 'templatefilter' => locate_config_value(:templatefilter) }
82
82
  )
83
83
 
84
84
  output_format(connection_result)
@@ -0,0 +1,108 @@
1
+ #
2
+ # Author:: Jeremy Baumont (<jbaumont@schubergphilis.com>)
3
+ # Copyright:: Copyright (c) Schuberg Philis 2013
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/knife/cs_base'
20
+
21
+ module KnifeCloudstack
22
+ class CsVolumeCreate < Chef::Knife
23
+
24
+ include Chef::Knife::KnifeCloudstackBase
25
+
26
+ deps do
27
+ require 'knife-cloudstack/connection'
28
+ Chef::Knife.load_deps
29
+ end
30
+
31
+ banner "knife cs volume create NAME (options)"
32
+
33
+ option :name,
34
+ :long => "--name NAME",
35
+ :description => "The name of the disk volume.",
36
+ :required => true,
37
+ :on => :head
38
+
39
+ option :account,
40
+ :long => "--account ACCOUNT_NAME",
41
+ :description => "The account associated with the disk volume. Must be used with the domainId parameter."
42
+
43
+ option :diskofferingid,
44
+ :long => "--diskofferingid ID",
45
+ :description => "The ID of the disk offering. Either diskOfferingId or snapshotId must be passed in."
46
+
47
+ option :domainid,
48
+ :long => "--domainid ID",
49
+ :description => "The domain ID associated with the disk offering. If used with the account parameter returns the disk volume associated with the account for the specified domain."
50
+
51
+ option :size,
52
+ :long => "--size SIZE",
53
+ :description => "Arbitrary volume size."
54
+
55
+ option :snapshotid,
56
+ :long => "--snapshotid ID",
57
+ :description => "The snapshot ID for the disk volume. Either diskOfferingId or snapshotId must be passed in."
58
+
59
+ option :zoneid,
60
+ :long => "--zoneid ID",
61
+ :description => "The ID of the availability zone.",
62
+ :required => true,
63
+ :on => :head
64
+
65
+
66
+ def run
67
+ validate_base_options
68
+
69
+ Chef::Log.debug("Validate hostname and options")
70
+ if locate_config_value(:name)
71
+ volumename = locate_config_value(:name)
72
+ else
73
+ volumename = @name_args.first
74
+ unless /^[a-zA-Z0-9][a-zA-Z0-9_\-#]*$/.match volumename then
75
+ ui.error "Invalid volumename. Please specify a simple name without any spaces"
76
+ exit 1
77
+ end
78
+ end
79
+
80
+ print "#{ui.color("Creating volume: #{volumename}", :magenta)}\n"
81
+
82
+ params = {
83
+ 'command' => 'createVolume',
84
+ 'name' => volumename,
85
+ }
86
+
87
+ params['account'] = locate_config_value(:account) if locate_config_value(:account)
88
+ params['diskofferingid'] = locate_config_value(:diskofferingid) if locate_config_value(:diskofferingid)
89
+ params['domainid'] = locate_config_value(:domainid) if locate_config_value(:domainid)
90
+ params['projectid'] = locate_config_value(:projectid) if locate_config_value(:projectid)
91
+ params['size'] = locate_config_value(:size) if locate_config_value(:size)
92
+ params['snapshotid'] = locate_config_value(:snapshotid) if locate_config_value(:snapshotid)
93
+ params['zoneid'] = locate_config_value(:zoneid) if locate_config_value(:zoneid)
94
+
95
+ json = connection.send_request(params)
96
+
97
+ if ! json then
98
+ ui.error("Unable to create volume")
99
+ exit 1
100
+ end
101
+
102
+ print "Volume #{json['id']} is being created in the background\n";
103
+
104
+ return json['id']
105
+ end
106
+
107
+ end # class
108
+ end
@@ -3,6 +3,7 @@
3
3
  # Author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
4
4
  # Author:: Sander Botman (<sbotman@schubergphilis.com>)
5
5
  # Author:: Frank Breedijk (<fbreedijk@schubergphilis.com>)
6
+ # Author:: Sander van Harmelen (<svanharmelen@schubergphilis.com>)
6
7
  # Copyright:: Copyright (c) 2011 Edmunds, Inc.
7
8
  # License:: Apache License, Version 2.0
8
9
  #
@@ -27,7 +28,24 @@ require 'cgi'
27
28
  require 'net/http'
28
29
  require 'json'
29
30
  require 'highline/import'
30
- require 'knife-cloudstack/string_to_regexp'
31
+
32
+ class String
33
+ def to_regexp
34
+ return nil unless self.strip.match(/\A\/(.*)\/(.*)\Z/mx)
35
+ regexp , flags = $1 , $2
36
+ return nil if !regexp || flags =~ /[^xim]/m
37
+
38
+ x = /x/.match(flags) && Regexp::EXTENDED
39
+ i = /i/.match(flags) && Regexp::IGNORECASE
40
+ m = /m/.match(flags) && Regexp::MULTILINE
41
+
42
+ Regexp.new regexp , [x,i,m].inject(0){|a,f| f ? a+f : a }
43
+ end
44
+
45
+ def is_uuid?
46
+ self.strip =~ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/ ? true : false
47
+ end
48
+ end
31
49
 
32
50
  module CloudstackClient
33
51
  class Connection
@@ -35,23 +53,26 @@ module CloudstackClient
35
53
  ASYNC_POLL_INTERVAL = 5.0
36
54
  ASYNC_TIMEOUT = 600
37
55
 
38
- def initialize(api_url, api_key, secret_key, project_name=nil, use_ssl=true)
56
+ def initialize(api_url, api_key, secret_key, project_name=nil, no_ssl_verify=false, api_proxy=nil)
39
57
  @api_url = api_url
40
58
  @api_key = api_key
41
59
  @secret_key = secret_key
42
- @project_id = nil
43
- @use_ssl = use_ssl
44
- if project_name
45
- project = get_project(project_name)
46
- if !project then
47
- puts "Project #{project_name} does not exist"
48
- exit 1
49
- end
50
- @project_id = project['id']
51
- end
52
-
60
+ @no_ssl_verify = no_ssl_verify
61
+ @project_id = get_project_id(project_name) if project_name || nil
62
+ @api_proxy = api_proxy
53
63
  end
54
64
 
65
+ ##
66
+ # Get project id
67
+ def get_project_id(name)
68
+ project = get_project(name)
69
+ if !project then
70
+ puts "Project #{project_name} does not exist"
71
+ exit 1
72
+ end
73
+ project['id']
74
+ end
75
+
55
76
  ##
56
77
  # Finds the server with the specified name.
57
78
 
@@ -76,16 +97,23 @@ module CloudstackClient
76
97
  ##
77
98
  # Finds the public ip for a server
78
99
 
79
- def get_server_public_ip(server, cached_rules=nil)
100
+ def get_server_public_ip(server, cached_rules=nil, cached_nat=nil)
80
101
  return nil unless server
81
102
  # find the public ip
82
103
  nic = get_server_default_nic(server)
104
+
83
105
  ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
84
- if ssh_rule
85
- return ssh_rule['ipaddress']
86
- end
106
+ return ssh_rule['ipaddress'] if ssh_rule
107
+
108
+ winrm_rule = get_winrm_port_forwarding_rule(server, cached_rules)
109
+ return winrm_rule['ipaddress'] if winrm_rule
110
+
87
111
  #check for static NAT
88
- ip_addr = list_public_ip_addresses.find {|v| v['virtualmachineid'] == server['id']}
112
+ if cached_nat
113
+ ip_addr = cached_nat.find {|v| v['virtualmachineid'] == server['id']}
114
+ else
115
+ ip_addr = list_public_ip_addresses.find {|v| v['virtualmachineid'] == server['id']}
116
+ end
89
117
  if ip_addr
90
118
  return ip_addr['ipaddress']
91
119
  end
@@ -116,23 +144,15 @@ module CloudstackClient
116
144
  end
117
145
  end
118
146
 
119
- ##
147
+ ##
120
148
  # List all the objects based on the command that is specified.
121
-
122
- def list_object(command, json_result, filter=nil, listall=nil, keyword=nil, name=nil, templatefilter=nil)
123
- params = {
124
- 'command' => command
125
- }
149
+
150
+ def list_object(command, json_result, filter=nil, listall=nil, keyword=nil, name=nil, params={})
151
+ params['command'] = command
126
152
  params['listall'] = true if listall || name || keyword unless listall == false
127
153
  params['keyword'] = keyword if keyword
128
154
  params['name'] = name if name
129
155
 
130
- if templatefilter
131
- template = 'featured'
132
- template = templatefilter.downcase if ["featured","self","self-executable","executable","community"].include?(templatefilter.downcase)
133
- params['templateFilter'] = template
134
- end
135
-
136
156
  json = send_request(params)
137
157
  Chef::Log.debug("JSON (list_object) result: #{json}")
138
158
 
@@ -155,7 +175,7 @@ module CloudstackClient
155
175
  ##
156
176
  # Deploys a new server using the specified parameters.
157
177
 
158
- def create_server(host_name, service_name, template_name, zone_name=nil, network_names=[], extra_params)
178
+ def create_server(host_name, service_name, template_name, disk_name=nil, zone_name=nil, network_names=[], extra_params)
159
179
 
160
180
  if host_name then
161
181
  if get_server(host_name) then
@@ -170,12 +190,20 @@ module CloudstackClient
170
190
  exit 1
171
191
  end
172
192
 
173
- template = get_template(template_name)
193
+ template = get_template(template_name, zone_name)
174
194
  if !template then
175
195
  puts "Error: Template '#{template_name}' is invalid"
176
196
  exit 1
177
197
  end
178
198
 
199
+ if disk_name then
200
+ disk = get_disk_offering(disk_name)
201
+ if !disk then
202
+ puts "Error: Disk offering '#{disk_name}' is invalid"
203
+ exit 1
204
+ end
205
+ end
206
+
179
207
  zone = zone_name ? get_zone(zone_name) : get_default_zone
180
208
  if !zone then
181
209
  msg = zone_name ? "Zone '#{zone_name}' is invalid" : "No default zone found"
@@ -183,37 +211,56 @@ module CloudstackClient
183
211
  exit 1
184
212
  end
185
213
 
186
- networks = []
187
- network_names.each do |name|
188
- network = get_network(name)
189
- if !network then
190
- puts "Error: Network '#{name}' not found"
214
+ if zone['networktype'] != 'Basic' then
215
+ # If this is a Basic zone no networkids are needed in the params
216
+
217
+ networks = []
218
+ if network_names.nil? then
219
+ networks << get_default_network(zone['id'])
220
+ else
221
+ network_names.each do |name|
222
+ network = get_network(name)
223
+ if !network then
224
+ puts "Error: Network '#{name}' not found"
225
+ end
226
+ networks << get_network(name)
227
+ end
228
+ end
229
+
230
+ if networks.empty? then
231
+ networks << get_default_network(zone['id'])
232
+ end
233
+ if networks.empty? then
234
+ puts "No default network found"
191
235
  exit 1
192
236
  end
193
- networks << get_network(name)
194
- end
195
- if networks.empty? then
196
- networks << get_default_network(zone['id'])
197
- end
198
- if networks.empty? then
199
- puts "No default network found"
200
- exit 1
201
- end
202
- network_ids = networks.map { |network|
203
- network['id']
204
- }
237
+ network_ids = networks.map { |network|
238
+ network['id']
239
+ }
240
+
241
+ params = {
242
+ 'command' => 'deployVirtualMachine',
243
+ 'serviceOfferingId' => service['id'],
244
+ 'templateId' => template['id'],
245
+ 'zoneId' => zone['id'],
246
+ 'networkids' => network_ids.join(',')
247
+ }
248
+
249
+ elsif
250
+
251
+ params = {
252
+ 'command' => 'deployVirtualMachine',
253
+ 'serviceOfferingId' => service['id'],
254
+ 'templateId' => template['id'],
255
+ 'zoneId' => zone['id']
256
+ }
205
257
 
206
- params = {
207
- 'command' => 'deployVirtualMachine',
208
- 'serviceOfferingId' => service['id'],
209
- 'templateId' => template['id'],
210
- 'zoneId' => zone['id'],
211
- 'networkids' => network_ids.join(',')
212
- }
258
+ end
213
259
 
214
260
  params.merge!(extra_params) if extra_params
215
261
 
216
262
  params['name'] = host_name if host_name
263
+ params['diskOfferingId'] = disk['id'] if disk
217
264
 
218
265
  json = send_async_request(params)
219
266
  json['virtualmachine']
@@ -318,11 +365,12 @@ module CloudstackClient
318
365
  return nil unless services
319
366
 
320
367
  services.each { |s|
321
- if s['name'] == name then
322
- return s
368
+ if name.is_uuid? then
369
+ return s if s['id'] == name
370
+ else
371
+ return s if s['name'] == name
323
372
  end
324
373
  }
325
-
326
374
  nil
327
375
  end
328
376
 
@@ -337,32 +385,63 @@ module CloudstackClient
337
385
  json['serviceoffering'] || []
338
386
  end
339
387
 
388
+ def list_security_groups
389
+ params = {
390
+ 'command' => 'listSecurityGroups'
391
+ }
392
+ json = send_request(params)
393
+ json['securitygroups'] || []
394
+ end
395
+
396
+
340
397
  ##
341
398
  # Finds the template with the specified name.
342
399
 
343
- def get_template(name)
400
+ def get_template(name, zone_name=nil)
344
401
 
345
402
  # TODO: use name parameter
346
403
  # listTemplates in CloudStack 2.2 doesn't seem to work
347
404
  # when the name parameter is specified. When this is fixed,
348
405
  # the name parameter should be added to the request.
406
+
407
+ zone = zone_name ? get_zone(zone_name) : get_default_zone
408
+
349
409
  params = {
350
410
  'command' => 'listTemplates',
351
- 'templateFilter' => 'executable'
411
+ 'templateFilter' => 'executable',
352
412
  }
413
+ params['zoneid'] = zone['id'] if zone
414
+
353
415
  json = send_request(params)
354
416
 
355
417
  templates = json['template']
356
- if !templates then
357
- return nil
358
- end
418
+ return nil unless templates
359
419
 
360
420
  templates.each { |t|
361
- if t['name'] == name then
362
- return t
421
+ if name.is_uuid? then
422
+ return t if t['id'] == name
423
+ else
424
+ return t if t['name'] == name
363
425
  end
364
426
  }
427
+ nil
428
+ end
429
+
430
+ ##
431
+ # Finds the disk offering with the specified name.
432
+
433
+ def get_disk_offering(name)
434
+ params = {
435
+ 'command' => 'listDiskOfferings',
436
+ }
437
+ json = send_request(params)
438
+ disks = json['diskoffering']
439
+
440
+ return nil if !disks
365
441
 
442
+ disks.each { |d|
443
+ return d if d['name'] == name
444
+ }
366
445
  nil
367
446
  end
368
447
 
@@ -397,12 +476,14 @@ module CloudstackClient
397
476
  json = send_request(params)
398
477
  projects = json['project']
399
478
  return nil unless projects
479
+
400
480
  projects.each { |n|
401
- if n['name'] == name then
402
- return n
481
+ if name.is_uuid? then
482
+ return n if n['id'] == name
483
+ else
484
+ return n if n['name'] == name
403
485
  end
404
486
  }
405
-
406
487
  nil
407
488
  end
408
489
 
@@ -436,11 +517,12 @@ module CloudstackClient
436
517
  return nil unless networks
437
518
 
438
519
  networks.each { |n|
439
- if n['name'] == name then
440
- return n
520
+ if name.is_uuid? then
521
+ return n if n['id'] == name
522
+ else
523
+ return n if n['name'] == name
441
524
  end
442
525
  }
443
-
444
526
  nil
445
527
  end
446
528
 
@@ -496,11 +578,12 @@ module CloudstackClient
496
578
  return nil unless networks
497
579
 
498
580
  networks.each { |z|
499
- if z['name'] == name then
500
- return z
581
+ if name.is_uuid? then
582
+ return z if z['id'] == name
583
+ else
584
+ return z if z['name'] == name
501
585
  end
502
586
  }
503
-
504
587
  nil
505
588
  end
506
589
 
@@ -516,7 +599,8 @@ module CloudstackClient
516
599
 
517
600
  zones = json['zone']
518
601
  return nil unless zones
519
-
602
+ # zones.sort! # sort zones so we always return the same zone
603
+ # !this gives error in our production environment so need to retest this
520
604
  zones.first
521
605
  end
522
606
 
@@ -545,8 +629,9 @@ module CloudstackClient
545
629
  json['publicipaddress'].first
546
630
  end
547
631
 
548
- def list_public_ip_addresses()
549
- params = { 'command' => 'listPublicIpAddresses'}
632
+ def list_public_ip_addresses(listall=false)
633
+ params = { 'command' => 'listPublicIpAddresses' }
634
+ params['listall'] = listall
550
635
 
551
636
  json = send_request(params)
552
637
  return json['publicipaddress'] || []
@@ -560,13 +645,13 @@ module CloudstackClient
560
645
  'zoneId' => zone_id
561
646
  }
562
647
  #Choose the first network from the list
563
- if networks.size > 0
564
- params['networkId'] = get_network(networks.first)['id']
565
- else
648
+ if networks.nil? || networks.empty?
566
649
  default_network = get_default_network(zone_id)
567
650
  params['networkId'] = default_network['id']
651
+ else
652
+ params['networkId'] = get_network(networks.first)['id']
568
653
  end
569
- print "params: #{params}"
654
+ Chef::Log.debug("associate ip params: #{params}")
570
655
  json = send_async_request(params)
571
656
  json['ipaddress']
572
657
  end
@@ -600,15 +685,26 @@ module CloudstackClient
600
685
  send_async_request(params)
601
686
  end
602
687
 
603
- def create_firewall_rule(ipaddress_id, protocol, start_port, end_port, cidr_list)
604
- params = {
605
- 'command' => 'createFirewallRule',
606
- 'ipaddressId' => ipaddress_id,
607
- 'protocol' => protocol,
608
- 'startport' => start_port,
609
- 'endport' => end_port,
610
- 'cidrlist' => cidr_list
611
- }
688
+ def create_firewall_rule(ipaddress_id, protocol, param3, param4, cidr_list)
689
+ if protocol == "ICMP"
690
+ params = {
691
+ 'command' => 'createFirewallRule',
692
+ 'ipaddressId' => ipaddress_id,
693
+ 'protocol' => protocol,
694
+ 'icmptype' => param3,
695
+ 'icmpcode' => param4,
696
+ 'cidrlist' => cidr_list
697
+ }
698
+ else
699
+ params = {
700
+ 'command' => 'createFirewallRule',
701
+ 'ipaddressId' => ipaddress_id,
702
+ 'protocol' => protocol,
703
+ 'startport' => param3,
704
+ 'endport' => param4,
705
+ 'cidrlist' => cidr_list
706
+ }
707
+ end
612
708
  send_async_request(params)
613
709
  end
614
710
 
@@ -629,11 +725,10 @@ module CloudstackClient
629
725
  ##
630
726
  # Lists all port forwarding rules.
631
727
 
632
- def list_port_forwarding_rules(ip_address_id=nil)
633
- params = {
634
- 'command' => 'listPortForwardingRules'
635
- }
728
+ def list_port_forwarding_rules(ip_address_id=nil, listall=false)
729
+ params = { 'command' => 'listPortForwardingRules' }
636
730
  params['ipAddressId'] = ip_address_id if ip_address_id
731
+ params['listall'] = listall
637
732
  json = send_request(params)
638
733
  json['portforwardingrule']
639
734
  end
@@ -649,6 +744,20 @@ module CloudstackClient
649
744
  r['publicport'] == '22'
650
745
  }.first
651
746
  end
747
+
748
+ ##
749
+ # Gets the WINRM port forwarding rule for the specified server.
750
+
751
+ def get_winrm_port_forwarding_rule(server, cached_rules=nil)
752
+ rules = cached_rules || list_port_forwarding_rules || []
753
+ rules.find_all { |r|
754
+ r['virtualmachineid'] == server['id'] &&
755
+ (r['privateport'] == '5985' &&
756
+ r['publicport'] == '5985') ||
757
+ (r['privateport'] == '5986' &&
758
+ r['publicport'] == '5986')
759
+ }.first
760
+ end
652
761
 
653
762
  ##
654
763
  # Creates a port forwarding rule.
@@ -666,6 +775,25 @@ module CloudstackClient
666
775
  json['portforwardingrule']
667
776
  end
668
777
 
778
+ def http_client_builder
779
+ http_proxy = proxy_uri
780
+ if http_proxy.nil?
781
+ Net::HTTP
782
+ else
783
+ Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
784
+ user = http_proxy.user if http_proxy.user
785
+ pass = http_proxy.password if http_proxy.password
786
+ Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass)
787
+ end
788
+ end
789
+
790
+ def proxy_uri
791
+ return nil if @api_proxy.nil?
792
+ result = URI.parse(@api_proxy)
793
+ return result unless result.host.nil? || result.host.empty?
794
+ nil
795
+ end
796
+
669
797
  ##
670
798
  # Sends a synchronous request to the CloudStack API and returns the response as a Hash.
671
799
  #
@@ -688,20 +816,34 @@ module CloudstackClient
688
816
  signature = Base64.encode64(signature).chomp
689
817
  signature = CGI.escape(signature)
690
818
 
819
+ if @api_url.nil? || @api_url.empty?
820
+ puts "Error: Please specify a valid API URL."
821
+ exit 1
822
+ end
823
+
691
824
  url = "#{@api_url}?#{data}&signature=#{signature}"
692
825
  Chef::Log.debug("URL: #{url}")
693
826
  uri = URI.parse(url)
694
- http = Net::HTTP.new(uri.host, uri.port)
695
- http.use_ssl = @use_ssl
696
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
827
+
828
+ http = http_client_builder.new(uri.host, uri.port)
829
+
830
+ if uri.scheme == "https"
831
+ http.use_ssl = true
832
+ # Still need to do some testing on SSL, so will fix this later
833
+ if @no_ssl_verify
834
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
835
+ else
836
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
837
+ end
838
+ end
697
839
  request = Net::HTTP::Get.new(uri.request_uri)
698
840
  response = http.request(request)
699
841
 
700
842
  if !response.is_a?(Net::HTTPOK) then
701
843
  case response.code
702
844
  when "432"
703
- puts "\n"
704
- puts "Error #{response.code}: Your account does not have the right to execute this command or the command does not exist."
845
+ puts "\n"
846
+ puts "Error #{response.code}: Your account does not have the right to execute this command is locked or the command does not exist."
705
847
  else
706
848
  puts "Error #{response.code}: #{response.message}"
707
849
  puts JSON.pretty_generate(JSON.parse(response.body))
@@ -736,6 +878,7 @@ module CloudstackClient
736
878
  print "."
737
879
 
738
880
  if status == 1 then
881
+ print "\n"
739
882
  return json['jobresult']
740
883
  elsif status == 2 then
741
884
  print "\n"