morpheus-cli 6.3.3 → 7.0.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 (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)