knife-cloudstack 0.0.14 → 0.0.15

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