morpheus-cli 6.3.3 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api/api_client.rb +8 -0
  4. data/lib/morpheus/api/backup_restores_interface.rb +4 -0
  5. data/lib/morpheus/api/backup_types_interface.rb +9 -0
  6. data/lib/morpheus/api/catalog_item_types_interface.rb +1 -2
  7. data/lib/morpheus/api/hub_interface.rb +25 -0
  8. data/lib/morpheus/cli/cli_command.rb +3 -0
  9. data/lib/morpheus/cli/commands/backup_types_command.rb +53 -0
  10. data/lib/morpheus/cli/commands/backups_command.rb +100 -21
  11. data/lib/morpheus/cli/commands/catalog_item_types_command.rb +3 -31
  12. data/lib/morpheus/cli/commands/clouds.rb +22 -4
  13. data/lib/morpheus/cli/commands/curl_command.rb +1 -1
  14. data/lib/morpheus/cli/commands/groups.rb +50 -7
  15. data/lib/morpheus/cli/commands/hub.rb +215 -0
  16. data/lib/morpheus/cli/commands/jobs_command.rb +10 -6
  17. data/lib/morpheus/cli/commands/library_forms_command.rb +1 -1
  18. data/lib/morpheus/cli/commands/library_option_lists_command.rb +1 -1
  19. data/lib/morpheus/cli/commands/library_option_types_command.rb +1 -1
  20. data/lib/morpheus/cli/commands/license.rb +151 -86
  21. data/lib/morpheus/cli/commands/networks_command.rb +1 -1
  22. data/lib/morpheus/cli/commands/security_packages.rb +1 -1
  23. data/lib/morpheus/cli/commands/setup.rb +1 -1
  24. data/lib/morpheus/cli/commands/tasks.rb +2 -2
  25. data/lib/morpheus/cli/commands/user_settings_command.rb +10 -1
  26. data/lib/morpheus/cli/mixins/backups_helper.rb +4 -3
  27. data/lib/morpheus/cli/mixins/jobs_helper.rb +3 -0
  28. data/lib/morpheus/cli/mixins/print_helper.rb +80 -2
  29. data/lib/morpheus/cli/option_types.rb +9 -2
  30. data/lib/morpheus/cli/version.rb +1 -1
  31. data/lib/morpheus/routes.rb +2 -1
  32. metadata +6 -2
