morpheus-cli 4.2.18 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +14 -0
  4. data/lib/morpheus/api/billing_interface.rb +33 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/rest_interface.rb +0 -6
  7. data/lib/morpheus/api/roles_interface.rb +14 -0
  8. data/lib/morpheus/cli.rb +2 -2
  9. data/lib/morpheus/cli/apps.rb +3 -4
  10. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  11. data/lib/morpheus/cli/backups_command.rb +3 -0
  12. data/lib/morpheus/cli/blueprints_command.rb +27 -61
  13. data/lib/morpheus/cli/budgets_command.rb +4 -4
  14. data/lib/morpheus/cli/catalog_command.rb +507 -0
  15. data/lib/morpheus/cli/cli_command.rb +30 -15
  16. data/lib/morpheus/cli/cloud_resource_pools_command.rb +16 -0
  17. data/lib/morpheus/cli/clouds.rb +7 -10
  18. data/lib/morpheus/cli/commands/standard/curl_command.rb +23 -7
  19. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  20. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  21. data/lib/morpheus/cli/containers_command.rb +14 -0
  22. data/lib/morpheus/cli/deployments.rb +1 -1
  23. data/lib/morpheus/cli/hosts.rb +30 -2
  24. data/lib/morpheus/cli/instances.rb +19 -1
  25. data/lib/morpheus/cli/invoices_command.rb +8 -9
  26. data/lib/morpheus/cli/jobs_command.rb +30 -8
  27. data/lib/morpheus/cli/library_instance_types_command.rb +17 -3
  28. data/lib/morpheus/cli/library_option_lists_command.rb +14 -6
  29. data/lib/morpheus/cli/mixins/accounts_helper.rb +7 -6
  30. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  31. data/lib/morpheus/cli/mixins/catalog_helper.rb +66 -0
  32. data/lib/morpheus/cli/mixins/deployments_helper.rb +0 -1
  33. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  34. data/lib/morpheus/cli/mixins/print_helper.rb +46 -0
  35. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  36. data/lib/morpheus/cli/ping.rb +0 -1
  37. data/lib/morpheus/cli/projects_command.rb +7 -7
  38. data/lib/morpheus/cli/provisioning_licenses_command.rb +2 -2
  39. data/lib/morpheus/cli/remote.rb +0 -2
  40. data/lib/morpheus/cli/roles.rb +305 -3
  41. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  42. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  43. data/lib/morpheus/cli/tasks.rb +24 -10
  44. data/lib/morpheus/cli/tenants_command.rb +1 -1
  45. data/lib/morpheus/cli/usage_command.rb +150 -0
  46. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  47. data/lib/morpheus/cli/users.rb +12 -1
  48. data/lib/morpheus/cli/version.rb +1 -1
  49. data/lib/morpheus/cli/workflows.rb +12 -9
  50. data/lib/morpheus/formatters.rb +26 -5
  51. metadata +8 -2
@@ -44,7 +44,7 @@ class Morpheus::Cli::InvoicesCommand
44
44
  opts.on('--prices', '--prices', "Display prices: Total, Compute, Storage, Network, Extra" ) do
45
45
  options[:show_prices] = true
46
46
  end
