morpheus-cli 8.0.5 → 8.0.6
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 +4 -0
- data/lib/morpheus/api/clusters_interface.rb +8 -0
- data/lib/morpheus/api/network_floating_ips_interface.rb +3 -0
- data/lib/morpheus/api/storage_datastores_interface.rb +44 -0
- data/lib/morpheus/cli/commands/clients_command.rb +14 -7
- data/lib/morpheus/cli/commands/cloud_datastores_command.rb +113 -1
- data/lib/morpheus/cli/commands/clouds.rb +0 -1
- data/lib/morpheus/cli/commands/clouds_types.rb +0 -1
- data/lib/morpheus/cli/commands/clusters.rb +35 -0
- data/lib/morpheus/cli/commands/containers_command.rb +21 -19
- data/lib/morpheus/cli/commands/network_domains_command.rb +1 -1
- data/lib/morpheus/cli/commands/network_floating_ips.rb +39 -1
- data/lib/morpheus/cli/commands/network_routers_command.rb +14 -1
- data/lib/morpheus/cli/commands/storage_datastores.rb +457 -0
- data/lib/morpheus/cli/commands/user_settings_command.rb +22 -6
- data/lib/morpheus/cli/mixins/infrastructure_helper.rb +1 -0
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +13 -2
- data/lib/morpheus/cli/option_types.rb +5 -5
- data/lib/morpheus/cli/version.rb +1 -1
- data/test/api/clients_interface_test.rb +23 -0
- data/test/cli/clients_test.rb +45 -0
- metadata +8 -2
@@ -0,0 +1,457 @@
|
|
1
|
+
require 'morpheus/cli/cli_command'
|
2
|
+
|
3
|
+
class Morpheus::Cli::StorageDatastores
|
4
|
+
include Morpheus::Cli::CliCommand
|
5
|
+
|
6
|
+
register_subcommands :list, :add, :update, :get
|
7
|
+
|
8
|
+
def connect(opts)
|
9
|
+
@api_client = establish_remote_appliance_connection(opts)
|
10
|
+
@storage_datastore_interface = @api_client.storage_datastores
|
11
|
+
@cloud_datastores_interface = @api_client.cloud_datastores
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle(args)
|
15
|
+
handle_subcommand(args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def list(args)
|
19
|
+
options = {}
|
20
|
+
params = {}
|
21
|
+
|
22
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
23
|
+
opts.banner = subcommand_usage()
|
24
|
+
build_standard_list_options(opts, options)
|
25
|
+
opts.footer = "List Datastores."
|
26
|
+
end
|
27
|
+
optparse.parse!(args)
|
28
|
+
connect(options)
|
29
|
+
# verify_args!(args:args, optparse:optparse, count:0)
|
30
|
+
if args.count > 0
|
31
|
+
options[:phrase] = args.join(" ")
|
32
|
+
end
|
33
|
+
begin
|
34
|
+
params.merge!(parse_list_options(options))
|
35
|
+
|
36
|
+
@storage_datastore_interface.setopts(options)
|
37
|
+
if options[:dry_run]
|
38
|
+
print_dry_run @storage_datastore_interface.dry.list(params)
|
39
|
+
return 0
|
40
|
+
end
|
41
|
+
|
42
|
+
json_response = @storage_datastore_interface.list(params)
|
43
|
+
render_response(json_response, options, 'datastores') do
|
44
|
+
datastores = json_response['datastores']
|
45
|
+
title = "Storage Datastores"
|
46
|
+
print_h1 title
|
47
|
+
if datastores.empty?
|
48
|
+
print cyan,"No datastores found.",reset,"\n"
|
49
|
+
else
|
50
|
+
columns = datastores_list_column_definitions(options).upcase_keys!
|
51
|
+
print as_pretty_table(datastores, columns, options)
|
52
|
+
print_results_pagination(json_response)
|
53
|
+
end
|
54
|
+
print reset,"\n"
|
55
|
+
end
|
56
|
+
return 0, nil
|
57
|
+
rescue RestClient::Exception => e
|
58
|
+
print_rest_exception(e, options)
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def datastores_list_column_definitions(options)
|
64
|
+
{
|
65
|
+
"ID" => 'id',
|
66
|
+
"Name" => 'name'
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def add(args)
|
71
|
+
options = {}
|
72
|
+
params = {}
|
73
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
74
|
+
opts.banner = subcommand_usage("[name] [options]")
|
75
|
+
build_common_options(opts, options, [:options, :payload, :json, :yaml, :dry_run, :quiet])
|
76
|
+
opts.on( '-n', '--name NAME', "Name" ) do |val|
|
77
|
+
options['name'] = val
|
78
|
+
end
|
79
|
+
opts.on( '-t', '--type DATASTORE_TYPE', "Datastore Type" ) do |val|
|
80
|
+
options['datastoreType'] = val
|
81
|
+
end
|
82
|
+
opts.on( '-c', '--cloud DATASTORE_CLOUD', "Datastore Cloud" ) do |val|
|
83
|
+
options['cloud'] = val
|
84
|
+
end
|
85
|
+
opts.footer = "Create a new Datastore.\n" +
|
86
|
+
"[name] is required. This is the name of the new datastore. It may also be passed as --name or inside your config."
|
87
|
+
end
|
88
|
+
optparse.parse!(args)
|
89
|
+
if args.count > 1
|
90
|
+
print_error Morpheus::Terminal.angry_prompt
|
91
|
+
puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args}\n#{optparse}"
|
92
|
+
return 1
|
93
|
+
end
|
94
|
+
# allow name as first argument
|
95
|
+
if args[0] # && !options[:name]
|
96
|
+
options[:name] = args[0]
|
97
|
+
end
|
98
|
+
connect(options)
|
99
|
+
begin
|
100
|
+
options[:options] ||= {}
|
101
|
+
passed_options = (options[:options] || {}).reject {|k,v| k.is_a?(Symbol) }
|
102
|
+
payload = {}
|
103
|
+
if options[:payload]
|
104
|
+
# payload is from parsed json|yaml files or arguments.
|
105
|
+
payload = options[:payload]
|
106
|
+
# merge -O options
|
107
|
+
payload.deep_merge!(passed_options) unless passed_options.empty?
|
108
|
+
# support some options on top of --payload
|
109
|
+
[:name, :description, :environment].each do |k|
|
110
|
+
if options.key?(k)
|
111
|
+
payload[k.to_s] = options[k]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
else
|
115
|
+
# prompt for payload
|
116
|
+
payload = {}
|
117
|
+
# merge -O options
|
118
|
+
payload.deep_merge!(passed_options) unless passed_options.empty?
|
119
|
+
|
120
|
+
# Name
|
121
|
+
if passed_options['name']
|
122
|
+
payload['name'] = passed_options['name']
|
123
|
+
else
|
124
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this archive bucket.'}], options, @api_client)
|
125
|
+
payload['name'] = v_prompt['name']
|
126
|
+
end
|
127
|
+
|
128
|
+
#Datastore Type
|
129
|
+
if passed_options['datastoreType']
|
130
|
+
payload['datastoreType'] = passed_options['datastoreType']
|
131
|
+
else
|
132
|
+
payload['datastoreType'] = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'datastoreType', 'fieldLabel' => 'Type', 'type' => 'select', 'required' => true, 'optionSource' => 'datastoreTypes'}], options[:options], @api_client)['datastoreType']
|
133
|
+
end
|
134
|
+
|
135
|
+
if passed_options['cloud']
|
136
|
+
zone = passed_options['cloud']
|
137
|
+
else
|
138
|
+
zone = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'zone', 'fieldLabel' => 'Cloud', 'type' => 'select', 'required' => true, 'optionSource' => 'cloudsForDatastores'}], options[:options], @api_client)['zone']
|
139
|
+
end
|
140
|
+
|
141
|
+
if zone
|
142
|
+
payload['refType'] = 'ComputeZone'
|
143
|
+
payload['refId'] = zone
|
144
|
+
end
|
145
|
+
|
146
|
+
option_types = load_option_types_for_datastore_type(payload['datastoreType'])
|
147
|
+
|
148
|
+
values = Morpheus::Cli::OptionTypes.prompt(option_types, options[:options], @api_client)
|
149
|
+
if values['domain']
|
150
|
+
payload.merge!(values['domain']) if values['domain'].is_a?(Hash)
|
151
|
+
end
|
152
|
+
if values['config']
|
153
|
+
payload['config'] = {}
|
154
|
+
payload['config'].merge!(values['config']) if values['config'].is_a?(Hash)
|
155
|
+
end
|
156
|
+
|
157
|
+
@storage_datastore_interface.setopts(options)
|
158
|
+
if options[:dry_run]
|
159
|
+
print_dry_run @storage_datastore_interface.dry.create({'datastore' => payload})
|
160
|
+
return
|
161
|
+
end
|
162
|
+
json_response = @storage_datastore_interface.create({'datastore' => payload})
|
163
|
+
datastore = json_response['datastore']
|
164
|
+
if options[:json]
|
165
|
+
print JSON.pretty_generate(json_response),"\n"
|
166
|
+
elsif !options[:quiet]
|
167
|
+
datastore = json_response['datastore']
|
168
|
+
print_green_success "Datastore #{datastore['name']} created"
|
169
|
+
#get([datastore['id']])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
rescue RestClient::Exception => e
|
173
|
+
print_rest_exception(e, options)
|
174
|
+
exit 1
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def get(args)
|
179
|
+
datastore_id = nil
|
180
|
+
options = {}
|
181
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
182
|
+
opts.banner = subcommand_usage("[datastore]")
|
183
|
+
build_common_options(opts, options, [:json, :yaml, :csv, :fields, :dry_run, :remote])
|
184
|
+
opts.footer = "Get details about a datastore." + "\n" +
|
185
|
+
"[datastore] is required. This is the name or id of a datastore."
|
186
|
+
end
|
187
|
+
optparse.parse!(args)
|
188
|
+
if args.count == 1
|
189
|
+
datastore_id = args[0]
|
190
|
+
else
|
191
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
192
|
+
end
|
193
|
+
connect(options)
|
194
|
+
begin
|
195
|
+
@storage_datastore_interface.setopts(options)
|
196
|
+
if options[:dry_run]
|
197
|
+
if datastore_id.to_s =~ /\A\d{1,}\Z/
|
198
|
+
print_dry_run @storage_datastore_interface.dry.get(datastore_id.to_i)
|
199
|
+
else
|
200
|
+
print_dry_run @storage_datastore_interface.dry.list({name:datastore_id})
|
201
|
+
end
|
202
|
+
return
|
203
|
+
end
|
204
|
+
datastore = find_datastore_by_name_or_id(datastore_id)
|
205
|
+
return 1 if datastore.nil?
|
206
|
+
json_response = {'datastore' => datastore} # skip redundant request
|
207
|
+
# json_response = @datastores_interface.get(datastore['id'])
|
208
|
+
datastore = json_response['datastore']
|
209
|
+
if options[:json]
|
210
|
+
puts as_json(json_response, options, "datastore")
|
211
|
+
return 0
|
212
|
+
elsif options[:yaml]
|
213
|
+
puts as_yaml(json_response, options, "datastore")
|
214
|
+
return 0
|
215
|
+
elsif options[:csv]
|
216
|
+
puts records_as_csv([datastore], options)
|
217
|
+
return 0
|
218
|
+
end
|
219
|
+
print_h1 "Datastore Details"
|
220
|
+
print cyan
|
221
|
+
description_cols = {
|
222
|
+
"ID" => 'id',
|
223
|
+
"Name" => 'name',
|
224
|
+
"Type" => lambda {|it| it['type'].to_s.capitalize },
|
225
|
+
"Cloud" => lambda {|it| it['zone'] ? it['zone']['name'] : '' },
|
226
|
+
"Capacity" => lambda {|it| it['freeSpace'] ? Filesize.from("#{it['freeSpace']} B").pretty.strip : "Unknown" },
|
227
|
+
"Online" => lambda {|it| format_boolean(it['online']) },
|
228
|
+
"Active" => lambda {|it| format_boolean(it['active']) },
|
229
|
+
"Visibility" => lambda {|it| it['visibility'].to_s.capitalize },
|
230
|
+
"Tenants" => lambda {|it| it['tenants'] ? it['tenants'].collect {|it| it['name'] }.uniq.join(', ') : '' },
|
231
|
+
# "Owner" => lambda {|it| it['owner'] ? it['owner']['name'] : '' },
|
232
|
+
}
|
233
|
+
print_description_list(description_cols, datastore)
|
234
|
+
|
235
|
+
if datastore['resourcePermission'].nil?
|
236
|
+
print "\n", "No group access found", "\n"
|
237
|
+
else
|
238
|
+
print_h2 "Group Access"
|
239
|
+
rows = []
|
240
|
+
if datastore['resourcePermission']['all']
|
241
|
+
rows.push({"name" => 'All'})
|
242
|
+
end
|
243
|
+
if datastore['resourcePermission']['sites']
|
244
|
+
datastore['resourcePermission']['sites'].each do |site|
|
245
|
+
rows.push(site)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
rows = rows.collect do |site|
|
249
|
+
# {group: site['name'], default: site['default'] ? 'Yes' : ''}
|
250
|
+
{group: site['name']}
|
251
|
+
end
|
252
|
+
# columns = [:group, :default]
|
253
|
+
columns = [:group]
|
254
|
+
print cyan
|
255
|
+
print as_pretty_table(rows, columns)
|
256
|
+
end
|
257
|
+
|
258
|
+
if datastore['tenants'].nil? || datastore['tenants'].nil?
|
259
|
+
#print "\n", "No tenant permissions found", "\n"
|
260
|
+
else
|
261
|
+
print_h2 "Tenant Permissions"
|
262
|
+
rows = []
|
263
|
+
rows = datastore['tenants'] || []
|
264
|
+
tenant_columns = {
|
265
|
+
"TENANT" => 'name',
|
266
|
+
#"DEFAULT" => lambda {|it| format_boolean(it['defaultTarget']) },
|
267
|
+
"IMAGE TARGET" => lambda {|it| format_boolean(it['defaultStore']) }
|
268
|
+
}
|
269
|
+
print cyan
|
270
|
+
print as_pretty_table(rows, tenant_columns)
|
271
|
+
end
|
272
|
+
|
273
|
+
print reset,"\n"
|
274
|
+
return 0
|
275
|
+
rescue RestClient::Exception => e
|
276
|
+
print_rest_exception(e, options)
|
277
|
+
return 1
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def update(args)
|
282
|
+
options = {}
|
283
|
+
datastore_id = nil
|
284
|
+
cloud_id = nil
|
285
|
+
tenants = nil
|
286
|
+
group_access_all = nil
|
287
|
+
group_access_list = nil
|
288
|
+
group_defaults_list = nil
|
289
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
290
|
+
opts.banner = subcommand_usage("[datastore] [options]")
|
291
|
+
opts.add_hidden_option('-c') # prefer args[0] for [cloud]
|
292
|
+
opts.on('--group-access-all [on|off]', String, "Toggle Access for all groups.") do |val|
|
293
|
+
group_access_all = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
294
|
+
end
|
295
|
+
opts.on('--group-access LIST', Array, "Group Access, comma separated list of group IDs.") do |list|
|
296
|
+
if list.size == 1 && list[0] == 'null' # hacky way to clear it
|
297
|
+
group_access_list = []
|
298
|
+
else
|
299
|
+
group_access_list = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
300
|
+
end
|
301
|
+
end
|
302
|
+
opts.on('--tenants LIST', Array, "Tenant Access, comma separated list of account IDs") do |list|
|
303
|
+
if list.size == 1 && list[0] == 'null' # hacky way to clear it
|
304
|
+
options['tenants'] = []
|
305
|
+
else
|
306
|
+
options['tenants'] = list.collect {|it| it.to_s.strip.empty? ? nil : it.to_s.strip }.compact.uniq
|
307
|
+
end
|
308
|
+
end
|
309
|
+
opts.on('--visibility [private|public]', String, "Visibility") do |val|
|
310
|
+
options['visibility'] = val
|
311
|
+
end
|
312
|
+
opts.on('--active [on|off]', String, "Can be used to disable a datastore") do |val|
|
313
|
+
options['active'] = val.to_s == 'on' || val.to_s == 'true' || val.to_s == ''
|
314
|
+
end
|
315
|
+
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
316
|
+
opts.footer = "Update a datastore." + "\n" +
|
317
|
+
"[cloud] is required. This is the name or id of the cloud." + "\n" +
|
318
|
+
"[datastore] is required. This is the name or id of a datastore."
|
319
|
+
end
|
320
|
+
optparse.parse!(args)
|
321
|
+
if args.count == 1
|
322
|
+
datastore_id = args[0]
|
323
|
+
else
|
324
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(' ')}\n#{optparse}"
|
325
|
+
end
|
326
|
+
connect(options)
|
327
|
+
|
328
|
+
begin
|
329
|
+
datastore = find_datastore_by_name_or_id(datastore_id)
|
330
|
+
return 1 if datastore.nil?
|
331
|
+
|
332
|
+
# merge -O options into normally parsed options
|
333
|
+
options.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
334
|
+
|
335
|
+
# construct payload
|
336
|
+
payload = nil
|
337
|
+
if options[:payload]
|
338
|
+
payload = options[:payload]
|
339
|
+
else
|
340
|
+
# prompt for datastore options
|
341
|
+
payload = {
|
342
|
+
'datastore' => {
|
343
|
+
}
|
344
|
+
}
|
345
|
+
|
346
|
+
# allow arbitrary -O options
|
347
|
+
payload['datastore'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
348
|
+
|
349
|
+
|
350
|
+
# Group Access
|
351
|
+
if group_access_all != nil
|
352
|
+
payload['resourcePermissions'] ||= {}
|
353
|
+
payload['resourcePermissions']['all'] = group_access_all
|
354
|
+
end
|
355
|
+
if group_access_list != nil
|
356
|
+
payload['resourcePermissions'] ||= {}
|
357
|
+
payload['resourcePermissions']['sites'] = group_access_list.collect do |site_id|
|
358
|
+
site = {"id" => site_id.to_i}
|
359
|
+
if group_defaults_list && group_defaults_list.include?(site_id)
|
360
|
+
site["default"] = true
|
361
|
+
end
|
362
|
+
site
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
# Tenants
|
367
|
+
if options['tenants']
|
368
|
+
payload['tenantPermissions'] = {}
|
369
|
+
payload['tenantPermissions']['accounts'] = options['tenants']
|
370
|
+
end
|
371
|
+
|
372
|
+
# Active
|
373
|
+
if options['active'] != nil
|
374
|
+
payload['datastore']['active'] = options['active']
|
375
|
+
end
|
376
|
+
|
377
|
+
# Visibility
|
378
|
+
if options['visibility'] != nil
|
379
|
+
payload['datastore']['visibility'] = options['visibility']
|
380
|
+
end
|
381
|
+
|
382
|
+
if payload['datastore'].empty? && payload['resourcePermissions'].nil? && payload['tenantPermissions'].nil?
|
383
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
384
|
+
end
|
385
|
+
|
386
|
+
end
|
387
|
+
@storage_datastore_interface.setopts(options)
|
388
|
+
if options[:dry_run]
|
389
|
+
print_dry_run @storage_datastore_interface.dry.update(datastore["id"], payload)
|
390
|
+
return
|
391
|
+
end
|
392
|
+
json_response = @storage_datastore_interface.update(datastore["id"], payload)
|
393
|
+
if options[:json]
|
394
|
+
puts as_json(json_response)
|
395
|
+
else
|
396
|
+
datastore = json_response['data']['datastore']
|
397
|
+
print_green_success "Updated datastore #{datastore['name']}"
|
398
|
+
get([datastore['id']])
|
399
|
+
end
|
400
|
+
return 0
|
401
|
+
rescue RestClient::Exception => e
|
402
|
+
print_rest_exception(e, options)
|
403
|
+
return 1
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def load_option_types_for_datastore_type(datastore_type)
|
408
|
+
return @storage_datastore_interface.load_type_options(datastore_type)
|
409
|
+
end
|
410
|
+
|
411
|
+
def find_datastore_by_name_or_id(val)
|
412
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
413
|
+
return find_datastore_by_id(val)
|
414
|
+
else
|
415
|
+
return find_datastore_by_name(val)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def find_datastore_by_id(id)
|
420
|
+
begin
|
421
|
+
json_response = @storage_datastore_interface.get(id.to_i)
|
422
|
+
return json_response['datastore']
|
423
|
+
rescue RestClient::Exception => e
|
424
|
+
if e.response && e.response.code == 404
|
425
|
+
print_red_alert "Datastore not found by id #{id}"
|
426
|
+
return nil
|
427
|
+
else
|
428
|
+
raise e
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def find_datastore_by_name(name)
|
434
|
+
json_response = @storage_datastore_interface.list({name: name.to_s})
|
435
|
+
datastores = json_response['datastores']
|
436
|
+
if datastores.empty?
|
437
|
+
print_red_alert "Datastore not found by name #{name}"
|
438
|
+
return nil
|
439
|
+
elsif datastores.size > 1
|
440
|
+
print_red_alert "#{datastores.size} datastores found by name #{name}"
|
441
|
+
rows = datastores.collect do |it|
|
442
|
+
{id: it['id'], name: it['name']}
|
443
|
+
end
|
444
|
+
print "\n"
|
445
|
+
puts as_pretty_table(rows, [:id, :name], {color:red})
|
446
|
+
return nil
|
447
|
+
else
|
448
|
+
datastore = datastores[0]
|
449
|
+
# merge in tenants map
|
450
|
+
if json_response['tenants'] && json_response['tenants'][datastore['id']]
|
451
|
+
datastore['tenants'] = json_response['tenants'][datastore['id']]
|
452
|
+
end
|
453
|
+
return datastore
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
end
|
@@ -98,10 +98,11 @@ EOT
|
|
98
98
|
if access_tokens && !access_tokens.empty?
|
99
99
|
print_h2 "API Access Tokens"
|
100
100
|
cols = {
|
101
|
-
|
101
|
+
"ID" => lambda {|it| it['id'] },
|
102
102
|
"CLIENT ID" => lambda {|it| it['clientId'] },
|
103
103
|
"USERNAME" => lambda {|it| it['username'] },
|
104
104
|
"ACCESS TOKEN" => lambda {|it| it['maskedAccessToken'] },
|
105
|
+
"SCOPE" => lambda {|it| it['scope'] },
|
105
106
|
"REFRESH TOKEN" => lambda {|it| it['maskedRefreshToken'] },
|
106
107
|
"ACCESS EXPIRATION" => lambda {|it| format_local_dt(it['expiration']) },
|
107
108
|
"ACCESS TTL" => lambda {|it|
|
@@ -115,6 +116,8 @@ EOT
|
|
115
116
|
end
|
116
117
|
}
|
117
118
|
}
|
119
|
+
cols.delete("ID") if access_tokens[0]['id'].nil?
|
120
|
+
cols.delete("SCOPE") if access_tokens[0]['scope'].nil?
|
118
121
|
print cyan
|
119
122
|
puts as_pretty_table(access_tokens, cols)
|
120
123
|
else
|
@@ -579,21 +582,27 @@ EOT
|
|
579
582
|
params['userId'] = val.to_s
|
580
583
|
end
|
581
584
|
#opts.add_hidden_option('--user-id')
|
585
|
+
opts.on("--id ID", String, "Token ID") do |val|
|
586
|
+
params['id'] = val.to_s
|
587
|
+
end
|
582
588
|
build_common_options(opts, options, [:payload, :options, :json, :dry_run, :quiet, :remote])
|
583
589
|
opts.footer = <<-EOT
|
584
590
|
Regenerate API access token for a specific client.
|
585
591
|
[client-id] is required. This is the id of an api client.
|
592
|
+
The --id [id] option can be used to clear a specific token by id.
|
586
593
|
Done for the current user by default, unless a user is specified with the --user option.
|
587
594
|
EOT
|
588
595
|
end
|
589
596
|
optparse.parse!(args)
|
590
597
|
connect(options)
|
591
|
-
if args.count != 1
|
598
|
+
if args.count != 1 && !params['id']
|
592
599
|
print_error Morpheus::Terminal.angry_prompt
|
593
600
|
puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
|
594
601
|
return 1
|
595
602
|
end
|
596
|
-
|
603
|
+
if args[0]
|
604
|
+
params['clientId'] = args[0]
|
605
|
+
end
|
597
606
|
begin
|
598
607
|
if options[:user]
|
599
608
|
user = find_user_by_username_or_id(nil, options[:user], {global:true})
|
@@ -655,16 +664,21 @@ EOT
|
|
655
664
|
params['userId'] = val.to_s
|
656
665
|
end
|
657
666
|
#opts.add_hidden_option('--user-id')
|
667
|
+
opts.on("--id ID", String, "Token ID") do |val|
|
668
|
+
params['id'] = val.to_s
|
669
|
+
end
|
658
670
|
build_common_options(opts, options, [:payload, :options, :json, :dry_run, :quiet, :remote])
|
659
671
|
opts.footer = <<-EOT
|
660
672
|
Clear API access token for a specific client.
|
661
673
|
[client-id] or --all is required. This is the id of an api client.
|
674
|
+
The --id [id] option can be used to clear a specific token by id.
|
662
675
|
Done for the current user by default, unless a user is specified with the --user option.
|
663
676
|
EOT
|
664
677
|
end
|
665
678
|
optparse.parse!(args)
|
666
679
|
connect(options)
|
667
|
-
|
680
|
+
|
681
|
+
if (args.count > 1 || (args.count == 0 && all_clients == false)) && !params['id']
|
668
682
|
print_error Morpheus::Terminal.angry_prompt
|
669
683
|
puts_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.inspect}\n#{optparse}"
|
670
684
|
return 1
|
@@ -704,11 +718,13 @@ EOT
|
|
704
718
|
success_msg = "Success"
|
705
719
|
if all_clients
|
706
720
|
success_msg = "Cleared all access tokens"
|
721
|
+
elsif params['id']
|
722
|
+
success_msg = "Cleared access token ID #{params['id']}"
|
707
723
|
else
|
708
|
-
success_msg = "Cleared #{params['clientId']}
|
724
|
+
success_msg = "Cleared access tokens for client #{params['clientId']}"
|
709
725
|
end
|
710
726
|
if params['userId']
|
711
|
-
success_msg << "
|
727
|
+
success_msg << " (user #{params['userId']})"
|
712
728
|
end
|
713
729
|
print_green_success success_msg
|
714
730
|
if params['clientId'] == Morpheus::APIClient::CLIENT_ID
|
@@ -802,6 +802,7 @@ module Morpheus::Cli::InfrastructureHelper
|
|
802
802
|
"Firewall" => lambda {|it| format_boolean(it['hasFirewall']) },
|
803
803
|
"Security Groups" => lambda {|it| format_boolean(it['hasSecurityGroups']) },
|
804
804
|
"Load Balancers" => lambda {|it| format_boolean(it['hasLoadBalancers']) },
|
805
|
+
"Floating Ips" => lambda {|it| format_boolean it['hasFloatingIps']},
|
805
806
|
# "Security Code" => lambda {|it| it['securityCode'] },
|
806
807
|
# "User Visible" => lambda {|it| format_boolean(it['userVisible']) },
|
807
808
|
}
|
@@ -1187,8 +1187,8 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1187
1187
|
#volume['size'] = plan_size
|
1188
1188
|
#volume['sizeId'] = nil #volume.delete('sizeId')
|
1189
1189
|
end
|
1190
|
-
|
1191
|
-
if !datastore_options.empty?
|
1190
|
+
|
1191
|
+
if !datastore_options.empty? && !provision_type['disableRootDatastore']
|
1192
1192
|
default_datastore = datastore_options.find {|ds| ds['value'].to_s == volume['datastoreId'].to_s}
|
1193
1193
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'datastoreId', 'type' => 'select', 'fieldLabel' => 'Root Datastore', 'selectOptions' => datastore_options, 'required' => true, 'description' => 'Choose a datastore.', 'defaultValue' => default_datastore ? default_datastore['name'] : volume['datastoreId']}], options[:options])
|
1194
1194
|
volume['datastoreId'] = v_prompt[field_context]['datastoreId']
|
@@ -2145,6 +2145,17 @@ module Morpheus::Cli::ProvisioningHelper
|
|
2145
2145
|
return payload
|
2146
2146
|
end
|
2147
2147
|
|
2148
|
+
def prompt_cluster_load_balancer(cluster, options)
|
2149
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'loadBalancerTypeId', 'type' => 'select', 'fieldLabel' => "Load Balancer", 'optionSource' => 'loadBalancerTypes', 'required' => false, 'description' => 'Select Load Balancer for Cluster', 'defaultValue' => '', 'excludeKubevip' => true}], options[:options], api_client, cluster)
|
2150
|
+
lb_type_id = v_prompt['loadBalancerTypeId']
|
2151
|
+
|
2152
|
+
if lb_type_id.empty?
|
2153
|
+
return false
|
2154
|
+
end
|
2155
|
+
|
2156
|
+
return lb_type_id
|
2157
|
+
end
|
2158
|
+
|
2148
2159
|
|
2149
2160
|
# reject old option types that now come from the selected service plan
|
2150
2161
|
# these will eventually get removed from the associated optionTypes
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'term/ansicolor'
|
2
2
|
require 'readline'
|
3
|
-
|
3
|
+
|
4
4
|
module Morpheus
|
5
5
|
module Cli
|
6
6
|
module OptionTypes
|
@@ -304,8 +304,8 @@ module Morpheus
|
|
304
304
|
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
|
305
305
|
elsif option_type['type'] == 'multiSelect'
|
306
306
|
# support value as csv like "thing1, thing2"
|
307
|
-
value_list = value.is_a?(String) ? value.
|
308
|
-
input_value_list = input_value.is_a?(String) ? input_value.
|
307
|
+
value_list = value.is_a?(String) ? value.split(",").collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
308
|
+
input_value_list = input_value.is_a?(String) ? input_value.split(",").collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
309
309
|
select_value_list = []
|
310
310
|
value_list.each_with_index do |v, i|
|
311
311
|
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true, nil, false, ignore_empty, options[:edit_mode])
|
@@ -315,8 +315,8 @@ module Morpheus
|
|
315
315
|
value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true)
|
316
316
|
elsif option_type['type'] == 'multiTypeahead'
|
317
317
|
# support value as csv like "thing1, thing2"
|
318
|
-
value_list = value.is_a?(String) ? value.
|
319
|
-
input_value_list = input_value.is_a?(String) ? input_value.
|
318
|
+
value_list = value.is_a?(String) ? value.split(",").collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
319
|
+
input_value_list = input_value.is_a?(String) ? input_value.split(",").collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
320
320
|
select_value_list = []
|
321
321
|
value_list.each_with_index do |v, i|
|
322
322
|
select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true)
|
data/lib/morpheus/cli/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'morpheus_test'
|
2
|
+
|
3
|
+
# Tests for Morpheus::InstancesInterface
|
4
|
+
class MorpheusTest::ClientsInterfaceTest < MorpheusTest::TestCase
|
5
|
+
|
6
|
+
def test_clients_interface
|
7
|
+
@clients_interface = client.clients
|
8
|
+
response = @clients_interface.list()
|
9
|
+
records = response['clients']
|
10
|
+
assert records.is_a?(Array)
|
11
|
+
if !records.empty?
|
12
|
+
response = @clients_interface.get(records[0]['id'])
|
13
|
+
record = response['client']
|
14
|
+
assert record.is_a?(Hash)
|
15
|
+
assert_equal record['id'], records[0]['id']
|
16
|
+
assert_equal record['clientId'], records[0]['clientId']
|
17
|
+
else
|
18
|
+
#puts "No clients found in this environment"
|
19
|
+
end
|
20
|
+
#todo: create and delete
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|