@@ -0,0 +1,215 @@
1
+ require 'morpheus/cli/cli_command'
2
+
3
+ class Morpheus::Cli::Hub
4
+ include Morpheus::Cli::CliCommand
5
+
6
+ set_command_description "View hub config and usage metrics."
7
+ set_command_name :'hub'
8
+ register_subcommands :get, {:usage => :usage_data}, :checkin, :register
9
+
10
+ # this is a hidden utility command
11
+ set_command_hidden
12
+
13
+ def connect(opts)
14
+ @api_client = establish_remote_appliance_connection(opts)
15
+ @hub_interface = @api_client.hub
16
+ end
17
+
18
+ def handle(args)
19
+ handle_subcommand(args)
20
+ end
21
+
22
+ def get(args)
23
+ options = {}
24
+ params = {}
25
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
26
+ opts.banner = subcommand_usage()
27
+ build_standard_get_options(opts, options)
28
+ opts.footer = "View hub configuration."
29
+ end
30
+ optparse.parse!(args)
31
+ verify_args!(args:args, optparse:optparse, count:0)
32
+ connect(options)
33
+ params.merge!(parse_query_options(options))
34
+ # execute api request
35
+ @hub_interface.setopts(options)
36
+ if options[:dry_run]
37
+ print_dry_run @hub_interface.dry.get(params)
38
+ return
39
+ end
40
+ json_response = @hub_interface.get(params)
41
+ # render response
42
+ render_response(json_response, options) do
43
+ print_h1 "Morpheus Hub Config", [], options
44
+ print_description_list({
45
+ "Hub URL" => lambda {|it| it['hub']['url'] },
46
+ "Appliance ID" => lambda {|it| it['hub']['applianceUniqueId'] },
47
+ "Registered" => lambda {|it| format_boolean(it['hub']['registered']) },
48
+ "Stats Reporting" => lambda {|it| format_boolean(it['hub']['reportStatus']) },
49
+ "Send Data" => lambda {|it| format_boolean(it['hub']['sendData']) },
50
+ }, json_response, options)
51
+ print reset,"\n"
52
+ end
53
+ return 0, nil
54
+ end
55
+
56
+ def usage_data(args)
57
+ options = {}
58
+ params = {}
59
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
60
+ opts.banner = subcommand_usage()
61
+ build_standard_get_options(opts, options)
62
+ opts.footer = "View appliance usage data that is sent to the hub."
63
+ end
64
+ optparse.parse!(args)
65
+ verify_args!(args:args, optparse:optparse, count:0)
66
+ connect(options)
67
+ params.merge!(parse_query_options(options))
68
+ # execute api request
69
+ @hub_interface.setopts(options)
70
+ if options[:dry_run]
71
+ print_dry_run @hub_interface.dry.usage(params)
72
+ return
73
+ end
74
+ json_response = @hub_interface.usage(params)
75
+ # render response
76
+ render_response(json_response, options) do
77
+ usage_data = json_response['data']
78
+ print_h1 "Morpheus Hub Usage", [], options
79
+ print_hub_usage_data_details(json_response, options)
80
+ print reset,"\n"
81
+ end
82
+ return 0, nil
83
+ end
84
+
85
+ def checkin(args)
86
+ options = {}
87
+ params = {}
88
+ payload = {}
89
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
90
+ opts.banner = subcommand_usage()
91
+ opts.on( nil, '--asynchronous', "Execute the checkin asynchronously" ) do
92
+ params[:async] = true
93
+ end
94
+ build_standard_post_options(opts, options)
95
+ opts.footer = <<-EOT
96
+ Checkin with the hub.
97
+ This sends the current appliance usage data to the hub and
98
+ it is only done if the appliance is registered with the hub
99
+ and the appliance license has Stats Reporting enabled.
100
+ EOT
101
+ end
102
+ optparse.parse!(args)
103
+ verify_args!(args:args, optparse:optparse, count:0)
104
+ connect(options)
105
+ payload = parse_payload(options)
106
+ # execute api request
107
+ @hub_interface.setopts(options)
108
+ if options[:dry_run]
109
+ print_dry_run @hub_interface.dry.checkin(payload, params)
110
+ return
111
+ end
112
+ json_response = @hub_interface.checkin(payload, params)
113
+ render_response(json_response, options) do
114
+ msg = json_response["msg"] || "Hub checkin complete"
115
+ print_green_success msg
116
+ end
117
+ end
118
+
119
+ def register(args)
120
+ options = {}
121
+ params = {}
122
+ payload = {}
123
+ optparse = Morpheus::Cli::OptionParser.new do |opts|
124
+ opts.banner = subcommand_usage()
125
+ opts.on( '-f', '--force', "Force registration" ) do
126
+ params[:force] = true
127
+ end
128
+ build_standard_post_options(opts, options)
129
+ opts.footer = <<-EOT
130
+ Register appliance with the hub to get a unique id for the appliance to checkin with.
131
+ The registration is skipped if the appliance is already registered.
132
+ The --force option can be used to execute this even if it is already registered.
133
+ EOT
134
+ end
135
+ optparse.parse!(args)
136
+ verify_args!(args:args, optparse:optparse, count:0)
137
+ connect(options)
138
+ payload = parse_payload(options)
139
+ # execute api request
140
+ @hub_interface.setopts(options)
141
+ if options[:dry_run]
142
+ print_dry_run @hub_interface.dry.register(payload, params)
143
+ return
144
+ end
145
+ json_response = @hub_interface.register(payload, params)
146
+ render_response(json_response, options) do
147
+ msg = json_response["msg"] || "Hub registration complete"
148
+ print_green_success msg
149
+ end
150
+ end
151
+
152
+ protected
153
+
154
+ def print_hub_usage_data_details(json_response, options)
155
+ usage_data = json_response['data']
156
+
157
+ #print_h2 "Appliance Info", options
158
+
159
+ print_description_list({
160
+ "Appliance URL" => lambda {|it| it['applianceUrl'] },
161
+ "Appliance Version" => lambda {|it| it['applianceVersion'] },
162
+ # "Appliance Unique ID" => lambda {|it| ['hubUniqueId'] },
163
+ "Stats Version" => lambda {|it| it['statsVersion'] },
164
+ "Last Login" => lambda {|it| it['lastLoggedIn'] ? format_local_dt(it['lastLoggedIn'] / 1000).to_s : '' },
165
+ "Timestamp (ms)" => lambda {|it| it['ts'] ? format_local_dt(it['ts'] / 1000).to_s : '' },
166
+ }, usage_data, options)
167
+
168
+
169
+ # print_h2 "Appliance Usage", options
170
+ # if usage_data['appliance']
171
+ # # print_h2 "Appliance", options
172
+ # print_description_list({
173
+ # "Total Groups" => lambda {|it| it['appliance']['totalGroups'] },
174
+ # "Total Clouds" => lambda {|it| it['appliance']['totalClouds'] },
175
+ # }, usage_data, options)
176
+ # end
177
+
178
+ print_h2 "Appliance", options
179
+ # print_details_raw(usage_data['appliance'], options)
180
+ print_details(usage_data['appliance'], {
181
+ pretty: true,
182
+ column_format: {
183
+ totalMemory: lambda {|it| format_bytes it['totalMemory'] },
184
+ totalMemoryUsed: lambda {|it| format_bytes it['totalMemoryUsed'] },
185
+ totalStorage: lambda {|it| format_bytes it['totalStorage'] },
186
+ totalStorageUsed: lambda {|it| format_bytes it['totalStorageUsed'] },
187
+ managedMemoryTotal: lambda {|it| format_bytes it['managedMemoryTotal'] },
188
+ managedMemoryUsed: lambda {|it| format_bytes it['managedMemoryUsed'] },
189
+ managedStorageTotal: lambda {|it| format_bytes it['managedStorageTotal'] },
190
+ managedStorageUsed: lambda {|it| format_bytes it['managedStorageUsed'] },
191
+ unmanagedMemoryTotal: lambda {|it| format_bytes it['unmanagedMemoryTotal'] },
192
+ unmanagedMemoryUsed: lambda {|it| format_bytes it['unmanagedMemoryUsed'] },
193
+ unmanagedStorageTotal: lambda {|it| format_bytes it['unmanagedStorageTotal'] },
194
+ unmanagedStorageUsed: lambda {|it| format_bytes it['unmanagedStorageUsed'] },
195
+ cloudTypes: lambda {|it| it['cloudTypes'] ? it['cloudTypes'].collect {|row| "#{row['code']} (#{row['count']})"}.join(", ") : '' },
196
+ instanceTypes: lambda {|it| it['instanceTypes'] ? it['instanceTypes'].collect {|row| "#{row['code']} (#{row['count']})"}.join(", ") : '' },
197
+ provisionTypes: lambda {|it| it['provisionTypes'] ? it['provisionTypes'].collect {|row| "#{row['code']} (#{row['count']})"}.join(", ") : '' },
198
+ serverTypes: lambda {|it| it['serverTypes'] ? it['serverTypes'].collect {|row| "#{row['code']} (#{row['count']})"}.join(", ") : '' },
199
+ clusterTypes: lambda {|it| it['clusterTypes'] ? it['clusterTypes'].collect {|row| "#{row['code']} (#{row['count']})"}.join(", ") : '' },
200
+ lastLoggedIn: lambda {|it| it['lastLoggedIn'] ? format_local_dt(it['lastLoggedIn'] / 1000).to_s : '' },
201
+ ts: lambda {|it| it['lastLoggedIn'] ? format_local_dt(it['ts'] / 1000).to_s : '' },
202
+ }
203
+ })
204
+
205
+ # print_h2 "Clouds", options
206
+ # print_h2 "Hosts", options
207
+ # print_h2 "Instances", options
208
+
209
+
210
+ end
211
+
212
+
213
+
214
+
215
+ end
@@ -840,6 +840,9 @@ class Morpheus::Cli::JobsCommand
840
840
  opts.on("--internal [true|false]", String, "Filters executions based on internal flag. Internal executions are excluded by default.") do |val|
