morpheus-cli 4.2.8 → 4.2.10

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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/lib/morpheus/api.rb +1 -1
  4. data/lib/morpheus/api/activity_interface.rb +9 -0
  5. data/lib/morpheus/api/api_client.rb +83 -27
  6. data/lib/morpheus/api/apps_interface.rb +21 -0
  7. data/lib/morpheus/api/dashboard_interface.rb +5 -21
  8. data/lib/morpheus/api/instances_interface.rb +3 -10
  9. data/lib/morpheus/api/invoice_line_items_interface.rb +14 -0
  10. data/lib/morpheus/api/invoices_interface.rb +7 -12
  11. data/lib/morpheus/api/library_layouts_interface.rb +8 -0
  12. data/lib/morpheus/api/ping_interface.rb +20 -0
  13. data/lib/morpheus/api/projects_interface.rb +33 -0
  14. data/lib/morpheus/api/setup_interface.rb +19 -36
  15. data/lib/morpheus/api/user_settings_interface.rb +0 -6
  16. data/lib/morpheus/api/whoami_interface.rb +4 -8
  17. data/lib/morpheus/benchmarking.rb +16 -26
  18. data/lib/morpheus/cli.rb +10 -5
  19. data/lib/morpheus/cli/access_token_command.rb +5 -8
  20. data/lib/morpheus/cli/activity_command.rb +146 -0
  21. data/lib/morpheus/cli/apps.rb +312 -121
  22. data/lib/morpheus/cli/archives_command.rb +1 -1
  23. data/lib/morpheus/cli/auth_command.rb +4 -11
  24. data/lib/morpheus/cli/blueprints_command.rb +196 -137
  25. data/lib/morpheus/cli/change_password_command.rb +1 -1
  26. data/lib/morpheus/cli/cli_command.rb +225 -72
  27. data/lib/morpheus/cli/cli_registry.rb +2 -2
  28. data/lib/morpheus/cli/cloud_datastores_command.rb +1 -1
  29. data/lib/morpheus/cli/clouds.rb +5 -20
  30. data/lib/morpheus/cli/clusters.rb +4 -28
  31. data/lib/morpheus/cli/commands/standard/alias_command.rb +2 -9
  32. data/lib/morpheus/cli/commands/standard/benchmark_command.rb +2 -0
  33. data/lib/morpheus/cli/commands/standard/curl_command.rb +2 -3
  34. data/lib/morpheus/cli/commands/standard/history_command.rb +3 -6
  35. data/lib/morpheus/cli/commands/standard/man_command.rb +10 -7
  36. data/lib/morpheus/cli/commands/standard/ssl_verification_command.rb +10 -9
  37. data/lib/morpheus/cli/containers_command.rb +3 -3
  38. data/lib/morpheus/cli/credentials.rb +13 -16
  39. data/lib/morpheus/cli/error_handler.rb +18 -12
  40. data/lib/morpheus/cli/errors.rb +45 -0
  41. data/lib/morpheus/cli/execute_schedules_command.rb +1 -1
  42. data/lib/morpheus/cli/execution_request_command.rb +4 -4
  43. data/lib/morpheus/cli/groups.rb +84 -132
  44. data/lib/morpheus/cli/hosts.rb +6 -16
  45. data/lib/morpheus/cli/instances.rb +100 -183
  46. data/lib/morpheus/cli/invoices_command.rb +505 -71
  47. data/lib/morpheus/cli/library_layouts_command.rb +254 -166
  48. data/lib/morpheus/cli/library_option_lists_command.rb +0 -87
  49. data/lib/morpheus/cli/library_option_types_command.rb +0 -96
  50. data/lib/morpheus/cli/license.rb +3 -0
  51. data/lib/morpheus/cli/login.rb +17 -37
  52. data/lib/morpheus/cli/logout.rb +9 -5
  53. data/lib/morpheus/cli/mixins/accounts_helper.rb +83 -7
  54. data/lib/morpheus/cli/mixins/operations_helper.rb +41 -0
  55. data/lib/morpheus/cli/mixins/option_source_helper.rb +255 -0
  56. data/lib/morpheus/cli/mixins/print_helper.rb +18 -4
  57. data/lib/morpheus/cli/mixins/provisioning_helper.rb +222 -13
  58. data/lib/morpheus/cli/mixins/remote_helper.rb +139 -0
  59. data/lib/morpheus/cli/monitoring_checks_command.rb +11 -3
  60. data/lib/morpheus/cli/network_groups_command.rb +8 -2
  61. data/lib/morpheus/cli/option_types.rb +1 -1
  62. data/lib/morpheus/cli/ping.rb +252 -0
  63. data/lib/morpheus/cli/price_sets_command.rb +16 -27
  64. data/lib/morpheus/cli/prices_command.rb +34 -27
  65. data/lib/morpheus/cli/processes_command.rb +81 -7
  66. data/lib/morpheus/cli/projects_command.rb +607 -0
  67. data/lib/morpheus/cli/recent_activity_command.rb +87 -65
  68. data/lib/morpheus/cli/remote.rb +965 -974
  69. data/lib/morpheus/cli/reports_command.rb +3 -15
  70. data/lib/morpheus/cli/roles.rb +8 -31
  71. data/lib/morpheus/cli/service_plans_command.rb +25 -31
  72. data/lib/morpheus/cli/setup.rb +392 -0
  73. data/lib/morpheus/cli/shell.rb +144 -56
  74. data/lib/morpheus/cli/subnets_command.rb +71 -11
  75. data/lib/morpheus/cli/tasks.rb +3 -3
  76. data/lib/morpheus/cli/user_sources_command.rb +4 -4
  77. data/lib/morpheus/cli/users.rb +135 -109
  78. data/lib/morpheus/cli/version.rb +1 -1
  79. data/lib/morpheus/cli/whitelabel_settings_command.rb +7 -7
  80. data/lib/morpheus/cli/whoami.rb +90 -129
  81. data/lib/morpheus/cli/wiki_command.rb +2 -14
  82. data/lib/morpheus/ext/rest_client.rb +36 -0
  83. data/lib/morpheus/formatters.rb +42 -5
  84. data/lib/morpheus/rest_client.rb +0 -10
  85. data/lib/morpheus/terminal.rb +41 -1
  86. data/lib/morpheus/util.rb +24 -0
  87. metadata +16 -3
  88. 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
- #$stderr.print "#{red}#{msg}#{reset}\n"
42
- print "#{red}#{msg}#{reset}\n"
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[:responsive_table] != false # && options[:include_fields].nil? && options[:all_fields] != true
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
- payload['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
585
- payload['instance']['plan'] = {'id' => service_plan["id"], 'code' => service_plan["code"], 'name' => service_plan["name"]}
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| {'id' => site_id.to_i}} || []
1716
- elsif !options[:no_prompt]
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.collect {|it| {'name' => it['name'], 'value' => it['id']}}
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
- if !visibility && !options[:no_prompt]
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