morpheus-cli 5.4.0 → 5.4.1
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/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
|