morpheus-cli 4.2.21 → 5.2.0

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