morpheus-cli 5.3.3 → 5.3.4

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +12 -0
  4. data/lib/morpheus/api/clouds_interface.rb +4 -11
  5. data/lib/morpheus/api/load_balancer_pools_interface.rb +4 -4
  6. data/lib/morpheus/api/load_balancer_profiles_interface.rb +10 -0
  7. data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +4 -4
  8. data/lib/morpheus/api/network_routers_interface.rb +21 -0
  9. data/lib/morpheus/api/network_servers_interface.rb +42 -0
  10. data/lib/morpheus/api/rest_interface.rb +2 -1
  11. data/lib/morpheus/api/virtual_servers_interface.rb +9 -0
  12. data/lib/morpheus/cli/cli_command.rb +2 -1
  13. data/lib/morpheus/cli/cloud_resource_pools_command.rb +1 -1
  14. data/lib/morpheus/cli/clouds.rb +22 -40
  15. data/lib/morpheus/cli/hosts.rb +0 -1
  16. data/lib/morpheus/cli/instances.rb +111 -7
  17. data/lib/morpheus/cli/invoices_command.rb +42 -38
  18. data/lib/morpheus/cli/library_option_lists_command.rb +3 -3
  19. data/lib/morpheus/cli/load_balancer_pools.rb +111 -0
  20. data/lib/morpheus/cli/load_balancer_virtual_servers.rb +136 -0
  21. data/lib/morpheus/cli/load_balancers.rb +0 -155
  22. data/lib/morpheus/cli/mixins/load_balancers_helper.rb +2 -2
  23. data/lib/morpheus/cli/mixins/provisioning_helper.rb +32 -11
  24. data/lib/morpheus/cli/mixins/rest_command.rb +53 -37
  25. data/lib/morpheus/cli/mixins/secondary_rest_command.rb +488 -0
  26. data/lib/morpheus/cli/network_routers_command.rb +291 -7
  27. data/lib/morpheus/cli/network_scopes_command.rb +442 -0
  28. data/lib/morpheus/cli/networks_command.rb +2 -2
  29. data/lib/morpheus/cli/option_types.rb +20 -0
  30. data/lib/morpheus/cli/subnets_command.rb +7 -2
  31. data/lib/morpheus/cli/tasks.rb +25 -2
  32. data/lib/morpheus/cli/version.rb +1 -1
  33. data/lib/morpheus/cli/virtual_images.rb +2 -0
  34. data/lib/morpheus/cli.rb +9 -1
  35. metadata +9 -2
