morpheus-cli 6.3.4 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 044ab9d85780cd6e8c58298dd7eea8c389093c0c40f97e444c1050ecb9447d65
4
- data.tar.gz: 54d733790244eea47f2310d0211dc4173ff26a89b9cde962cc64627f1ea8a68a
3
+ metadata.gz: 763ccc16e1c853b5d76e8466a21fd94abbd098615a884ff252f72015d5031338
4
+ data.tar.gz: c8ea0dcb3534ca6c3c557ce58763dfcef22ed59e7bdd33bde6eff00ee8513c1f
5
5
  SHA512:
6
- metadata.gz: 527cceb963b7b673ec478a1717cf39b0481a295a59026430944621d6e9133044e6296107d90c336750ff5413599032379e0d012285a23a63e1ecd1287558a863
7
- data.tar.gz: a160e2dc2676bcaaded2318268f72df801b9c6514293ba6c1ce59c54cc6803221e04885066d961bdb78dbcfa49855236f79ab24727685d35442e40dda5933ad5
6
+ metadata.gz: a68ef51fb607ce70ba7c93bdeb5ab637c741ce03ad24e987a63a2ba0db2d2ffac674bae2ff4aab02c543c76df93da178fe5a1d8e709df99444f2b4ba8e506945
7
+ data.tar.gz: 90b6702b8ebc4b75a0aa29da1802155dab2aca529c2d9538adb363f6c663b4241f632c7b22cc17e9e5b949598994e152107072ff19d7429bf944c69fcc4ad35e
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.7.5
2
2
 
3
- RUN gem install morpheus-cli -v 6.3.4
3
+ RUN gem install morpheus-cli -v 7.0.0
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -846,6 +846,10 @@ class Morpheus::APIClient
846
846
  Morpheus::HealthInterface.new(common_interface_options).setopts(@options)
847
847
  end
848
848
 
849
+ def hub
850
+ Morpheus::HubInterface.new(common_interface_options).setopts(@options)
851
+ end
852
+
849
853
  def audit
850
854
  Morpheus::AuditInterface.new(common_interface_options).setopts(@options)
851
855
  end
@@ -8,8 +8,7 @@ class Morpheus::CatalogItemTypesInterface < Morpheus::RestInterface
8
8
 
9
9
  # NOT json, multipart file upload, uses PUT update endpoint
10
10
  def update_logo(id, logo_file, dark_logo_file=nil)
11
- #url = "#{base_path}/#{id}/update-logo"
12
- url = "#{base_path}/#{id}"
11
+ url = "#{base_path}/#{id}/update-logo"
13
12
  headers = { :params => {}, :authorization => "Bearer #{@access_token}"}
14
13
  payload = {}
15
14
  payload["catalogItemType"] = {}
@@ -0,0 +1,25 @@
1
+ require 'morpheus/api/api_client'
2
+
3
+ class Morpheus::HubInterface < Morpheus::APIClient
4
+
5
+ def base_path
6
+ "/api/hub"
7
+ end
8
+
9
+ def get(params={}, headers={})
10
+ execute(method: :get, url: "#{base_path}", params: params, headers: headers)
11
+ end
12
+
13
+ def usage(params={}, headers={})
14
+ execute(method: :get, url: "#{base_path}/usage", params: params, headers: headers)
15
+ end
16
+
17
+ def checkin(payload={}, params={}, headers={})
18
+ execute(method: :post, url: "#{base_path}/checkin", payload: payload, params: params, headers: headers)
19
+ end
20
+
21
+ def register(payload={}, params={}, headers={})
22
+ execute(method: :post, url: "#{base_path}/register", payload: payload, params: params, headers: headers)
23
+ end
24
+
25
+ end
@@ -71,9 +71,6 @@ class Morpheus::Cli::CatalogItemTypesCommand
71
71
  print cyan,"No catalog item types found.",reset,"\n"
72
72
  else
73
73
  list_columns = catalog_item_type_list_column_definitions.upcase_keys!
74
- list_columns.delete("Blueprint")
75
- list_columns.delete("Workflow")
76
- list_columns.delete("Context")
77
74
  #list_columns["Config"] = lambda {|it| truncate_string(it['config'], 100) }
78
75
  print as_pretty_table(catalog_item_types, list_columns.upcase_keys!, options)
79
76
  print_results_pagination(json_response)
