morpheus-cli 5.4.0 → 5.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/account_users_interface.rb +68 -0
- data/lib/morpheus/api/api_client.rb +51 -9
- data/lib/morpheus/api/audit_interface.rb +9 -0
- data/lib/morpheus/api/instances_interface.rb +21 -0
- data/lib/morpheus/api/load_balancer_monitors_interface.rb +9 -0
- data/lib/morpheus/api/load_balancer_pools_interface.rb +4 -4
- data/lib/morpheus/api/load_balancer_profiles_interface.rb +4 -5
- data/lib/morpheus/api/load_balancer_virtual_servers_interface.rb +13 -4
- data/lib/morpheus/api/load_balancers_interface.rb +5 -0
- data/lib/morpheus/api/network_routers_interface.rb +9 -0
- data/lib/morpheus/api/network_static_routes_interface.rb +36 -0
- data/lib/morpheus/api/read_interface.rb +4 -3
- data/lib/morpheus/api/rest_interface.rb +3 -3
- data/lib/morpheus/api/secondary_read_interface.rb +1 -1
- data/lib/morpheus/api/secondary_rest_interface.rb +19 -19
- data/lib/morpheus/api/storage_server_types_interface.rb +14 -0
- data/lib/morpheus/api/storage_servers_interface.rb +9 -0
- data/lib/morpheus/api/storage_volume_types_interface.rb +9 -0
- data/lib/morpheus/api/storage_volumes_interface.rb +9 -0
- data/lib/morpheus/api/users_interface.rb +16 -63
- data/lib/morpheus/cli/cli_command.rb +253 -5
- data/lib/morpheus/cli/cli_registry.rb +1 -1
- data/lib/morpheus/cli/commands/alias_command.rb +1 -1
- data/lib/morpheus/cli/commands/apps.rb +14 -78
- data/lib/morpheus/cli/commands/audit.rb +188 -0
- data/lib/morpheus/cli/commands/blueprints_command.rb +1 -1
- data/lib/morpheus/cli/commands/change_password_command.rb +4 -4
- data/lib/morpheus/cli/commands/clusters.rb +37 -12
- data/lib/morpheus/cli/commands/hosts.rb +15 -15
- data/lib/morpheus/cli/commands/instances.rb +109 -2
- data/lib/morpheus/cli/commands/load_balancer_monitors.rb +71 -0
- data/lib/morpheus/cli/commands/load_balancer_pools.rb +30 -50
- data/lib/morpheus/cli/commands/load_balancer_profiles.rb +65 -0
- data/lib/morpheus/cli/commands/load_balancer_types.rb +9 -4
- data/lib/morpheus/cli/commands/load_balancer_virtual_servers.rb +77 -57
- data/lib/morpheus/cli/commands/load_balancers.rb +93 -6
- data/lib/morpheus/cli/commands/network_firewalls_command.rb +22 -5
- data/lib/morpheus/cli/commands/network_routers_command.rb +96 -45
- data/lib/morpheus/cli/commands/network_static_routes_command.rb +446 -0
- data/lib/morpheus/cli/commands/network_transport_zones_command.rb +4 -4
- data/lib/morpheus/cli/commands/open_command.rb +30 -0
- data/lib/morpheus/cli/commands/options.rb +98 -0
- data/lib/morpheus/cli/commands/policies_command.rb +1 -1
- data/lib/morpheus/cli/commands/prices_command.rb +7 -7
- data/lib/morpheus/cli/commands/remote.rb +4 -2
- data/lib/morpheus/cli/commands/roles.rb +1 -1
- data/lib/morpheus/cli/commands/shell.rb +2 -2
- data/lib/morpheus/cli/commands/storage_server_types.rb +50 -0
- data/lib/morpheus/cli/commands/storage_servers.rb +122 -0
- data/lib/morpheus/cli/commands/storage_volume_types.rb +50 -0
- data/lib/morpheus/cli/commands/storage_volumes.rb +103 -0
- data/lib/morpheus/cli/commands/tenants_command.rb +1 -1
- data/lib/morpheus/cli/commands/user_groups_command.rb +1 -1
- data/lib/morpheus/cli/commands/user_settings_command.rb +2 -1
- data/lib/morpheus/cli/commands/user_sources_command.rb +1 -1
- data/lib/morpheus/cli/commands/users.rb +28 -28
- data/lib/morpheus/cli/commands/view.rb +102 -0
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -5
- data/lib/morpheus/cli/mixins/load_balancers_helper.rb +24 -4
- data/lib/morpheus/cli/mixins/print_helper.rb +50 -18
- data/lib/morpheus/cli/mixins/processes_helper.rb +1 -2
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +15 -5
- data/lib/morpheus/cli/mixins/rest_command.rb +145 -73
- data/lib/morpheus/cli/mixins/secondary_rest_command.rb +174 -81
- data/lib/morpheus/cli/mixins/storage_servers_helper.rb +156 -0
- data/lib/morpheus/cli/mixins/storage_volumes_helper.rb +119 -0
- data/lib/morpheus/cli/option_types.rb +45 -24
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli.rb +1 -0
- data/lib/morpheus/ext/string.rb +29 -6
- data/lib/morpheus/routes.rb +238 -0
- data/lib/morpheus/util.rb +6 -1
- metadata +29 -8
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'morpheus/cli/mixins/print_helper'
|
2
|
+
require 'morpheus/cli/option_types'
|
3
|
+
require 'morpheus/rest_client'
|
4
|
+
# Mixin for Morpheus::Cli command classes
|
5
|
+
# Provides common methods for storage volume management
|
6
|
+
# including storage volumes and storage volume types
|
7
|
+
module Morpheus::Cli::StorageVolumesHelper
|
8
|
+
|
9
|
+
def self.included(klass)
|
10
|
+
klass.send :include, Morpheus::Cli::PrintHelper
|
11
|
+
end
|
12
|
+
|
13
|
+
def storage_volumes_interface
|
14
|
+
# @api_client.storage_volumes
|
15
|
+
raise "#{self.class} has not defined @storage_volumes_interface" if @storage_volumes_interface.nil?
|
16
|
+
@storage_volumes_interface
|
17
|
+
end
|
18
|
+
|
19
|
+
def storage_volume_types_interface
|
20
|
+
# @api_client.storage_volume_types
|
21
|
+
raise "#{self.class} has not defined @storage_volume_types_interface" if @storage_volume_types_interface.nil?
|
22
|
+
@storage_volume_types_interface
|
23
|
+
end
|
24
|
+
|
25
|
+
def storage_volume_object_key
|
26
|
+
'storageVolume'
|
27
|
+
end
|
28
|
+
|
29
|
+
def storage_volume_list_key
|
30
|
+
'storageVolumes'
|
31
|
+
end
|
32
|
+
|
33
|
+
def storage_volume_label
|
34
|
+
'Storage Volume'
|
35
|
+
end
|
36
|
+
|
37
|
+
def storage_volume_label_plural
|
38
|
+
'Storage Volume'
|
39
|
+
end
|
40
|
+
|
41
|
+
def storage_volume_type_object_key
|
42
|
+
'storageVolumeType'
|
43
|
+
end
|
44
|
+
|
45
|
+
def storage_volume_type_list_key
|
46
|
+
'storageVolumeTypes'
|
47
|
+
end
|
48
|
+
|
49
|
+
def storage_volume_type_label
|
50
|
+
'Storage Volume Type'
|
51
|
+
end
|
52
|
+
|
53
|
+
def storage_volume_type_label_plural
|
54
|
+
'Storage Volume Types'
|
55
|
+
end
|
56
|
+
|
57
|
+
def get_available_storage_volume_types(refresh=false)
|
58
|
+
if !@available_storage_volume_types || refresh
|
59
|
+
@available_storage_volume_types = storage_volume_types_interface.list({max:1000})[storage_volume_type_list_key]
|
60
|
+
end
|
61
|
+
return @available_storage_volume_types
|
62
|
+
end
|
63
|
+
|
64
|
+
def storage_volume_type_for_name_or_id(val)
|
65
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
66
|
+
return storage_volume_type_for_id(val)
|
67
|
+
else
|
68
|
+
return storage_volume_type_for_name(val)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def storage_volume_type_for_id(val)
|
73
|
+
record = get_available_storage_volume_types().find { |z| z['id'].to_i == val.to_i}
|
74
|
+
label = "Storage Volume Type"
|
75
|
+
if record.nil?
|
76
|
+
print_red_alert "#{label} not found by id #{val}"
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
return record
|
80
|
+
end
|
81
|
+
|
82
|
+
def storage_volume_type_for_name(val)
|
83
|
+
records = get_available_storage_volume_types().select { |z| z['name'].downcase == val.downcase || z['code'].downcase == val.downcase}
|
84
|
+
label = "Storage Volume Type"
|
85
|
+
if records.empty?
|
86
|
+
print_red_alert "#{label} not found by name '#{val}'"
|
87
|
+
return nil
|
88
|
+
elsif records.size > 1
|
89
|
+
print_red_alert "More than one #{label.downcase} found by name '#{val}'"
|
90
|
+
print_error "\n"
|
91
|
+
puts_error as_pretty_table(records, [:id, :name], {color:red})
|
92
|
+
print_red_alert "Try using ID instead"
|
93
|
+
print_error reset,"\n"
|
94
|
+
return nil
|
95
|
+
else
|
96
|
+
return records[0]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def format_storage_volume_status(record, return_color=cyan)
|
101
|
+
out = ""
|
102
|
+
status_string = record['status']
|
103
|
+
if status_string.nil? || status_string.empty? || status_string == "unknown"
|
104
|
+
out << "#{white}UNKNOWN#{return_color}"
|
105
|
+
elsif status_string == 'provisioned' || status_string == 'unattached'
|
106
|
+
out << "#{cyan}#{status_string.capitalize}#{return_color}"
|
107
|
+
elsif status_string == 'syncing'
|
108
|
+
out << "#{yellow}#{status_string.capitalize}#{return_color}"
|
109
|
+
else
|
110
|
+
out << "#{red}#{status_string ? status_string.capitalize : 'N/A'}#{record['statusMessage'] ? "#{return_color} - #{record['statusMessage']}" : ''}#{return_color}"
|
111
|
+
end
|
112
|
+
out
|
113
|
+
end
|
114
|
+
|
115
|
+
def format_storage_volume_source(storage_volume)
|
116
|
+
storage_volume['source']
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -128,7 +128,11 @@ module Morpheus
|
|
128
128
|
get_object_value(options, depends_on_field_key) ||
|
129
129
|
get_object_value(api_params, depends_on_field_key)
|
130
130
|
|
131
|
-
if
|
131
|
+
if field_value.nil? && !options['_object_key'].nil?
|
132
|
+
field_value = get_object_value({options['_object_key'] => results}, depends_on_field_key)
|
133
|
+
end
|
134
|
+
|
135
|
+
if !field_value.nil? && (depends_on_value.nil? || depends_on_value.empty? || field_value.to_s.match?(depends_on_value))
|
132
136
|
found_dep_value = true if match_type != 'all'
|
133
137
|
else
|
134
138
|
found_dep_value = false if match_type == 'all'
|
@@ -151,6 +155,10 @@ module Morpheus
|
|
151
155
|
context_map = context_map[ns.to_s]
|
152
156
|
end
|
153
157
|
|
158
|
+
# build parameters for option source api request
|
159
|
+
option_params = (option_type['noParams'] ? {} : (api_params || {}).merge(results))
|
160
|
+
option_params.merge!(option_type['optionParams']) if option_type['optionParams']
|
161
|
+
|
154
162
|
# use the value passed in the options map
|
155
163
|
if cur_namespace.respond_to?('key?') && cur_namespace.key?(field_name)
|
156
164
|
value = cur_namespace[field_name]
|
@@ -161,25 +169,25 @@ module Morpheus
|
|
161
169
|
end
|
162
170
|
# these select prompts should just fall down through below, with the extra params no_prompt, use_value
|
163
171
|
elsif option_type['type'] == 'select'
|
164
|
-
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client,
|
172
|
+
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true)
|
165
173
|
elsif option_type['type'] == 'multiSelect'
|
166
174
|
# support value as csv like "thing1, thing2"
|
167
175
|
value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
168
176
|
input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
169
177
|
select_value_list = []
|
170
178
|
value_list.each_with_index do |v, i|
|
171
|
-
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client,
|
179
|
+
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true)
|
172
180
|
end
|
173
181
|
value = select_value_list
|
174
182
|
elsif option_type['type'] == 'typeahead'
|
175
|
-
value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client,
|
183
|
+
value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, option_params, true)
|
176
184
|
elsif option_type['type'] == 'multiTypeahead'
|
177
185
|
# support value as csv like "thing1, thing2"
|
178
186
|
value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
179
187
|
input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
180
188
|
select_value_list = []
|
181
189
|
value_list.each_with_index do |v, i|
|
182
|
-
select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client,
|
190
|
+
select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, option_params, true)
|
183
191
|
end
|
184
192
|
value = select_value_list
|
185
193
|
end
|
@@ -205,11 +213,11 @@ module Morpheus
|
|
205
213
|
# select type is special because it supports skipSingleOption
|
206
214
|
# and prints the available options on error
|
207
215
|
if ['select', 'multiSelect'].include?(option_type['type'])
|
208
|
-
value = select_prompt(option_type, api_client,
|
216
|
+
value = select_prompt(option_type, api_client, option_params, true)
|
209
217
|
value_found = !!value
|
210
218
|
end
|
211
219
|
if ['typeahead', 'multiTypeahead'].include?(option_type['type'])
|
212
|
-
value = typeahead_prompt(option_type, api_client,
|
220
|
+
value = typeahead_prompt(option_type, api_client, option_params, true)
|
213
221
|
value_found = !!value
|
214
222
|
end
|
215
223
|
if !value_found
|
@@ -246,11 +254,11 @@ module Morpheus
|
|
246
254
|
# I suppose the entered value should take precedence
|
247
255
|
# api_params = api_params.merge(options) # this might be good enough
|
248
256
|
# dup it
|
249
|
-
value = select_prompt(option_type, api_client,
|
257
|
+
value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
|
250
258
|
if value && option_type['type'] == 'multiSelect'
|
251
259
|
value = [value]
|
252
260
|
while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
|
253
|
-
if addn_value = select_prompt(option_type, api_client,
|
261
|
+
if addn_value = select_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
|
254
262
|
value << addn_value
|
255
263
|
else
|
256
264
|
break
|
@@ -258,11 +266,11 @@ module Morpheus
|
|
258
266
|
end
|
259
267
|
end
|
260
268
|
elsif ['typeahead', 'multiTypeahead'].include?(option_type['type'])
|
261
|
-
value = typeahead_prompt(option_type, api_client,
|
269
|
+
value = typeahead_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
|
262
270
|
if value && option_type['type'] == 'multiTypeahead'
|
263
271
|
value = [value]
|
264
272
|
while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
|
265
|
-
if addn_value = typeahead_prompt(option_type, api_client,
|
273
|
+
if addn_value = typeahead_prompt(option_type, api_client, option_params, options[:no_prompt], nil, paging_enabled)
|
266
274
|
value << addn_value
|
267
275
|
else
|
268
276
|
break
|
@@ -273,7 +281,7 @@ module Morpheus
|
|
273
281
|
if option_type['optionSource'].nil?
|
274
282
|
value = option_type['defaultValue']
|
275
283
|
else
|
276
|
-
value = load_source_options(option_type['optionSource'],
|
284
|
+
value = load_source_options(option_type['optionSource'], option_params, api_client, api_params || {})
|
277
285
|
end
|
278
286
|
elsif option_type['type'] == 'file'
|
279
287
|
value = file_prompt(option_type)
|
@@ -287,6 +295,11 @@ module Morpheus
|
|
287
295
|
if option_type['type'] == 'multiSelect'
|
288
296
|
value = [value] if !value.nil? && !value.is_a?(Array)
|
289
297
|
# parent_context_map[parent_ns] = value
|
298
|
+
elsif option_type['type'] == 'multiText'
|
299
|
+
# multiText expects csv value
|
300
|
+
if value && value.is_a?(String)
|
301
|
+
value = value.split(",").collect {|it| it.strip }
|
302
|
+
end
|
290
303
|
end
|
291
304
|
context_map[field_name] = value if !(value.nil? || (value.is_a?(Hash) && value.empty?))
|
292
305
|
parent_context_map.reject! {|k,v| k == parent_ns && (v.nil? || (v.is_a?(Hash) && v.empty?))}
|
@@ -305,7 +318,7 @@ module Morpheus
|
|
305
318
|
end
|
306
319
|
optionString = options.collect{ |b| b[:checked] ? "(#{b[:key]})" : b[:key]}.join(', ')
|
307
320
|
while !value_found do
|
308
|
-
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
|
321
|
+
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }[#{optionString}]: "
|
309
322
|
input = $stdin.gets.chomp!
|
310
323
|
if input == '?'
|
311
324
|
help_prompt(option_type)
|
@@ -332,7 +345,7 @@ module Morpheus
|
|
332
345
|
value_found = false
|
333
346
|
value = nil
|
334
347
|
while !value_found do
|
335
|
-
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
348
|
+
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
336
349
|
input = $stdin.gets.chomp!
|
337
350
|
value = input.empty? ? option_type['defaultValue'] : input
|
338
351
|
if !value.to_s.empty?
|
@@ -356,7 +369,7 @@ module Morpheus
|
|
356
369
|
Thread.current[:_last_select]
|
357
370
|
end
|
358
371
|
|
359
|
-
def self.select_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
|
372
|
+
def self.select_prompt(option_type, api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
|
360
373
|
paging_enabled = false if Morpheus::Cli.windows?
|
361
374
|
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
362
375
|
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
@@ -365,7 +378,12 @@ module Morpheus
|
|
365
378
|
value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
|
366
379
|
default_value = option_type['defaultValue']
|
367
380
|
default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
|
368
|
-
|
381
|
+
|
382
|
+
if !option_type['params'].nil?
|
383
|
+
api_params = (api_params || {}).select {|k,v| option_type['params'].key?(k) || option_type['params'].key?(k.to_s)}
|
384
|
+
option_type['params'].select {|k,v| !v.empty?}.each {|k,v| api_params[k] = v}
|
385
|
+
end
|
386
|
+
|
369
387
|
# local array of options
|
370
388
|
if option_type['selectOptions']
|
371
389
|
# calculate from inline lambda
|
@@ -424,7 +442,8 @@ module Morpheus
|
|
424
442
|
default_value = found_default_option['name'] # name is prettier than value
|
425
443
|
end
|
426
444
|
else
|
427
|
-
found_default_option
|
445
|
+
found_default_option = select_options.find {|opt| opt[value_field].to_s == default_value.to_s || opt['name'] == default_value.to_s}
|
446
|
+
found_default_option = select_options.find {|opt| opt[value_field].to_s.start_with?(default_value.to_s) || opt['name'].to_s.start_with?(default_value.to_s)} if !found_default_option
|
428
447
|
if found_default_option
|
429
448
|
default_value = found_default_option['name'] # name is prettier than value
|
430
449
|
end
|
@@ -483,7 +502,7 @@ module Morpheus
|
|
483
502
|
}
|
484
503
|
|
485
504
|
has_more_pages = paging && (paging[:cur_page] * paging[:page_size]) < paging[:total]
|
486
|
-
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages && paging[:cur_page] > 0 ? ' more ' : ' '}options]: ", false).to_s
|
505
|
+
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages && paging[:cur_page] > 0 ? ' more ' : ' '}options]: ", false).to_s
|
487
506
|
input = input.chomp.strip
|
488
507
|
if input.empty? && default_value
|
489
508
|
input = default_value.to_s
|
@@ -554,7 +573,7 @@ module Morpheus
|
|
554
573
|
}
|
555
574
|
# prompt for typeahead input value
|
556
575
|
has_more_pages = paging && ((paging[:cur_page] + 1) * paging[:page_size]) < paging[:total]
|
557
|
-
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages ? ' more ' : ' '}options]: ", false).to_s
|
576
|
+
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for#{has_more_pages ? ' more ' : ' '}options]: ", false).to_s
|
558
577
|
input = input.chomp.strip
|
559
578
|
end
|
560
579
|
|
@@ -743,7 +762,7 @@ module Morpheus
|
|
743
762
|
value_found = false
|
744
763
|
value = nil
|
745
764
|
while !value_found do
|
746
|
-
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
765
|
+
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!option_type['defaultValue'].to_s.empty? ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
747
766
|
input = $stdin.gets.chomp!
|
748
767
|
value = input.empty? ? option_type['defaultValue'] : input
|
749
768
|
if input == '?'
|
@@ -760,7 +779,7 @@ module Morpheus
|
|
760
779
|
value = nil
|
761
780
|
while !value_found do
|
762
781
|
if value.nil?
|
763
|
-
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)} [Type 'EOF' to stop input]: \n"
|
782
|
+
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)} [Type 'EOF' to stop input]: \n"
|
764
783
|
end
|
765
784
|
input = $stdin.gets.chomp!
|
766
785
|
# value = input.empty? ? option_type['defaultValue'] : input
|
@@ -784,7 +803,7 @@ module Morpheus
|
|
784
803
|
def self.password_prompt(option_type)
|
785
804
|
value_found = false
|
786
805
|
while !value_found do
|
787
|
-
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
806
|
+
print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
788
807
|
input = $stdin.noecho(&:gets).chomp!
|
789
808
|
value = input
|
790
809
|
print "\n"
|
@@ -804,11 +823,11 @@ module Morpheus
|
|
804
823
|
value_found = false
|
805
824
|
value = nil
|
806
825
|
while !value_found do
|
807
|
-
#print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
826
|
+
#print "#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: "
|
808
827
|
Readline.completion_append_character = ""
|
809
828
|
Readline.basic_word_break_characters = ''
|
810
829
|
Readline.completion_proc = proc {|s| Readline::FILENAME_COMPLETION_PROC.call(s) }
|
811
|
-
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: ", false).to_s
|
830
|
+
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? (' (' + option_type['fieldAddOn'] + ') ') : '' }#{optional_label(option_type)}#{option_type['defaultValue'] ? ' ['+option_type['defaultValue'].to_s+']' : ''}: ", false).to_s
|
812
831
|
input = input.chomp.strip
|
813
832
|
#input = $stdin.gets.chomp!
|
814
833
|
value = input.empty? ? option_type['defaultValue'] : input.to_s
|
@@ -926,6 +945,8 @@ module Morpheus
|
|
926
945
|
select_options = filtered_options
|
927
946
|
end
|
928
947
|
elsif option_type['optionSource']
|
948
|
+
api_params = api_params.select {|k,v| option_type['params'].include(k)} if option_type['params'].nil? && api_params
|
949
|
+
|
929
950
|
# calculate from inline lambda
|
930
951
|
if option_type['optionSource'].is_a?(Proc)
|
931
952
|
select_options = option_type['optionSource'].call(api_client, api_params || {})
|
data/lib/morpheus/cli/version.rb
CHANGED
data/lib/morpheus/cli.rb
CHANGED
data/lib/morpheus/ext/string.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class String
|
2
2
|
|
3
3
|
def pluralize
|
4
|
-
value = self
|
4
|
+
value = self.dup
|
5
5
|
if value == ""
|
6
6
|
value
|
7
7
|
elsif value[-1].chr == "y"
|
@@ -18,7 +18,7 @@ class String
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def singularize
|
21
|
-
value = self
|
21
|
+
value = self.dup
|
22
22
|
if value == ""
|
23
23
|
value
|
24
24
|
elsif value.size > 3 && value[-3..-1] == "ies"
|
@@ -27,15 +27,38 @@ class String
|
|
27
27
|
value[0..-3]
|
28
28
|
elsif value[-1] == "s"
|
29
29
|
value[0..-2]
|
30
|
+
else
|
31
|
+
value
|
30
32
|
end
|
31
33
|
end
|
32
34
|
|
33
|
-
def
|
34
|
-
|
35
|
+
def underscore
|
36
|
+
value = self.dup
|
37
|
+
value.gsub!(/::/, '/')
|
38
|
+
value.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
39
|
+
value.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
40
|
+
value.tr!("-", "_")
|
41
|
+
value.tr!(" ", "_")
|
42
|
+
value.downcase!
|
43
|
+
value
|
44
|
+
end
|
45
|
+
|
46
|
+
def camelcase
|
47
|
+
value = self.underscore.gsub(/\_([a-z])/) do $1.upcase end
|
48
|
+
value = value[0, 1].downcase + value[1..-1]
|
49
|
+
value
|
35
50
|
end
|
36
51
|
|
37
|
-
def
|
38
|
-
self.
|
52
|
+
def upcamelcase
|
53
|
+
self.camelcase.capitalize
|
54
|
+
end
|
55
|
+
|
56
|
+
def titleize
|
57
|
+
self.underscore.split("_").map(&:capitalize).join(" ")
|
58
|
+
end
|
59
|
+
|
60
|
+
def dasherize
|
61
|
+
self.underscore.gsub("_", "-")
|
39
62
|
end
|
40
63
|
|
41
64
|
end
|
@@ -0,0 +1,238 @@
|
|
1
|
+
# Routes is a module to provide a way to look up routes in the Morpheus UI.
|
2
|
+
# Examples:
|
3
|
+
# Routes.lookup("users") == "/admin/users"
|
4
|
+
# Routes.lookup("instances") == "/provisioning/instances"
|
5
|
+
#
|
6
|
+
# puts "All routes", Morpheus::Routes.routes
|
7
|
+
#
|
8
|
+
module Morpheus::Routes
|
9
|
+
|
10
|
+
# A static site map for the Morpheus UI.
|
11
|
+
# todo: move to YAML or load from api
|
12
|
+
SITE_MAP = {
|
13
|
+
operations: {
|
14
|
+
dashboard: {},
|
15
|
+
reports: {},
|
16
|
+
analytics: {},
|
17
|
+
guidance: {},
|
18
|
+
wiki: {},
|
19
|
+
costing: {
|
20
|
+
budgets: {},
|
21
|
+
invoices: {},
|
22
|
+
usage: {},
|
23
|
+
},
|
24
|
+
approvals: {},
|
25
|
+
activity: {},
|
26
|
+
alarms: {},
|
27
|
+
},
|
28
|
+
provisioning: {
|
29
|
+
instances: {},
|
30
|
+
apps: {},
|
31
|
+
catalog: {},
|
32
|
+
jobs: {},
|
33
|
+
executions: {},
|
34
|
+
code: {
|
35
|
+
respositories: {},
|
36
|
+
deployments: {},
|
37
|
+
# integrations: {}, # Integrations
|
38
|
+
},
|
39
|
+
},
|
40
|
+
library: {
|
41
|
+
automation: [
|
42
|
+
"#!tasks",
|
43
|
+
"#!workflows",
|
44
|
+
"#!thresholds",
|
45
|
+
"#!power-schedules",
|
46
|
+
"#!execute-schedules",
|
47
|
+
],
|
48
|
+
blueprints: [
|
49
|
+
"#instance-types",
|
50
|
+
"#!instance-type-layouts",
|
51
|
+
"#!container-types",
|
52
|
+
"#!app-templates", # App Blueprints (blueprints)
|
53
|
+
"#!catalog-items",
|
54
|
+
"#!compute-type-layouts", # Cluster Layouts
|
55
|
+
],
|
56
|
+
:'virtual-images' => {},
|
57
|
+
options: [
|
58
|
+
"#!option-type-lists", # Option Lists
|
59
|
+
],
|
60
|
+
templates: [
|
61
|
+
"#!specs",
|
62
|
+
"#!files",
|
63
|
+
"#!scripts",
|
64
|
+
"#!security-specs",
|
65
|
+
],
|
66
|
+
services: {}, # Integrations
|
67
|
+
|
68
|
+
},
|
69
|
+
infrastructure: {
|
70
|
+
groups: {},
|
71
|
+
clouds: {},
|
72
|
+
clusters: {},
|
73
|
+
servers: {}, # Hosts (still used for loading by id)
|
74
|
+
inventory: [ # Compute
|
75
|
+
"#!hosts",
|
76
|
+
"#!virtual-machines",
|
77
|
+
"#!containers",
|
78
|
+
"#!resources",
|
79
|
+
"#!bare-metal",
|
80
|
+
],
|
81
|
+
networks: {},
|
82
|
+
:'load-balancers' => {},
|
83
|
+
storage: {
|
84
|
+
buckets: {},
|
85
|
+
shares: {}, # File Shares
|
86
|
+
volumes: {},
|
87
|
+
:'data-stores' => {}, # ugh, should be datastores
|
88
|
+
servers: {}, # Storage Servers
|
89
|
+
},
|
90
|
+
# :'keys-and-certs' => {},
|
91
|
+
:'key-pairs' => {},
|
92
|
+
certificates: {},
|
93
|
+
},
|
94
|
+
backups: {
|
95
|
+
|
96
|
+
},
|
97
|
+
monitoring: {
|
98
|
+
status: {},
|
99
|
+
logs: {},
|
100
|
+
apps: {},
|
101
|
+
checks: {},
|
102
|
+
groups: {},
|
103
|
+
incidents: {},
|
104
|
+
contacts: {},
|
105
|
+
:'alert-rules' => {},
|
106
|
+
},
|
107
|
+
tools: {
|
108
|
+
cypher: {},
|
109
|
+
archives: {
|
110
|
+
buckets: {},
|
111
|
+
},
|
112
|
+
:'image-builder' => {},
|
113
|
+
:vdi => {}
|
114
|
+
},
|
115
|
+
admin: {
|
116
|
+
accounts: {}, # Tenants
|
117
|
+
:'service-plans' => [
|
118
|
+
"#prices",
|
119
|
+
"#pricesets"
|
120
|
+
],
|
121
|
+
roles: {},
|
122
|
+
users: {},
|
123
|
+
:'user-groups' => {},
|
124
|
+
integrations: {},
|
125
|
+
policies: {},
|
126
|
+
health: ["logs"],
|
127
|
+
settings: {},
|
128
|
+
},
|
129
|
+
:'user-settings' => {}, # User Settings (Profile)
|
130
|
+
} unless defined?(SITE_MAP)
|
131
|
+
|
132
|
+
# A list of routes generated from the site map and cached
|
133
|
+
@@routes = nil
|
134
|
+
|
135
|
+
# @return an array of well known Morpheus UI routes.
|
136
|
+
def self.routes
|
137
|
+
if !@@routes
|
138
|
+
@@routes = build_routes(SITE_MAP)
|
139
|
+
end
|
140
|
+
return @@routes
|
141
|
+
end
|
142
|
+
|
143
|
+
# lookup a route in the morpheus UI
|
144
|
+
# @param path [String] The input to lookup a route for eg. "dashboard"
|
145
|
+
# @return full path like "/operations/dashboard"
|
146
|
+
def self.lookup(input)
|
147
|
+
path = input.to_s
|
148
|
+
if path.start_with?("/")
|
149
|
+
# absolute path is being looked up
|
150
|
+
return path
|
151
|
+
else
|
152
|
+
# todo: make this smarter, regex, case insensitive, etc
|
153
|
+
# find the one with smallest match index
|
154
|
+
|
155
|
+
# map well known aliases
|
156
|
+
case(path.underscore.singularize)
|
157
|
+
when "server","host","vm","virtual-machine"
|
158
|
+
# actually should be "/infrastructure/inventory" unless id is passed, show route uses /servers though
|
159
|
+
path = "/infrastructure/servers"
|
160
|
+
when "compute"
|
161
|
+
path = "/infrastructure/inventory"
|
162
|
+
when "tenant"
|
163
|
+
path = "/admin/accounts"
|
164
|
+
end
|
165
|
+
# dasherize path and attempt to match the plural first
|
166
|
+
plural_path = path.pluralize
|
167
|
+
paths = [path.dasherize]
|
168
|
+
if plural_path != path
|
169
|
+
paths.unshift(plural_path.dasherize)
|
170
|
+
end
|
171
|
+
|
172
|
+
best_route = nil
|
173
|
+
best_index = nil
|
174
|
+
best_prefix_words = nil
|
175
|
+
paths.each do |p|
|
176
|
+
if best_route.nil?
|
177
|
+
self.routes.each do |it|
|
178
|
+
match_index = it.index(p)
|
179
|
+
if match_index
|
180
|
+
prefix_route = match_index == 0 ? "" : it[0..(match_index-1)]
|
181
|
+
prefix_words = prefix_route.split("/")
|
182
|
+
#if best_index.nil? || match_index < best_index
|
183
|
+
if best_prefix_words.nil? || prefix_words.size < best_prefix_words.size
|
184
|
+
best_route = it
|
185
|
+
best_index = match_index
|
186
|
+
best_prefix_words = prefix_words
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
if best_route
|
193
|
+
return best_route
|
194
|
+
else
|
195
|
+
# no match found
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
|
204
|
+
# build_routes constructs an array of routes (paths)
|
205
|
+
# This traversing the route map recursively and appends paths to output
|
206
|
+
# @params route_map [Hash] map of routes
|
207
|
+
# @params context [String] the current content
|
208
|
+
# @params output [Array] the list of route paths being constructed for return
|
209
|
+
# @return array like ["/operations", "/operations/dashboard", "/admin", "/etc"]
|
210
|
+
def self.build_routes(route_map, context="", output = nil)
|
211
|
+
if output.nil?
|
212
|
+
output = []
|
213
|
+
end
|
214
|
+
route_map.each do |k,v|
|
215
|
+
leaf_path = "#{context}/#{k}"
|
216
|
+
output << leaf_path
|
217
|
+
if v.is_a?(Hash)
|
218
|
+
build_routes(v, leaf_path, output)
|
219
|
+
elsif v.is_a?(Array)
|
220
|
+
v.each do |obj|
|
221
|
+
if obj.is_a?(Hash)
|
222
|
+
build_routes(obj, leaf_path, output)
|
223
|
+
elsif obj.is_a?(Symbol) || obj.is_a?(String)
|
224
|
+
# route leaf type not handled
|
225
|
+
output << "#{leaf_path}/#{obj}"
|
226
|
+
else
|
227
|
+
# route leaf type not handled
|
228
|
+
end
|
229
|
+
end
|
230
|
+
else
|
231
|
+
# route leaf type not handled
|
232
|
+
end
|
233
|
+
end
|
234
|
+
return output
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
end
|