knife-cosmic 0.2.0

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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGES.rdoc +186 -0
  3. data/LICENSE +202 -0
  4. data/README.rdoc +427 -0
  5. data/lib/chef/knife/cosmic_aag_list.rb +58 -0
  6. data/lib/chef/knife/cosmic_account_list.rb +87 -0
  7. data/lib/chef/knife/cosmic_base.rb +108 -0
  8. data/lib/chef/knife/cosmic_baselist.rb +111 -0
  9. data/lib/chef/knife/cosmic_cluster_list.rb +60 -0
  10. data/lib/chef/knife/cosmic_config_list.rb +56 -0
  11. data/lib/chef/knife/cosmic_disk_list.rb +58 -0
  12. data/lib/chef/knife/cosmic_domain_list.rb +53 -0
  13. data/lib/chef/knife/cosmic_firewallrule_create.rb +138 -0
  14. data/lib/chef/knife/cosmic_firewallrule_list.rb +62 -0
  15. data/lib/chef/knife/cosmic_forwardrule_create.rb +145 -0
  16. data/lib/chef/knife/cosmic_host_list.rb +61 -0
  17. data/lib/chef/knife/cosmic_hosts.rb +58 -0
  18. data/lib/chef/knife/cosmic_iso_list.rb +89 -0
  19. data/lib/chef/knife/cosmic_keypair_create.rb +72 -0
  20. data/lib/chef/knife/cosmic_keypair_delete.rb +60 -0
  21. data/lib/chef/knife/cosmic_keypair_list.rb +44 -0
  22. data/lib/chef/knife/cosmic_network_list.rb +63 -0
  23. data/lib/chef/knife/cosmic_oscategory_list.rb +50 -0
  24. data/lib/chef/knife/cosmic_ostype_list.rb +52 -0
  25. data/lib/chef/knife/cosmic_pod_list.rb +60 -0
  26. data/lib/chef/knife/cosmic_project_list.rb +63 -0
  27. data/lib/chef/knife/cosmic_publicip_list.rb +55 -0
  28. data/lib/chef/knife/cosmic_router_list.rb +64 -0
  29. data/lib/chef/knife/cosmic_securitygroup_list.rb +59 -0
  30. data/lib/chef/knife/cosmic_server_add_nic.rb +109 -0
  31. data/lib/chef/knife/cosmic_server_create.rb +674 -0
  32. data/lib/chef/knife/cosmic_server_delete.rb +153 -0
  33. data/lib/chef/knife/cosmic_server_list.rb +167 -0
  34. data/lib/chef/knife/cosmic_server_passwordreset.rb +91 -0
  35. data/lib/chef/knife/cosmic_server_reboot.rb +99 -0
  36. data/lib/chef/knife/cosmic_server_remove_nic.rb +101 -0
  37. data/lib/chef/knife/cosmic_server_start.rb +104 -0
  38. data/lib/chef/knife/cosmic_server_stop.rb +118 -0
  39. data/lib/chef/knife/cosmic_server_update.rb +47 -0
  40. data/lib/chef/knife/cosmic_service_list.rb +74 -0
  41. data/lib/chef/knife/cosmic_stack_create.rb +298 -0
  42. data/lib/chef/knife/cosmic_stack_delete.rb +79 -0
  43. data/lib/chef/knife/cosmic_template_create.rb +129 -0
  44. data/lib/chef/knife/cosmic_template_extract.rb +104 -0
  45. data/lib/chef/knife/cosmic_template_list.rb +88 -0
  46. data/lib/chef/knife/cosmic_template_register.rb +187 -0
  47. data/lib/chef/knife/cosmic_user_list.rb +62 -0
  48. data/lib/chef/knife/cosmic_volume_attach.rb +70 -0
  49. data/lib/chef/knife/cosmic_volume_create.rb +108 -0
  50. data/lib/chef/knife/cosmic_volume_delete.rb +97 -0
  51. data/lib/chef/knife/cosmic_volume_detach.rb +61 -0
  52. data/lib/chef/knife/cosmic_volume_list.rb +77 -0
  53. data/lib/chef/knife/cosmic_zone_list.rb +53 -0
  54. data/lib/knife-cosmic/connection.rb +1046 -0
  55. metadata +127 -0
