morpheus-cli 6.3.4 → 7.0.0

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