morpheus-cli 8.1.1.1 → 9.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 +2 -2
- data/lib/morpheus/api/api_client.rb +8 -0
- data/lib/morpheus/api/groups_interface.rb +2 -1
- data/lib/morpheus/api/servers_interface.rb +0 -1
- data/lib/morpheus/api/support_bundles_interface.rb +46 -0
- data/lib/morpheus/api/systems_interface.rb +32 -0
- data/lib/morpheus/api/tokens_interface.rb +39 -0
- data/lib/morpheus/cli/cli_command.rb +6 -1
- data/lib/morpheus/cli/commands/clients_command.rb +60 -74
- data/lib/morpheus/cli/commands/clouds.rb +30 -2
- data/lib/morpheus/cli/commands/clusters.rb +5 -0
- data/lib/morpheus/cli/commands/execution_request_command.rb +6 -2
- data/lib/morpheus/cli/commands/groups.rb +46 -23
- data/lib/morpheus/cli/commands/hosts.rb +21 -14
- data/lib/morpheus/cli/commands/instances.rb +32 -6
- data/lib/morpheus/cli/commands/storage_volumes.rb +1 -1
- data/lib/morpheus/cli/commands/support_bundles_command.rb +606 -0
- data/lib/morpheus/cli/commands/systems.rb +606 -2
- data/lib/morpheus/cli/commands/tokens_command.rb +391 -0
- data/lib/morpheus/cli/commands/workflows.rb +16 -3
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +30 -14
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +21 -7
- data/lib/morpheus/cli/version.rb +1 -1
- data/test/api/systems_interface_test.rb +26 -0
- data/test/cli/systems_test.rb +206 -0
- metadata +10 -2
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
|
2
|
+
|
|
3
|
+
# This provides commands for authentication
|
|
4
|
+
# This also includes credential management.
|
|
5
|
+
class Morpheus::Cli::TokensCommand
|
|
6
|
+
include Morpheus::Cli::CliCommand
|
|
7
|
+
include Morpheus::Cli::AccountsHelper
|
|
8
|
+
|
|
9
|
+
set_command_name :'tokens'
|
|
10
|
+
set_command_description "View and manage API access tokens."
|
|
11
|
+
register_subcommands :list, :get, :add, :remove, :remove_all
|
|
12
|
+
|
|
13
|
+
def connect(opts)
|
|
14
|
+
@api_client = establish_remote_appliance_connection(opts)
|
|
15
|
+
@tokens_interface = @api_client.tokens
|
|
16
|
+
@accounts_interface = @api_client.accounts
|
|
17
|
+
@account_users_interface = @api_client.account_users
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def handle(args)
|
|
21
|
+
handle_subcommand(args)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def list(args)
|
|
25
|
+
options = {}
|
|
26
|
+
params = {}
|
|
27
|
+
ref_ids = []
|
|
28
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
29
|
+
opts.banner = subcommand_usage("[search]")
|
|
30
|
+
build_standard_list_options(opts, options)
|
|
31
|
+
opts.on("--client-id CLIENT", "Filter by Client ID. eg. morph-api, morph-cli") do |val|
|
|
32
|
+
params['clientId'] = val.to_s
|
|
33
|
+
end
|
|
34
|
+
opts.on("--name TOKEN", "Filter by name") do |val|
|
|
35
|
+
params['name'] = val.to_s
|
|
36
|
+
end
|
|
37
|
+
opts.on("--value TOKEN", "Filter by access token value") do |val|
|
|
38
|
+
params['token'] = val.to_s
|
|
39
|
+
end
|
|
40
|
+
opts.on("-u", "--user USER", "User username or ID") do |val|
|
|
41
|
+
options[:user] = val.to_s
|
|
42
|
+
end
|
|
43
|
+
opts.on("--user-id ID", String, "User ID") do |val|
|
|
44
|
+
params['userId'] = val.to_s
|
|
45
|
+
end
|
|
46
|
+
opts.footer = "List API access tokens."
|
|
47
|
+
end
|
|
48
|
+
optparse.parse!(args)
|
|
49
|
+
connect(options)
|
|
50
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
|
51
|
+
if args.count > 0
|
|
52
|
+
options[:phrase] = args.join(" ")
|
|
53
|
+
end
|
|
54
|
+
params.merge!(parse_list_options(options))
|
|
55
|
+
if options[:user]
|
|
56
|
+
user = find_user_by_username_or_id(nil, options[:user], {global:true})
|
|
57
|
+
return 1 if user.nil?
|
|
58
|
+
params['userId'] = user['id']
|
|
59
|
+
end
|
|
60
|
+
@tokens_interface.setopts(options)
|
|
61
|
+
if options[:dry_run]
|
|
62
|
+
print_dry_run @tokens_interface.dry.list(params)
|
|
63
|
+
return
|
|
64
|
+
end
|
|
65
|
+
json_response = @tokens_interface.list(params)
|
|
66
|
+
tokens = json_response['tokens']
|
|
67
|
+
render_response(json_response, options, 'tokens') do
|
|
68
|
+
print_h1 "Morpheus API Tokens", parse_list_subtitles(options), options
|
|
69
|
+
if tokens.empty?
|
|
70
|
+
print yellow,"No tokens found.",reset,"\n"
|
|
71
|
+
else
|
|
72
|
+
columns = token_columns.select {|k,v| ["ID", "Name", "Client ID", "Username", "Access Token", "Expiration", "TTL", "Date Created"].include?(k) }.upcase_keys!
|
|
73
|
+
#columns = token_columns.upcase_keys!
|
|
74
|
+
print as_pretty_table(tokens, columns, options)
|
|
75
|
+
print_results_pagination(json_response)
|
|
76
|
+
end
|
|
77
|
+
print reset,"\n"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def get(args)
|
|
82
|
+
params = {}
|
|
83
|
+
options = {}
|
|
84
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
85
|
+
opts.banner = subcommand_usage("[id]")
|
|
86
|
+
opts.on("-u", "--user USER", "User username or ID") do |val|
|
|
87
|
+
options[:user] = val.to_s
|
|
88
|
+
end
|
|
89
|
+
opts.on("--user-id ID", String, "User ID") do |val|
|
|
90
|
+
params['userId'] = val.to_s
|
|
91
|
+
end
|
|
92
|
+
build_standard_get_options(opts, options)
|
|
93
|
+
opts.footer = <<-EOT
|
|
94
|
+
Get details about a specific token.
|
|
95
|
+
[token] is required. This is the id or name or value of the token.
|
|
96
|
+
EOT
|
|
97
|
+
end
|
|
98
|
+
optparse.parse!(args)
|
|
99
|
+
verify_args!(args:args, optparse:optparse, min:1)
|
|
100
|
+
connect(options)
|
|
101
|
+
if options[:user]
|
|
102
|
+
user = find_user_by_username_or_id(nil, options[:user], {global:true})
|
|
103
|
+
return 1 if user.nil?
|
|
104
|
+
params['userId'] = user['id']
|
|
105
|
+
end
|
|
106
|
+
id_list = parse_id_list(args).collect do |id|
|
|
107
|
+
if id.to_s =~ /\A\d{1,}\Z/
|
|
108
|
+
id
|
|
109
|
+
else
|
|
110
|
+
# Looking for a token by secret value? eg. "93cb5548-********""
|
|
111
|
+
if id.to_s =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
112
|
+
token = find_token_by_token(id, params['userId'])
|
|
113
|
+
if token
|
|
114
|
+
token['id']
|
|
115
|
+
else
|
|
116
|
+
return 1, "Token not found for '#{id[0..8]}********'"
|
|
117
|
+
end
|
|
118
|
+
else
|
|
119
|
+
token = find_token_by_name(id, params['userId'])
|
|
120
|
+
if token
|
|
121
|
+
token['id']
|
|
122
|
+
else
|
|
123
|
+
return 1, "Token not found for '#{id}'"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
return run_command_for_each_arg(id_list) do |id|
|
|
129
|
+
_get(id, params, options)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def _get(id, params, options)
|
|
134
|
+
@tokens_interface.setopts(options)
|
|
135
|
+
if options[:dry_run]
|
|
136
|
+
print_dry_run @tokens_interface.dry.get(id, params)
|
|
137
|
+
return
|
|
138
|
+
end
|
|
139
|
+
json_response = @tokens_interface.get(id, params)
|
|
140
|
+
render_response(json_response, options, 'token') do
|
|
141
|
+
token = json_response['token']
|
|
142
|
+
print_h1 "Token Details", [], options
|
|
143
|
+
print cyan
|
|
144
|
+
print_description_list(token_columns, token)
|
|
145
|
+
print reset,"\n"
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def add(args)
|
|
150
|
+
options = {}
|
|
151
|
+
params = {}
|
|
152
|
+
|
|
153
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
154
|
+
opts.banner = subcommand_usage("[name]")
|
|
155
|
+
build_option_type_options(opts, options, add_token_option_types)
|
|
156
|
+
opts.on("-u", "--user USER", "User username or ID") do |val|
|
|
157
|
+
options[:user] = val.to_s
|
|
158
|
+
end
|
|
159
|
+
opts.on("--user-id ID", String, "User ID") do |val|
|
|
160
|
+
params['userId'] = val.to_s
|
|
161
|
+
end
|
|
162
|
+
build_standard_add_options(opts, options)
|
|
163
|
+
opts.footer = <<-EOT
|
|
164
|
+
Create a new token
|
|
165
|
+
EOT
|
|
166
|
+
end
|
|
167
|
+
optparse.parse!(args)
|
|
168
|
+
verify_args!(args:args, optparse:optparse, min:0, max:1)
|
|
169
|
+
connect(options)
|
|
170
|
+
if args[0]
|
|
171
|
+
options[:options]['name'] = args[0]
|
|
172
|
+
end
|
|
173
|
+
payload = {}
|
|
174
|
+
if options[:payload]
|
|
175
|
+
payload = options[:payload]
|
|
176
|
+
payload.deep_merge!({'token' => parse_passed_options(options)})
|
|
177
|
+
else
|
|
178
|
+
payload.deep_merge!({'token' => parse_passed_options(options)})
|
|
179
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(add_token_option_types, options[:options], @api_client, options[:params])
|
|
180
|
+
params.deep_merge!(v_prompt)
|
|
181
|
+
payload['token'].deep_merge!(params)
|
|
182
|
+
end
|
|
183
|
+
if options[:user]
|
|
184
|
+
user = find_user_by_username_or_id(nil, options[:user], {global:true})
|
|
185
|
+
return 1 if user.nil?
|
|
186
|
+
params['userId'] = user['id']
|
|
187
|
+
end
|
|
188
|
+
@tokens_interface.setopts(options)
|
|
189
|
+
if options[:dry_run]
|
|
190
|
+
print_dry_run @tokens_interface.dry.create(payload, params)
|
|
191
|
+
return 0, nil
|
|
192
|
+
end
|
|
193
|
+
json_response = @tokens_interface.create(payload, params)
|
|
194
|
+
render_response(json_response, options, 'token') do
|
|
195
|
+
token = json_response['token']
|
|
196
|
+
# print_green_success "Created new token"
|
|
197
|
+
# print_green_success "Access Token: #{token['accessToken']}"
|
|
198
|
+
# print_green_success "Refresh Token: #{token['refreshToken']}"
|
|
199
|
+
# return _get(token["id"], {}, options)
|
|
200
|
+
print_green_success "Added token #{token['name'] || token['id']}"
|
|
201
|
+
# show new access and refresh tokens unmasked and in green
|
|
202
|
+
columns = token_columns
|
|
203
|
+
columns["Access Token"] = lambda {|it| "#{green}#{it['accessToken']}#{cyan}" }
|
|
204
|
+
columns["Refresh Token"] = lambda {|it| "#{green}#{it['refreshToken']}#{cyan}" }
|
|
205
|
+
print_h1 "New Token Details", [], options
|
|
206
|
+
print cyan
|
|
207
|
+
print_description_list(columns, token)
|
|
208
|
+
print reset,"\n"
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def remove(args)
|
|
213
|
+
options = {}
|
|
214
|
+
params = {}
|
|
215
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
216
|
+
opts.banner = subcommand_usage("[id list]")
|
|
217
|
+
opts.on("--client-id CLIENT", "Filter by Client ID. eg. morph-api, morph-cli") do |val|
|
|
218
|
+
params['clientId'] = val.to_s
|
|
219
|
+
end
|
|
220
|
+
opts.on("-u", "--user USER", "User username or ID") do |val|
|
|
221
|
+
options[:user] = val.to_s
|
|
222
|
+
end
|
|
223
|
+
opts.on("--user-id ID", String, "User ID") do |val|
|
|
224
|
+
params['userId'] = val.to_s
|
|
225
|
+
end
|
|
226
|
+
build_standard_remove_options(opts, options)
|
|
227
|
+
opts.footer = <<-EOT
|
|
228
|
+
Delete a token.
|
|
229
|
+
[id] is required. This is the id of a token.
|
|
230
|
+
EOT
|
|
231
|
+
end
|
|
232
|
+
optparse.parse!(args)
|
|
233
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
|
234
|
+
connect(options)
|
|
235
|
+
if options[:user]
|
|
236
|
+
user = find_user_by_username_or_id(nil, options[:user], {global:true})
|
|
237
|
+
return 1 if user.nil?
|
|
238
|
+
params['userId'] = user['id']
|
|
239
|
+
end
|
|
240
|
+
token = find_token_by_name_or_id(args[0], params['userId'])
|
|
241
|
+
return 1, "Token not found" if token.nil?
|
|
242
|
+
parse_options(options, params)
|
|
243
|
+
confirm!("Are you sure you want to delete the token ID: #{token['id']} Value: #{token['maskedAccessToken']}?", options)
|
|
244
|
+
execute_api(@tokens_interface, :destroy, [token['id']], options) do |json_response|
|
|
245
|
+
print_green_success "Removed token #{token['maskedAccessToken']}"
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def remove_all(args)
|
|
250
|
+
client_id = nil
|
|
251
|
+
options = {}
|
|
252
|
+
params = {}
|
|
253
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
|
254
|
+
opts.banner = subcommand_usage("[id]")
|
|
255
|
+
opts.on("--client-id CLIENT", "Delete all tokens for a specific Client ID. eg. morph-api, morph-cli") do |val|
|
|
256
|
+
client_id = val.to_s
|
|
257
|
+
end
|
|
258
|
+
opts.on("-u", "--user USER", "User username or ID") do |val|
|
|
259
|
+
options[:user] = val.to_s
|
|
260
|
+
end
|
|
261
|
+
opts.on("--user-id ID", String, "User ID") do |val|
|
|
262
|
+
params['userId'] = val.to_s
|
|
263
|
+
end
|
|
264
|
+
build_standard_remove_options(opts, options)
|
|
265
|
+
opts.footer = <<-EOT
|
|
266
|
+
Delete many tokens at once.
|
|
267
|
+
[id list] is required. This is the list of token ids to be deleted
|
|
268
|
+
This command supports using --client-id CLIENT option instead of [id list]
|
|
269
|
+
EOT
|
|
270
|
+
end
|
|
271
|
+
optparse.parse!(args)
|
|
272
|
+
verify_args!(args:args, optparse:optparse)
|
|
273
|
+
connect(options)
|
|
274
|
+
if options[:user]
|
|
275
|
+
user = find_user_by_username_or_id(nil, options[:user], {global:true})
|
|
276
|
+
return 1 if user.nil?
|
|
277
|
+
params['userId'] = user['id']
|
|
278
|
+
end
|
|
279
|
+
id_list = parse_id_list(args)
|
|
280
|
+
if client_id
|
|
281
|
+
confirm!("Are you sure you want to perform this bulk delete tokens '#{client_id}'?", options)
|
|
282
|
+
params['clientId'] = client_id
|
|
283
|
+
execute_api(@tokens_interface, :destroy_all, [params], options) do |json_response|
|
|
284
|
+
print_green_success "Removed all your tokens for client #{client_id}"
|
|
285
|
+
end
|
|
286
|
+
elsif id_list && !id_list.empty?
|
|
287
|
+
confirm!("Are you sure you want to perform this bulk delete of #{id_list.size} tokens?", options)
|
|
288
|
+
params['id'] = id_list
|
|
289
|
+
execute_api(@tokens_interface, :destroy_all, [params], options) do |json_response|
|
|
290
|
+
print_green_success "Removed #{id_list.size} tokens"
|
|
291
|
+
end
|
|
292
|
+
else
|
|
293
|
+
raise_command_error "Bulk delete requires a list of ids"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
protected
|
|
298
|
+
|
|
299
|
+
def token_columns
|
|
300
|
+
{
|
|
301
|
+
"ID" => lambda {|it| it['id'] },
|
|
302
|
+
"Name" => lambda {|it| it['name'] },
|
|
303
|
+
"Client ID" => lambda {|it| it['clientId'] },
|
|
304
|
+
"Username" => lambda {|it| it['username'] },
|
|
305
|
+
"Access Token" => lambda {|it| it['maskedAccessToken'] },
|
|
306
|
+
#"Refresh Token" => lambda {|it| it['maskedRefreshToken'] },
|
|
307
|
+
"Scope" => lambda {|it| it['scope'] },
|
|
308
|
+
"Expiration" => lambda {|it| format_local_dt(it['expiration']) },
|
|
309
|
+
"TTL" => lambda {|it|
|
|
310
|
+
if it['expiration']
|
|
311
|
+
expires_on = parse_time(it['expiration'])
|
|
312
|
+
if expires_on && expires_on < Time.now
|
|
313
|
+
"Expired"
|
|
314
|
+
else
|
|
315
|
+
it['expiration'] ? (format_duration(it['expiration']) rescue '') : ''
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
},
|
|
319
|
+
# "Date Created" => lambda {|it| format_local_dt(it['dateCreated']) },
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def add_token_option_types
|
|
324
|
+
[
|
|
325
|
+
{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'description' => "Optional display name for this access token"},
|
|
326
|
+
{'fieldName' => 'clientId', 'fieldLabel' => 'Client ID', 'type' => 'select', 'optionSource' => 'clients', 'required' => true, 'defaultValue' => 'morph-api'},
|
|
327
|
+
]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def find_token_by_name_or_id(val, user_id=nil)
|
|
331
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
|
332
|
+
return find_token_by_id(val, user_id=nil)
|
|
333
|
+
else
|
|
334
|
+
return find_token_by_name(val, user_id=nil)
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def find_token_by_id(id, user_id=nil)
|
|
339
|
+
params = {}
|
|
340
|
+
params['userId'] = user_id if user_id
|
|
341
|
+
begin
|
|
342
|
+
json_response = @tokens_interface.get(id.to_i, params)
|
|
343
|
+
return json_response['token']
|
|
344
|
+
rescue RestClient::Exception => e
|
|
345
|
+
if e.response && e.response.code == 404
|
|
346
|
+
print_red_alert "Token not found by id '#{id}'" + (user_id ? " for user #{user_id}" : "")
|
|
347
|
+
else
|
|
348
|
+
raise e
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def find_token_by_name(name, user_id=nil)
|
|
354
|
+
params = {name: name.to_s}
|
|
355
|
+
params['userId'] = user_id if user_id
|
|
356
|
+
json_response = @tokens_interface.list(params)
|
|
357
|
+
tokens = json_response['tokens']
|
|
358
|
+
if tokens.empty?
|
|
359
|
+
print_red_alert "Token not found by name '#{name}'" + (user_id ? " for user #{user_id}" : "")
|
|
360
|
+
return nil
|
|
361
|
+
elsif tokens.size > 1
|
|
362
|
+
print_red_alert "#{tokens.size} tokens found matching '#{name}'" + (user_id ? " for user #{user_id}" : "")
|
|
363
|
+
puts_error as_pretty_table(tokens, [:id, :name], {color:red})
|
|
364
|
+
print_red_alert "Try using ID instead"
|
|
365
|
+
print reset,"\n"
|
|
366
|
+
return nil
|
|
367
|
+
else
|
|
368
|
+
return tokens[0]
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def find_token_by_token(value, user_id=nil)
|
|
373
|
+
params = {token: value.to_s}
|
|
374
|
+
params['userId'] = user_id if user_id
|
|
375
|
+
json_response = @tokens_interface.list(params)
|
|
376
|
+
tokens = json_response['tokens']
|
|
377
|
+
if tokens.empty?
|
|
378
|
+
print_red_alert "Tokens not found by value" + (user_id ? " for user #{user_id}" : "")
|
|
379
|
+
return nil
|
|
380
|
+
elsif tokens.size > 1
|
|
381
|
+
print_red_alert "#{tokens.size} tokens found matching '#{value}'" + (user_id ? " for user #{user_id}" : "")
|
|
382
|
+
puts_error as_pretty_table(tokens, [:id, :'accessToken'], {color:red})
|
|
383
|
+
print_red_alert "Try using ID instead"
|
|
384
|
+
print reset,"\n"
|
|
385
|
+
return nil
|
|
386
|
+
else
|
|
387
|
+
return tokens[0]
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
end
|
|
@@ -726,11 +726,24 @@ class Morpheus::Cli::Workflows
|
|
|
726
726
|
# prompt to workflow optionTypes for customOptions
|
|
727
727
|
custom_options = nil
|
|
728
728
|
if workflow['optionTypes'] && workflow['optionTypes'].size() > 0
|
|
729
|
+
# Clone to avoid mutating workflow data, and clear fieldContext so that
|
|
730
|
+
# accumulated results remain flat (not nested under 'customOptions') when
|
|
731
|
+
# passed as params to dependent option source API calls. If fieldContext
|
|
732
|
+
# were left as 'customOptions', option_params would be sent to the options
|
|
733
|
+
# API as customOptions[zoneId]=2 instead of the expected flat zoneId=2,
|
|
734
|
+
# causing dependent option lists (e.g. Resource Pools) to return empty.
|
|
729
735
|
custom_option_types = workflow['optionTypes'].collect {|it|
|
|
730
|
-
|
|
731
|
-
|
|
736
|
+
ot = it.clone
|
|
737
|
+
ot.delete('fieldContext')
|
|
738
|
+
ot
|
|
732
739
|
}
|
|
733
|
-
|
|
740
|
+
# Support both -O fieldName=value AND the legacy -O customOptions.fieldName=value syntax.
|
|
741
|
+
prompt_options = (options[:options] || {}).dup
|
|
742
|
+
if prompt_options['customOptions'].is_a?(Hash)
|
|
743
|
+
prompt_options.merge!(prompt_options.delete('customOptions'))
|
|
744
|
+
end
|
|
745
|
+
prompt_results = Morpheus::Cli::OptionTypes.prompt(custom_option_types, prompt_options, @api_client, {})
|
|
746
|
+
custom_options = {'customOptions' => prompt_results} unless prompt_results.empty?
|
|
734
747
|
end
|
|
735
748
|
job_payload = {}
|
|
736
749
|
job_payload.deep_merge!(params)
|
|
@@ -63,11 +63,11 @@ module Morpheus::Cli::InfrastructureHelper
|
|
|
63
63
|
@subnet_types_interface
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
def find_group_by_name_or_id(val)
|
|
66
|
+
def find_group_by_name_or_id(val, include_tenants=true)
|
|
67
67
|
if val.to_s =~ /\A\d{1,}\Z/
|
|
68
68
|
return find_group_by_id(val)
|
|
69
69
|
else
|
|
70
|
-
return find_group_by_name(val)
|
|
70
|
+
return find_group_by_name(val, include_tenants)
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
@@ -85,21 +85,29 @@ module Morpheus::Cli::InfrastructureHelper
|
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def find_group_by_name(name)
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
def find_group_by_name(name, include_tenants=false)
|
|
89
|
+
params = {name: name}
|
|
90
|
+
params['includeTenants'] = true if include_tenants
|
|
91
|
+
groups = groups_interface.list(params)['groups']
|
|
92
|
+
if groups.empty?
|
|
91
93
|
print_red_alert "Group not found by name #{name}"
|
|
92
94
|
exit 1
|
|
95
|
+
elsif groups.size > 1
|
|
96
|
+
print_red_alert "Multiple groups exist with the name '#{name}'"
|
|
97
|
+
print_error "\n"
|
|
98
|
+
puts_error as_pretty_table(groups, [:id, :name], {color:red})
|
|
99
|
+
print_red_alert "Try using ID instead"
|
|
100
|
+
print_error reset,"\n"
|
|
101
|
+
exit 1
|
|
93
102
|
end
|
|
94
|
-
|
|
95
|
-
return group
|
|
103
|
+
return groups[0]
|
|
96
104
|
end
|
|
97
105
|
|
|
98
|
-
def find_cloud_by_name_or_id(val)
|
|
106
|
+
def find_cloud_by_name_or_id(val, include_tenants=false)
|
|
99
107
|
if val.to_s =~ /\A\d{1,}\Z/
|
|
100
108
|
return find_cloud_by_id(val)
|
|
101
109
|
else
|
|
102
|
-
return find_cloud_by_name(val)
|
|
110
|
+
return find_cloud_by_name(val, include_tenants)
|
|
103
111
|
end
|
|
104
112
|
end
|
|
105
113
|
|
|
@@ -113,14 +121,22 @@ module Morpheus::Cli::InfrastructureHelper
|
|
|
113
121
|
return cloud
|
|
114
122
|
end
|
|
115
123
|
|
|
116
|
-
def find_cloud_by_name(name)
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
def find_cloud_by_name(name, include_tenants=false)
|
|
125
|
+
params = {name: name}
|
|
126
|
+
params['includeTenants'] = true if include_tenants
|
|
127
|
+
clouds = clouds_interface.list(params)['zones']
|
|
128
|
+
if clouds.empty?
|
|
119
129
|
print_red_alert "Cloud not found by name #{name}"
|
|
120
130
|
exit 1
|
|
131
|
+
elsif clouds.size > 1
|
|
132
|
+
print_red_alert "Multiple clouds exist with the name '#{name}'"
|
|
133
|
+
print_error "\n"
|
|
134
|
+
puts_error as_pretty_table(clouds, [:id, :name], {color:red})
|
|
135
|
+
print_red_alert "Try using ID instead"
|
|
136
|
+
print_error reset,"\n"
|
|
137
|
+
exit 1
|
|
121
138
|
end
|
|
122
|
-
|
|
123
|
-
return cloud
|
|
139
|
+
return clouds[0]
|
|
124
140
|
end
|
|
125
141
|
|
|
126
142
|
def get_available_cloud_types(refresh=false, params = {})
|
|
@@ -225,11 +225,11 @@ module Morpheus::Cli::ProvisioningHelper
|
|
|
225
225
|
end
|
|
226
226
|
end
|
|
227
227
|
|
|
228
|
-
def find_instance_by_name_or_id(val)
|
|
228
|
+
def find_instance_by_name_or_id(val, include_tenants=false)
|
|
229
229
|
if val.to_s =~ /\A\d{1,}\Z/
|
|
230
230
|
return find_instance_by_id(val)
|
|
231
231
|
else
|
|
232
|
-
return find_instance_by_name(val)
|
|
232
|
+
return find_instance_by_name(val, include_tenants)
|
|
233
233
|
end
|
|
234
234
|
end
|
|
235
235
|
|
|
@@ -247,14 +247,21 @@ module Morpheus::Cli::ProvisioningHelper
|
|
|
247
247
|
end
|
|
248
248
|
end
|
|
249
249
|
|
|
250
|
-
def find_instance_by_name(name)
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
def find_instance_by_name(name, include_tenants=false)
|
|
251
|
+
params = {name: name.to_s}
|
|
252
|
+
params['includeTenants'] = true if include_tenants
|
|
253
|
+
instances = instances_interface.list(params)['instances']
|
|
254
|
+
if instances.empty?
|
|
253
255
|
print_red_alert "Instance not found by name #{name}"
|
|
254
256
|
exit 1
|
|
257
|
+
elsif instances.size > 1
|
|
258
|
+
print_red_alert "Multiple Instances exist with the name '#{name}'"
|
|
259
|
+
puts_error as_pretty_table(instances, [:id, :name], {color:red})
|
|
260
|
+
print_red_alert "Try using ID instead"
|
|
261
|
+
print_error reset,"\n"
|
|
262
|
+
exit 1
|
|
255
263
|
end
|
|
256
|
-
|
|
257
|
-
return instance
|
|
264
|
+
return instances[0]
|
|
258
265
|
end
|
|
259
266
|
|
|
260
267
|
def parse_instance_id_list(id_list)
|
|
@@ -1056,6 +1063,13 @@ module Morpheus::Cli::ProvisioningHelper
|
|
|
1056
1063
|
end
|
|
1057
1064
|
end
|
|
1058
1065
|
|
|
1066
|
+
# Fix any hugepages option type is using on/off which
|
|
1067
|
+
if payload['config']
|
|
1068
|
+
if payload['config']['hugepages'] == "off"
|
|
1069
|
+
payload['config'].delete('hugepages')
|
|
1070
|
+
end
|
|
1071
|
+
end
|
|
1072
|
+
|
|
1059
1073
|
return payload
|
|
1060
1074
|
end
|
|
1061
1075
|
|
data/lib/morpheus/cli/version.rb
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'test/unit'
|
|
2
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __dir__))
|
|
3
|
+
require 'morpheus/api/systems_interface'
|
|
4
|
+
|
|
5
|
+
class SystemsInterfaceTest < Test::Unit::TestCase
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
@systems_interface = Morpheus::SystemsInterface.new(access_token: 'token', url: 'https://example.test', verify_ssl: false)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def test_list_network_server_update_definitions_builds_expected_request
|
|
12
|
+
request = @systems_interface.dry.list_network_server_update_definitions(2, 3, {phrase: 'fw'})
|
|
13
|
+
assert_equal :get, request[:method]
|
|
14
|
+
assert_equal 'https://example.test/api/infrastructure/systems/2/network-servers/3/update-definitions', request[:url]
|
|
15
|
+
assert_equal({phrase: 'fw'}, request[:params])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def test_apply_network_server_update_definition_builds_expected_request
|
|
19
|
+
request = @systems_interface.dry.apply_network_server_update_definition(2, 3, 4, {dryRun: true}, {foo: 'bar'})
|
|
20
|
+
assert_equal :post, request[:method]
|
|
21
|
+
assert_equal 'https://example.test/api/infrastructure/systems/2/network-servers/3/update-definitions/4', request[:url]
|
|
22
|
+
assert_equal({foo: 'bar'}, request[:params])
|
|
23
|
+
assert_equal({dryRun: true}.to_json, request[:payload])
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|