841
841
  params["internalOnly"] = (val.to_s != "false")
842
842
  end
843
+ opts.on("--automation [true|false]", String, "Filters executions based on automation flag. Non-automation executions include ansible and kubernetes job types.") do |val|
844
+ params["automation"] = (val.to_s != "false")
845
+ end
843
846
  build_standard_list_options(opts, options, [:details])
844
847
  opts.footer = "List job executions.\n" +
845
848
  "[job] is optional. Job ID or name to filter executions."
@@ -876,16 +879,15 @@ class Morpheus::Cli::JobsCommand
876
879
  if params["internalOnly"]
877
880
  subtitles << "internalOnly: #{params['internalOnly']}"
878
881
  end
882
+ if !params["automation"].nil?
883
+ subtitles << "automation: #{params['automation']}"
884
+ end
879
885
  print_h1 title, subtitles, options
880
886
  print_job_executions(job_executions, options)
881
- print_results_pagination(json_response)
887
+ print_results_pagination(json_response) unless job_executions.size == 0
882
888
  print reset,"\n"
883
889
  end
884
- if job_executions.empty?
885
- return 3, "no executions found"
886
- else
887
- return 0, nil
888
- end
890
+ return 0, nil
889
891
  end
890
892
 
891
893
  def get_execution(args)
@@ -968,7 +970,9 @@ class Morpheus::Cli::JobsCommand
968
970
  end
969
971
  if event_data[:error] && event_data[:error].strip.length > 0
970
972
  print_h2 "Error"
973
+ print red
971
974
  print event['message'] || event['error']
975
+ print reset
972
976
  end
