morpheus-cli 4.2.8 → 4.2.10
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.rb +1 -1
- data/lib/morpheus/api/activity_interface.rb +9 -0
- data/lib/morpheus/api/api_client.rb +83 -27
- data/lib/morpheus/api/apps_interface.rb +21 -0
- data/lib/morpheus/api/dashboard_interface.rb +5 -21
- data/lib/morpheus/api/instances_interface.rb +3 -10
- data/lib/morpheus/api/invoice_line_items_interface.rb +14 -0
- data/lib/morpheus/api/invoices_interface.rb +7 -12
- data/lib/morpheus/api/library_layouts_interface.rb +8 -0
- data/lib/morpheus/api/ping_interface.rb +20 -0
- data/lib/morpheus/api/projects_interface.rb +33 -0
- data/lib/morpheus/api/setup_interface.rb +19 -36
- data/lib/morpheus/api/user_settings_interface.rb +0 -6
- data/lib/morpheus/api/whoami_interface.rb +4 -8
- data/lib/morpheus/benchmarking.rb +16 -26
- data/lib/morpheus/cli.rb +10 -5
- data/lib/morpheus/cli/access_token_command.rb +5 -8
- data/lib/morpheus/cli/activity_command.rb +146 -0
- data/lib/morpheus/cli/apps.rb +312 -121
- data/lib/morpheus/cli/archives_command.rb +1 -1
- data/lib/morpheus/cli/auth_command.rb +4 -11
- data/lib/morpheus/cli/blueprints_command.rb +196 -137
- data/lib/morpheus/cli/change_password_command.rb +1 -1
- data/lib/morpheus/cli/cli_command.rb +225 -72
- data/lib/morpheus/cli/cli_registry.rb +2 -2
- data/lib/morpheus/cli/cloud_datastores_command.rb +1 -1
- data/lib/morpheus/cli/clouds.rb +5 -20
- data/lib/morpheus/cli/clusters.rb +4 -28
- data/lib/morpheus/cli/commands/standard/alias_command.rb +2 -9
- data/lib/morpheus/cli/commands/standard/benchmark_command.rb +2 -0
- data/lib/morpheus/cli/commands/standard/curl_command.rb +2 -3
- data/lib/morpheus/cli/commands/standard/history_command.rb +3 -6
- data/lib/morpheus/cli/commands/standard/man_command.rb +10 -7
- data/lib/morpheus/cli/commands/standard/ssl_verification_command.rb +10 -9
- data/lib/morpheus/cli/containers_command.rb +3 -3
- data/lib/morpheus/cli/credentials.rb +13 -16
- data/lib/morpheus/cli/error_handler.rb +18 -12
- data/lib/morpheus/cli/errors.rb +45 -0
- data/lib/morpheus/cli/execute_schedules_command.rb +1 -1
- data/lib/morpheus/cli/execution_request_command.rb +4 -4
- data/lib/morpheus/cli/groups.rb +84 -132
- data/lib/morpheus/cli/hosts.rb +6 -16
- data/lib/morpheus/cli/instances.rb +100 -183
- data/lib/morpheus/cli/invoices_command.rb +505 -71
- data/lib/morpheus/cli/library_layouts_command.rb +254 -166
- data/lib/morpheus/cli/library_option_lists_command.rb +0 -87
- data/lib/morpheus/cli/library_option_types_command.rb +0 -96
- data/lib/morpheus/cli/license.rb +3 -0
- data/lib/morpheus/cli/login.rb +17 -37
- data/lib/morpheus/cli/logout.rb +9 -5
- data/lib/morpheus/cli/mixins/accounts_helper.rb +83 -7
- data/lib/morpheus/cli/mixins/operations_helper.rb +41 -0
- data/lib/morpheus/cli/mixins/option_source_helper.rb +255 -0
- data/lib/morpheus/cli/mixins/print_helper.rb +18 -4
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +222 -13
- data/lib/morpheus/cli/mixins/remote_helper.rb +139 -0
- data/lib/morpheus/cli/monitoring_checks_command.rb +11 -3
- data/lib/morpheus/cli/network_groups_command.rb +8 -2
- data/lib/morpheus/cli/option_types.rb +1 -1
- data/lib/morpheus/cli/ping.rb +252 -0
- data/lib/morpheus/cli/price_sets_command.rb +16 -27
- data/lib/morpheus/cli/prices_command.rb +34 -27
- data/lib/morpheus/cli/processes_command.rb +81 -7
- data/lib/morpheus/cli/projects_command.rb +607 -0
- data/lib/morpheus/cli/recent_activity_command.rb +87 -65
- data/lib/morpheus/cli/remote.rb +965 -974
- data/lib/morpheus/cli/reports_command.rb +3 -15
- data/lib/morpheus/cli/roles.rb +8 -31
- data/lib/morpheus/cli/service_plans_command.rb +25 -31
- data/lib/morpheus/cli/setup.rb +392 -0
- data/lib/morpheus/cli/shell.rb +144 -56
- data/lib/morpheus/cli/subnets_command.rb +71 -11
- data/lib/morpheus/cli/tasks.rb +3 -3
- data/lib/morpheus/cli/user_sources_command.rb +4 -4
- data/lib/morpheus/cli/users.rb +135 -109
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/whitelabel_settings_command.rb +7 -7
- data/lib/morpheus/cli/whoami.rb +90 -129
- data/lib/morpheus/cli/wiki_command.rb +2 -14
- data/lib/morpheus/ext/rest_client.rb +36 -0
- data/lib/morpheus/formatters.rb +42 -5
- data/lib/morpheus/rest_client.rb +0 -10
- data/lib/morpheus/terminal.rb +41 -1
- data/lib/morpheus/util.rb +24 -0
- metadata +16 -3
- data/lib/morpheus/cli/command_error.rb +0 -22
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'morpheus/cli/mixins/print_helper'
|
2
|
+
|
3
|
+
# Mixin for Morpheus::Cli command classes
|
4
|
+
# Provides common methods for fetching objects via /api/options/:optionSource
|
5
|
+
# The including class must establish @options_interface or @api_client
|
6
|
+
# This is useful for when the user does not need to have permission to other endpoints
|
7
|
+
module Morpheus::Cli::OptionSourceHelper
|
8
|
+
|
9
|
+
def self.included(klass)
|
10
|
+
klass.send :include, Morpheus::Cli::PrintHelper
|
11
|
+
end
|
12
|
+
|
13
|
+
def options_interface
|
14
|
+
api_interface = @options_interface
|
15
|
+
api_interface = @api_client.options if api_interface.nil? && @api_client
|
16
|
+
# @api_client.options
|
17
|
+
raise "#{self.class} has not defined @options_interface or @api_client" if api_interface.nil?
|
18
|
+
api_interface
|
19
|
+
end
|
20
|
+
|
21
|
+
# todo: rewrite these specific methods to use the generic one.
|
22
|
+
|
23
|
+
def get_available_user_options(refresh=false)
|
24
|
+
if !@available_user_options || refresh
|
25
|
+
option_results = options_interface.options_for_source('users',{})
|
26
|
+
@available_user_options = option_results['data'].collect {|it|
|
27
|
+
{"name" => it["name"], "value" => it["value"],
|
28
|
+
"username" => it["name"], "id" => it["value"]}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
return @available_user_options
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_available_user_option(name)
|
35
|
+
users = get_available_user_options().select {|it|
|
36
|
+
name && (it['name'].to_s.downcase == name.to_s.downcase || it['value'].to_s == name.to_s) }
|
37
|
+
if users.empty?
|
38
|
+
print_red_alert "User not found by username or id '#{name}'"
|
39
|
+
return nil
|
40
|
+
elsif users.size > 1
|
41
|
+
print_red_alert "#{users.size} users found by username or id '#{name}'"
|
42
|
+
return nil
|
43
|
+
else
|
44
|
+
return users[0]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
def get_group_options(refresh=false, api_params={})
|
50
|
+
if !@available_group_options || refresh
|
51
|
+
option_results = options_interface.options_for_source('groups', api_params)
|
52
|
+
@available_group_options = option_results['data'].collect {|it|
|
53
|
+
{"name" => it["name"], "value" => it["value"], "id" => it["value"]}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
return @available_group_options
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_group_option(group_id, refresh=false, api_params={})
|
60
|
+
if group_id.to_s.strip == ""
|
61
|
+
print_red_alert "Group not found by for blank id"
|
62
|
+
return nil
|
63
|
+
end
|
64
|
+
groups = get_group_options(refresh, api_params).select {|it| (it['name'].to_s == group_id.to_s || it['id'].to_s == group_id.to_s) }
|
65
|
+
if groups.empty?
|
66
|
+
print_red_alert "Group not found by '#{group_id}'"
|
67
|
+
return nil
|
68
|
+
elsif groups.size > 1
|
69
|
+
print_red_alert "#{groups.size} groups found by '#{group_id}'"
|
70
|
+
return nil
|
71
|
+
else
|
72
|
+
return groups[0]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_cloud_options(refresh=false, api_params={})
|
77
|
+
if !@available_cloud_options || refresh
|
78
|
+
option_results = options_interface.options_for_source('clouds', api_params)
|
79
|
+
@available_cloud_options = option_results['data'].collect {|it|
|
80
|
+
{"name" => it["name"], "value" => it["value"], "id" => it["value"]}
|
81
|
+
}
|
82
|
+
end
|
83
|
+
return @available_cloud_options
|
84
|
+
end
|
85
|
+
|
86
|
+
def find_cloud_option(cloud_id, refresh=false, api_params={})
|
87
|
+
if cloud_id.to_s.strip == ""
|
88
|
+
print_red_alert "Cloud not found by for blank id"
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
clouds = get_cloud_options(refresh, api_params).select {|it| (it['name'].to_s == cloud_id.to_s || it['id'].to_s == cloud_id.to_s) }
|
92
|
+
if clouds.empty?
|
93
|
+
print_red_alert "Cloud not found by '#{cloud_id}'"
|
94
|
+
return nil
|
95
|
+
elsif clouds.size > 1
|
96
|
+
print_red_alert "#{clouds.size} clouds found by '#{cloud_id}'"
|
97
|
+
return nil
|
98
|
+
else
|
99
|
+
return clouds[0]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_tenant_options(refresh=false, api_params={})
|
104
|
+
if !@available_tenant_options || refresh
|
105
|
+
# source should be 'tenants' or 'allTenants'
|
106
|
+
# allTenants includes the current tenant
|
107
|
+
# I think we should always use that so you can use your own id
|
108
|
+
option_source = 'allTenants'
|
109
|
+
option_results = options_interface.options_for_source(option_source, api_params)
|
110
|
+
@available_tenant_options = option_results['data'].collect {|it|
|
111
|
+
{"name" => it["name"], "value" => it["value"], "id" => it["value"]}
|
112
|
+
}
|
113
|
+
end
|
114
|
+
return @available_tenant_options
|
115
|
+
end
|
116
|
+
|
117
|
+
def find_tenant_option(tenant_id, refresh=false, api_params={})
|
118
|
+
tenant_id = tenant_id.to_s.strip
|
119
|
+
tenant_id_downcase = tenant_id.to_s.downcase
|
120
|
+
if tenant_id == ""
|
121
|
+
print_red_alert "Tenant not found by for blank id"
|
122
|
+
return nil
|
123
|
+
end
|
124
|
+
tenants = get_tenant_options(refresh, api_params).select {|it| (it['name'].to_s.downcase == tenant_id_downcase || it['id'].to_s == tenant_id.to_s) }
|
125
|
+
if tenants.empty?
|
126
|
+
print_red_alert "Tenant not found by '#{tenant_id}'"
|
127
|
+
return nil
|
128
|
+
elsif tenants.size > 1
|
129
|
+
print_red_alert "#{tenants.size} tenants found by '#{tenant_id}'"
|
130
|
+
return nil
|
131
|
+
else
|
132
|
+
return tenants[0]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# parse cloud names or IDs into a name, works with array or csv
|
137
|
+
# skips validation of ID for now, just worry about translating names/codes
|
138
|
+
# def parse_cloud_id_list(id_list)
|
139
|
+
# cloud_ids = parse_id_list(id_list).collect {|cloud_id|
|
140
|
+
# if cloud_id.to_s =~ /\A\d{1,}\Z/
|
141
|
+
# cloud_id
|
142
|
+
# else
|
143
|
+
# cloud = find_cloud_option(cloud_id)
|
144
|
+
# if cloud.nil?
|
145
|
+
# # exit 1
|
146
|
+
# return nil
|
147
|
+
# end
|
148
|
+
# cloud['id']
|
149
|
+
# end
|
150
|
+
# }
|
151
|
+
# return cloud_ids
|
152
|
+
# end
|
153
|
+
|
154
|
+
# todo: some other common ones, accounts (tenants), etc.
|
155
|
+
|
156
|
+
|
157
|
+
|
158
|
+
# a generic set of parse and find methods for any option source data
|
159
|
+
def load_option_source_data(option_source, api_params={}, refresh=false, &block)
|
160
|
+
@_option_source_cache ||= {}
|
161
|
+
option_source_hash = "#{option_source}#{api_params.empty? ? '' : api_params.to_s}"
|
162
|
+
data = @_option_source_cache[option_source_hash]
|
163
|
+
if data.nil? || refresh
|
164
|
+
json_response = options_interface.options_for_source(option_source, api_params)
|
165
|
+
data = json_response['data'].collect {|it|
|
166
|
+
{
|
167
|
+
"name" => it["name"],
|
168
|
+
"value" => it["value"],
|
169
|
+
"id" => (it["id"] || it["value"]),
|
170
|
+
"code" => it["code"]
|
171
|
+
}
|
172
|
+
}
|
173
|
+
@_option_source_cache[option_source_hash] = data
|
174
|
+
end
|
175
|
+
return data
|
176
|
+
end
|
177
|
+
|
178
|
+
# todo: some other common ones, accounts (tenants), etc.
|
179
|
+
# todo: a generic set of parse and find methods like
|
180
|
+
# like this:
|
181
|
+
def parse_option_source_id_list(option_source, id_list, api_params={}, refresh=false)
|
182
|
+
option_source_label = option_source.to_s # .capitalize
|
183
|
+
option_data = load_option_source_data(option_source, api_params, refresh)
|
184
|
+
found_ids = []
|
185
|
+
parse_id_list(id_list).each {|record_id|
|
186
|
+
lowercase_id = record_id.to_s.downcase
|
187
|
+
# need to parameterize this behavior
|
188
|
+
if record_id.to_s.empty?
|
189
|
+
# never match blank nil or empty strings
|
190
|
+
print_red_alert "#{option_source_label} cannot be not found by with a blank id!"
|
191
|
+
return nil
|
192
|
+
# elsif record_id.to_s =~ /\A\d{1,}\Z/
|
193
|
+
# # always allow any ID for now..
|
194
|
+
# found_ids << record_id
|
195
|
+
else
|
196
|
+
# search with in a presedence by value, then name, then id (usually same as value)
|
197
|
+
# exact match on value first.
|
198
|
+
matching_results = []
|
199
|
+
matching_results = option_data.select {|it| it['value'] && it['value'] == record_id }
|
200
|
+
# match on value case /i
|
201
|
+
if matching_results.empty?
|
202
|
+
matching_results = option_data.select {|it| it['value'] && it['value'].to_s.downcase == lowercase_id }
|
203
|
+
end
|
204
|
+
# match on name case /i
|
205
|
+
if matching_results.empty?
|
206
|
+
matching_results = option_data.select {|it| it['name'] && it['name'].to_s.downcase == lowercase_id }
|
207
|
+
end
|
208
|
+
# match on id too, in case it is returned and different from value?
|
209
|
+
if matching_results.empty?
|
210
|
+
matching_results = option_data.select {|it| it['id'] && it['id'].to_s.downcase == lowercase_id }
|
211
|
+
end
|
212
|
+
if matching_results.empty?
|
213
|
+
print_red_alert "No #{option_source_label} found matching name or id '#{record_id}'"
|
214
|
+
return nil
|
215
|
+
elsif matching_results.size > 1
|
216
|
+
print_red_alert "#{matching_results.size} #{option_source_label} found matching name '#{record_id}'. Try specifying the id instead."
|
217
|
+
return nil
|
218
|
+
else
|
219
|
+
matching_result = matching_results[0]
|
220
|
+
if matching_result['value']
|
221
|
+
found_ids << matching_result['value']
|
222
|
+
else
|
223
|
+
found_ids << matching_result['id']
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
}
|
228
|
+
return found_ids
|
229
|
+
end
|
230
|
+
|
231
|
+
def parse_cloud_id_list(id_list, api_params={}, refresh=false)
|
232
|
+
parse_option_source_id_list('clouds', id_list, api_params, refresh)
|
233
|
+
end
|
234
|
+
|
235
|
+
def parse_group_id_list(id_list, api_params={}, refresh=false)
|
236
|
+
parse_option_source_id_list('groups', id_list, api_params, refresh)
|
237
|
+
end
|
238
|
+
|
239
|
+
def parse_user_id_list(id_list, api_params={}, refresh=false)
|
240
|
+
parse_option_source_id_list('users', id_list, api_params, refresh)
|
241
|
+
end
|
242
|
+
|
243
|
+
def parse_tenant_id_list(id_list, api_params={}, refresh=false)
|
244
|
+
parse_option_source_id_list('allTenants', id_list, api_params, refresh)
|
245
|
+
end
|
246
|
+
|
247
|
+
# def parse_blueprints_id_list(id_list)
|
248
|
+
# parse_option_source_id_list('blueprints', id_list, api_params, refresh)
|
249
|
+
# end
|
250
|
+
|
251
|
+
def parse_project_id_list(id_list, api_params={}, refresh=false)
|
252
|
+
parse_option_source_id_list('projects', id_list, api_params, refresh)
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
@@ -38,13 +38,16 @@ module Morpheus::Cli::PrintHelper
|
|
38
38
|
# puts red message to stderr
|
39
39
|
# why this not stderr yet? use print_error or if respond_to?(:my_terminal)
|
40
40
|
def print_red_alert(msg)
|
41
|
-
|
42
|
-
|
41
|
+
$stderr.print "#{red}#{msg}#{reset}\n"
|
42
|
+
#print_error "#{red}#{msg}#{reset}\n"
|
43
43
|
#puts_error "#{red}#{msg}#{reset}"
|
44
44
|
end
|
45
45
|
|
46
46
|
# puts green message to stdout
|
47
|
-
def print_green_success(msg)
|
47
|
+
def print_green_success(msg=nil)
|
48
|
+
if msg.nil?
|
49
|
+
msg = "success"
|
50
|
+
end
|
48
51
|
print "#{green}#{msg}#{reset}\n"
|
49
52
|
end
|
50
53
|
|
@@ -59,10 +62,21 @@ module Morpheus::Cli::PrintHelper
|
|
59
62
|
# print_h1(title, options={})
|
60
63
|
# print_h1(title, subtitles, options={})
|
61
64
|
# this can go away when we have a dirty @current_options
|
65
|
+
|
66
|
+
|
67
|
+
# auto include remote name in h1 titles
|
68
|
+
# eg. Morpheus Instances [dev]
|
69
|
+
# if title && @appliance_name
|
70
|
+
# title = "#{title} [#{@appliance_name}]"
|
71
|
+
# end
|
72
|
+
|
62
73
|
if subtitles.is_a?(Hash)
|
63
74
|
options = subtitles
|
64
75
|
subtitles = (options[:subtitles] || []).flatten
|
65
76
|
end
|
77
|
+
if subtitles.is_a?(String)
|
78
|
+
subtitles = [subtitles]
|
79
|
+
end
|
66
80
|
subtitles = (subtitles || []).flatten
|
67
81
|
options ||= {}
|
68
82
|
color = options[:color] || cyan
|
@@ -728,7 +742,7 @@ module Morpheus::Cli::PrintHelper
|
|
728
742
|
# could use some options[:preferred_columns] logic here to throw away in some specified order
|
729
743
|
# --all fields disables this
|
730
744
|
trimmed_columns = []
|
731
|
-
if options[:
|
745
|
+
if options[:wrap] != true # && options[:include_fields].nil? && options[:all_fields] != true
|
732
746
|
|
733
747
|
begin
|
734
748
|
term_width = current_terminal_width()
|
@@ -17,6 +17,14 @@ module Morpheus::Cli::ProvisioningHelper
|
|
17
17
|
@api_client.instances
|
18
18
|
end
|
19
19
|
|
20
|
+
def apps_interface
|
21
|
+
@api_client.apps
|
22
|
+
end
|
23
|
+
|
24
|
+
def servers_interface
|
25
|
+
@api_client.servers
|
26
|
+
end
|
27
|
+
|
20
28
|
def options_interface
|
21
29
|
@api_client.options
|
22
30
|
end
|
@@ -82,7 +90,10 @@ module Morpheus::Cli::ProvisioningHelper
|
|
82
90
|
|
83
91
|
def get_available_accounts(refresh=false)
|
84
92
|
if !@available_accounts || refresh
|
85
|
-
@available_accounts = accounts_interface.list()['accounts']
|
93
|
+
# @available_accounts = accounts_interface.list()['accounts']
|
94
|
+
@available_accounts = options_interface.options_for_source("allTenants", {})['data'].collect {|it|
|
95
|
+
{"name" => it["name"], "value" => it["value"], "id" => it["value"]}
|
96
|
+
}
|
86
97
|
end
|
87
98
|
@available_accounts
|
88
99
|
end
|
@@ -242,6 +253,129 @@ module Morpheus::Cli::ProvisioningHelper
|
|
242
253
|
return instance
|
243
254
|
end
|
244
255
|
|
256
|
+
def parse_instance_id_list(id_list)
|
257
|
+
parse_id_list(id_list).collect do |instance_id|
|
258
|
+
find_instance_by_name_or_id(instance_id)['id']
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
## apps
|
263
|
+
|
264
|
+
def find_app_by_id(id)
|
265
|
+
app_results = apps_interface.get(id.to_i)
|
266
|
+
if app_results['app'].empty?
|
267
|
+
print_red_alert "App not found by id #{id}"
|
268
|
+
exit 1
|
269
|
+
end
|
270
|
+
return app_results['app']
|
271
|
+
end
|
272
|
+
|
273
|
+
def find_app_by_name(name)
|
274
|
+
app_results = apps_interface.list({name: name})
|
275
|
+
apps = app_results['apps']
|
276
|
+
if apps.empty?
|
277
|
+
print_red_alert "App not found by name #{name}"
|
278
|
+
exit 1
|
279
|
+
elsif apps.size > 1
|
280
|
+
print_red_alert "#{apps.size} apps exist with the name #{name}. Try using id instead"
|
281
|
+
exit 1
|
282
|
+
end
|
283
|
+
|
284
|
+
return app_results['apps'][0]
|
285
|
+
end
|
286
|
+
|
287
|
+
def find_app_by_name_or_id(val)
|
288
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
289
|
+
return find_app_by_id(val)
|
290
|
+
else
|
291
|
+
return find_app_by_name(val)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
## servers
|
296
|
+
|
297
|
+
def find_server_by_id(id)
|
298
|
+
begin
|
299
|
+
json_response = servers_interface.get(id.to_i)
|
300
|
+
return json_response['server']
|
301
|
+
rescue RestClient::Exception => e
|
302
|
+
if e.response && e.response.code == 404
|
303
|
+
print_red_alert "Server not found by id #{id}"
|
304
|
+
exit 1
|
305
|
+
else
|
306
|
+
raise e
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
def find_server_by_name(name)
|
312
|
+
results = servers_interface.list({name: name})
|
313
|
+
if results['servers'].empty?
|
314
|
+
print_red_alert "Server not found by name #{name}"
|
315
|
+
exit 1
|
316
|
+
elsif results['servers'].size > 1
|
317
|
+
print_red_alert "Multiple servers exist with the name #{name}. Try using id instead"
|
318
|
+
exit 1
|
319
|
+
end
|
320
|
+
return results['servers'][0]
|
321
|
+
end
|
322
|
+
|
323
|
+
def find_server_by_name_or_id(val)
|
324
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
325
|
+
return find_server_by_id(val)
|
326
|
+
else
|
327
|
+
return find_server_by_name(val)
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def parse_server_id_list(id_list)
|
332
|
+
parse_id_list(id_list).collect do |server_id|
|
333
|
+
find_server_by_name_or_id(server_id)['id']
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
## hosts is the same as servers, just says 'Host' instead of 'Server'
|
338
|
+
|
339
|
+
def find_host_by_id(id)
|
340
|
+
begin
|
341
|
+
json_response = servers_interface.get(id.to_i)
|
342
|
+
return json_response['server']
|
343
|
+
rescue RestClient::Exception => e
|
344
|
+
if e.response && e.response.code == 404
|
345
|
+
print_red_alert "Host not found by id #{id}"
|
346
|
+
exit 1
|
347
|
+
else
|
348
|
+
raise e
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
def find_host_by_name(name)
|
354
|
+
results = servers_interface.list({name: name})
|
355
|
+
if results['servers'].empty?
|
356
|
+
print_red_alert "Host not found by name #{name}"
|
357
|
+
exit 1
|
358
|
+
elsif results['servers'].size > 1
|
359
|
+
print_red_alert "#{results['servers'].size} hosts exist with the name #{name}. Try using id instead"
|
360
|
+
exit 1
|
361
|
+
end
|
362
|
+
return results['servers'][0]
|
363
|
+
end
|
364
|
+
|
365
|
+
def find_host_by_name_or_id(val)
|
366
|
+
if val.to_s =~ /\A\d{1,}\Z/
|
367
|
+
return find_host_by_id(val)
|
368
|
+
else
|
369
|
+
return find_host_by_name(val)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def parse_host_id_list(id_list)
|
374
|
+
parse_id_list(id_list).collect do |host_id|
|
375
|
+
find_host_by_name_or_id(host_id)['id']
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
245
379
|
def find_instance_type_layout_by_id(layout_id, id)
|
246
380
|
json_results = instance_type_layouts_interface.get(layout_id, id)
|
247
381
|
if json_results['instanceTypeLayout'].empty?
|
@@ -416,7 +550,7 @@ module Morpheus::Cli::ProvisioningHelper
|
|
416
550
|
arbitrary_options.delete('cloud')
|
417
551
|
arbitrary_options.delete('type')
|
418
552
|
arbitrary_options.delete('name')
|
419
|
-
arbitrary_options.delete('version')
|
553
|
+
#arbitrary_options.delete('version')
|
420
554
|
arbitrary_options.delete('layout')
|
421
555
|
arbitrary_options.delete('servicePlan')
|
422
556
|
arbitrary_options.delete('description')
|
@@ -470,6 +604,8 @@ module Morpheus::Cli::ProvisioningHelper
|
|
470
604
|
layout_id = nil
|
471
605
|
if options[:layout]
|
472
606
|
layout_id = options[:layout]
|
607
|
+
# elsif options[:options]['layout']
|
608
|
+
# layout_id = options[:options]['layout']
|
473
609
|
end
|
474
610
|
if layout_id.is_a?(Hash)
|
475
611
|
layout_id = layout_id['id'] || layout_id['code'] || layout_id['name']
|
@@ -479,6 +615,8 @@ module Morpheus::Cli::ProvisioningHelper
|
|
479
615
|
default_layout_value = nil
|
480
616
|
if options[:version]
|
481
617
|
version_value = options[:version]
|
618
|
+
elsif options[:options]['version']
|
619
|
+
version_value = options[:options]['version']
|
482
620
|
else
|
483
621
|
available_versions = options_interface.options_for_source('instanceVersions',{groupId: group_id, cloudId: cloud_id, instanceTypeId: instance_type['id']})['data']
|
484
622
|
default_version_value = payload['instance']['version'] ? payload['instance']['version'] : payload['version']
|
@@ -581,8 +719,11 @@ module Morpheus::Cli::ProvisioningHelper
|
|
581
719
|
end
|
582
720
|
end
|
583
721
|
#todo: consolidate these, instances api looks for instance.plan.id and apps looks for plan.id
|
584
|
-
|
585
|
-
|
722
|
+
if options[:for_app]
|
723
|
+
payload['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
|
724
|
+
else
|
725
|
+
payload['instance']['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
|
726
|
+
end
|
586
727
|
end
|
587
728
|
|
588
729
|
# build config option types
|
@@ -964,7 +1105,6 @@ module Morpheus::Cli::ProvisioningHelper
|
|
964
1105
|
default_datastore = datastore_options.find {|ds| ds['value'].to_s == volume['datastoreId'].to_s}
|
965
1106
|
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])
|
966
1107
|
volume['datastoreId'] = v_prompt[field_context]['datastoreId']
|
967
|
-
volume['hasDatastore'] = true
|
968
1108
|
end
|
969
1109
|
|
970
1110
|
volumes << volume
|
@@ -1029,7 +1169,6 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1029
1169
|
if !datastore_options.empty?
|
1030
1170
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldContext' => field_context, 'fieldName' => 'datastoreId', 'type' => 'select', 'fieldLabel' => "Disk #{volume_index} Datastore", 'selectOptions' => datastore_options, 'required' => true, 'description' => 'Choose a datastore.', 'defaultValue' => volume['datastoreId']}], options[:options])
|
1031
1171
|
volume['datastoreId'] = v_prompt[field_context]['datastoreId']
|
1032
|
-
volume['hasDatastore'] = true
|
1033
1172
|
end
|
1034
1173
|
|
1035
1174
|
volumes << volume
|
@@ -1507,6 +1646,45 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1507
1646
|
return evars
|
1508
1647
|
end
|
1509
1648
|
|
1649
|
+
# Converts metadata tags array into a string like "name:value, foo:bar"
|
1650
|
+
def format_metadata(tags)
|
1651
|
+
if tags.nil? || tags.empty?
|
1652
|
+
""
|
1653
|
+
elsif tags.instance_of?(Array)
|
1654
|
+
tags.collect {|tag| "#{tag['name']}: #{tag['value']}" }.sort.join(", ")
|
1655
|
+
elsif tags.instance_of?(Hash)
|
1656
|
+
tags.collect {|k,v| "#{k}: #{v}" }.sort.join(", ")
|
1657
|
+
else
|
1658
|
+
tags.to_s
|
1659
|
+
end
|
1660
|
+
end
|
1661
|
+
|
1662
|
+
# Parses metadata tags object (string) into an array
|
1663
|
+
def parse_metadata(val)
|
1664
|
+
metadata = nil
|
1665
|
+
if val
|
1666
|
+
if val == "[]" || val == "null"
|
1667
|
+
metadata = []
|
1668
|
+
elsif val.is_a?(Array)
|
1669
|
+
metadata = val
|
1670
|
+
else
|
1671
|
+
# parse string into format name:value, name:value
|
1672
|
+
# merge IDs from current metadata
|
1673
|
+
# todo: should allow quoted semicolons..
|
1674
|
+
metadata_list = val.to_s.split(",").select {|it| !it.to_s.empty? }
|
1675
|
+
metadata_list = metadata_list.collect do |it|
|
1676
|
+
metadata_pair = it.include?(":") ? it.split(":") : it.split("=")
|
1677
|
+
row = {}
|
1678
|
+
row['name'] = metadata_pair[0].to_s.strip
|
1679
|
+
row['value'] = metadata_pair[1].to_s.strip
|
1680
|
+
row
|
1681
|
+
end
|
1682
|
+
metadata = metadata_list
|
1683
|
+
end
|
1684
|
+
end
|
1685
|
+
return metadata
|
1686
|
+
end
|
1687
|
+
|
1510
1688
|
# Prompts user for environment variables for new instance
|
1511
1689
|
# returns array of metadata objects {id: null, name: "MYTAG", value: "myvalue"}
|
1512
1690
|
def prompt_metadata(options={})
|
@@ -1712,8 +1890,12 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1712
1890
|
end
|
1713
1891
|
|
1714
1892
|
if !options[:groupAccessList].empty?
|
1715
|
-
group_access = options[:groupAccessList].collect {|site_id|
|
1716
|
-
|
1893
|
+
group_access = options[:groupAccessList].collect {|site_id|
|
1894
|
+
found_group = find_group_by_name_or_id_for_provisioning(site_id)
|
1895
|
+
return 1, "group not found by #{site_id}" if found_group.nil?
|
1896
|
+
{'id' => found_group['id']}
|
1897
|
+
} || []
|
1898
|
+
elsif !options[:no_prompt] && !all_groups
|
1717
1899
|
available_groups = options[:available_groups] || get_available_groups
|
1718
1900
|
|
1719
1901
|
if available_groups.empty?
|
@@ -1806,19 +1988,20 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1806
1988
|
|
1807
1989
|
permissions = {'resourcePermissions' => resource_perms}
|
1808
1990
|
|
1809
|
-
available_accounts = get_available_accounts
|
1991
|
+
available_accounts = get_available_accounts() #.collect {|it| {'name' => it['name'], 'value' => it['id']}}
|
1810
1992
|
accounts = []
|
1811
1993
|
|
1812
1994
|
# Prompts for multi tenant
|
1813
1995
|
if available_accounts.count > 1
|
1814
1996
|
visibility = options[:visibility]
|
1997
|
+
if !excludes.include?('visibility')
|
1998
|
+
if !visibility && !options[:no_prompt]
|
1999
|
+
visibility = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Tenant Permissions Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility']
|
2000
|
+
end
|
1815
2001
|
|
1816
|
-
|
1817
|
-
visibility = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'visibility', 'fieldLabel' => 'Tenant Permissions Visibility', 'type' => 'select', 'defaultValue' => 'private', 'required' => true, 'selectOptions' => [{'name' => 'Private', 'value' => 'private'},{'name' => 'Public', 'value' => 'public'}]}], options[:options], @api_client, {})['visibility']
|
2002
|
+
permissions['resourcePool'] = {'visibility' => visibility} if visibility
|
1818
2003
|
end
|
1819
2004
|
|
1820
|
-
permissions['resourcePool'] = {'visibility' => visibility} if visibility
|
1821
|
-
|
1822
2005
|
# Tenants
|
1823
2006
|
if !excludes.include?('tenants')
|
1824
2007
|
if !options[:tenants].nil?
|
@@ -1980,4 +2163,30 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1980
2163
|
|
1981
2164
|
return ports
|
1982
2165
|
end
|
2166
|
+
|
2167
|
+
def format_blueprint_type(type_code)
|
2168
|
+
return type_code.to_s # just show it as is
|
2169
|
+
if type_code.to_s.empty?
|
2170
|
+
type_code = "morpheus"
|
2171
|
+
end
|
2172
|
+
if type_code.to_s.downcase == "arm"
|
2173
|
+
"ARM"
|
2174
|
+
else
|
2175
|
+
return type_code.to_s.capitalize
|
2176
|
+
end
|
2177
|
+
end
|
2178
|
+
|
2179
|
+
def parse_blueprint_type(type_code)
|
2180
|
+
return type_code.to_s # just use it as is
|
2181
|
+
# if type_code.to_s.empty?
|
2182
|
+
# type_code = "morpheus"
|
2183
|
+
# end
|
2184
|
+
if type_code.to_s.downcase == "arm"
|
2185
|
+
"arm"
|
2186
|
+
elsif type_code.to_s.downcase == "cloudformation"
|
2187
|
+
type_code = "cloudFormation"
|
2188
|
+
else
|
2189
|
+
return type_code.to_s.downcase
|
2190
|
+
end
|
2191
|
+
end
|
1983
2192
|
end
|