@@ -701,12 +698,13 @@ EOT
701
698
  "Description" => 'description',
702
699
  "Type" => lambda {|it| format_catalog_type(it) },
703
700
  "Visibility" => 'visibility',
704
- "Layout Code" => 'layoutCode',
701
+ #"Layout Code" => 'layoutCode',
705
702
  "Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : nil },
706
703
  "Workflow" => lambda {|it| it['workflow'] ? it['workflow']['name'] : nil },
707
- "Context" => lambda {|it| it['context'] },
704
+ # "Context" => lambda {|it| it['context'] },
708
705
  # "Content" => lambda {|it| it['content'] },
709
706
  "Form Type" => lambda {|it| it['formType'] == 'form' ? "Form" : "Inputs" },
707
+ "Form" => lambda {|it| it['form'] ? it['form']['name'] : nil },
710
708
  "Enabled" => lambda {|it| format_boolean(it['enabled']) },
711
709
  "Featured" => lambda {|it| format_boolean(it['featured']) },
712
710
  #"Config" => lambda {|it| it['config'] },
@@ -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
@@ -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
@@ -84,6 +84,7 @@ module Morpheus::Cli::JobsHelper
84
84
  end
85
85
  if process_data[:error] && process_data[:error].strip.length > 0
86
86
  print_h2 "Error"
87
+ print red
87
88
  print (process['message'] || process['error']).to_s.strip
88
89
  print reset,"\n"
89
90
  end
@@ -129,6 +130,8 @@ module Morpheus::Cli::JobsHelper
129
130
  out << "#{green}#{status_string.upcase}"
130
131
  elsif ['error', 'offline', 'failed', 'failure'].include?(status_string)
131
132
  out << "#{red}#{status_string.upcase}"
133
+ elsif ['running'].include?(status_string)
134
+ out << "#{cyan}#{status_string.upcase}"
132
135
  else
133
136
  out << "#{yellow}#{status_string.upcase}"
134
137
  end
@@ -514,7 +514,8 @@ module Morpheus::Cli::PrintHelper
514
514
  else
515
515
  percent = ((used_value.to_f / max_value.to_f) * 100)
516
516
  end
517
- percent_label = ((used_value.nil? || max_value.to_f == 0.0) ? "n/a" : "#{percent.round(percent_sigdig)}%").rjust(6, ' ')
517
+ unlimited_label = opts[:unlimited_label] || "n/a"
518
+ percent_label = ((used_value.nil? || max_value.to_f == 0.0) ? unlimited_label : "#{percent.round(percent_sigdig)}%").rjust(6, ' ')
518
519
  bar_display = ""
519
520
  if percent > 100
520
521
  max_bars.times { bars << "|" }
@@ -1000,6 +1001,83 @@ module Morpheus::Cli::PrintHelper
1000
1001
  print as_description_list(obj, columns, opts)
1001
1002
  end
1002
1003
 