@@ -0,0 +1,53 @@
1
+ #
2
+ # Original knife-cloudstack author:: Ryan Holmes (<rholmes@edmunds.com>)
3
+ # Original knife-cloudstack author:: Sander Botman (<sbotman@schubergphilis.com>)
4
+ # Copyright:: Copyright (c) 2011 Edmunds, Inc.
5
+ # Copyright:: Copyright (c) 2013 Sander Botman.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require 'chef/knife'
22
+ require 'chef/knife/cosmic_baselist'
23
+
24
+ module Knifecosmic
25
+ class CosmicZoneList < Chef::Knife
26
+
27
+ include Chef::Knife::KnifecosmicBaseList
28
+
29
+ banner "knife cosmic zone list (options)"
30
+
31
+ option :keyword,
32
+ :long => "--keyword KEY",
33
+ :description => "List by keyword"
34
+
35
+ def run
36
+ validate_base_options
37
+
38
+ columns = [
39
+ 'Name :name',
40
+ 'Network Type :networktype',
41
+ 'Security Groups :securitygroupsenabled'
42
+ ]
43
+
44
+ params = { 'command' => "listZones" }
45
+ params['filter'] = locate_config_value(:filter) if locate_config_value(:filter)
46
+ params['keyword'] = locate_config_value(:keyword) if locate_config_value(:keyword)
47
+
48
+ result = connection.list_object(params, "zone")
49
+ list_object(columns, result)
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,1046 @@
1
+ #
2
+ # knife-cosmic author:: Robbert-Jan Sperna Weiland (<rspernaweiland@schubergphilis.com>)
3
+ # Original knife-cloudstack author:: Ryan Holmes (<rholmes@edmunds.com>)
4
+ # Original knife-cloudstack author:: KC Braunschweig (<kcbraunschweig@gmail.com>)
5
+ # Original knife-cloudstack author:: Sander Botman (<sbotman@schubergphilis.com>)
6
+ # Original knife-cloudstack author:: Frank Breedijk (<fbreedijk@schubergphilis.com>)
7
+ # Original knife-cloudstack author:: Sander van Harmelen (<svanharmelen@schubergphilis.com>)
8
+ # Copyright:: Copyright (c) 2011 Edmunds, Inc.
9
+ # License:: Apache License, Version 2.0
10
+ #
11
+ # Licensed under the Apache License, Version 2.0 (the "License");
12
+ # you may not use this file except in compliance with the License.
13
+ # You may obtain a copy of the License at
14
+ #
15
+ # http://www.apache.org/licenses/LICENSE-2.0
16
+ #
17
+ # Unless required by applicable law or agreed to in writing, software
18
+ # distributed under the License is distributed on an "AS IS" BASIS,
19
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
+ # See the License for the specific language governing permissions and
21
+ # limitations under the License.
22
+ #
23
+
24
+ require 'rubygems'
25
+ require 'base64'
26
+ require 'openssl'
27
+ require 'uri'
28
+ require 'cgi'
29
+ require 'net/http'
30
+ require 'json'
31
+ require 'highline/import'
32
+
33
+ class String
34
+ def to_regexp
35
+ return nil unless self.strip.match(/\A\/(.*)\/(.*)\Z/mx)
36
+ regexp , flags = $1 , $2
37
+ return nil if !regexp || flags =~ /[^xim]/m
38
+
39
+ x = /x/.match(flags) && Regexp::EXTENDED
40
+ i = /i/.match(flags) && Regexp::IGNORECASE
41
+ m = /m/.match(flags) && Regexp::MULTILINE
42
+
43
+ Regexp.new regexp , [x,i,m].inject(0){|a,f| f ? a+f : a }
44
+ end
45
+
46
+ def is_uuid?
47
+ 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
48
+ end
49
+ end
50
+
51
+ module CosmicClient
52
+ class Connection
53
+
54
+ ASYNC_POLL_INTERVAL = 5.0
55
+ ASYNC_TIMEOUT = 3600
56
+
57
+ def initialize(api_url, api_key, secret_key, project_name=nil, no_ssl_verify=false, api_proxy=nil)
58
+ @api_url = api_url
59
+ @api_key = api_key
60
+ @secret_key = secret_key
61
+ @no_ssl_verify = no_ssl_verify
62
+ @project_id = get_project_id(project_name) if project_name || nil
63
+ @api_proxy = api_proxy
64
+ end
65
+
66
+ ##
67
+ # Get project id
68
+ def get_project_id(name)
69
+ project = get_project(name)
70
+ if !project then
71
+ puts "Project #{project_name} does not exist"
72
+ exit 1
73
+ end
74
+ project['id']
75
+ end
76
+
77
+ ##
78
+ # Finds the server with the specified name.
79
+
80
+ def get_server(name)
81
+ params = {
82
+ 'command' => 'listVirtualMachines',
83
+ 'name' => name
84
+ }
85
+ json = send_request(params)
86
+ machines = json['virtualmachine']
87
+
88
+ if !machines || machines.empty? then
89
+ return nil
90
+ end
91
+ machine = machines.select { |item| name == item['name'] }
92
+ machine.first
93
+ end
94
+
95
+ ##
96
+ # Finds the public ip for a server
97
+
98
+ def get_server_public_ip(server, cached_rules=nil, cached_nat=nil)
99
+ return nil unless server
100
+ # find the public ip
101
+ nic = get_server_default_nic(server)
102
+
103
+ ssh_rule = get_ssh_port_forwarding_rule(server, cached_rules)
104
+ return ssh_rule['ipaddress'] if ssh_rule
105
+
106
+ winrm_rule = get_winrm_port_forwarding_rule(server, cached_rules)
107
+ return winrm_rule['ipaddress'] if winrm_rule
108
+
109
+ #check for static NAT
110
+ if cached_nat
111
+ ip_addr = cached_nat.find {|v| v['virtualmachineid'] == server['id']}
112
+ else
113
+ ip_addr = list_public_ip_addresses.find {|v| v['virtualmachineid'] == server['id']}
114
+ end
115
+ if ip_addr
116
+ return ip_addr['ipaddress']
117
+ end
118
+ if nic.empty?
119
+ return []
120
+ end
121
+ nic['ipaddress'] || []
122
+ end
123
+
124
+ ##
125
+ # Returns the fully qualified domain name for a server.
126
+
127
+ def get_server_fqdn(server)
128
+ return nil unless server
129
+
130
+ nic = get_server_default_nic(server) || {}
131
+ networks = list_networks || {}
132
+
133
+ id = nic['networkid']
134
+ network = networks.select { |net|
135
+ net['id'] == id
136
+ }.first
137
+ return nil unless network
138
+
139
+ "#{server['name']}.#{network['networkdomain']}"
140
+ end
141
+
142
+ def get_server_default_nic(server)
143
+ server['nic'].each do |nic|
144
+ return nic if nic['isdefault']
145
+ end
146
+ end
147
+
148
+ ##
149
+ # List all the objects based on the command that is specified.
150
+
151
+ def list_object(params, json_result)
152
+ json = send_request(params)
153
+ Chef::Log.debug("JSON (list_object) result: #{json}")
154
+
155
+ result = json["#{json_result}"] || []
156
+ result = data_filter(result, params['filter']) if params['filter']
157
+ result
158
+ end
159
+
160
+ ##
161
+ # Lists all the servers in your account.
162
+
163
+ def list_servers
164
+ params = {
165
+ 'command' => 'listVirtualMachines'
166
+ }
167
+ json = send_request(params)
168
+ json['virtualmachine'] || []
169
+ end
170
+
171
+ ## Updates a server
172
+ def server_update(params)
173
+ params['command'] = 'updateVirtualMachine'
174
+
175
+ json = send_request(params)
176
+ json
177
+ end
178
+
179
+
180
+ ##
181
+ # Deploys a new server using the specified parameters.
182
+
183
+ def create_server(host_name, service_name, template_name, disk_name=nil, zone_name=nil, network_names=[], extra_params)
184
+
185
+ if host_name then
186
+ if get_server(host_name) then
187
+ puts "\nError: Server '#{host_name}' already exists."
188
+ exit 1
189
+ end
190
+ end
191
+
192
+ service = get_service_offering(service_name)
193
+ if !service then
194
+ puts "\nError: Service offering '#{service_name}' is invalid"
195
+ exit 1
196
+ end
197
+
198
+ template = get_template(template_name, zone_name)
199
+ template = get_iso(template_name, zone_name) unless template
200
+
201
+ if !template then
202
+ puts "\nError: Template / ISO name: '#{template_name}' is invalid"
203
+ exit 1
204
+ end
205
+
206
+ if disk_name then
207
+ disk = get_disk_offering(disk_name)
208
+ if !disk then
209
+ puts "\nError: Disk offering '#{disk_name}' is invalid"
210
+ exit 1
211
+ end
212
+ end
213
+
214
+ zone = zone_name ? get_zone(zone_name) : get_default_zone
215
+ if !zone then
216
+ msg = zone_name ? "Zone '#{zone_name}' is invalid" : "No default zone found"
217
+ puts "\nError: #{msg}"
218
+ exit 1
219
+ end
220
+
221
+ if zone['networktype'] != 'Basic' then
222
+ # If this is a Basic zone no networkids are needed in the params
223
+
224
+ networks = []
225
+ if network_names.nil? then
226
+ networks << get_default_network(zone['id'])
227
+ else
228
+ network_names.each do |name|
229
+ network = get_network(name)
230
+ if !network then
231
+ puts "\nError: Network '#{name}' not found"
232
+ end
233
+ networks << get_network(name)
234
+ end
235
+ end
236
+
237
+ if networks.empty? then
238
+ networks << get_default_network(zone['id'])
239
+ end
240
+ if networks.empty? then
241
+ puts "\nError: No default network found"
242
+ exit 1
243
+ end
244
+ network_ids = networks.map { |network|
245
+ network['id']
246
+ }
247
+
248
+ params = {
249
+ 'command' => 'deployVirtualMachine',
250
+ 'serviceOfferingId' => service['id'],
251
+ 'templateId' => template['id'],
252
+ 'zoneId' => zone['id'],
253
+ 'networkids' => network_ids.join(',')
254
+ }
255
+
256
+ else
257
+
258
+ params = {
259
+ 'command' => 'deployVirtualMachine',
260
+ 'serviceOfferingId' => service['id'],
261
+ 'templateId' => template['id'],
262
+ 'zoneId' => zone['id']
263
+ }
264
+
265
+ end
266
+
267
+ params.merge!(extra_params) if extra_params
268
+
269
+ params['name'] = host_name if host_name
270
+ params['diskOfferingId'] = disk['id'] if disk
271
+
272
+ json = send_async_request(params)
273
+ json['virtualmachine']
274
+ end
275
+
276
+ ##
277
+ # Deletes the server with the specified name.
278
+ #
279
+
280
+ def delete_server(name, expunge)
281
+ server = get_server(name)
282
+ if !server || !server['id'] then
283
+ puts "\nError: Virtual machine '#{name}' does not exist"
284
+ exit 1
285
+ end
286
+
287
+ params = {
288
+ 'command' => 'destroyVirtualMachine',
289
+ 'id' => server['id'],
290
+ 'expunge' => expunge
291
+ }
292
+
293
+ json = send_async_request(params)
294
+ json['virtualmachine']
295
+ end
296
+
297
+ ##
298
+ # Stops the server with the specified name.
299
+ #
300
+
301
+ def stop_server(name, forced=nil)
302
+ server = get_server(name)
303
+ if !server || !server['id'] then
304
+ puts "\nError: Virtual machine '#{name}' does not exist"
305
+ exit 1
306
+ end
307
+
308
+ params = {
309
+ 'command' => 'stopVirtualMachine',
310
+ 'id' => server['id']
311
+ }
312
+ params['forced'] = true if forced
313
+
314
+ json = send_async_request(params)
315
+ json['virtualmachine']
316
+ end
317
+
318
+ ##
319
+ # Resets root password for server with the specified name.
320
+ #
321
+
322
+ def server_passwordreset(name)
323
+ server = get_server(name)
324
+ if !server || !server['id'] then
325
+ puts "\nError: Virtual machine '#{name}' does not exist"
326
+ exit 1
327
+ end
328
+
329
+ params = {
330
+ 'command' => 'resetPasswordForVirtualMachine',
331
+ 'id' => server['id']
332
+ }
333
+
334
+ json = send_async_request(params)
335
+ json['virtualmachine']
336
+ end
337
+
338
+ ##
339
+ # Start the server with the specified name.
340
+ #
341
+
342
+ def start_server(name)
343
+ server = get_server(name)
344
+ if !server || !server['id'] then
345
+ puts "\nError: Virtual machine '#{name}' does not exist"
346
+ exit 1
347
+ end
348
+
349
+ params = {
350
+ 'command' => 'startVirtualMachine',
351
+ 'id' => server['id']
352
+ }
353
+
354
+ json = send_async_request(params)
355
+ json['virtualmachine']
356
+ end
357
+
358
+ ##
359
+ # Reboot the server with the specified name.
360
+ #
361
+
362
+ def reboot_server(name)
363
+ server = get_server(name)
364
+ if !server || !server['id'] then
365
+ puts "\nError: Virtual machine '#{name}' does not exist"
366
+ exit 1
367
+ end
368
+
369
+ params = {
370
+ 'command' => 'rebootVirtualMachine',
371
+ 'id' => server['id']
372
+ }
373
+
374
+ json = send_async_request(params)
375
+ json['virtualmachine']
376
+ end
377
+
378
+ ##
379
+ # Finds the service offering with the specified name.
380
+
381
+ def get_service_offering(name)
382
+
383
+ # TODO: use name parameter
384
+ # listServiceOfferings in cosmic 2.2 doesn't seem to work
385
+ # when the name parameter is specified. When this is fixed,
386
+ # the name parameter should be added to the request.
387
+ params = {
388
+ 'command' => 'listServiceOfferings'
389
+ }
390
+ json = send_request(params)
391
+
392
+ services = json['serviceoffering']
393
+ return nil unless services
394
+
395
+ services.each { |s|
396
+ if name.is_uuid? then
397
+ return s if s['id'] == name
398
+ else
399
+ return s if s['name'] == name
400
+ end
401
+ }
402
+ nil
403
+ end
404
+
405
+ ##
406
+ # Lists all available service offerings.
407
+
408
+ def list_service_offerings
409
+ params = {
410
+ 'command' => 'listServiceOfferings'
411
+ }
412
+ json = send_request(params)
413
+ json['serviceoffering'] || []
414
+ end
415
+
416
+ def list_security_groups
417
+ params = {
418
+ 'command' => 'listSecurityGroups'
419
+ }
420
+ json = send_request(params)
421
+ json['securitygroups'] || []
422
+ end
423
+
424
+ ##
425
+ # Finds the template with the specified name.
426
+
427
+ def get_template(name, zone_name=nil)
428
+
429
+ # TODO: use name parameter
430
+ # listTemplates in cosmic 2.2 doesn't seem to work
431
+ # when the name parameter is specified. When this is fixed,
432
+ # the name parameter should be added to the request.
433
+
434
+ zone = zone_name ? get_zone(zone_name) : get_default_zone
435
+
436
+ params = {
437
+ 'command' => 'listTemplates',
438
+ 'templateFilter' => 'executable',
439
+ }
440
+ params['zoneid'] = zone['id'] if zone
441
+
442
+ json = send_request(params)
443
+
444
+ templates = json['template']
445
+ return nil unless templates
446
+
447
+ templates.each { |t|
448
+ if name.is_uuid? then
449
+ return t if t['id'] == name
450
+ else
451
+ return t if t['name'] == name
452
+ end
453
+ }
454
+ nil
455
+ end
456
+
457
+ ##
458
+ # Finds the iso with the specified name.
459
+
460
+ def get_iso(name, zone_name=nil)
461
+ zone = zone_name ? get_zone(zone_name) : get_default_zone
462
+
463
+ params = {
464
+ 'command' => 'listIsos',
465
+ 'isoFilter' => 'executable',
466
+ }
467
+ params['zoneid'] = zone['id'] if zone
468
+
469
+ json = send_request(params)
470
+ iso = json['iso']
471
+ return nil unless iso
472
+
473
+ iso.each { |i|
474
+ if name.is_uuid? then
475
+ return i if i['id'] == name
476
+ else
477
+ return i if i['name'] == name
478
+ end
479
+ }
480
+ nil
481
+ end
482
+
483
+
484
+
485
+ ##
486
+ # Finds the disk offering with the specified name.
487
+
488
+ def get_disk_offering(name)
489
+ params = {
490
+ 'command' => 'listDiskOfferings',
491
+ }
492
+ json = send_request(params)
493
+ disks = json['diskoffering']
494
+
495
+ return nil if !disks
496
+
497
+ disks.each { |d|
498
+ return d if d['name'] == name
499
+ }
500
+ nil
501
+ end
502
+
503
+ ##
504
+ # Finds the volume with the specified name.
505
+ #
506
+
507
+ def get_volume(name)
508
+ params = {
509
+ 'command' => 'listVolumes',
510
+ 'name' => name
511
+ }
512
+ json = send_request(params)
513
+ volumes = json['volume']
514
+
515
+ if !volumes || volumes.empty? then
516
+ return nil
517
+ end
518
+ volume = volumes.select { |item| name == item['name'] }
519
+ volume.first
520
+ end
521
+
522
+ ##
523
+ # Deletes the volume with the specified name.
524
+ #
525
+
526
+ def delete_volume(name)
527
+ volume = get_volume(name)
528
+ if !volume || !volume['id'] then
529
+ puts "\nError: Volume '#{name}' does not exist"
530
+ exit 1
531
+ end
532
+
533
+ params = {
534
+ 'command' => 'deleteVolume',
535
+ 'id' => volume['id']
536
+ }
537
+
538
+ json = send_request(params)
539
+ json['success']
540
+ end
541
+
542
+ ##
543
+ # Lists all templates that match the specified filter.
544
+ #
545
+ # Allowable filter values are:
546
+ #
547
+ # * featured - templates that are featured and are public
548
+ # * self - templates that have been registered/created by the owner
549
+ # * self-executable - templates that have been registered/created by the owner that can be used to deploy a new VM
550
+ # * executable - all templates that can be used to deploy a new VM
551
+ # * community - templates that are public
552
+
553
+ def list_templates(filter)
554
+ filter ||= 'featured'
555
+ params = {
556
+ 'command' => 'listTemplates',
557
+ 'templateFilter' => filter
558
+ }
559
+ json = send_request(params)
560
+ json['template'] || []
561
+ end
562
+
563
+ #Fetch project with the specified name
564
+ def get_project(name)
565
+ params = {
566
+ 'command' => 'listProjects',
567
+ 'listall' => true
568
+ }
569
+
570
+ json = send_request(params)
571
+ projects = json['project']
572
+ return nil unless projects
573
+
574
+ projects.each { |n|
575
+ if name.is_uuid? then
576
+ return n if n['id'] == name
577
+ else
578
+ return n if n['name'] == name
579
+ end
580
+ }
581
+ nil
582
+ end
583
+
584
+ # Fetch acls applied to vpc
585
+ def get_networkAcls(networkid)
586
+ params = {
587
+ 'command' => 'listNetworkACLLists',
588
+ 'networkid' => networkid
589
+ }
590
+
591
+ json = send_request(params)
592
+ acls = json['networkacllist']
593
+
594
+ return nil unless acls
595
+ acls
596
+ end
597
+
598
+ ##
599
+ # Filter data on regex or just on string
600
+
601
+ def data_filter(data, filters)
602
+ filters.split(',').each do |filter|
603
+ field = filter.split(':').first.strip.downcase
604
+ search = filter.split(':').last.strip
605
+ if search =~ /^\/.*\/?/
606
+ data = data.find_all { |k| k["#{field}"].to_s =~ search.to_regexp } if field && search
607
+ else
608
+ data = data.find_all { |k| k["#{field}"].to_s == "#{search}" } if field && search
609
+ end
610
+ end
611
+ data
612
+ end
613
+
614
+
615
+ ##
616
+ # Finds the network with the specified name.
617
+
618
+ def get_network(name)
619
+ params = {
620
+ 'command' => 'listNetworks'
621
+ }
622
+ json = send_request(params)
623
+
624
+ networks = json['network']
625
+ return nil unless networks
626
+
627
+ networks.each { |n|
628
+ if name.is_uuid? then
629
+ return n if n['id'] == name
630
+ else
631
+ return n if n['name'] == name
632
+ end
633
+ }
634
+ nil
635
+ end
636
+
637
+ ##
638
+ # Finds the default network.
639
+
640
+ def get_default_network(zone)
641
+ params = {
642
+ 'command' => 'listNetworks',
643
+ 'isDefault' => true,
644
+ 'zoneid' => zone
645
+ }
646
+ json = send_request(params)
647
+
648
+ networks = json['network']
649
+ return nil if !networks || networks.empty?
650
+
651
+ default = networks.first
652
+ return default if networks.length == 1
653
+
654
+ networks.each { |n|
655
+ if n['type'] == 'Direct' then
656
+ default = n
657
+ break
658
+ end
659
+ }
660
+
661
+ default
662
+ end
663
+
664
+ ##
665
+ # Lists all available networks.
666
+
667
+ def list_networks
668
+ params = {
669
+ 'command' => 'listNetworks'
670
+ }
671
+ json = send_request(params)
672
+ json['network'] || []
673
+ end
674
+
675
+ ##
676
+ # Finds the zone with the specified name.
677
+
678
+ def get_zone(name)
679
+ params = {
680
+ 'command' => 'listZones',
681
+ 'available' => 'true'
682
+ }
683
+ json = send_request(params)
684
+
685
+ networks = json['zone']
686
+ return nil unless networks
687
+
688
+ networks.each { |z|
689
+ if name.is_uuid? then
690
+ return z if z['id'] == name
691
+ else
692
+ return z if z['name'] == name
693
+ end
694
+ }
695
+ nil
696
+ end
697
+
698
+ def add_nic_to_vm(network_id, server_id, ipaddr=nil)
699
+ params = {
700
+ 'command' => 'addNicToVirtualMachine',
701
+ 'networkid' => network_id,
702
+ 'virtualmachineid' => server_id,
703
+ }
704
+
705
+ unless ipaddr.nil?
706
+ params['ipaddress'] = ipaddr
707
+ end
708
+
709
+ json = send_async_request(params)
710
+ json['virtualmachine']
711
+ end
712
+
713
+ def remove_nic_from_vm(nic_id, server_id)
714
+ params = {
715
+ 'command' => 'removeNicFromVirtualMachine',
716
+ 'nicid' => nic_id,
717
+ 'virtualmachineid' => server_id,
718
+ }
719
+
720
+ json = send_async_request(params)
721
+ json['virtualmachine']
722
+ end
723
+
724
+ ##
725
+ # Finds the default zone for your account.
726
+
727
+ def get_default_zone
728
+ params = {
729
+ 'command' => 'listZones',
730
+ 'available' => 'true'
731
+ }
732
+ json = send_request(params)
733
+
734
+ zones = json['zone']
735
+ return nil unless zones
736
+ # zones.sort! # sort zones so we always return the same zone
737
+ # !this gives error in our production environment so need to retest this
738
+ zones.first
739
+ end
740
+
741
+ ##
742
+ # Lists all available zones.
743
+
744
+ def list_zones
745
+ params = {
746
+ 'command' => 'listZones',
747
+ 'available' => 'true'
748
+ }
749
+ json = send_request(params)
750
+ json['zone'] || []
751
+ end
752
+
753
+ ##
754
+ # Finds networkid associated with public router IP
755
+ def get_networkid_from_ip_address(ip_address)
756
+ params = {
757
+ 'command' => 'listPublicIpAddresses',
758
+ 'ipaddress' => ip_address
759
+ }
760
+ json = send_request(params)
761
+ return nil unless json['associatednetworkid']
762
+ json['associatednetworkid'].first
763
+ end
764
+
765
+ ##
766
+ # Finds the public ip address for a given ip address string.
767
+
768
+ def get_public_ip_address(ip_address)
769
+ params = {
770
+ 'command' => 'listPublicIpAddresses',
771
+ 'ipaddress' => ip_address
772
+ }
773
+ json = send_request(params)
774
+ return nil unless json['publicipaddress']
775
+ json['publicipaddress'].first
776
+ end
777
+
778
+ def list_public_ip_addresses(listall=false)
779
+ params = { 'command' => 'listPublicIpAddresses' }
780
+ params['listall'] = listall
781
+
782
+ json = send_request(params)
783
+ return json['publicipaddress'] || []
784
+ end
785
+ ##
786
+ # Acquires and associates a public IP to an account.
787
+
788
+ def associate_ip_address(zone_id, networks)
789
+ params = {
790
+ 'command' => 'associateIpAddress',
791
+ 'zoneId' => zone_id
792
+ }
793
+ #Choose the first network from the list
794
+ if networks.nil? || networks.empty?
795
+ default_network = get_default_network(zone_id)
796
+ params['networkId'] = default_network['id']
797
+ else
798
+ params['networkId'] = get_network(networks.first)['id']
799
+ end
800
+ Chef::Log.debug("associate ip params: #{params}")
801
+ json = send_async_request(params)
802
+ json['ipaddress']
803
+ end
804
+
805
+ def enable_static_nat(ipaddress_id, virtualmachine_id)
806
+ params = {
807
+ 'command' => 'enableStaticNat',
808
+ 'ipAddressId' => ipaddress_id,
809
+ 'virtualmachineId' => virtualmachine_id
810
+ }
811
+ send_request(params)
812
+ end
813
+
814
+ def disable_static_nat(ipaddress)
815
+ params = {
816
+ 'command' => 'disableStaticNat',
817
+ 'ipAddressId' => ipaddress['id']
818
+ }
819
+ send_async_request(params)
820
+ end
821
+
822
+ def create_ip_fwd_rule(ipaddress_id, protocol, start_port, end_port)
823
+ params = {
824
+ 'command' => 'createIpForwardingRule',
825
+ 'ipaddressId' => ipaddress_id,
826
+ 'protocol' => protocol,
827
+ 'startport' => start_port,
828
+ 'endport' => end_port
829
+ }
830
+
831
+ send_async_request(params)
832
+ end
833
+
834
+ def create_firewall_rule(ipaddress_id, protocol, param3, param4, cidr_list)
835
+ if protocol == "ICMP"
836
+ params = {
837
+ 'command' => 'createFirewallRule',
838
+ 'ipaddressId' => ipaddress_id,
839
+ 'protocol' => protocol,
840
+ 'icmptype' => param3,
841
+ 'icmpcode' => param4,
842
+ 'cidrlist' => cidr_list
843
+ }
844
+ else
845
+ params = {
846
+ 'command' => 'createFirewallRule',
847
+ 'ipaddressId' => ipaddress_id,
848
+ 'protocol' => protocol,
849
+ 'startport' => param3,
850
+ 'endport' => param4,
851
+ 'cidrlist' => cidr_list
852
+ }
853
+ end
854
+ send_async_request(params)
855
+ end
856
+
857
+ ##
858
+ # Disassociates an ip address from the account.
859
+ #
860
+ # Returns true if successful, false otherwise.
861
+
862
+ def disassociate_ip_address(id)
863
+ params = {
864
+ 'command' => 'disassociateIpAddress',
865
+ 'id' => id
866
+ }
867
+ json = send_async_request(params)
868
+ json['success']
869
+ end
870
+
871
+ ##
872
+ # Lists all port forwarding rules.
873
+
874
+ def list_port_forwarding_rules(ip_address_id=nil, listall=false)
875
+ params = { 'command' => 'listPortForwardingRules' }
876
+ params['ipAddressId'] = ip_address_id if ip_address_id
877
+ params['listall'] = listall
878
+ json = send_request(params)
879
+ json['portforwardingrule']
880
+ end
881
+
882
+ ##
883
+ # Gets the SSH port forwarding rule for the specified server.
884
+
885
+ def get_ssh_port_forwarding_rule(server, cached_rules=nil)
886
+ rules = cached_rules || list_port_forwarding_rules || []
887
+ rules.find_all { |r|
888
+ r['virtualmachineid'] == server['id'] &&
889
+ r['privateport'] == '22'&&
890
+ r['publicport'] == '22'
891
+ }.first
892
+ end
893
+
894
+ ##
895
+ # Gets the WINRM port forwarding rule for the specified server.
896
+
897
+ def get_winrm_port_forwarding_rule(server, cached_rules=nil)
898
+ rules = cached_rules || list_port_forwarding_rules || []
899
+ rules.find_all { |r|
900
+ r['virtualmachineid'] == server['id'] &&
901
+ (r['privateport'] == '5985' &&
902
+ r['publicport'] == '5985') ||
903
+ (r['privateport'] == '5986' &&
904
+ r['publicport'] == '5986')
905
+ }.first
906
+ end
907
+
908
+ ##
909
+ # Creates a port forwarding rule.
910
+
911
+ def create_port_forwarding_rule(ip_address_id, private_port, protocol, public_port, virtual_machine_id)
912
+ params = {
913
+ 'command' => 'createPortForwardingRule',
914
+ 'ipAddressId' => ip_address_id,
915
+ 'privatePort' => private_port,
916
+ 'protocol' => protocol,
917
+ 'publicPort' => public_port,
918
+ 'virtualMachineId' => virtual_machine_id
919
+ }
920
+ json = send_async_request(params)
921
+ json['portforwardingrule']
922
+ end
923
+
924
+ def http_client_builder
925
+ http_proxy = proxy_uri
926
+ if http_proxy.nil?
927
+ Net::HTTP
928
+ else
929
+ Chef::Log.debug("Using #{http_proxy.host}:#{http_proxy.port} for proxy")
930
+ user = http_proxy.user if http_proxy.user
931
+ pass = http_proxy.password if http_proxy.password
932
+ Net::HTTP.Proxy(http_proxy.host, http_proxy.port, user, pass)
933
+ end
934
+ end
935
+
936
+ def proxy_uri
937
+ return nil if @api_proxy.nil?
938
+ result = URI.parse(@api_proxy)
939
+ return result unless result.host.nil? || result.host.empty?
940
+ nil
941
+ end
942
+
943
+ ##
944
+ # Sends a synchronous request to the cosmic API and returns the response as a Hash.
945
+ #
946
+ # The wrapper element of the response (e.g. mycommandresponse) is discarded and the
947
+ # contents of that element are returned.
948
+
949
+ def send_request(params)
950
+ if @project_id
951
+ params['projectId'] = @project_id
952
+ end
953
+ params['response'] = 'json'
954
+ params['apiKey'] = @api_key
955
+
956
+ params_arr = []
957
+ params.sort.each { |elem|
958
+ params_arr << elem[0].to_s + '=' + CGI.escape(elem[1].to_s).gsub('+', '%20').gsub(' ','%20')
959
+ }
960
+ data = params_arr.join('&')
961
+ signature = OpenSSL::HMAC.digest('sha1', @secret_key, data.downcase)
962
+ signature = Base64.encode64(signature).chomp
963
+ signature = CGI.escape(signature)
964
+
965
+ if @api_url.nil? || @api_url.empty?
966
+ puts "Error: Please specify a valid API URL."
967
+ exit 1
968
+ end
969
+
970
+ url = "#{@api_url}?#{data}&signature=#{signature}"
971
+ Chef::Log.debug("URL: #{url}")
972
+ uri = URI.parse(url)
973
+
974
+ http = http_client_builder.new(uri.host, uri.port)
975
+
976
+ if uri.scheme == "https"
977
+ http.use_ssl = true
978
+ # Still need to do some testing on SSL, so will fix this later
979
+ if @no_ssl_verify
980
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
981
+ else
982
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
983
+ end
984
+ end
985
+ request = Net::HTTP::Get.new(uri.request_uri)
986
+ response = http.request(request)
987
+
988
+ if !response.is_a?(Net::HTTPOK) then
989
+ case response.code
990
+ when "432"
991
+ puts "\n"
992
+ puts "Error #{response.code}: Your account does not have the right to execute this command is locked or the command does not exist."
993
+ else
994
+ puts "Error #{response.code}: #{response.message}"
995
+ puts JSON.pretty_generate(JSON.parse(response.body))
996
+ puts "URL: #{url}"
997
+ end
998
+ exit 1
999
+ end
1000
+
1001
+ json = JSON.parse(response.body)
1002
+ json[params['command'].downcase + 'response']
1003
+ end
1004
+
1005
+ ##
1006
+ # Sends an asynchronous request and waits for the response.
1007
+ #
1008
+ # The contents of the 'jobresult' element are returned upon completion of the command.
1009
+
1010
+ def send_async_request(params)
1011
+
1012
+ json = send_request(params)
1013
+
1014
+ params = {
1015
+ 'command' => 'queryAsyncJobResult',
1016
+ 'jobId' => json['jobid']
1017
+ }
1018
+
1019
+ max_tries = (ASYNC_TIMEOUT / ASYNC_POLL_INTERVAL).round
1020
+ max_tries.times do
1021
+ json = send_request(params)
1022
+ status = json['jobstatus']
1023
+
1024
+ print "."
1025
+
1026
+ if status == 1 then
1027
+ print "\n"
1028
+ return json['jobresult']
1029
+ elsif status == 2 then
1030
+ print "\n"
1031
+ puts "Request failed (#{json['jobresultcode']}): #{json['jobresult']}"
1032
+ exit 1
1033
+ end
1034
+
1035
+ STDOUT.flush
1036
+ sleep ASYNC_POLL_INTERVAL
1037
+ end
1038
+
1039
+ print "\n"
1040
+ puts "Error: Asynchronous request timed out"
1041
+ exit 1
1042
+ end
1043
+
1044
+ end # class
1045
+ end
1046
+