47
- opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
47
+ opts.on('-t', '--type TYPE', "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
48
48
  params['refType'] ||= []
49
49
  values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
50
50
  values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
@@ -193,9 +193,8 @@ class Morpheus::Cli::InvoicesCommand
193
193
  subtitles += parse_list_subtitles(options)
194
194
  print_h1 title, subtitles
195
195
  if invoices.empty?
196
- unless options[:totals_only]
197
- print cyan,"No invoices found.",reset,"\n"
198
- end
196
+ print cyan,"No invoices found.",reset,"\n"
197
+ print reset,"\n"
199
198
  else
200
199
  # current_date = Time.now
201
200
  # current_period = "#{current_date.year}#{current_date.month.to_s.rjust(2, '0')}"
@@ -300,8 +299,8 @@ class Morpheus::Cli::InvoicesCommand
300
299
  end
301
300
  if options[:show_estimates]
302
301
  cost_rows += [
303
- {label: 'Estimated Cost'.upcase, compute: invoice_totals['estimatedComputeCost'], memory: invoice_totals['estimatedMemoryCost'], storage: invoice_totals['estimatedStorageCost'], network: invoice_totals['estimatedNetworkCost'], license: invoice_totals['estimatedLicenseCost'], extra: invoice_totals['estimatedExtraCost'], running: invoice_totals['estimatedRunningCost'], total: invoice_totals['estimatedTotalCost']},
304
- {label: 'Estimated Price'.upcase, compute: invoice_totals['estimatedComputePrice'], memory: invoice_totals['estimatedMemoryPrice'], storage: invoice_totals['estimatedStoragePrice'], network: invoice_totals['estimatedNetworkPrice'], license: invoice_totals['estimatedLicensePrice'], extra: invoice_totals['estimatedExtraPrice'], running: invoice_totals['estimatedRunningPrice'], total: invoice_totals['estimatedTotalPrice']},
302
+ {label: 'Metered Cost'.upcase, compute: invoice_totals['estimatedComputeCost'], memory: invoice_totals['estimatedMemoryCost'], storage: invoice_totals['estimatedStorageCost'], network: invoice_totals['estimatedNetworkCost'], license: invoice_totals['estimatedLicenseCost'], extra: invoice_totals['estimatedExtraCost'], running: invoice_totals['estimatedRunningCost'], total: invoice_totals['estimatedTotalCost']},
303
+ {label: 'Metered Price'.upcase, compute: invoice_totals['estimatedComputePrice'], memory: invoice_totals['estimatedMemoryPrice'], storage: invoice_totals['estimatedStoragePrice'], network: invoice_totals['estimatedNetworkPrice'], license: invoice_totals['estimatedLicensePrice'], extra: invoice_totals['estimatedExtraPrice'], running: invoice_totals['estimatedRunningPrice'], total: invoice_totals['estimatedTotalPrice']},
305
304
  ]
306
305
  end
307
306
  cost_columns = {
@@ -534,8 +533,8 @@ EOT
534
533
  end
535
534
  if options[:show_estimates]
536
535
  cost_rows += [
537
- {label: 'Estimated Cost'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
538
- {label: 'Estimated Price'.upcase, compute: invoice['estimatedComputePrice'], memory: invoice['estimatedMemoryPrice'], storage: invoice['estimatedStoragePrice'], network: invoice['estimatedNetworkPrice'], license: invoice['estimatedLicensePrice'], extra: invoice['estimatedExtraPrice'], running: invoice['estimatedRunningPrice'], total: invoice['estimatedTotalPrice']},
536
+ {label: 'Metered Cost'.upcase, compute: invoice['estimatedComputeCost'], memory: invoice['estimatedMemoryCost'], storage: invoice['estimatedStorageCost'], network: invoice['estimatedNetworkCost'], license: invoice['estimatedLicenseCost'], extra: invoice['estimatedExtraCost'], running: invoice['estimatedRunningCost'], total: invoice['estimatedTotalCost']},
537
+ {label: 'Metered Price'.upcase, compute: invoice['estimatedComputePrice'], memory: invoice['estimatedMemoryPrice'], storage: invoice['estimatedStoragePrice'], network: invoice['estimatedNetworkPrice'], license: invoice['estimatedLicensePrice'], extra: invoice['estimatedExtraPrice'], running: invoice['estimatedRunningPrice'], total: invoice['estimatedTotalPrice']},
539
538
  ]
540
539
  end
541
540
  cost_columns = {
@@ -683,7 +682,7 @@ EOT
683
682
  params['externalId'] ||= []
684
683
  params['externalId'] << val
685
684
  end
686
- opts.on('--type TYPE', String, "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
685
+ opts.on('-t', '--type TYPE', "Filter by Ref Type eg. ComputeSite (Group), ComputeZone (Cloud), ComputeServer (Host), Instance, Container, User") do |val|
687
686
  params['refType'] ||= []
688
687
  values = val.split(",").collect {|it| it.strip }.select {|it| it != "" }
689
688
  values.each { |it| params['refType'] << parse_invoice_ref_type(it) }
@@ -102,15 +102,15 @@ class Morpheus::Cli::JobsCommand
102
102
  if stats = json_response['stats']
103
103
  label_width = 17
104
104
 
105
- print_h2 "Executions Stats - Last 7 Days"
105
+ print_h2 "Execution Stats - Last 7 Days"
106
106
  print cyan
107
107
 
108
108
  print "Jobs".rjust(label_width, ' ') + ": #{stats['jobCount']}\n"
109
109
  print "Executions Today".rjust(label_width, ' ') + ": #{stats['todayCount']}\n"
110
110
  print "Daily Executions".rjust(label_width, ' ') + ": " + stats['executionsPerDay'].join(' | ') + "\n"
111
111
  print "Total Executions".rjust(label_width, ' ') + ": #{stats['execCount']}\n"
112
- print "Completed".rjust(label_width, ' ') + ": " + generate_usage_bar(stats['execSuccessRate'].to_f, 100) + "#{stats['execSuccess']}".rjust(15, ' ') + " of " + "#{stats['execCount']}".ljust(15, ' ') + "\n#{cyan}"
113
- print "Failed".rjust(label_width, ' ') + ": " + generate_usage_bar(stats['execFailedRate'].to_f, 100) + "#{stats['execFailed']}".rjust(15, ' ') + " of " + "#{stats['execCount']}".ljust(15, ' ') + "\n#{cyan}"
112
+ print "Completed".rjust(label_width, ' ') + ": " + generate_usage_bar(stats['execSuccessRate'].to_f, 100, {bar_color:green}) + "#{stats['execSuccess']}".rjust(15, ' ') + " of " + "#{stats['execCount']}".ljust(15, ' ') + "\n#{cyan}"
113
+ print "Failed".rjust(label_width, ' ') + ": " + generate_usage_bar(stats['execFailedRate'].to_f, 100, {bar_color:red}) + "#{stats['execFailed']}".rjust(15, ' ') + " of " + "#{stats['execCount']}".ljust(15, ' ') + "\n#{cyan}"
114
114
  end
115
115
  print reset,"\n"
116
116
  end
@@ -304,7 +304,7 @@ class Morpheus::Cli::JobsCommand
304
304
  job_options = @jobs_interface.options(job_type_id)
305
305
 
306
306
  # prompt task / workflow
307
- if job_type['code'] == 'morpheus.task'
307
+ if ['morpheus.task.jobType', 'morpheus.task'].include?(job_type['code'])
308
308
  params['task'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'task.id', 'fieldLabel' => 'Task', 'type' => 'select', 'required' => true, 'optionSource' => 'tasks'}], options[:options], @api_client, {})['task']
309
309
  else
310
310
  params['workflow'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'workflow.id', 'fieldLabel' => 'Workflow', 'type' => 'select', 'required' => true, 'optionSource' => 'operationTaskSets'}], options[:options], @api_client, {})['workflow']
@@ -320,10 +320,11 @@ class Morpheus::Cli::JobsCommand
320
320
  exit 1
321
321
  end
322
322
  params['task'] = {'id' => task['id']}
323
- job_type_id = load_job_type_id_by_code('morpheus.task')
323
+ job_type_id = load_job_type_id_by_code('morpheus.task.jobType') || load_job_type_id_by_code('morpheus.task')
324
324
  end
325
325
 
326
326
  # workflow
327
+ task_set = nil
327
328
  if !options[:workflow].nil?
328
329
  task_set = find_by_name_or_id('task_set', options[:workflow])
329
330
 
@@ -332,8 +333,29 @@ class Morpheus::Cli::JobsCommand
332
333
  exit 1
333
334
  end
334
335
  params['workflow'] = {'id' => task_set['id']}
335
- job_type_id = load_job_type_id_by_code('morpheus.workflow')
336
+ job_type_id = load_job_type_id_by_code('morpheus.workflow.jobType') || load_job_type_id_by_code('morpheus.workflow')
336
337
  end
338
+ # load workflow if we havent yet
339
+ if (params['workflow'] && params['workflow']['id']) && task_set.nil?
340
+ task_set = find_by_name_or_id('task_set', params['workflow']['id'])
341
+ if task_set.nil?
342
+ print_red_alert "Workflow #{params['workflow']['id']} not found"
343
+ exit 1
344
+ end
345
+ end
346
+ # prompt for custom options for workflow
347
+ custom_option_types = task_set ? task_set['optionTypes'] : nil
348
+ if custom_option_types && custom_option_types.size() > 0
349
+ # they are all returned in a single array right now, so skip prompting for the jobType optionTypes
350
+ custom_option_types.reject! { |it| it['code'] && it['code'].include?('job.type') }
351
+ custom_option_types = custom_option_types.collect {|it|
352
+ it['fieldContext'] = 'customOptions'
353
+ it
354
+ }
355
+ custom_options = Morpheus::Cli::OptionTypes.prompt(custom_option_types, options[:options], @api_client, {})
356
+ params['customOptions'] = custom_options['customOptions']
357
+ end
358
+
337
359
 
338
360
  # load options based upon job type + task / taskset
339
361
  job_options = @jobs_interface.options(job_type_id, {'taskId' => params['task'] ? params['task']['id'] : nil, 'workflowId' => params['workflow'] ? params['workflow']['id'] : nil})
@@ -768,7 +790,7 @@ class Morpheus::Cli::JobsCommand
768
790
  params = {}
769
791
  optparse = Morpheus::Cli::OptionParser.new do |opts|
770
792
  opts.banner = subcommand_usage("[id]")
771
- opts.on('-D', '--details [on|off]', String, "Can be used to enable / disable execution details. Default if on") do |val|
793
+ opts.on('-D', '--details [on|off]', String, "Can be used to enable / disable execution details. Default is on") do |val|
772
794
  options[:details] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == '1' || val.to_s == ''
773
795
  end
774
796
  build_common_options(opts, options, [:json, :dry_run, :remote])
@@ -970,7 +992,7 @@ class Morpheus::Cli::JobsCommand
970
992
  def format_status(status_string, return_color=cyan)
971
993
  out = ""
972
994
  if status_string
973
- if ['success', 'successful', 'ok'].include?(status_string)
995
+ if ['complete','success', 'successful', 'ok'].include?(status_string)
974
996
  out << "#{green}#{status_string.upcase}"
975
997
  elsif ['error', 'offline', 'failed', 'failure'].include?(status_string)
976
998
  out << "#{red}#{status_string.upcase}"
@@ -244,15 +244,14 @@ class Morpheus::Cli::LibraryInstanceTypesCommand
244
244
  options = {}
245
245
  params = {}
246
246
  logo_file = nil
247
- option_type_ids = nil
248
247
  optparse = Morpheus::Cli::OptionParser.new do|opts|
249
248
  opts.banner = subcommand_usage("[name]")
250
249
  build_option_type_options(opts, options, add_instance_type_option_types())
251
250
  opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
252
251
  if list.nil?
253
- option_type_ids = []
252
+ params['optionTypes'] = []
254
253
  else
255
- option_type_ids = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
254
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
256
255
  end
257
256
  end
258
257
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
@@ -348,6 +347,13 @@ class Morpheus::Cli::LibraryInstanceTypesCommand
348
347
  opts.banner = subcommand_usage("[name] [options]")
349
348
  build_option_type_options(opts, options, update_instance_type_option_types())
350
349
  build_common_options(opts, options, [:options, :json, :dry_run, :remote])
350
+ opts.on('--option-types [x,y,z]', Array, "List of Option Type IDs") do |list|
351
+ if list.nil?
352
+ params['optionTypes'] = []
353
+ else
354
+ params['optionTypes'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
355
+ end
356
+ end
351
357
  opts.footer = "Update an instance type." + "\n" +
352
358
  "[name] is required. This is the name or id of a instance type."
353
359
  end
@@ -375,6 +381,14 @@ class Morpheus::Cli::LibraryInstanceTypesCommand
375
381
  params['hasSettings'] = ['on','true','1'].include?(params['hasSettings'].to_s) if params.key?('hasSettings')
376
382
  params['hasAutoScale'] = ['on','true','1'].include?(params['hasAutoScale'].to_s) if params.key?('hasAutoScale')
377
383
  params['hasDeployment'] = ['on','true','1'].include?(params['hasDeployment'].to_s) if params.key?('hasDeployment')
384
+ if params['optionTypes']
385
+ prompt_results = prompt_for_option_types(params, options, @api_client)
386
+ if prompt_results[:success]
387
+ params['optionTypes'] = prompt_results[:data] unless prompt_results[:data].nil?
388
+ else
389
+ return 1
390
+ end
391
+ end
378
392
  if params.empty?
379
393
  puts optparse
380
394
  #option_lines = update_instance_type_option_types.collect {|it| "\t-O #{it['fieldName']}=\"value\"" }.join("\n")
@@ -90,6 +90,9 @@ class Morpheus::Cli::LibraryOptionListsCommand
90
90
  optparse = Morpheus::Cli::OptionParser.new do |opts|
91
91
  opts.banner = subcommand_usage("[name]")
92
92
  build_standard_get_options(opts, options)
93
+ opts.on(nil,'--no-items', "Do not display List Items") do |val|
94
+ options[:no_list_items] = true
95
+ end
93
96
  opts.footer = "Get details about an option list.\n" +
94
97
  "[name] is required. This is the name or id of an option list. Supports 1-N [name] arguments."
95
98
  end
@@ -166,15 +169,20 @@ class Morpheus::Cli::LibraryOptionListsCommand
166
169
  print_h2 "Translation Script"
167
170
  print reset,"#{option_type_list['translationScript']}","\n",reset
168
171
  end
172
+ if !option_type_list['requestScript'].empty?
173
+ print_h2 "Request Script"
174
+ print reset,"#{option_type_list['requestScript']}","\n",reset
175
+ end
169
176
  end
170
- print_h2 "List Items"
171
- if option_type_list['listItems']
172
- print as_pretty_table(option_type_list['listItems'], ['name', 'value'], options)
173
- else
174
- puts "No data"
177
+ if options[:no_list_items] != true
178
+ list_items = option_type_list['listItems']
179
+ if list_items && list_items.size > 0
180
+ print_h2 "List Items"
181
+ print as_pretty_table(list_items, [:name, :value], options)
182
+ print_results_pagination({size: list_items.size, total: list_items.size})
183
+ end
175
184
  end
176
185
  print reset,"\n"
177
-
178
186
  rescue RestClient::Exception => e
179
187
  print_rest_exception(e, options)
180
188
  exit 1
@@ -136,6 +136,7 @@ module Morpheus::Cli::AccountsHelper
136
136
  "Multitenant" => lambda {|it|
137
137
  format_boolean(it['multitenant']).to_s + (it['multitenantLocked'] ? " (LOCKED)" : "")
138
138
  },
139
+ "Default Persona" => lambda {|it| it['defaultPersona'] ? it['defaultPersona']['name'] : '(standard)' },
139
140
  "Owner" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
140
141
  #"Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
141
142
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
@@ -196,7 +197,7 @@ module Morpheus::Cli::AccountsHelper
196
197
 
197
198
  ## Users
198
199
 
199
- def user_column_definitions()
200
+ def user_column_definitions(opts={})
200
201
  {
201
202
  "ID" => 'id',
202
203
  "Tenant" => lambda {|it| it['account'] ? it['account']['name'] : '' },
@@ -206,15 +207,15 @@ module Morpheus::Cli::AccountsHelper
206
207
  "Email" => 'email',
207
208
  "Role" => lambda {|it| format_user_role_names(it) },
208
209
  "Notifications" => lambda {|it| it['receiveNotifications'].nil? ? '' : format_boolean(it['receiveNotifications']) },
209
- "Status" => lambda {|it| format_user_status(it) },
210
+ "Status" => lambda {|it| format_user_status(it, opts[:color] || cyan) },
210
211
  "Last Login" => lambda {|it| format_duration_ago(it['lastLoginDate']) },
211
212
  "Created" => lambda {|it| format_local_dt(it['dateCreated']) },
212
213
  "Updated" => lambda {|it| format_local_dt(it['lastUpdated']) },
213
214
  }
214
215
  end
215
216
 
216
- def list_user_column_definitions()
217
- columns = user_column_definitions
217
+ def list_user_column_definitions(opts={})
218
+ columns = user_column_definitions(opts)
218
219
  columns.delete("Notifications")
219
220
  return columns.upcase_keys!
220
221
  end
@@ -261,8 +262,8 @@ module Morpheus::Cli::AccountsHelper
261
262
  return nil
262
263
  elsif users.size > 1
263
264
  print_red_alert "Found #{users.size} users by username '#{username}'. Try using ID instead: #{format_list(users.collect {|it| it['id']}, 'or', 3)}"
264
- print "\n"
265
- print as_pretty_table(users, list_user_column_definitions, {color: red, thin: true})
265
+ print_error "\n"
266
+ print_error as_pretty_table(users, list_user_column_definitions({color: red}), {color: red, thin: true})
266
267
  print reset,"\n"
267
268
  return nil
268
269
  else
@@ -1,6 +1,6 @@
1
1
  require 'morpheus/cli/mixins/print_helper'
2
2
  # Mixin for Morpheus::Cli command classes
3
- # Provides common methods for infrastructure management
3
+ # Provides common methods for backups management
4
4
  module Morpheus::Cli::BackupsHelper
5
5
 
6
6
  def self.included(klass)
@@ -8,13 +8,11 @@ module Morpheus::Cli::BackupsHelper
8
8
  end
9
9
 
10
10
  def backups_interface
11
- # @api_client.groups
12
11
  raise "#{self.class} has not defined @backups_interface" if @backups_interface.nil?
13
12
  @backups_interface
14
13
  end
15
14
 
16
- def backup_jobs_interface
17
- # @api_client.groups
15
+ def backup_jobs_interfaces
18
16
  raise "#{self.class} has not defined @backup_jobs_interface" if @backup_jobs_interface.nil?
19
17
  @backup_jobs_interface
20
18
  end
@@ -0,0 +1,66 @@
1
+ require 'morpheus/cli/mixins/print_helper'
2
+ # Mixin for Morpheus::Cli command classes
3
+ # Provides common methods for infrastructure management
4
+ module Morpheus::Cli::CatalogHelper
5
+
6
+ def self.included(klass)
7
+ klass.send :include, Morpheus::Cli::PrintHelper
8
+ end
9
+
10
+ def catalog_item_types_interface
11
+ raise "#{self.class} has not defined @catalog_item_types_interface" if @catalog_item_types_interface.nil?
12
+ @catalog_item_types_interface
13
+ end
14
+
15
+ # def service_catalog_interface
16
+ # raise "#{self.class} has not defined @service_catalog_interface" if @service_catalog_interface.nil?
17
+ # @service_catalog_interface
18
+ # end
19
+
20
+ def catalog_item_type_object_key
21
+ 'catalogItemType'
22
+ end
23
+
24
+ def catalog_item_type_list_key
25
+ 'catalogItemTypes'
26
+ end
27
+
28
+ def find_catalog_item_type_by_name_or_id(val)
29
+ if val.to_s =~ /\A\d{1,}\Z/
30
+ return find_catalog_item_type_by_id(val)
31
+ else
32
+ return find_catalog_item_type_by_name(val)
33
+ end
34
+ end
35
+
36
+ def find_catalog_item_type_by_id(id)
37
+ begin
38
+ json_response = catalog_item_types_interface.get(id.to_i)
39
+ return json_response[catalog_item_type_object_key]
40
+ rescue RestClient::Exception => e
41
+ if e.response && e.response.code == 404
42
+ print_red_alert "catalog_item_type not found by id '#{id}'"
43
+ else
44
+ raise e
45
+ end
46
+ end
47
+ end
48
+
49
+ def find_catalog_item_type_by_name(name)
50
+ json_response = catalog_item_types_interface.list({name: name.to_s})
51
+ catalog_item_types = json_response[catalog_item_type_list_key]
52
+ if catalog_item_types.empty?
53
+ print_red_alert "catalog_item_type not found by name '#{name}'"
54
+ return nil
55
+ elsif catalog_item_types.size > 1
56
+ print_red_alert "#{catalog_item_types.size} catalog_item_types found by name '#{name}'"
57
+ puts_error as_pretty_table(catalog_item_types, [:id, :name], {color:red})
58
+ print_red_alert "Try using ID instead"
59
+ print reset,"\n"
60
+ return nil
61
+ else
62
+ return catalog_item_types[0]
63
+ end
64
+ end
65
+
66
+ end
@@ -10,7 +10,6 @@ module Morpheus::Cli::DeploymentsHelper
10
10
  ## Deployments
11
11
 
12
12
  def deployments_interface
13
- # @api_client.groups
14
13
  raise "#{self.class} has not defined @deployments_interface" if @deployments_interface.nil?
15
14
  @deployments_interface
16
15
  end
@@ -75,7 +75,7 @@ module Morpheus::Cli::OptionSourceHelper
75
75
 
76
76
  def get_cloud_options(refresh=false, api_params={})
77
77
  if !@available_cloud_options || refresh
78
- option_results = options_interface.options_for_source('clouds', api_params.mege({'default' => 'false'}))
78
+ option_results = options_interface.options_for_source('clouds', api_params.merge({'default' => 'false'}))
79
79
  @available_cloud_options = option_results['data'].collect {|it|
80
80
  {"name" => it["name"], "value" => it["value"], "id" => it["value"]}
81
81
  }
@@ -1285,4 +1285,50 @@ module Morpheus::Cli::PrintHelper
1285
1285
  end
1286
1286
  end
1287
1287
 
1288
+ # convert JSON or YAML string to a map
1289
+ def parse_json_or_yaml(config, parsers = [:json, :yaml])
1290
+ rtn = {success: false, data: nil, err: nil}
1291
+ err = nil
1292
+ config = config.strip
1293
+ if config[0..2] == "---"
1294
+ parsers = [:yaml]
1295
+ end
1296
+ # ok only parse json for strings that start with {, consolidated yaml can look like json and cause issues}
1297
+ if config[0] && config[0].chr == "{" && config[-1] && config[-1].chr == "}"
1298
+ parsers = [:json]
1299
+ end
1300
+ parsers.each do |parser|
1301
+ if parser == :yaml
1302
+ begin
1303
+ # todo: one method to parse and return Hash
1304
+ # load does not raise an exception, it just returns the bad string
1305
+ #YAML.parse(config)
1306
+ config_map = YAML.load(config)
1307
+ if !config_map.is_a?(Hash)
1308
+ raise "Failed to parse config as YAML"
1309
+ end
1310
+ rtn[:data] = config_map
1311
+ rtn[:success] = true
1312
+ break
1313
+ rescue => ex
1314
+ rtn[:err] = ex if rtn[:err].nil?
1315
+ end
1316
+ elsif parser == :json
1317
+ begin
1318
+ config_map = JSON.parse(config)
1319
+ rtn[:data] = config_map
1320
+ rtn[:success] = true
1321
+ break
1322
+ rescue => ex
1323
+ rtn[:err] = ex if rtn[:err].nil?
1324
+ end
1325
+ end
1326
+ end
1327
+ return rtn
1328
+ end
1329
+
1330
+ def parse_yaml_or_json(config, parsers = [:yaml, :json])
1331
+ parse_json_or_yaml(config, parsers)
1332
+ end
1333
+
1288
1334
  end
@@ -596,22 +596,28 @@ class Morpheus::Cli::NetworkPoolsCommand
596
596
  def add_ip(args)
597
597
  options = {}
598
598
  params = {}
599
+ next_free_ip = false
599
600
  optparse = Morpheus::Cli::OptionParser.new do |opts|
600
- opts.banner = subcommand_usage("[network-pool] [ip]")
601
+ opts.banner = subcommand_usage("[network-pool] [ip] [--next]")
601
602
  opts.on('--ip-address VALUE', String, "IP Address for this network pool IP") do |val|
602
603
  options[:options]['ipAddress'] = val
603
604
  end
605
+ opts.on('--next-free-ip', '--next-free-ip', "Use the next available ip address. This can be used instead of specifying an ip address") do
606
+ next_free_ip = true
607
+ end
604
608
  opts.on('--hostname VALUE', String, "Hostname for this network pool IP") do |val|
605
609
  options[:options]['hostname'] = val
606
610
  end
607
611
  build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
608
612
  opts.footer = "Create a new network pool IP." + "\n" +
609
613
  "[network-pool] is required. This is the name or id of a network pool.\n" +
610
- "[ip] is required and can be passed as --ip-address instead."
614
+ "[ip] is required or --next-free-ip to use the next available address instead."
611
615
  end
612
616
  optparse.parse!(args)
613
- if args.count < 1 || args.count > 2
614
- raise_command_error "wrong number of arguments, expected 1-2 and got (#{args.count}) #{args}\n#{optparse}"
617
+ if next_free_ip
618
+ verify_args!(args:args, count:1, optparse:optparse)
619
+ else
620
+ verify_args!(args:args, min:1, max:2, optparse:optparse)
615
621
  end
616
622
  connect(options)
617
623
  begin
@@ -639,8 +645,10 @@ class Morpheus::Cli::NetworkPoolsCommand
639
645
  payload['networkPoolIp'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
640
646
 
641
647
  # IP Address
642
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ipAddress', 'fieldLabel' => 'IP Address', 'type' => 'text', 'required' => true, 'description' => 'IP Address for this network pool IP.'}], options[:options])
643
- payload['networkPoolIp']['ipAddress'] = v_prompt['ipAddress'] unless v_prompt['ipAddress'].to_s.empty?
648
+ unless next_free_ip
649
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ipAddress', 'fieldLabel' => 'IP Address', 'type' => 'text', 'required' => true, 'description' => 'IP Address for this network pool IP.'}], options[:options])
650
+ payload['networkPoolIp']['ipAddress'] = v_prompt['ipAddress'] unless v_prompt['ipAddress'].to_s.empty?
651
+ end
644
652
 
645
653
  # Hostname
646
654
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'hostname', 'fieldLabel' => 'Hostname', 'type' => 'text', 'required' => true, 'description' => 'Hostname for this network pool IP.'}], options[:options])