morpheus-cli 4.2.21 → 5.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +30 -0
  4. data/lib/morpheus/api/billing_interface.rb +34 -0
  5. data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
  6. data/lib/morpheus/api/deploy_interface.rb +1 -1
  7. data/lib/morpheus/api/deployments_interface.rb +20 -1
  8. data/lib/morpheus/api/forgot_password_interface.rb +17 -0
  9. data/lib/morpheus/api/instances_interface.rb +16 -2
  10. data/lib/morpheus/api/rest_interface.rb +0 -6
  11. data/lib/morpheus/api/roles_interface.rb +14 -0
  12. data/lib/morpheus/api/search_interface.rb +13 -0
  13. data/lib/morpheus/api/servers_interface.rb +14 -0
  14. data/lib/morpheus/api/service_catalog_interface.rb +89 -0
  15. data/lib/morpheus/api/usage_interface.rb +18 -0
  16. data/lib/morpheus/cli.rb +7 -3
  17. data/lib/morpheus/cli/apps.rb +6 -27
  18. data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
  19. data/lib/morpheus/cli/backups_command.rb +3 -0
  20. data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
  21. data/lib/morpheus/cli/cli_command.rb +70 -21
  22. data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
  23. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
  24. data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
  25. data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
  26. data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
  27. data/lib/morpheus/cli/containers_command.rb +14 -24
  28. data/lib/morpheus/cli/cypher_command.rb +6 -2
  29. data/lib/morpheus/cli/deploy.rb +199 -90
  30. data/lib/morpheus/cli/deployments.rb +341 -28
  31. data/lib/morpheus/cli/deploys.rb +206 -41
  32. data/lib/morpheus/cli/error_handler.rb +7 -0
  33. data/lib/morpheus/cli/forgot_password.rb +133 -0
  34. data/lib/morpheus/cli/groups.rb +1 -1
  35. data/lib/morpheus/cli/health_command.rb +59 -2
  36. data/lib/morpheus/cli/hosts.rb +265 -34
  37. data/lib/morpheus/cli/instances.rb +186 -100
  38. data/lib/morpheus/cli/invoices_command.rb +33 -16
  39. data/lib/morpheus/cli/jobs_command.rb +28 -6
  40. data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
  41. data/lib/morpheus/cli/library_option_types_command.rb +5 -2
  42. data/lib/morpheus/cli/logs_command.rb +9 -6
  43. data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
  44. data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
  45. data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
  46. data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
  47. data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
  48. data/lib/morpheus/cli/mixins/provisioning_helper.rb +100 -4
  49. data/lib/morpheus/cli/network_pools_command.rb +14 -6
  50. data/lib/morpheus/cli/option_types.rb +271 -22
  51. data/lib/morpheus/cli/ping.rb +0 -1
  52. data/lib/morpheus/cli/remote.rb +35 -12
  53. data/lib/morpheus/cli/reports_command.rb +99 -30
  54. data/lib/morpheus/cli/roles.rb +453 -113
  55. data/lib/morpheus/cli/search_command.rb +182 -0
  56. data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
  57. data/lib/morpheus/cli/service_plans_command.rb +2 -2
  58. data/lib/morpheus/cli/setup.rb +1 -1
  59. data/lib/morpheus/cli/shell.rb +33 -11
  60. data/lib/morpheus/cli/storage_providers_command.rb +40 -56
  61. data/lib/morpheus/cli/tasks.rb +29 -32
  62. data/lib/morpheus/cli/usage_command.rb +203 -0
  63. data/lib/morpheus/cli/user_settings_command.rb +1 -0
  64. data/lib/morpheus/cli/users.rb +12 -1
  65. data/lib/morpheus/cli/version.rb +1 -1
  66. data/lib/morpheus/cli/virtual_images.rb +429 -254
  67. data/lib/morpheus/cli/whoami.rb +6 -6
  68. data/lib/morpheus/cli/workflows.rb +34 -41
  69. data/lib/morpheus/formatters.rb +75 -7
  70. data/lib/morpheus/terminal.rb +6 -2
  71. metadata +14 -2