973
977
  print reset,"\n"
974
978
  return 0
@@ -323,7 +323,7 @@ EOT
323
323
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Form name'},
324
324
  {'fieldName' => 'code', 'fieldLabel' => 'Code', 'type' => 'text', 'required' => true, 'description' => 'Unique form code'},
325
325
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text'},
326
- {'shorthand' => '-l', 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }},
326
+ {'shorthand' => '-l', 'optionalValue' => true, 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }},
327
327
  ]
328
328
  end
329
329
 
@@ -431,7 +431,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
431
431
  # rest
432
432
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
433
433
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
434
- {'shorthand' => '-l', 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }, 'displayOrder' => 3},
434
+ {'shorthand' => '-l', 'optionalValue' => true, 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }, 'displayOrder' => 3},
435
435
  {'code' => 'optionTypeList.type', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => get_available_option_list_types, 'defaultValue' => 'rest', 'required' => true, 'description' => 'Option List Type. eg. rest, api, ldap, manual', 'displayOrder' => 4},
436
436
  {'fieldName' => 'visibility', 'fieldLabel' => 'Visibility', 'type' => 'select', 'selectOptions' => [{'name' => 'Private', 'value' => 'private'}, {'name' => 'Public', 'value' => 'public'}], 'defaultValue' => 'private', 'displayOrder' => 5},
437
437
  {'dependsOnCode' => 'optionTypeList.type:rest', 'fieldName' => 'sourceUrl', 'fieldLabel' => 'Source Url', 'type' => 'text', 'required' => true, 'description' => "A REST URL can be used to fetch list data and is cached in the appliance database.", 'displayOrder' => 6},
@@ -302,7 +302,7 @@ class Morpheus::Cli::LibraryOptionTypesCommand
302
302
  [
303
303
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'displayOrder' => 1},
304
304
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'displayOrder' => 2},
305
- {'shorthand' => '-l', 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }, 'displayOrder' => 3},
305
+ {'shorthand' => '-l', 'optionalValue' => true, 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }, 'displayOrder' => 3},
306
306
  {'fieldName' => 'fieldName', 'fieldLabel' => 'Field Name', 'type' => 'text', 'required' => true, 'description' => 'This is the input property that the value gets assigned to.', 'displayOrder' => 4},
307
307
  {'code' => 'optionType.type', 'fieldName' => 'type', 'fieldLabel' => 'Type', 'type' => 'select', 'selectOptions' => [{'name' => 'Text', 'value' => 'text'}, {'name' => 'Password', 'value' => 'password'}, {'name' => 'Number', 'value' => 'number'}, {'name' => 'Checkbox', 'value' => 'checkbox'}, {'name' => 'Select', 'value' => 'select'}, {'name' => 'Hidden', 'value' => 'hidden'}], 'defaultValue' => 'text', 'required' => true, 'displayOrder' => 5},
308
308
  {'fieldName' => 'optionList', 'fieldLabel' => 'Option List', 'type' => 'select', 'optionSource' => 'optionTypeLists', 'required' => true, 'dependsOnCode' => 'optionType.type:select', 'description' => "The Option List to be the source of options when type is 'select'.", 'displayOrder' => 6},
@@ -17,7 +17,6 @@ class Morpheus::Cli::License
17
17
 
18
18
  def connect(opts)
19
19
  @api_client = establish_remote_appliance_connection(opts)
20
- @api_client = @api_client
21
20
  @license_interface = @api_client.license
22
21
  end
23
22
 
@@ -40,78 +39,26 @@ class Morpheus::Cli::License
40
39
  raise_command_error "wrong number of arguments, expected 0 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
41
40
  end
42
41
  connect(options)
43
- begin
44
- # make api request
45
- @license_interface.setopts(options)
46
- if options[:dry_run]
47
- print_dry_run @license_interface.dry.get()
48
- return 0, nil
49
- end
50
- json_response = @license_interface.get()
51
- # 200 OK, parse results
42
+
43
+ # make api request
44
+ @license_interface.setopts(options)
45
+ if options[:dry_run]
46
+ print_dry_run @license_interface.dry.get()
47
+ return 0, nil
48
+ end
49
+ json_response = @license_interface.get()
50
+ render_response(json_response, options) do
52
51
  license = json_response['license']
53
52
  current_usage = json_response['currentUsage'] || {}
54
- used_memory = json_response['licenseUsedMemory'] || current_usage['memory']
55
- used_storage = current_usage['storage']
56
- used_workloads = current_usage['workloads']
57
-
58
- # Determine exit status and any error conditions for the command
59
- exit_code = 0
60
- err = nil
53
+ print_h1 "License"
61
54
  if license.nil?