1004
+ def print_pretty_details(obj, opts={})
1005
+ print as_pretty_details(obj, opts)
1006
+ end
1007
+
1008
+ def as_pretty_details(obj, opts={})
1009
+ print as_pretty_details(obj, opts.merge({pretty: true}))
1010
+ end
1011
+
1012
+ def print_details(obj, opts={})
1013
+ print as_details(obj, opts)
1014
+ end
1015
+
1016
+ def as_details(obj, opts={})
1017
+ #return "" if obj.nil?
1018
+ columns = {}
1019
+ keys = obj.keys
1020
+ if opts[:sort]
1021
+ keys.sort!
1022
+ end
1023
+ keys.each do |key|
1024
+ display_proc = nil
1025
+ if opts[:column_format] && opts[:column_format].key?(key.to_sym)
1026
+ display_proc = opts[:column_format][key.to_sym]
1027
+ else
1028
+ display_proc = lambda {|it| format_detail_value(it[key]) }
1029
+ end
1030
+ label = opts[:pretty] ? key.to_s.titleize : key.to_s
1031
+ columns[label] = display_proc
1032
+ end
1033
+ as_description_list(obj, columns, opts)
1034
+ end
1035
+
1036
+ def format_detail_value(value)
1037
+ rtn = value
1038
+ if value.is_a?(Array)
1039
+ # show the first three objects
1040
+ rtn = value.first(3).collect { |row| format_abbreviated_value(row) }.join(", ")
1041
+ elsif value.is_a?(Hash)
1042
+ # show the first three properties
1043
+ keys = values.keys.select { |key| value[key].is_a?(Numeric) || value[key].is_a?(String) }.first(3)
1044
+ rtn = keys.collect { |key|
1045
+ "#{key.to_s.titleize}: #{format_abbreviated_value(value[key])}"
1046
+ }.join(", ")
1047
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
1048
+ rtn = format_boolean(value)
1049
+ else
1050
+ rtn = value.to_s
1051
+ end
1052
+ return rtn
1053
+ end
1054
+
1055
+ def format_abbreviated_value(value)
1056
+ if value.is_a?(Hash)
1057
+ if value.keys.size == 0
1058
+ "{}"
1059
+ else
1060
+ # show the first three properties
1061
+ keys = value.keys.select { |key| value[key].is_a?(Numeric) || value[key].is_a?(String) }.first(3)
1062
+ keys.collect { |key|
1063
+ "#{key.to_s.titleize}: #{value[key]}"
1064
+ }.join(", ")
1065
+ end
1066
+ elsif value.is_a?(Array)
1067
+ if value.size == 0
1068
+ return "[]"
1069
+ elsif obj.size == 1
1070
+ "[(#{obj.size})]"
1071
+ else
1072
+ "[(#{obj.size})]"
1073
+ end
1074
+ elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
1075
+ format_boolean(value)
1076
+ else
1077
+ value.to_s
1078
+ end
1079
+ end
1080
+
1003
1081
  # build_column_definitions constructs an Array of column definitions (OpenStruct)
1004
1082
  # Each column is defined by a label (String), and a display_method (Proc)
1005
1083
  #
@@ -1059,7 +1137,7 @@ module Morpheus::Cli::PrintHelper
1059
1137
  column_def.display_method = lambda {|data| get_object_value(data, v) }
1060
1138
  elsif v.is_a?(Proc)
1061
1139
  column_def.display_method = v
1062
- elsif v.is_a?(Hash) || v.is_a?(OStruct)
1140
+ elsif v.is_a?(Hash) || v.is_a?(OpenStruct)
1063
1141
  if v[:display_name] || v[:label]
1064
1142
  column_def.label = v[:display_name] || v[:label]
1065
1143
  end
@@ -1,6 +1,6 @@
1
1
 
2
2
  module Morpheus
3
3
  module Cli
4
- VERSION = "6.3.4"
4
+ VERSION = "7.0.0"
5
5
  end
6
6
  end
@@ -223,9 +223,10 @@ module Morpheus::Routes
223
223
  path = "/admin/settings/environments"
224
224
  when "software-licenses"
225
225
  path = "/admin/settings/software-licenses"
226
- when "license"
226
+ when "license","licenses"
227
227
  path = "/admin/settings/#!license"
228
228
  end
229
+ # todo: this is weird, fix it so "view license matches license before software-licenses without needing the above alias..
229
230
  # dasherize path and attempt to match the plural first
230
231
  plural_path = path.pluralize
231
232
  paths = [path.dasherize]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: morpheus-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.4
4
+ version: 7.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Estes
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2024-02-15 00:00:00.000000000 Z
14
+ date: 2024-03-21 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -239,6 +239,7 @@ files:
239
239
  - lib/morpheus/api/guidance_interface.rb
240
240
  - lib/morpheus/api/guidance_settings_interface.rb
241
241
  - lib/morpheus/api/health_interface.rb
242
+ - lib/morpheus/api/hub_interface.rb
242
243
  - lib/morpheus/api/image_builder_boot_scripts_interface.rb
243
244
  - lib/morpheus/api/image_builder_image_builds_interface.rb
244
245
  - lib/morpheus/api/image_builder_interface.rb
@@ -426,6 +427,7 @@ files:
426
427
  - lib/morpheus/cli/commands/health_command.rb
427
428
  - lib/morpheus/cli/commands/history_command.rb
428
429
  - lib/morpheus/cli/commands/hosts.rb
430
+ - lib/morpheus/cli/commands/hub.rb
429
431
  - lib/morpheus/cli/commands/image_builder_command.rb
430
432
  - lib/morpheus/cli/commands/instance_types.rb
431
433
  - lib/morpheus/cli/commands/instances.rb