@@ -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})
@@ -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
@@ -262,7 +270,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
262
270
  else
263
271
  payload = {}
264
272
  payload.deep_merge!({'optionTypeList' => parse_passed_options(options)})
265
- list_payload = Morpheus::Cli::OptionTypes.no_prompt(update_option_type_option_types(), options[:options], @api_client)
273
+ list_payload = Morpheus::Cli::OptionTypes.no_prompt(update_option_type_list_option_types(), options[:options], @api_client)
266
274
  if list_payload['type'] == 'rest'
267
275
  # parse Source Headers
268
276
  if !(payload['optionTypeList']['config'] && payload['optionTypeList']['config']['sourceHeaders'])
@@ -129,7 +129,7 @@ class Morpheus::Cli::LibraryOptionTypesCommand
129
129
 
130
130
  print_h1 "Option Type Details"
131
131
  print cyan
132
- print_description_list({
132
+ columns = {
133
133
  "ID" => 'id',
134
134
  "Name" => 'name',
135
135
  "Description" => 'description',
@@ -138,11 +138,14 @@ class Morpheus::Cli::LibraryOptionTypesCommand
138
138
  # "Field Name" => 'fieldName',
139
139
  "Full Field Name" => lambda {|it| [it['fieldContext'], it['fieldName']].select {|it| !it.to_s.empty? }.join('.') },
140
140
  "Type" => lambda {|it| it['type'].to_s.capitalize },
141
+ "Option List" => lambda {|it| it['optionList'] ? it['optionList']['name'] : nil },
141
142
  "Placeholder" => 'placeHolder',
142
143
  "Default Value" => 'defaultValue',
143
144
  "Required" => lambda {|it| format_boolean(it['required']) },
144
145
  "Export As Tag" => lambda {|it| it['exportMeta'].nil? ? '' : format_boolean(it['exportMeta']) },
145
- }, option_type)
146
+ }
147
+ columns.delete("Option List") if option_type['optionList'].nil?
148
+ print as_description_list(option_type, columns, options)
146
149
  print reset,"\n"
147
150
  return 0
148
151
  rescue RestClient::Exception => e
@@ -34,7 +34,7 @@ class Morpheus::Cli::LogsCommand
34
34
  options = {}
35
35
  params = {}
36
36
  optparse = Morpheus::Cli::OptionParser.new do |opts|
37
- opts.banner = subcommand_usage("[id]")
37
+ opts.banner = subcommand_usage("[search]")
38
38
  opts.on('--hosts HOSTS', String, "Filter logs to specific Host ID(s)") do |val|
39
39
  params['servers'] = val.to_s.split(",").collect {|it| it.to_s.strip }.select {|it| it }.compact
40
40
  end
@@ -72,18 +72,21 @@ class Morpheus::Cli::LogsCommand
72
72
  options[:details] = true
73
73
  end
74
74
  build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
75
- opts.footer = "List logs for a container.\n" +
76
- "[id] is required. This is the id of a container."
75
+ opts.footer = "List logs for all hosts and containers."
77
76
  end
78
77
  optparse.parse!(args)
79
- if args.count != 0
80
- raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
78
+ if args.count > 0
79
+ options[:phrase] = args.join(" ")
81
80
  end
82
81
  connect(options)
83
82
  begin
84
83
  params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
85
84
  params.merge!(parse_list_options(options))
86
- params['query'] = params.delete('phrase') if params['phrase']
85
+ if params['phrase']
86
+ options.delete(:phrase)
87
+ search_phrase = params.delete('phrase')
88
+ params['query'] = search_phrase
89
+ end
87
90
  params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
88
91
  params['startMs'] = (options[:start].to_i * 1000) if options[:start]
89
92
  params['endMs'] = (options[:end].to_i * 1000) if options[:end]
@@ -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
@@ -400,7 +401,7 @@ module Morpheus::Cli::AccountsHelper
400
401
  end
401
402
 
402
403
  def get_access_string(access, return_color=cyan)
403
- get_access_color(access) + access + return_color
404
+ get_access_color(access) + access.to_s + return_color.to_s
404
405
  # access ||= 'none'
405
406
  # if access == 'none'