62
- exit_code = 1
63
- err = "No license currently installed."
64
- end
65
-
66
- # render common formats like dry run, curl, json , yaml , etc.
67
- render_result = render_with_format(json_response, options)
68
- return exit_code, err if render_result
69
-
70
- if options[:quiet]
71
- return exit_code, err
72
- end
73
-
74
- if exit_code != 0
75
- print_error red, err.to_s, reset, "\n"
76
- return exit_code, err
55
+ print "#{yellow}No license currently installed#{reset}\n\n"
56
+ else
57
+ print_license_details(json_response)
58
+ print_h2 "Current Usage", [], options
59
+ print_license_usage(license, current_usage)
60
+ print reset,"\n"
77
61
  end
78
-
79
- # render output
80
-
81
- print_h1 "License"
82
- max_memory = "Unlimited"
83
- max_storage = "Unlimited"
84
- max_memory = Filesize.from("#{license['maxMemory']} B").pretty if license['maxMemory'].to_i != 0
85
- max_storage = Filesize.from("#{license['maxStorage']} B").pretty if license['maxStorage'].to_i != 0
86
- used_memory = Filesize.from("#{used_memory} B").pretty if used_memory.to_i != 0
87
- used_storage = Filesize.from("#{used_storage} B").pretty if used_storage.to_i != 0
88
- #used_workloads = "n/a" if used_workloads.nil?
89
- max_workloads = license["maxInstances"].to_i == 0 ? 'Unlimited' : license["maxInstances"]
90
- print cyan
91
- description_cols = {
92
- "Account" => 'accountName',
93
- "Product Tier" => lambda {|it| format_product_tier(it) },
94
- "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
95
- "End Date" => lambda {|it|
96
- if it['endDate']
97
- format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
98
- else
99
- 'Never'
100
- end
101
- },
102
- "Memory" => lambda {|it| "#{used_memory} / #{max_memory}" },
103
- "Storage" => lambda {|it| "#{used_storage} / #{max_storage}" },
104
- "Workloads" => lambda {|it| "#{used_workloads} / #{max_workloads}" },
105
- "Hard Limit" => lambda {|it| it[""] == false ? 'Yes' : 'No' },
106
- }
107
- print_description_list(description_cols, license)
108
- print reset,"\n"
109
-
110
- return exit_code, err
111
-
112
- rescue RestClient::Exception => e
113
- print_rest_exception(e, options)
114
- return false
115
62
  end
116
63
  end
117
64
 
@@ -126,6 +73,8 @@ class Morpheus::Cli::License
126
73
  optparse = Morpheus::Cli::OptionParser.new do |opts|
127
74
  opts.banner = subcommand_usage("[key]")
128
75
  build_common_options(opts, options, [:json, :dry_run, :remote])
76
+ opts.footer = "Install a new license key.\n" +
77
+ "This will potentially change the enabled features and capabilities of your appliance."
129
78
  end
130
79
  optparse.parse!(args)
131
80
  if args.count > 1
@@ -192,7 +141,7 @@ class Morpheus::Cli::License
192
141
  end
193
142
  json_response = @license_interface.test(key)
194
143
  license = json_response['license']
195
-
144
+ current_usage = json_response['currentUsage'] || {}
196
145
  exit_code, err = 0, nil
197
146
  if license.nil?
198
147
  err = "Unable to decode license."
@@ -213,22 +162,9 @@ class Morpheus::Cli::License
213
162
 
214
163
  # all good
215
164
  print_h1 "License"
216
- max_memory = "Unlimited"
217
- max_storage = "Unlimited"
218
- max_memory = Filesize.from("#{license['maxMemory']} B").pretty if license['maxMemory'].to_i != 0
219
- max_storage = Filesize.from("#{license['maxStorage']} B").pretty if license['maxStorage'].to_i != 0
220
- print cyan
221
- description_cols = {
222
- "Account" => 'accountName',
223
- "Product Tier" => lambda {|it| format_product_tier(it) },
224
- "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
225
- "End Date" => lambda {|it| format_local_dt(it['endDate']) },
226
- "Max Memory" => lambda {|it| max_memory },
227
- "Max Storage" => lambda {|it| max_storage },
228
- "Max Instances" => lambda {|it| it["maxInstances"].to_i == 0 ? 'Unlimited' : it["maxInstances"] },
229
- "Hard Limit" => lambda {|it| it[""] == false ? 'Yes' : 'No' },
230
- }
231
- print_description_list(description_cols, license)
165
+ print_license_details(json_response)
166
+ print_h2 "License Usage", [], options
167
+ print_license_usage(license, current_usage)
232
168
  print reset,"\n"
233
169
  return exit_code, err
234
170
  rescue RestClient::Exception => e
