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.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +8 -0
- data/lib/morpheus/api/backup_restores_interface.rb +4 -0
- data/lib/morpheus/api/backup_types_interface.rb +9 -0
- data/lib/morpheus/api/catalog_item_types_interface.rb +1 -2
- data/lib/morpheus/api/hub_interface.rb +25 -0
- data/lib/morpheus/cli/cli_command.rb +3 -0
- data/lib/morpheus/cli/commands/backup_types_command.rb +53 -0
- data/lib/morpheus/cli/commands/backups_command.rb +100 -21
- data/lib/morpheus/cli/commands/catalog_item_types_command.rb +3 -31
- data/lib/morpheus/cli/commands/clouds.rb +22 -4
- data/lib/morpheus/cli/commands/curl_command.rb +1 -1
- data/lib/morpheus/cli/commands/groups.rb +50 -7
- data/lib/morpheus/cli/commands/hub.rb +215 -0
- data/lib/morpheus/cli/commands/jobs_command.rb +10 -6
- data/lib/morpheus/cli/commands/library_forms_command.rb +1 -1
- data/lib/morpheus/cli/commands/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/commands/library_option_types_command.rb +1 -1
- data/lib/morpheus/cli/commands/license.rb +151 -86
- data/lib/morpheus/cli/commands/networks_command.rb +1 -1
- data/lib/morpheus/cli/commands/security_packages.rb +1 -1
- data/lib/morpheus/cli/commands/setup.rb +1 -1
- data/lib/morpheus/cli/commands/tasks.rb +2 -2
- data/lib/morpheus/cli/commands/user_settings_command.rb +10 -1
- data/lib/morpheus/cli/mixins/backups_helper.rb +4 -3
- data/lib/morpheus/cli/mixins/jobs_helper.rb +3 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +80 -2
- data/lib/morpheus/cli/option_types.rb +9 -2
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/routes.rb +2 -1
- 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
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
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
|
-
|
217
|
-
|
218
|
-
|
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(
|
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.
|
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|
|
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)
|