406
407
  # "#{white}#{access.to_s}#{return_color}"
@@ -425,10 +426,14 @@ module Morpheus::Cli::AccountsHelper
425
426
  # Examples: format_permission_access("read")
426
427
  # format_permission_access("custom", "full,custom,none")
427
428
  def format_access_string(access, access_levels=nil, return_color=cyan)
429
+ # nevermind all this, just colorized access level
430
+ return get_access_string(access, return_color)
431
+
428
432
  access = access.to_s.downcase.strip
429
433
  if access.empty?
430
434
  access = "none"
431
435
  end
436
+
432
437
  if access_levels.nil?
433
438
  access_levels = ["none","read","user","full"]
434
439
  elsif access_levels.is_a?(Array)
@@ -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
@@ -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
@@ -52,9 +51,10 @@ module Morpheus::Cli::DeploymentsHelper
52
51
  return nil
53
52
  elsif deployments.size > 1
54
53
  print_red_alert "#{deployments.size} deployments found by name '#{name}'"
54
+ print_error "\n"
55
55
  puts_error as_pretty_table(deployments, [:id, :name], {color:red})
56
56
  print_red_alert "Try using ID instead"
57
- print reset,"\n"
57
+ print_error reset,"\n"
58
58
  return nil
59
59
  else
60
60
  return deployments[0]
@@ -123,13 +123,41 @@ module Morpheus::Cli::DeploymentsHelper
123
123
  return nil
124
124
  elsif deployment_versions.size > 1
125
125
  print_red_alert "#{deployment_versions.size} deployment versions found by version '#{name}'"
126
+ print_error "\n"
126
127
  puts_error as_pretty_table(deployment_versions, {"ID" => 'id', "VERSION" => 'userVersion'}, {color:red})
127
128
  print_red_alert "Try using ID instead"
128
- print reset,"\n"
129
+ print_error reset,"\n"
129
130
  return nil
130
131
  else
131
132
  return deployment_versions[0]
132
133
  end
133
134
  end
134
135
 
136
+ def format_deployment_version_number(deployment_version)
137
+ if deployment_version
138
+ deployment_version['userVersion'] || deployment_version['version'] || ''
139
+ else
140
+ ''
141
+ end
142
+ end
143
+
144
+ def format_app_deploy_status(status, return_color=cyan)
145
+ out = ""
146
+ s = status.to_s.downcase
147
+ if s == 'deployed' || s == 'committed'
148
+ out << "#{green}#{s.upcase}#{return_color}"
149
+ elsif s == 'open' || s == 'archived'
150
+ out << "#{cyan}#{s.upcase}#{return_color}"
151
+ elsif s == 'failed'
152
+ out << "#{red}#{s.upcase}#{return_color}"
153
+ else
154
+ out << "#{yellow}#{s.upcase}#{return_color}"
155
+ end
156
+ out
157
+ end
158
+
159
+ def format_deploy_type(val)
160
+ return val
161
+ end
162
+
135
163
  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
  }
@@ -1151,27 +1151,6 @@ module Morpheus::Cli::PrintHelper
1151
1151
  out
1152
1152
  end
1153
1153
 
1154
- def format_list(items, conjunction="and", limit=nil)
1155
- items = items ? items.clone : []
1156
- if limit
1157
- items = items.first(limit)
1158
- end
1159
- last_item = items.pop
1160
- if items.empty?
1161
- return "#{last_item}"
1162
- else
1163
- return items.join(", ") + (conjunction.to_s.empty? ? ", " : " #{conjunction} ") + "#{last_item}" + ((limit && limit < (items.size+1)) ? " ..." : "")
1164
- end
1165
- end
1166
-
1167
- def anded_list(items, limit=nil)
1168
- format_list(items, "and", limit)
1169
- end
1170
-
1171
- def ored_list(items, limit=nil)
1172
- format_list(items, "or", limit)
1173
- end
1174
-
1175
1154
  def sleep_with_dots(sleep_seconds, dots=3, dot_chr=".")
1176
1155
  dot_interval = (sleep_seconds.to_f / dots.to_i)