@@ -0,0 +1,488 @@
1
+ # SecondaryRestCommand is a mixin for Morpheus::Cli command classes.
2
+ # for resources that are secondary to some parent resource.
3
+ # Provides basic CRUD commands: list, get, add, update, remove
4
+ # The parent resource is specified as the first argument for all the comments.
5
+ #
6
+ # Example of a SecondaryRestCommand for `morpheus load-balancer-virtual-servers`.
7
+ #
8
+ # class Morpheus::Cli::LoadBalancerVirtualServers
9
+ #
10
+ # include Morpheus::Cli::CliCommand
11
+ # include Morpheus::Cli::RestCommand
12
+ # include Morpheus::Cli::SecondaryRestCommand
13
+ # include Morpheus::Cli::LoadBalancersHelper
14
+ #
15
+ # set_command_name :'load-balancer-virtual-servers'
16
+ # register_subcommands :list, :get, :add, :update, :remove
17
+ #
18
+ # register_interfaces :load_balancer_virtual_servers,
19
+ # :load_balancers, :load_balancer_types
20
+ #
21
+ # set_rest_parent_name :load_balancers
22
+ #
23
+ # end
24
+ #
25
+ module Morpheus::Cli::SecondaryRestCommand
26
+ def self.included(base)
27
+ base.extend ClassMethods
28
+ end
29
+
30
+ module ClassMethods
31
+
32
+ ## duplicated the rest_* settings with rest_parent_*, for defining the parent resource
33
+
34
+ # rest_parent_name is the rest_name for the parent
35
+ def rest_parent_name
36
+ @rest_parent_name || default_rest_parent_name
37
+ end
38
+
39
+ def default_rest_parent_name
40
+ words = rest_name.split("_")
41
+ if words.size > 1
42
+ words.pop
43
+ return words.join("_") + "s"
44
+ else
45
+ # this wont happen, default wont make sense in this scenario
46
+ # "parent_" + rest_name
47
+ raise "Unable to determine default_rest_parent_name for rest_name: #{rest_name}, class: #{self}"
48
+ end
49
+ end
50
+
51
+ def rest_parent_name=(v)
52
+ @rest_parent_name = v.to_s
53
+ end
54
+
55
+ alias :set_rest_parent_name :rest_parent_name=
56
+ alias :set_rest_parent :rest_parent_name=
57
+ #alias :rest_parent= :rest_parent_name=
58
+
59
+ # rest_parent_key is the singular name of the resource eg. "neat_thing"
60
+ def rest_parent_key
61
+ @rest_parent_key || default_rest_parent_key
62
+ end
63
+
64
+ def default_rest_parent_key
65
+ rest_parent_name.chomp("s")
66
+ end
67
+
68
+ def rest_parent_key=(v)
69
+ @rest_parent_key = v.to_s
70
+ end
71
+
72
+ alias :set_rest_parent_key :rest_parent_key=
73
+
74
+ def rest_parent_arg
75
+ @rest_parent_arg || default_rest_parent_arg
76
+ end
77
+
78
+ def default_rest_parent_arg
79
+ rest_parent_key.to_s.gsub("_", " ")
80
+ end
81
+
82
+ def rest_parent_arg=(v)
83
+ @rest_parent_arg = v.to_s
84
+ end
85
+
86
+ alias :set_rest_parent_arg :rest_parent_arg=
87
+
88
+ # rest_parent_label is the capitalized resource label eg. "Neat Thing"
89
+ def rest_parent_label
90
+ @rest_parent_label || default_rest_parent_label
91
+ end
92
+
93
+ def default_rest_parent_label
94
+ rest_parent_key.to_s.split("_").collect {|it| it.to_s.capitalize }.join(" ")
95
+ end
96
+
97
+ def rest_parent_label=(v)
98
+ @rest_parent_label = v.to_s
99
+ end
100
+
101
+ alias :set_rest_parent_label :rest_parent_label=
102
+
103
+ # the plural version of the label eg. "Neat Things"
104
+ def rest_parent_label_plural
105
+ @rest_parent_label_plural || default_rest_parent_label_plural
106
+ end
107
+
108
+ def default_rest_parent_label_plural
109
+ #rest_parent_name.to_s.split("_").collect {|it| it.to_s.capitalize }.join(" ")
110
+ rest_parent_label.to_s.pluralize
111
+ end
112
+
113
+ def rest_parent_label_plural=(v)
114
+ @rest_parent_label_plural = v.to_s
115
+ end
116
+
117
+ alias :set_rest_parent_label_plural :rest_parent_label_plural=
118
+
119
+ # the name of the default interface, matches the rest name eg. "neat_things"
120
+ def rest_parent_interface_name
121
+ @rest_parent_interface_name || default_rest_parent_interface_name
122
+ end
123
+
124
+ def default_rest_parent_interface_name
125
+ rest_parent_name
126
+ end
127
+
128
+ def rest_parent_interface_name=(v)
129
+ @rest_parent_interface_name = v.to_s
130
+ end
131
+
132
+ alias :set_rest_parent_interface_name :rest_parent_interface_name=
133
+
134
+ end
135
+
136
+ ## duplicated the rest_* settings with rest_parent, for the parents resource
137
+
138
+ def rest_parent_name
139
+ self.class.rest_parent_name
140
+ end
141
+
142
+ def rest_parent_key
143
+ self.class.rest_parent_key
144
+ end
145
+
146
+ def rest_parent_arg
147
+ self.class.rest_parent_arg
148
+ end
149
+
150
+ def rest_parent_label
151
+ self.class.rest_parent_label
152
+ end
153
+
154
+ def rest_parent_label_plural
155
+ self.class.rest_parent_label_plural
156
+ end
157
+
158
+ def rest_parent_interface_name
159
+ self.class.rest_parent_interface_name # || "@#{rest_parent_name}_interface"
160
+ end
161
+
162
+ def rest_parent_interface
163
+ instance_variable_get("@#{rest_parent_interface_name}_interface")
164
+ end
165
+
166
+ def rest_parent_object_key
167
+ self.send("#{rest_parent_key}_object_key")
168
+ end
169
+
170
+ def rest_parent_list_key
171
+ self.send("#{rest_parent_key}_list_key")
172
+ end
173
+
174
+ def rest_parent_column_definitions
175
+ self.send("#{rest_parent_key}_column_definitions")
176
+ end
177
+
178
+ def rest_parent_list_column_definitions
179
+ self.send("#{rest_parent_key}_list_column_definitions")
180
+ end
181
+
182
+ def rest_parent_find_by_name_or_id(name)
183
+ return self.send("find_#{rest_parent_key}_by_name_or_id", name)
184
+ end
185
+
186
+ def registered_interfaces
187
+ self.class.registered_interfaces
188
+ end
189
+
190
+ def list(args)
191
+ parent_id, parent_record = nil, nil
192
+ params = {}
193
+ options = {}
194
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
195
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [search]")
196
+ build_standard_list_options(opts, options)
197
+ opts.footer = <<-EOT
198
+ List #{rest_label_plural.downcase}.
199
+ [#{rest_parent_arg}] is required. This is the name or id of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
200
+ [search] is optional. This is a search phrase to filter the results.
201
+ EOT
202
+ end
203
+ optparse.parse!(args)
204
+ parent_id = args[0]
205
+ if args[1] # && rest_has_name
206
+ record_name = args[1]
207
+ end
208
+ verify_args!(args:args, optparse:optparse, min:1)
209
+ if args.count > 1
210
+ options[:phrase] = args[1..-1].join(" ")
211
+ end
212
+ connect(options)
213
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
214
+ if parent_record.nil?
215
+ raise_command_error "#{rest_parent_label} not found for '#{parent_id}'.\n#{optparse}"
216
+ end
217
+ parent_id = parent_record['id']
218
+ params.merge!(parse_list_options(options))
219
+ rest_interface.setopts(options)
220
+ if options[:dry_run]
221
+ print_dry_run rest_interface.dry.list(parent_id, params)
222
+ return
223
+ end
224
+ json_response = rest_interface.list(parent_id, params)
225
+ render_response(json_response, options, rest_list_key) do
226
+ records = json_response[rest_list_key]
227
+ print_h1 "Morpheus #{rest_label_plural}"
228
+ if records.nil? || records.empty?
229
+ print cyan,"No #{rest_label_plural.downcase} found.",reset,"\n"
230
+ else
231
+ print as_pretty_table(records, rest_list_column_definitions.upcase_keys!, options)
232
+ print_results_pagination(json_response) if json_response['meta']
233
+ end
234
+ print reset,"\n"
235
+ end
236
+ return 0, nil
237
+ end
238
+
239
+ def get(args)
240
+ params = {}
241
+ options = {}
242
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
243
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
244
+ build_standard_get_options(opts, options)
245
+ opts.footer = <<-EOT
246
+ Get details about #{a_or_an(rest_label)} #{rest_label.downcase}.
247
+ [#{rest_parent_arg}] is required. This is the name or id of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
248
+ [#{rest_arg}] is required. This is the name or id of #{a_or_an(rest_label)} #{rest_label.downcase}.
249
+ EOT
250
+ end
251
+ optparse.parse!(args)
252
+ verify_args!(args:args, optparse:optparse, min:2)
253
+ connect(options)
254
+ parent_id = args[0]
255
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
256
+ if parent_record.nil?
257
+ raise_command_error "#{rest_parent_label} not found for '#{parent_id}'.\n#{optparse}"
258
+ end
259
+ parent_id = parent_record['id']
260
+ params.merge!(parse_query_options(options))
261
+ id_list = parse_id_list(args[1..-1])
262
+ return run_command_for_each_arg(id_list) do |arg|
263
+ _get(parent_id, arg, params, options)
264
+ end
265
+ end
266
+
267
+ def _get(parent_id, id, params, options)
268
+ if id !~ /\A\d{1,}\Z/
269
+ record = rest_find_by_name_or_id(id)
270
+ if record.nil?
271
+ raise_command_error "#{rest_label} not found for name '#{id}'"
272
+ end
273
+ id = record['id']
274
+ end
275
+ rest_interface.setopts(options)
276
+ if options[:dry_run]
277
+ print_dry_run rest_interface.dry.get(id, params)
278
+ return
279
+ end
280
+ json_response = rest_interface.get(id, params)
281
+ render_response_for_get(json_response, options)
282
+ return 0, nil
283
+ end
284
+
285
+ def render_response_for_get(json_response, options)
286
+ render_response(json_response, options, rest_object_key) do
287
+ record = json_response[rest_object_key]
288
+ print_h1 rest_label, [], options
289
+ print cyan
290
+ print_description_list(rest_column_definitions, record, options)
291
+ # show config settings...
292
+ if record['optionTypes'] && record['optionTypes'].size > 0
293
+ print_h2 "Option Types", options
294
+ print format_option_types_table(record['optionTypes'], options, rest_object_key)
295
+ end
296
+ print reset,"\n"
297
+ end
298
+ end
299
+
300
+ def add(args)
301
+ parent_id, parent_record = nil, nil
302
+ record_type_id = nil
303
+ options = {}
304
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
305
+ if rest_has_type
306
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}] -t TYPE")
307
+ opts.on( '-t', "--#{rest_type_arg} TYPE", "#{rest_type_label}" ) do |val|
308
+ record_type_id = val
309
+ end
310
+ else
311
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
312
+ end
313
+ # if defined?(add_#{rest_key}_option_types)
314
+ # build_option_type_options(opts, options, add_#{rest_key}_option_types)
315
+ # end
316
+ build_standard_add_options(opts, options)
317
+ opts.footer = <<-EOT
318
+ Create a new #{rest_label.downcase}.
319
+ [#{rest_parent_arg}] is required. This is the name or id of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
320
+ [#{rest_arg}] is required. This is the name of the new #{rest_label.downcase}.
321
+ EOT
322
+ end
323
+ optparse.parse!(args)
324
+ verify_args!(args:args, optparse:optparse, min:1, max: 2)
325
+ # todo: make supporting args[0] optional and more flexible
326
+ # for now args[0] is assumed to be the 'name'
327
+ record_name = nil
328
+ parent_id = args[0]
329
+ if args[1] # && rest_has_name
330
+ record_name = args[1]
331
+ end
332
+ # todo: maybe need a flag to make this required, it could be an option type too, so
333
+ if rest_has_type
334
+ if record_type_id.nil?
335
+ raise_command_error "#{rest_type_label} is required.\n#{optparse}"
336
+ end
337
+ end
338
+ connect(options)
339
+ if rest_has_type
340
+ record_type = rest_type_find_by_name_or_id(record_type_id)
341
+ if record_type.nil?
342
+ raise_command_error "#{rest_type_label} not found for '#{record_type_id}'.\n#{optparse}"
343
+ end
344
+ end
345
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
346
+ if parent_record.nil?
347
+ raise_command_error "#{rest_parent_label} not found for '#{parent_id}'.\n#{optparse}"
348
+ end
349
+ parent_id = parent_record['id']
350
+ passed_options = parse_passed_options(options)
351
+ payload = {}
352
+ if options[:payload]
353
+ payload = options[:payload]
354
+ payload.deep_merge!({rest_object_key => passed_options})
355
+ else
356
+ record_payload = {}
357
+ if record_name
358
+ record_payload['name'] = record_name
359
+ options[:options]['name'] = record_name # injected for prompt
360
+ end
361
+ if rest_has_type && record_type
362
+ # record_payload['type'] = {'code' => record_type['code']}
363
+ record_payload['type'] = record_type['code']
364
+ options[:options]['type'] = record_type['code'] # injected for prompt
365
+ end
366
+ record_payload.deep_merge!(passed_options)
367
+ # options by type
368
+ my_option_types = record_type ? record_type['optionTypes'] : nil
369
+ if my_option_types && !my_option_types.empty?
370
+ # remove redundant fieldContext
371
+ my_option_types.each do |option_type|
372
+ if option_type['fieldContext'] == rest_object_key
373
+ option_type['fieldContext'] = nil
374
+ end
375
+ end
376
+ v_prompt = Morpheus::Cli::OptionTypes.prompt(my_option_types, options[:options], @api_client, options[:params])
377
+ v_prompt.deep_compact!
378
+ v_prompt.booleanize! # 'on' => true
379
+ record_payload.deep_merge!(v_prompt)
380
+ end
381
+ payload[rest_object_key] = record_payload
382
+ end
383
+ rest_interface.setopts(options)
384
+ if options[:dry_run]
385
+ print_dry_run rest_interface.dry.create(parent_id, payload)
386
+ return
387
+ end
388
+ json_response = rest_interface.create(parent_id, payload)
389
+ render_response(json_response, options, rest_object_key) do
390
+ record = json_response[rest_object_key]
391
+ print_green_success "Added #{rest_label.downcase} #{record['name'] || record['id']}"
392
+ return _get(parent_id, record["id"], {}, options)
393
+ end
394
+ return 0, nil
395
+ end
396
+
397
+ def update(args)
398
+ parent_id = args[0]
399
+ id = args[1]
400
+ options = {}
401
+ params = {}
402
+ account_name = nil
403
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
404
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}] [options]")
405
+ build_standard_update_options(opts, options)
406
+ opts.footer = <<-EOT
407
+ Update an existing #{rest_label.downcase}.
408
+ [#{rest_parent_arg}] is required. This is the name or id of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
409
+ [#{rest_arg}] is required. This is the name or id of #{a_or_an(rest_label)} #{rest_label.downcase}.
410
+ EOT
411
+ end
412
+ optparse.parse!(args)
413
+ verify_args!(args:args, optparse:optparse, count:2)
414
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
415
+ if parent_record.nil?
416
+ raise_command_error "#{rest_parent_label} not found for '#{parent_id}'.\n#{optparse}"
417
+ end
418
+ parent_id = parent_record['id']
419
+ connect(options)
420
+ record = rest_find_by_name_or_id(id)
421
+ passed_options = parse_passed_options(options)
422
+ payload = nil
423
+ if options[:payload]
424
+ payload = options[:payload]
425
+ payload.deep_merge!({rest_object_key => passed_options}) unless passed_options.empty?
426
+ else
427
+ record_payload = passed_options
428
+ if record_payload.empty?
429
+ raise_command_error "Specify at least one option to update.\n#{optparse}"
430
+ end
431
+ payload[rest_object_key] = record_payload
432
+ end
433
+ rest_interface.setopts(options)
434
+ if options[:dry_run]
435
+ print_dry_run rest_interface.dry.update(parent_id, record['id'], payload)
436
+ return
437
+ end
438
+ json_response = rest_interface.update(parent_id, record['id'], payload)
439
+ render_response(json_response, options, rest_object_key) do
440
+ print_green_success "Updated #{rest_label.downcase} #{record['name'] || record['id']}"
441
+ _get(parent_id, record["id"], {}, options)
442
+ end
443
+ return 0, nil
444
+ end
445
+
446
+ def remove(args)
447
+ parent_id = args[0]
448
+ id = args[1]
449
+ params = {}
450
+ options = {}
451
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
452
+ opts.banner = subcommand_usage("[#{rest_parent_arg}] [#{rest_arg}]")
453
+ build_standard_remove_options(opts, options)
454
+ opts.footer = <<-EOT
455
+ Delete an existing #{rest_label.downcase}.
456
+ [#{rest_parent_arg}] is required. This is the name or id of #{a_or_an(rest_parent_label)} #{rest_parent_label.downcase}.
457
+ [#{rest_arg}] is required. This is the name or id of #{a_or_an(rest_label)} #{rest_label.downcase}.
458
+ EOT
459
+ end
460
+ optparse.parse!(args)
461
+ verify_args!(args:args, optparse:optparse, count:2)
462
+ connect(options)
463
+ parent_record = rest_parent_find_by_name_or_id(parent_id)
464
+ if parent_record.nil?
465
+ raise_command_error "#{rest_parent_label} not found for '#{parent_id}'.\n#{optparse}"
466
+ end
467
+ record = rest_find_by_name_or_id(id)
468
+ if record.nil?
469
+ return 1, "#{rest_name} not found for '#{id}'"
470
+ end
471
+ unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to delete the #{rest_label.downcase} #{record['name'] || record['id']}?")
472
+ return 9, "aborted"
473
+ end
474
+ params.merge!(parse_query_options(options))
475
+ rest_interface.setopts(options)
476
+ if options[:dry_run]
477
+ print_dry_run rest_interface.dry.destroy(parent_id, record['id'])
478
+ return 0, nil
479
+ end
480
+ json_response = rest_interface.destroy(parent_id, record['id'], params)
481
+ render_response(json_response, options) do
482
+ print_green_success "Removed #{rest_label.downcase} #{record['name'] || record['id']}"
483
+ end
484
+ return 0, nil
485
+ end
486
+
487
+ end
488
+