@@ -299,4 +235,133 @@ class Morpheus::Cli::License
299
235
  product_tier.to_s.capitalize
300
236
  end
301
237
  end
238
+
239
+ def format_limit_type(license)
240
+ limit_type = license['limitType'] || 'workload'
241
+ if limit_type == 'standard' || limit_type == 'metrics'
242
+ 'Standard'
243
+ else
244
+ limit_type.to_s.capitalize
245
+ end
246
+ end
247
+
248
+ def format_limit(max, used)
249
+ formatted_max = max.to_i > 0 ? format_number(max) : "Unlimited"
250
+ if used
251
+ formatted_used = format_number(used)
252
+ "#{formatted_used} / #{formatted_max}"
253
+ else
254
+ "#{formatted_max}"
255
+ end
256
+ end
257
+
258
+ def format_limit_bytes(max, used)
259
+ formatted_max = max.to_i > 0 ? format_bytes(max) : "Unlimited"
260
+ if used
261
+ formatted_used = format_bytes(used)
262
+ "#{formatted_used} / #{formatted_max}"
263
+ else
264
+ "#{formatted_max}"
265
+ end
266
+ end
267
+
268
+ def print_license_details(json_response)
269
+ license = json_response['license']
270
+ current_usage = json_response['currentUsage'] || {}
271
+ description_cols = {
272
+ "Account" => 'accountName',
273
+ "Product Tier" => lambda {|it| format_product_tier(it) },
274
+ "Start Date" => lambda {|it| format_local_dt(it['startDate']) },
275
+ "End Date" => lambda {|it|
276
+ if it['endDate']
277
+ format_local_dt(it['endDate']).to_s + ' (' + format_duration(Time.now, it['endDate']).to_s + ')'
278
+ else
279
+ 'Never'
280
+ end
281
+ },
282
+ "Multi-Tenant" => lambda {|it| format_boolean it["multiTenant"] },
283
+ "White Label" => lambda {|it| format_boolean it["whitelabel"] },
284
+ "Stats Reporting" => lambda {|it| format_boolean it["reportStatus"] },
285
+ "Hard Limit" => lambda {|it| format_boolean it["hardLimit"] },
286
+ "Limit Type" => lambda {|it| format_limit_type(it) },
287
+ }
288
+
289
+ description_cols.delete("Multi-Tenant") if !license['multiTenant']
290
+ description_cols.delete("White Label") if !license['whitelabel']
291
+
292
+ if license['zoneTypes'] && license['zoneTypes'].size > 0
293
+ description_cols["Included Clouds"] = lambda {|it| it['zoneTypes'].join(', ') }
294
+ elsif license['zoneTypesExcluded'] && license['zoneTypesExcluded'].size > 0
295
+ description_cols["Excluded Clouds"] = lambda {|it| it['zoneTypesExcluded'].join(', ') }
296
+ end
297
+ print_description_list(description_cols, license)
298
+ end
299
+
300
+ def print_license_usage(license, current_usage)
301
+ unlimited_label = "Unlimited"
302
+ # unlimited_label = "∞"
303
+ if license['limitType'] == 'standard'
304
+ # new standard metrics limit
305
+ used_managed_servers = current_usage['managedServers']
306
+ used_discovered_servers = current_usage['discoveredServers']
307
+ used_hosts = current_usage['hosts']
308
+ used_mvm = current_usage['mvm']
309
+ used_iac = current_usage['iac']
310
+ used_xaas = current_usage['xaas']
311
+ used_executions = current_usage['executions']
312
+ used_distributed_workers = current_usage['distributedWorkers']
313
+ used_discovered_objects = current_usage['discoveredObjects']
314
+
315
+
316
+ max_managed_servers = license["maxManagedServers"].to_i
317
+ max_discovered_servers = license['maxDiscoveredServers'].to_i
318
+ max_hosts = license['maxHosts'].to_i
319
+ max_mvm = license['maxMvm'].to_i
320
+ max_iac = license['maxIac'].to_i
321
+ max_xaas = license['maxXaas'].to_i
322
+ max_executions = license['maxExecutions'].to_i
323
+ max_distributed_workers = license['maxDistributedWorkers'].to_i
324
+ max_discovered_objects = license['maxDiscoveredObjects'].to_i
325
+ label_width = 20
326
+ chart_opts = {max_bars: 20, unlimited_label: '0%', percent_sigdig: 0}
327
+ out = ""
328
+ out << cyan + "Managed Servers".rjust(label_width, ' ') + ": " + generate_usage_bar(used_managed_servers, max_managed_servers, chart_opts) + cyan + used_managed_servers.to_s.rjust(8, ' ') + " / " + (max_managed_servers.to_i > 0 ? max_managed_servers.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
329
+ out << cyan + "Discovered Servers".rjust(label_width, ' ') + ": " + generate_usage_bar(used_discovered_servers, max_discovered_servers, chart_opts) + cyan + used_discovered_servers.to_s.rjust(8, ' ') + " / " + (max_discovered_servers.to_i > 0 ? max_discovered_servers.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
330
+ out << cyan + "Hosts".rjust(label_width, ' ') + ": " + generate_usage_bar(used_hosts, max_hosts, chart_opts) + cyan + used_hosts.to_s.rjust(8, ' ') + " / " + (max_hosts.to_i > 0 ? max_hosts.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
331
+ out << cyan + "MVM Hosts".rjust(label_width, ' ') + ": " + generate_usage_bar(used_mvm, max_mvm, chart_opts) + cyan + used_mvm.to_s.rjust(8, ' ') + " / " + (max_mvm.to_i > 0 ? max_mvm.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
332
+ out << cyan + "Iac Deployments".rjust(label_width, ' ') + ": " + generate_usage_bar(used_iac, max_iac, chart_opts) + cyan + used_iac.to_s.rjust(8, ' ') + " / " + (max_iac.to_i > 0 ? max_iac.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
333
+ out << cyan + "Xaas Instances".rjust(label_width, ' ') + ": " + generate_usage_bar(used_xaas, max_xaas, chart_opts) + cyan + used_xaas.to_s.rjust(8, ' ') + " / " + (max_xaas.to_i > 0 ? max_xaas.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
334
+ out << cyan + "Executions".rjust(label_width, ' ') + ": " + generate_usage_bar(used_executions, max_executions, chart_opts) + cyan + used_executions.to_s.rjust(8, ' ') + " / " + (max_executions.to_i > 0 ? max_executions.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
335
+ out << cyan + "Distributed Workers".rjust(label_width, ' ') + ": " + generate_usage_bar(used_distributed_workers, max_distributed_workers, chart_opts) + cyan + used_distributed_workers.to_s.rjust(8, ' ') + " / " + (max_distributed_workers.to_i > 0 ? max_distributed_workers.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
336
+ #out << cyan + "Discovered Objects".rjust(label_width, ' ') + ": " + generate_usage_bar(used_discovered_objects, max_discovered_objects, chart_opts) + cyan + used_discovered_objects.to_s.rjust(8, ' ') + " / " + (max_discovered_objects.to_i > 0 ? max_discovered_objects.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
337
+ print out
338
+ else
339
+ # old workloads limit
340
+ used_memory = current_usage['memory']
341
+ used_storage = current_usage['storage']
342
+ used_workloads = current_usage['workloads']
343
+ max_memory = license['maxMemory'].to_i
344
+ max_storage = license['maxStorage'].to_i
345
+ max_workloads = license['maxInstances'].to_i
346
+ label_width = 15
347
+ chart_opts = {max_bars: 20, unlimited_label: '0%', percent_sigdig: 0}
348
+ out = ""
349
+ out << cyan + "Workloads".rjust(label_width, ' ') + ": " + generate_usage_bar(used_workloads, max_workloads, chart_opts) + cyan + used_workloads.to_s.rjust(15, ' ') + " / " + (max_workloads.to_i > 0 ? max_workloads.to_s : unlimited_label).to_s.ljust(15, ' ') + "\n"
350
+ out << cyan + "Memory".rjust(label_width, ' ') + ": " + generate_usage_bar(used_memory, max_memory, chart_opts) + cyan + Filesize.from("#{used_memory} B").pretty.strip.rjust(15, ' ') + " / " + (max_memory.to_i > 0 ? Filesize.from("#{max_memory} B").pretty : unlimited_label).strip.ljust(15, ' ') + "\n"
351
+ out << cyan + "Storage".rjust(label_width, ' ') + ": " + generate_usage_bar(used_storage, max_storage, chart_opts) + cyan + Filesize.from("#{used_storage} B").pretty.strip.rjust(15, ' ') + " / " + (max_storage.to_i > 0 ? Filesize.from("#{max_storage} B").pretty : unlimited_label).strip.ljust(15, ' ') + "\n"
352
+ print out
353
+ end
354
+ end
355
+
356
+ # todo: use this to dry print_license_usage
357
+ def format_limit(label, used, max, opts={})
358
+ unlimited_label = "Unlimited"
359
+ # unlimited_label = "∞"
360
+ label_width = opts[:label_width] || 15
361
+ chart_opts = {max_bars: 20, unlimited_label: '0%', percent_sigdig: 0}
362
+ chart_opts.merge!(opts[:chart_opts]) if opts[:chart_opts]
363
+ cyan + label.rjust(label_width, ' ') + ": " + generate_usage_bar(used, max, chart_opts) + cyan + used.to_s.rjust(label_width, ' ') + " / " + (max.to_i > 0 ? max.to_s : unlimited_label).to_s.ljust(label_width, ' ') + "\n"
364
+ end
365
+
366
+
302
367
  end
@@ -132,7 +132,7 @@ class Morpheus::Cli::NetworksCommand
132
132
  pool: subnet['pool'] ? subnet['pool']['name'] : '',
133
133
  dhcp: subnet['dhcpServer'] ? 'Yes' : 'No',
134
134
  visibility: subnet['visibility'].to_s.capitalize,
135
- active: format_boolean(network['active']),
135
+ active: format_boolean(subnet['active']),
136
136
  tenants: subnet['tenants'] ? subnet['tenants'].collect {|it| it['name'] }.uniq.join(', ') : ''
137
137
  }
138
138
  rows << subnet_row
@@ -57,7 +57,7 @@ class Morpheus::Cli::SecurityPackagesCommand
57
57
  api_client.security_package_types.list({max:10000})['securityPackageTypes'].collect { |it| {"name" => it["name"], "value" => it["code"]} }
58
58
  }, 'required' => true, 'defaultValue' => 'SCAP Package'},
59
59
  {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true},
60
- {'shorthand' => '-l', 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }},
60
+ {'shorthand' => '-l', 'optionalValue' => true, 'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false, 'noPrompt' => true, 'processValue' => lambda {|val| parse_labels(val) }},
61
61
  {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false},
62
62
  {'fieldName' => 'enabled', 'fieldLabel' => 'Enabled', 'type' => 'checkbox', 'required' => false, 'defaultValue' => true},
63
63
  # {'code' => 'securityPackage.sourceType', 'fieldName' => 'sourceType', 'fieldLabel' => 'Source', 'type' => 'select', 'selectOptions' => [{'name'=>'url','value'=>'url'}], 'defaultValue' => 'url', 'required' => true},
@@ -385,7 +385,7 @@ EOT
385
385
 
386
386
  if hubmode == 'skip'
387
387
  if ::Morpheus::Cli::OptionTypes::confirm("Would you like to install your License Key now?", options.merge({:default => true}))
388
- cmd_res = Morpheus::Cli::License.new.apply([] + (options[:remote] ? ["-r",options[:remote]] : []))
388
+ cmd_res = Morpheus::Cli::License.new.install([] + (options[:remote] ? ["-r",options[:remote]] : []))
389
389
  # license_is_valid = cmd_res != false
390
390
  end
391
391
  end
@@ -1373,11 +1373,11 @@ class Morpheus::Cli::Tasks
1373
1373
  # this makes us all sad
1374
1374
  option_types.each do |option_type|
1375
1375
  if option_type['type'] == 'typeahead'
1376
- if ['operationalWorkflowName','containerScript','containerTemplate'].include?(option_type['code'])
1376
+ if ['operationalWorkflowName','ifOperationalWorkflowName','elseOperationalWorkflowName','containerScript','containerTemplate'].include?(option_type['code'])
1377
1377
  option_type.deep_merge!({'config' => {'valueField' => 'name'}})
1378
1378
  end
1379
1379
  elsif option_type['type'] == 'hidden'
1380
- if ['operationalWorkflowId','containerScriptId','containerTemplateId'].include?(option_type['code'])
1380
+ if ['operationalWorkflowId','ifOperationalWorkflowId','elseOperationalWorkflowId','containerScriptId','containerTemplateId'].include?(option_type['code'])
1381
1381
  option_type['processValue'] = lambda {|val|
1382
1382
  if val.to_s.empty?
1383
1383
  selected_option = Morpheus::Cli::OptionTypes.get_last_select()
@@ -104,7 +104,16 @@ EOT
104
104
  "ACCESS TOKEN" => lambda {|it| it['maskedAccessToken'] },
105
105
  "REFRESH TOKEN" => lambda {|it| it['maskedRefreshToken'] },
106
106
  "ACCESS EXPIRATION" => lambda {|it| format_local_dt(it['expiration']) },
107
- "ACCESS TTL" => lambda {|it| it['expiration'] ? (format_duration(it['expiration']) rescue '') : '' }
107
+ "ACCESS TTL" => lambda {|it|
108
+ if it['expiration']
109
+ expires_on = parse_time(it['expiration'])
110
+ if expires_on && expires_on < Time.now
111
+ "Expired"
112
+ else
113
+ it['expiration'] ? (format_duration(it['expiration']) rescue '') : ''
114
+ end
115
+ end
116
+ }
108
117
  }
109
118
  print cyan
110
119
  puts as_pretty_table(access_tokens, cols)