1177
1156
  dots.to_i.times do |dot_index|
@@ -1285,4 +1264,50 @@ module Morpheus::Cli::PrintHelper
1285
1264
  end
1286
1265
  end
1287
1266
 
1267
+ # convert JSON or YAML string to a map
1268
+ def parse_json_or_yaml(config, parsers = [:json, :yaml])
1269
+ rtn = {success: false, data: nil, err: nil}
1270
+ err = nil
1271
+ config = config.strip
1272
+ if config[0..2] == "---"
1273
+ parsers = [:yaml]
1274
+ end
1275
+ # ok only parse json for strings that start with {, consolidated yaml can look like json and cause issues}
1276
+ if config[0] && config[0].chr == "{" && config[-1] && config[-1].chr == "}"
1277
+ parsers = [:json]
1278
+ end
1279
+ parsers.each do |parser|
1280
+ if parser == :yaml
1281
+ begin
1282
+ # todo: one method to parse and return Hash
1283
+ # load does not raise an exception, it just returns the bad string
1284
+ #YAML.parse(config)
1285
+ config_map = YAML.load(config)
1286
+ if !config_map.is_a?(Hash)
1287
+ raise "Failed to parse config as YAML"
1288
+ end
1289
+ rtn[:data] = config_map
1290
+ rtn[:success] = true
1291
+ break
1292
+ rescue => ex
1293
+ rtn[:err] = ex if rtn[:err].nil?
1294
+ end
1295
+ elsif parser == :json
1296
+ begin
1297
+ config_map = JSON.parse(config)
1298
+ rtn[:data] = config_map
1299
+ rtn[:success] = true
1300
+ break
1301
+ rescue => ex
1302
+ rtn[:err] = ex if rtn[:err].nil?
1303
+ end
1304
+ end
1305
+ end
1306
+ return rtn
1307
+ end
1308
+
1309
+ def parse_yaml_or_json(config, parsers = [:yaml, :json])
1310
+ parse_json_or_yaml(config, parsers)
1311
+ end
1312
+
1288
1313
  end
@@ -596,11 +596,13 @@ module Morpheus::Cli::ProvisioningHelper
596
596
  v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'select', 'required' => false, 'selectOptions' => get_available_environments()}], options[:options])
597
597
  payload['instance']['instanceContext'] = v_prompt['environment'] if !v_prompt['environment'].empty?
598
598
 
599
- # Labels (tags)
600
- if options[:tags]
601
- payload['instance']['tags'] = options[:tags].is_a?(Array) ? options[:tags] : options[:tags].to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq
599
+ # Labels (Provisioning API still refers to these as tags)
600
+ # and tags (metadata tags) is called metadata.
601
+ # todo: switch this from 'tags' to labels' when the api changes
602
+ if options[:labels]
603
+ payload['instance']['tags'] = options[:labels].is_a?(Array) ? options[:labels] : options[:labels].to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq
602
604
  else
603
- v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'tags', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false}], options[:options])
605
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false}], options[:options])
604
606
  payload['instance']['tags'] = v_prompt['tags'].split(',').collect {|it| it.to_s.strip }.compact.uniq if !v_prompt['tags'].empty?
605
607
  end
606
608
 
@@ -976,6 +978,9 @@ module Morpheus::Cli::ProvisioningHelper
976
978
  metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
977
979
  metadata_list = metadata_list.collect do |it|
978
980
  metadata_pair = it.split(":")
981
+ if metadata_pair.size < 2 && it.include?("=")
982
+ metadata_pair = it.split("=")
983
+ end
979
984
  row = {}
980
985
  row['name'] = metadata_pair[0].to_s.strip
981
986
  row['value'] = metadata_pair[1].to_s.strip
@@ -2175,6 +2180,82 @@ module Morpheus::Cli::ProvisioningHelper
2175
2180
  return ports
2176
2181
  end
2177
2182
 
2183
+ def format_instance_status(instance, return_color=cyan)
2184
+ out = ""
2185
+ status_string = instance['status'].to_s
2186
+ if status_string == 'running'
2187
+ out << "#{green}#{status_string.upcase}#{return_color}"
2188
+ elsif status_string == 'provisioning'
2189
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
2190
+ elsif status_string == 'stopped' or status_string == 'failed'
2191
+ out << "#{red}#{status_string.upcase}#{return_color}"
2192
+ else
2193
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
2194
+ end
2195
+ out
2196
+ end
2197
+
2198
+ def format_instance_connection_string(instance)
2199
+ if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
2200
+ connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
2201
+ end
2202
+ end
2203
+
2204
+ def format_app_status(app, return_color=cyan)
2205
+ out = ""
2206
+ status_string = app['status'] || app['appStatus'] || ''
2207
+ if status_string == 'running'
2208
+ out = "#{green}#{status_string.upcase}#{return_color}"
2209
+ elsif status_string == 'provisioning'
2210
+ out = "#{cyan}#{status_string.upcase}#{cyan}"
2211
+ elsif status_string == 'stopped' or status_string == 'failed'
2212
+ out = "#{red}#{status_string.upcase}#{return_color}"
2213
+ elsif status_string == 'unknown'
2214
+ out = "#{yellow}#{status_string.upcase}#{return_color}"
2215
+ elsif status_string == 'warning' && app['instanceCount'].to_i == 0
2216
+ # show this instead of WARNING
2217
+ out = "#{cyan}EMPTY#{return_color}"
2218
+ else
2219
+ out = "#{yellow}#{status_string.upcase}#{return_color}"
2220
+ end
2221
+ out
2222
+ end
2223
+
2224
+ def format_container_status(container, return_color=cyan)
2225
+ out = ""
2226
+ status_string = container['status'].to_s
2227
+ if status_string == 'running'
2228
+ out << "#{green}#{status_string.upcase}#{return_color}"
2229
+ elsif status_string == 'provisioning'
2230
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
2231
+ elsif status_string == 'stopped' or status_string == 'failed'
2232
+ out << "#{red}#{status_string.upcase}#{return_color}"
2233
+ else
2234
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
2235
+ end
2236
+ out
2237
+ end
2238
+
2239
+ def format_container_connection_string(container)
2240
+ if !container['ports'].nil? && container['ports'].empty? == false
2241
+ connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
2242
+ else
2243
+ # eh? more logic needed here i think, see taglib morph:containerLocationMenu
2244
+ connection_string = "#{container['ip']}"
2245
+ end
2246
+ end
2247
+
2248
+ def format_instance_container_display_name(instance, plural=false)
2249
+ #<span class="info-label">${[null,'docker'].contains(instance.layout?.provisionType?.code) ? 'Containers' : 'Virtual Machines'}:</span> <span class="info-value">${instance.containers?.size()}</span>
2250
+ v = plural ? "Containers" : "Container"
2251
+ if instance && instance['layout'] && instance['layout'].key?("provisionTypeCode")
2252
+ if [nil, 'docker'].include?(instance['layout']["provisionTypeCode"])
2253
+ v = plural ? "Virtual Machines" : "Virtual Machine"
2254
+ end
2255
+ end
2256
+ return v
2257
+ end
2258
+
2178
2259
  def format_blueprint_type(type_code)
2179
2260
  return type_code.to_s # just show it as is
2180
2261
  if type_code.to_s.empty?
@@ -2200,4 +2281,19 @@ module Morpheus::Cli::ProvisioningHelper
2200
2281
  return type_code.to_s.downcase
2201
2282
  end
2202
2283
  end
2284
+
2285
+ def format_snapshot_status(snapshot, return_color=cyan)
2286
+ out = ""
2287
+ status_string = snapshot['status'].to_s
2288
+ if status_string == 'complete'
2289
+ out << "#{green}#{status_string.upcase}#{return_color}"
2290
+ elsif status_string == 'creating'
2291
+ out << "#{cyan}#{status_string.upcase}#{return_color}"
2292
+ elsif status_string == 'failed'
2293
+ out << "#{red}#{status_string.upcase}#{return_color}"
2294
+ else
2295
+ out << "#{yellow}#{status_string.upcase}#{return_color}"
2296
+ end
2297
+ out
2298
+ end
2203
2299
  end