morpheus-cli 3.6.3 → 3.6.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c4ff5dbc24449eb5603e8f786653d613a1e204e03c5f5071647074127c7144e
4
- data.tar.gz: '08e8a66410060ac421d790ab7b0ace8c3e0e2d923b99080bc686efae00047ebc'
3
+ metadata.gz: ebb3ffe2cbabe89c6f9ba8148cf1e2d2a7bfd51893a71f69acf05c406013454d
4
+ data.tar.gz: 298427126d4530f189b3ffbc09a9c06baeaf91018412a9b5633dda1ed76e5571
5
5
  SHA512:
6
- metadata.gz: 4da0dd9d22fe6907b5c8adf120a22650802a310eacfa3b499963e6df240eebec5d13fd8462d7497ddbaed48af27fe9069a64a806e819bd09f0a0e2402518882b
7
- data.tar.gz: 2329a3d24c1a24e7b12c71bd8b5ae6dec2997577d89a3eb2926fef3d7e1dffa6be0f03c46be7518ece5c0e9e81cee4c9d8bc0753b434654802459df631f47241
6
+ metadata.gz: 0bcc8b841e8a23e6c90e81c724dcc5fecd967eee7294c97069708f849db2e98880c2f3832188a5740c4dd3aa81267682b7cd5b8bd84af0d229df8807ad6f9c33
7
+ data.tar.gz: 6b94513b1484b9caa2d04ced8c24f143f5e41f4746e3bbeb92c074aa0c2dacaf9166f4b5d409c390d25ec11965fa58678d0d9568e6d4dd9b27c66e33aac3107c
@@ -14,6 +14,8 @@ class Morpheus::Cli::Apps
14
14
  include Morpheus::Cli::ProcessesHelper
15
15
 
16
16
  register_subcommands :list, :get, :add, :update, :remove, :add_instance, :remove_instance, :logs, :firewall_disable, :firewall_enable, :security_groups, :apply_security_groups, :history
17
+ register_subcommands :stop, :start, :restart
18
+ #register_subcommands :validate # add --validate instead
17
19
  alias_subcommand :details, :get
18
20
  set_default_subcommand :list
19
21
 
@@ -24,6 +26,7 @@ class Morpheus::Cli::Apps
24
26
  def connect(opts)
25
27
  @api_client = establish_remote_appliance_connection(opts)
26
28
  @apps_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).apps
29
+ @blueprints_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).blueprints
27
30
  @instance_types_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instance_types
28
31
  @instances_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).instances
29
32
  @options_interface = Morpheus::APIClient.new(@access_token,nil,nil, @appliance_url).options
@@ -41,21 +44,19 @@ class Morpheus::Cli::Apps
41
44
  options = {}
42
45
  optparse = Morpheus::Cli::OptionParser.new do |opts|
43
46
  opts.banner = subcommand_usage()
44
- build_common_options(opts, options, [:list, :json, :dry_run])
47
+ build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
45
48
  opts.footer = "List apps."
46
49
  end
47
50
  optparse.parse!(args)
48
51
  if args.count != 0
49
52
  print_error Morpheus::Terminal.angry_prompt
50
- puts_error "#{command_name} list expects 0 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
53
+ puts_error "#{command_name} list expects 0 arguments and received #{args.count}: #{args}\n#{optparse}"
51
54
  return 1
52
55
  end
53
56
  connect(options)
54
57
  begin
55
58
  params = {}
56
- [:phrase, :offset, :max, :sort, :direction].each do |k|
57
- params[k] = options[k] unless options[k].nil?
58
- end
59
+ params.merge!(parse_list_options(options))
59
60
 
60
61
  if options[:dry_run]
61
62
  print_dry_run @apps_interface.dry.get(params)
@@ -71,15 +72,7 @@ class Morpheus::Cli::Apps
71
72
  apps = json_response['apps']
72
73
  title = "Morpheus Apps"
73
74
  subtitles = []
74
- # if group
75
- # subtitles << "Group: #{group['name']}".strip
76
- # end
77
- # if cloud
78
- # subtitles << "Cloud: #{cloud['name']}".strip
79
- # end
80
- if params[:phrase]
81
- subtitles << "Search: #{params[:phrase]}".strip
82
- end
75
+ subtitles += parse_list_subtitles(options)
83
76
  print_h1 title, subtitles
84
77
  if apps.empty?
85
78
  print cyan,"No apps found.",reset,"\n"
@@ -95,128 +88,323 @@ class Morpheus::Cli::Apps
95
88
  end
96
89
 
97
90
  def add(args)
98
- template_id = nil
99
91
  options = {}
92
+ params = {}
100
93
  optparse = Morpheus::Cli::OptionParser.new do |opts|
101
94
  opts.banner = subcommand_usage("[name] [options]")
102
- build_option_type_options(opts, options, add_app_option_types(false))
103
- # opts.on( '-b', '--blueprint ID', "Blueprint ID. The blueprint to use. The default value is 'existing' which means no blueprint, for creating a blank app and adding existing instances." ) do |val|
104
- # options['template'] = val
105
- # end
106
- # opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
107
- # options[:group] = val
108
- # end
109
- # opts.on( '-c', '--cloud CLOUD', "Cloud Name or ID." ) do |val|
110
- # options[:cloud] = val
111
- # end
112
- opts.on('--config JSON', String, "App Config JSON") do |val|
113
- options[:config] = JSON.parse(val.to_s)
95
+ #build_option_type_options(opts, options, add_app_option_types(false))
96
+ # these come from build_options_types
97
+ opts.on( '-b', '--blueprint BLUEPRINT', "Blueprint Name or ID. The default value is 'existing' which means no blueprint, for creating a blank app and adding existing instances." ) do |val|
98
+ options[:blueprint] = val
99
+ end
100
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
101
+ options[:group] = val
102
+ end
103
+ opts.on( '-c', '--cloud CLOUD', "Default Cloud Name or ID." ) do |val|
104
+ options[:cloud] = val
114
105
  end
115
- opts.on('--config-yaml YAML', String, "App Config YAML") do |val|
116
- options[:config] = YAML.load(val.to_s)
106
+ opts.on( '--name VALUE', String, "Name" ) do |val|
107
+ options[:name] = val
117
108
  end
118
- opts.on('--config-file FILE', String, "App Config from a local JSON or YAML file") do |val|
119
- options[:config_file] = val.to_s
109
+ opts.on( '--description VALUE', String, "Description" ) do |val|
110
+ options[:description] = val
120
111
  end
121
- opts.on('--config-dir DIRECTORY', String, "Blueprint Config from a local directory, merging all JSON or YAML files") do |val|
122
- options[:config_dir] = val.to_s
112
+ opts.on( '-e', '--environment VALUE', "Environment Name" ) do |val|
113
+ options[:environment] = val.to_s == 'null' ? nil : val
123
114
  end
124
- build_common_options(opts, options, [:options, :json, :dry_run, :quiet])
115
+ # config is being deprecated in favor of the standard --payload options
116
+ # opts.add_hidden_option(['config', 'config-dir', 'config-file', 'config-yaml'])
117
+ opts.on('--validate','--validate', "Validate Only. Validates the configuration and skips creating it.") do
118
+ options[:validate_only] = true
119
+ end
120
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is 5 seconds.") do |val|
121
+ options[:refresh_interval] = val.to_s.empty? ? 5 : val.to_f
122
+ end
123
+ build_common_options(opts, options, [:options, :payload, :json, :yaml, :dry_run, :quiet])
125
124
  opts.footer = "Create a new app.\n" +
126
125
  "[name] is required. This is the name of the new app. It may also be passed as --name or inside your config."
127
126
  end
128
127
  optparse.parse!(args)
129
128
  if args.count > 1
130
129
  print_error Morpheus::Terminal.angry_prompt
131
- puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
130
+ puts_error "#{command_name} add expects 0-1 arguments and received #{args.count}: #{args}\n#{optparse}"
132
131
  return 1
133
132
  end
133
+ # allow name as first argument
134
+ if args[0] # && !options[:name]
135
+ options[:name] = args[0]
136
+ end
134
137
  connect(options)
135
138
  begin
136
139
  options[:options] ||= {}
137
- if args[0] && !options[:options]['name']
138
- options[:options]['name'] = args[0]
139
- end
140
- # options[:options]['template'] ||= options['template']
141
- if options[:group] # || @active_group_id
142
- options[:options]['group'] ||= options[:group] # || @active_group_id
143
- end
144
-
140
+ passed_options = (options[:options] || {}).reject {|k,v| k.is_a?(Symbol) }
145
141
  payload = {}
146
- if options[:config]
147
- config_payload = options[:config]
148
- payload = config_payload
149
- payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
150
- elsif options[:config_file]
151
- config_file = File.expand_path(options[:config_file])
152
- if !File.exists?(config_file) || !File.file?(config_file)
153
- print_red_alert "File not found: #{config_file}"
154
- return false
142
+ if options[:payload]
143
+ # payload is from parsed json|yaml files or arguments.
144
+ payload = options[:payload]
145
+ # merge -O options
146
+ payload.deep_merge!(passed_options) unless passed_options.empty?
147
+ # support some options on top of --payload
148
+ [:name, :description, :environment].each do |k|
149
+ if options.key?(k)
150
+ payload[k.to_s] = options[k]
151
+ end
152
+ end
153
+ else
154
+ # prompt for payload
155
+ payload = {}
156
+ # merge -O options
157
+ payload.deep_merge!(passed_options) unless passed_options.empty?
158
+
159
+ # this could have some special -O context, like -O tier.Web.0.instance.name
160
+ # tier_config_options = payload.delete('tier')
161
+
162
+ # Blueprint
163
+ blueprint_id = 'existing'
164
+ blueprint = nil
165
+ if options[:blueprint]
166
+ blueprint_id = options[:blueprint]
167
+ options[:options]['blueprint'] = options[:blueprint]
168
+ end
169
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'selectOptions' => get_available_blueprints(), 'required' => true, 'defaultValue' => 'existing', 'description' => "The blueprint to use. The default value is 'existing' which means no template, for creating a blank app and adding existing instances."}], options[:options])
170
+ blueprint_id = v_prompt['blueprint']
171
+
172
+ if blueprint_id.to_s.empty? || blueprint_id == 'existing'
173
+ blueprint = {"id" => "existing", "name" => "Existing Instances", "value" => "existing", "type" => "morpheus"}
174
+ else
175
+ blueprint = find_blueprint_by_name_or_id(blueprint_id)
176
+ if blueprint.nil?
177
+ print_red_alert "Blueprint not found by name or id '#{blueprint_id}'"
178
+ return 1
179
+ end
155
180
  end
156
- if config_file =~ /\.ya?ml\Z/
157
- config_payload = YAML.load_file(config_file)
181
+
182
+ payload['templateId'] = blueprint['id'] # for pre-3.6 api
183
+ payload['blueprintId'] = blueprint['id']
184
+ payload['blueprintName'] = blueprint['name'] #for future api plz
185
+
186
+ # Name
187
+ options[:options]['name'] = options[:name] if options.key?(:name)
188
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this app'}], options[:options])
189
+ payload['name'] = v_prompt['name']
190
+
191
+
192
+ # Description
193
+ options[:options]['description'] = options[:description] if options.key?(:description)
194
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false}], options[:options])
195
+ payload['description'] = v_prompt['description']
196
+
197
+
198
+ # Group
199
+ group_id = nil
200
+ options[:options]['group'] = options[:group] if options.key?(:group)
201
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => get_available_groups(), 'required' => true, 'defaultValue' => @active_group_id}], options[:options])
202
+ group_id = v_prompt['group']
203
+
204
+ group = find_group_by_name_or_id_for_provisioning(group_id)
205
+ return 1 if group.nil?
206
+ payload['group'] = {'id' => group['id'], 'name' => group['name']}
207
+
208
+ # Default Cloud
209
+ cloud_id = nil
210
+ scoped_available_clouds = get_available_clouds(group['id'])
211
+ if options[:cloud]
212
+ cloud_id = options[:cloud]
158
213
  else
159
- config_payload = JSON.parse(File.read(config_file))
214
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'cloud', 'fieldLabel' => 'Default Cloud', 'type' => 'select', 'selectOptions' => scoped_available_clouds}], options[:options])
215
+ cloud_id = v_prompt['cloud'] unless v_prompt['cloud'].to_s.empty?
160
216
  end
161
- payload = config_payload
162
- elsif options[:config_dir]
163
- config_dir = File.expand_path(options[:config_dir])
164
- if !Dir.exists?(config_dir) || !File.directory?(config_dir)
165
- print_red_alert "Directory not found: #{config_dir}"
166
- return false
217
+ if cloud_id
218
+ cloud = find_cloud_by_name_or_id_for_provisioning(group['id'], cloud_id)
219
+ #payload['cloud'] = {'id' => cloud['id'], 'name' => cloud['name']}
167
220
  end
168
- merged_payload = {}
169
- config_files = []
170
- config_files += Dir["#{config_dir}/*.json"]
171
- config_files += Dir["#{config_dir}/*.yml"]
172
- config_files += Dir["#{config_dir}/*.yaml"]
173
- if config_files.empty?
174
- print_red_alert "No .json/yaml files found in config directory: #{config_dir}"
175
- return false
221
+
222
+ # Environment
223
+ if options[:environment]
224
+ payload['environment'] = options[:environment]
225
+ else
226
+ v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'text', 'required' => false}], options[:options])
227
+ payload['environment'] = v_prompt['environment'] unless v_prompt['environment'].to_s.empty?
228
+ end
229
+ # payload['appContext'] = payload['environment'] if payload['environment']
230
+
231
+
232
+ if !payload['tiers']
233
+ if payload['blueprintId'] != 'existing'
234
+
235
+ # fetch the app template
236
+ blueprint = find_blueprint_by_name_or_id(payload['blueprintId'])
237
+ return 1 if blueprint.nil?
238
+
239
+ unless options[:quiet]
240
+ print cyan, "Configuring app with blueprint id: #{blueprint['id']}, name: #{blueprint['name']}, type: #{blueprint['type']}\n"
241
+ end
242
+
243
+ blueprint_type = blueprint['type'] || 'morpheus'
244
+ if blueprint_type == 'morpheus'
245
+ # configure each tier and instance in the blueprint
246
+ # tiers are a map, heh, sort them by tierIndex
247
+ tiers = blueprint["config"]["tiers"] ? blueprint["config"]["tiers"] : (blueprint["tiers"] || {})
248
+ sorted_tiers = tiers.collect {|k,v| [k,v] }.sort {|a,b| a[1]['tierIndex'] <=> b[1]['tierIndex'] }
249
+ sorted_tiers.each do |tier_obj|
250
+ tier_name = tier_obj[0]
251
+ tier_config = tier_obj[1]
252
+ payload['tiers'] ||= {}
253
+ payload['tiers'][tier_name] ||= tier_config.clone
254
+ # remove instances, they will be iterated over and merged back in
255
+ tier_instances = payload['tiers'][tier_name].delete("instances")
256
+ # remove other blank stuff
257
+ if payload['tiers'][tier_name]['linkedTiers'] && payload['tiers'][tier_name]['linkedTiers'].empty?
258
+ payload['tiers'][tier_name].delete('linkedTiers')
259
+ end
260
+ # remove extra instance options at tierName.index, probabl need a namespace here like tier.TierName.index
261
+ tier_extra_options = {}
262
+ if payload[tier_name]
263
+ tier_extra_options = payload.delete(tier_name)
264
+ end
265
+ tier_instance_types = tier_instances ? tier_instances.collect {|it| (it['instance'] && it['instance']['type']) ? it['instance']['type'].to_s : 'unknown'}.compact : []
266
+ unless options[:quiet]
267
+ # print cyan, "Configuring Tier: #{tier_name} (#{tier_instance_types.empty? ? 'empty' : tier_instance_types.join(', ')})", "\n"
268
+ print cyan, "Configuring tier #{tier_name}", "\n"
269
+ end
270
+ # todo: also prompt for tier settings here, like linkedTiers: []
271
+ if tier_instances
272
+ tier_instances = tier_config['instances'] || []
273
+ tier_instances.each_with_index do |instance_config, instance_index|
274
+ instance_type_code = instance_config['type']
275
+ if instance_config['instance'] && instance_config['instance']['type']
276
+ instance_type_code = instance_config['instance']['type']
277
+ end
278
+ if instance_type_code.nil?
279
+ print_red_alert "Unable to determine instance type for tier: #{tier_name} index: #{instance_index}"
280
+ return 1
281
+ else
282
+ unless options[:quiet]
283
+ print cyan, "Configuring #{instance_type_code} instance #{tier_name}.#{instance_index}", "\n"
284
+ end
285
+ # prompt for the cloud for this instance
286
+ # the cloud is part of finding the scoped config in the blueprint
287
+
288
+ scoped_instance_config = get_scoped_instance_config(instance_config.clone, payload['environment'], group ? group['name'] : nil, cloud ? cloud['name'] : nil)
289
+ # now configure an instance like normal, use the config as default options with :always_prompt
290
+ instance_prompt_options = {}
291
+ instance_prompt_options[:group] = group ? group['id'] : nil
292
+ instance_prompt_options[:default_cloud] = cloud ? cloud['id'] : nil
293
+ instance_prompt_options[:no_prompt] = options[:no_prompt]
294
+ instance_prompt_options[:always_prompt] = options[:no_prompt] != true # options[:always_prompt]
295
+ instance_prompt_options[:options] = scoped_instance_config # meh, actually need to make these default values instead..
296
+ instance_prompt_options[:options][:always_prompt] = instance_prompt_options[:no_prompt] != true
297
+ instance_prompt_options[:options][:no_prompt] = instance_prompt_options[:no_prompt]
298
+
299
+ # also allow arbritrary options passed as tierName.instanceIndex
300
+ instance_extra_options = {}
301
+ if tier_extra_options && tier_extra_options[instance_index.to_s]
302
+ instance_extra_options = tier_extra_options[instance_index.to_s]
303
+ end
304
+ instance_prompt_options[:options].deep_merge!(instance_extra_options)
305
+
306
+ #instance_prompt_options[:name_required] = true
307
+ instance_prompt_options[:instance_type_code] = instance_type_code
308
+
309
+ # this provisioning helper method handles all (most) of the parsing and prompting
310
+ instance_config_payload = prompt_new_instance(instance_prompt_options)
311
+
312
+ # strip all empty string and nil
313
+ instance_config_payload.deep_compact!
314
+ # use the blueprint config as the base
315
+ final_config = scoped_instance_config.clone
316
+ # merge the prompted values
317
+ final_config.deep_merge!(instance_config_payload)
318
+ final_config.delete('environments')
319
+ final_config.delete('groups')
320
+ final_config.delete('clouds')
321
+ # add config to payload
322
+ payload['tiers'][tier_name]['instances'] ||= []
323
+ payload['tiers'][tier_name]['instances'] << final_config
324
+ end
325
+ end
326
+ else
327
+ puts yellow, "Tier '#{tier_name}' is empty", reset
328
+ end
329
+ end
330
+ elsif blueprint_type == 'terraform'
331
+ # prompt for Terraform config
332
+ # todo
333
+ elsif blueprint_type == 'arm'
334
+ # prompt for ARM config
335
+ # todo
336
+ elsif blueprint_type == 'cloudFormation'
337
+ # prompt for cloudFormation config
338
+ # todo
339
+ else
340
+ print yellow, "Unknown template type: #{template_type})", "\n"
341
+ end
342
+ end
176
343
  end
177
- config_files.each do |config_file|
178
- if config_file =~ /\.ya?ml\Z/
179
- config_payload = YAML.load_file(config_file)
344
+ end
345
+
346
+
347
+ # Validate Only
348
+ if options[:validate_only] == true
349
+ # Validate Only Dry run
350
+ if options[:dry_run]
351
+ if options[:json]
352
+ puts as_json(payload, options)
353
+ elsif options[:yaml]
354
+ puts as_yaml(payload, options)
180
355
  else
181
- config_payload = JSON.parse(File.read(config_file))
356
+ print_dry_run @apps_interface.dry.validate(payload)
182
357
  end
183
- merged_payload.deep_merge!(config_payload)
358
+ return 0
184
359
  end
185
- payload = merged_payload
186
- payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
187
- else
188
- # prompt for Name, Description, Group, Environment
189
- payload = {}
190
- payload.deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
191
- params = Morpheus::Cli::OptionTypes.prompt(add_app_option_types, options[:options], @api_client, options[:params])
192
- params = params.deep_compact! # remove nulls and blank strings
193
- template_id = params.delete('blueprint')
194
- if template_id.to_s.empty? || template_id == 'existing'
195
- # new API parameter
196
- payload['templateId'] = 'existing'
197
- # API versions before 3.3.1 expect both of these instead of templateId
198
- payload['id'] = 'existing'
199
- payload['templateName'] = 'Existing Instances'
360
+ json_response = @apps_interface.validate(payload)
361
+
362
+ if options[:json]
363
+ puts as_json(json_response, options)
200
364
  else
201
- found_app_template = get_available_blueprints.find {|it| it['id'].to_s == template_id.to_s || it['name'].to_s == template_id.to_s }
202
- if found_app_template.nil?
203
- print_red_alert "Blueprint not found by id #{template_id}"
204
- return 1
365
+ if !options[:quiet]
366
+ if json_response['success'] == true
367
+ print_green_success "New app '#{payload['name']}' validation passed. #{json_response['msg']}".strip
368
+ else
369
+ print_red_alert "New app '#{payload['name']}' validation failed. #{json_response['msg']}".strip
370
+ if json_response['errors'] && json_response['errors']['instances']
371
+ json_response['errors']['instances'].each do |error_obj|
372
+ tier_name = error_obj['tier']
373
+ instance_index = error_obj['index']
374
+ instance_errors = error_obj['instanceErrors']
375
+ print_error red, "#{tier_name} : #{instance_index}", "\n", reset
376
+ if instance_errors
377
+ instance_errors.each do |err_key, err_msg|
378
+ print_error red, " * #{err_key} : #{err_msg}", "\n", reset
379
+ end
380
+ end
381
+ end
382
+ else
383
+ # a default way to print errors
384
+ (json_response['errors'] || []).each do |error_key, error_msg|
385
+ print_error " * #{error_key} : #{error_msg}", "\n"
386
+ end
387
+ end
388
+ end
205
389
  end
206
- payload['templateId'] = found_app_template['id']
207
- # API versions before 3.3.1 expect both of these instead of templateId
208
- payload['id'] = found_app_template['id']
209
- payload['templateName'] = found_app_template['name']
210
390
  end
211
- group = find_group_by_name_or_id_for_provisioning(params.delete('group'))
212
- return if group.nil?
213
- payload.merge!(params)
214
- payload['group'] = {id: group['id'], name: group['name']}
391
+ if json_response['success'] == true
392
+ return 0
393
+ else
394
+ return 1
395
+ end
215
396
  end
216
397
 
398
+ # Dry Run?
217
399
  if options[:dry_run]
218
- print_dry_run @apps_interface.dry.create(payload)
219
- return
400
+ if options[:json]
401
+ puts as_json(payload, options)
402
+ elsif options[:yaml]
403
+ puts as_yaml(payload, options)
404
+ else
405
+ print_dry_run @apps_interface.dry.create(payload)
406
+ end
407
+ return 0
220
408
  end
221
409
 
222
410
  json_response = @apps_interface.create(payload)
@@ -237,7 +425,11 @@ class Morpheus::Cli::Apps
237
425
  end
238
426
  end
239
427
  # print details
240
- get([app['id']])
428
+ if options[:refresh_interval]
429
+ get([app['id'], '--refresh', options[:refresh_interval].to_s])
430
+ else
431
+ get([app['id']])
432
+ end
241
433
  end
242
434
  return 0
243
435
  rescue RestClient::Exception => e
@@ -250,49 +442,65 @@ class Morpheus::Cli::Apps
250
442
  options = {}
251
443
  optparse = Morpheus::Cli::OptionParser.new do |opts|
252
444
  opts.banner = subcommand_usage("[app]")
253
- opts.on('--refresh [status]', String, "Refresh until status is reached. Default status is running.") do |val|
254
- if val.to_s.empty?
255
- options[:refresh_until_status] = "running,failed"
256
- else
257
- options[:refresh_until_status] = val.to_s.downcase
445
+ opts.on('--refresh [SECONDS]', String, "Refresh until status is running,failed. Default interval is 5 seconds.") do |val|
446
+ options[:refresh_until_status] ||= "running,failed"
447
+ if !val.to_s.empty?
448
+ options[:refresh_interval] = val.to_f
258
449
  end
259
450
  end
260
- opts.on('--refresh-interval seconds', String, "Refresh interval. Default is 5 seconds.") do |val|
261
- options[:refresh_interval] = val.to_f
451
+ opts.on('--refresh-until STATUS', String, "Refresh until a specified status is reached.") do |val|
452
+ options[:refresh_until_status] = val.to_s.downcase
262
453
  end
263
- build_common_options(opts, options, [:json, :dry_run])
454
+ build_common_options(opts, options, [:json, :yaml, :csv, :fields, :outfile, :dry_run, :remote])
264
455
  opts.footer = "Get details about an app.\n" +
265
- "[app] is required. This is the name or id of an app."
456
+ "[app] is required. This is the name or id of an app. Supports 1-N [app] arguments."
266
457
  end
267
458
  optparse.parse!(args)
268
- if args.count != 1
459
+ if args.count < 1
269
460
  print_error Morpheus::Terminal.angry_prompt
270
- puts_error "#{command_name} get expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
461
+ puts_error "#{command_name} get expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
271
462
  return 1
272
463
  end
464
+
273
465
  connect(options)
466
+
467
+ id_list = parse_id_list(args)
468
+ return run_command_for_each_arg(id_list) do |arg|
469
+ _get(arg, options)
470
+ end
471
+
472
+ end
473
+
474
+ def _get(arg, options={})
274
475
  begin
275
- app = find_app_by_name_or_id(args[0])
476
+ app = find_app_by_name_or_id(arg)
276
477
  if options[:dry_run]
277
478
  print_dry_run @apps_interface.dry.get(app['id'])
278
479
  return
279
480
  end
280
481
  json_response = @apps_interface.get(app['id'])
482
+
483
+ render_result = render_with_format(json_response, options, 'blueprint')
484
+ return 0 if render_result
485
+
281
486
  app = json_response['app']
282
- if options[:json]
283
- print JSON.pretty_generate(json_response)
284
- return
285
- end
286
487
  print_h1 "App Details"
287
488
  print cyan
288
489
  description_cols = {
289
490
  "ID" => 'id',
290
491
  "Name" => 'name',
291
492
  "Description" => 'description',
292
- # "Group" => lambda {|it| it['group'] ? it['group']['name'] : it['siteId'] },
493
+ "Blueprint" => lambda {|it| it['blueprint'] ? it['blueprint']['name'] : '' },
494
+ "Group" => lambda {|it| it['group'] ? it['group']['name'] : it['siteId'] },
293
495
  "Account" => lambda {|it| it['account'] ? it['account']['name'] : '' },
294
496
  "Status" => lambda {|it| format_app_status(it) }
295
497
  }
498
+ if app['blueprint'].nil?
499
+ description_cols.delete("Blueprint")
500
+ end
501
+ # if app['description'].nil?
502
+ # description_cols.delete("Description")
503
+ # end
296
504
  print_description_list(description_cols, app)
297
505
 
298
506
  stats = app['stats']
@@ -306,7 +514,7 @@ class Morpheus::Cli::Apps
306
514
  puts yellow, "This app is empty", reset
307
515
  else
308
516
  app_tiers.each do |app_tier|
309
- print_h2 "Tier: #{app_tier['tier']['name']}\n"
517
+ print_h2 "Tier: #{app_tier['tier']['name']}"
310
518
  print cyan
311
519
  instances = (app_tier['appInstances'] || []).collect {|it| it['instance']}
312
520
  if instances.empty?
@@ -317,6 +525,8 @@ class Morpheus::Cli::Apps
317
525
  status_string = instance['status'].to_s
318
526
  if status_string == 'running'
319
527
  status_string = "#{green}#{status_string.upcase}#{cyan}"
528
+ elsif status_string == 'provisioning'
529
+ status_string = "#{cyan}#{status_string.upcase}#{cyan}"
320
530
  elsif status_string == 'stopped' or status_string == 'failed'
321
531
  status_string = "#{red}#{status_string.upcase}#{cyan}"
322
532
  elsif status_string == 'unknown'
@@ -334,12 +544,12 @@ class Morpheus::Cli::Apps
334
544
  print cyan
335
545
  print as_pretty_table(instances_rows, [:id, :name, :cloud, :type, :environment, :nodes, :connection, :status])
336
546
  print reset
547
+ print "\n"
337
548
  end
338
549
  end
339
550
  end
340
551
  print cyan
341
552
 
342
- print reset,"\n"
343
553
 
344
554
  # refresh until a status is reached
345
555
  if options[:refresh_until_status]
@@ -348,12 +558,10 @@ class Morpheus::Cli::Apps
348
558
  end
349
559
  statuses = options[:refresh_until_status].to_s.downcase.split(",").collect {|s| s.strip }.select {|s| !s.to_s.empty? }
350
560
  if !statuses.include?(app['status'])
351
- print cyan
352
- print "Status is #{app['status'] || 'unknown'}. Refreshing in #{options[:refresh_interval]} seconds"
353
- #sleep(options[:refresh_interval])
561
+ print cyan, "Refreshing in #{options[:refresh_interval]} seconds"
354
562
  sleep_with_dots(options[:refresh_interval])
355
563
  print "\n"
356
- get(args)
564
+ _get(arg, options)
357
565
  end
358
566
  end
359
567
 
@@ -367,39 +575,70 @@ class Morpheus::Cli::Apps
367
575
  options = {}
368
576
  optparse = Morpheus::Cli::OptionParser.new do |opts|
369
577
  opts.banner = subcommand_usage("[app] [options]")
370
- build_option_type_options(opts, options, update_app_option_types(false))
371
- build_common_options(opts, options, [:options, :json, :dry_run])
578
+ #build_option_type_options(opts, options, update_app_option_types(false))
579
+ opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
580
+ options[:group] = val
581
+ end
582
+ opts.on( '--name VALUE', String, "Name" ) do |val|
583
+ options[:name] = val
584
+ end
585
+ opts.on( '--description VALUE', String, "Description" ) do |val|
586
+ options[:description] = val
587
+ end
588
+ opts.on( '--environment VALUE', String, "Environment" ) do |val|
589
+ options[:environment] = val
590
+ end
591
+ build_common_options(opts, options, [:options, :payload, :json, :dry_run])
372
592
  opts.footer = "Update an app.\n" +
373
593
  "[app] is required. This is the name or id of an app."
374
594
  end
375
595
  optparse.parse!(args)
376
596
  if args.count != 1
377
597
  print_error Morpheus::Terminal.angry_prompt
378
- puts_error "#{command_name} update expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
598
+ puts_error "#{command_name} update expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
379
599
  return 1
380
600
  end
381
601
  connect(options)
382
602
 
383
603
  begin
384
604
  app = find_app_by_name_or_id(args[0])
605
+ return 1 if app.nil?
385
606
 
386
- payload = {
387
- 'app' => {id: app["id"]}
388
- }
389
-
390
- params = options[:options] || {}
391
-
392
- if params.empty?
393
- print_red_alert "Specify atleast one option to update"
394
- puts optparse
395
- exit 1
607
+ payload = {}
608
+ if options[:payload]
609
+ payload = options[:payload]
610
+ else
611
+ payload = {
612
+ 'app' => {id: app["id"]}
613
+ }
614
+ params = options[:options] || {}
615
+ if options[:name]
616
+ params['name'] = options[:name]
617
+ end
618
+ if options[:description]
619
+ params['description'] = options[:description]
620
+ end
621
+ if options[:environment]
622
+ # params['environment'] = options[:environment]
623
+ params['appContext'] = options[:environment]
624
+ end
625
+ if options[:group]
626
+ group = find_group_by_name_or_id_for_provisioning(options[:group])
627
+ return 1 if group.nil?
628
+ params['group'] = {'id' => group['id'], 'name' => group['name']}
629
+ end
630
+ if params.empty?
631
+ print_red_alert "Specify atleast one option to update"
632
+ puts optparse
633
+ return 1
634
+ end
635
+ payload['app'].merge!(params)
636
+ # api bug requires this to be at the root level as well right now
637
+ if payload['app'] && payload['app']['group']
638
+ payload['group'] = payload['app']['group']
639
+ end
396
640
  end
397
641
 
398
- #puts "parsed params is : #{params.inspect}"
399
- app_keys = ['name', 'description', 'environment']
400
- params = params.select {|k,v| app_keys.include?(k) }
401
- payload['app'].merge!(params)
402
-
403
642
  if options[:dry_run]
404
643
  print_dry_run @apps_interface.dry.update(app["id"], payload)
405
644
  return
@@ -436,7 +675,7 @@ class Morpheus::Cli::Apps
436
675
  optparse.parse!(args)
437
676
  if args.count < 1 || args.count > 3
438
677
  print_error Morpheus::Terminal.angry_prompt
439
- puts_error "#{command_name} add-instance expects 1-3 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
678
+ puts_error "#{command_name} add-instance expects 1-3 arguments and received #{args.count}: #{args}\n#{optparse}"
440
679
  return 1
441
680
  end
442
681
  # optional [tier] and [instance] arguments
@@ -495,16 +734,16 @@ class Morpheus::Cli::Apps
495
734
  opts.banner = subcommand_usage("[app]")
496
735
  #JD: UI defaults to on, but perhaps better to be explicate for now.
497
736
  opts.on('--remove-instances [on|off]', ['on','off'], "Remove instances. Default is off.") do |val|
498
- query_params[:removeInstances] = val
737
+ query_params[:removeInstances] = val.nil? ? 'on' : val
499
738
  end
500
739
  opts.on('--preserve-volumes [on|off]', ['on','off'], "Preserve Volumes. Default is off. Applies to certain types only.") do |val|
501
- query_params[:preserveVolumes] = val
740
+ query_params[:preserveVolumes] = val.nil? ? 'on' : val
502
741
  end
503
742
  opts.on( '-B', '--keep-backups', "Preserve copy of backups" ) do
504
743
  query_params[:keepBackups] = 'on'
505
744
  end
506
- opts.on('--releaseEIPs', ['on','off'], "Release EIPs. Default is on. Applies to Amazon only.") do |val|
507
- query_params[:releaseEIPs] = val
745
+ opts.on('--releaseEIPs [on|off]', ['on','off'], "Release EIPs. Default is on. Applies to Amazon only.") do |val|
746
+ query_params[:releaseEIPs] = val.nil? ? 'on' : val
508
747
  end
509
748
  opts.on( '-f', '--force', "Force Delete" ) do
510
749
  query_params[:force] = 'on'
@@ -516,7 +755,7 @@ class Morpheus::Cli::Apps
516
755
  optparse.parse!(args)
517
756
  if args.count != 1
518
757
  print_error Morpheus::Terminal.angry_prompt
519
- puts_error "#{command_name} remove expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
758
+ puts_error "#{command_name} remove expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
520
759
  return 1
521
760
  end
522
761
  connect(options)
@@ -560,7 +799,7 @@ class Morpheus::Cli::Apps
560
799
  optparse.parse!(args)
561
800
  if args.count < 1 || args.count > 2
562
801
  print_error Morpheus::Terminal.angry_prompt
563
- puts_error "#{command_name} remove-instance expects 1-2 arguments and received #{args.count}: #{args.join(' ')}\n#{optparse}"
802
+ puts_error "#{command_name} remove-instance expects 1-2 arguments and received #{args.count}: #{args}\n#{optparse}"
564
803
  return 1
565
804
  end
566
805
  # optional [tier] and [instance] arguments
@@ -615,7 +854,7 @@ class Morpheus::Cli::Apps
615
854
  optparse.parse!(args)
616
855
  if args.count !=1
617
856
  print_error Morpheus::Terminal.angry_prompt
618
- puts_error "#{command_name} logs expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
857
+ puts_error "#{command_name} logs expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
619
858
  return 1
620
859
  end
621
860
  connect(options)
@@ -673,88 +912,137 @@ class Morpheus::Cli::Apps
673
912
  end
674
913
  end
675
914
 
676
- =begin
677
915
  def stop(args)
678
916
  options = {}
679
917
  optparse = Morpheus::Cli::OptionParser.new do |opts|
680
918
  opts.banner = subcommand_usage("[app]")
681
- build_common_options(opts, options, [:json, :dry_run])
919
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
920
+ opts.footer = "Stop an app.\n" +
921
+ "[app] is required. This is the name or id of an app. Supports 1-N [app] arguments."
682
922
  end
683
923
  optparse.parse!(args)
684
- if args.count != 1
685
- print_error Morpheus::Terminal.angry_prompt
686
- puts_error "#{command_name} stop expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
924
+ if args.count < 1
925
+ puts_error "[id] argument is required"
926
+ puts_error optparse
687
927
  return 1
688
928
  end
689
929
  connect(options)
690
- begin
691
- app = find_app_by_name_or_id(args[0])
692
- if options[:dry_run]
693
- print_dry_run @apps_interface.dry.stop(app['id'])
694
- return
930
+ id_list = parse_id_list(args)
931
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to stop #{id_list.size == 1 ? 'app' : 'apps'} #{anded_list(id_list)}?", options)
932
+ return 9, "aborted command"
933
+ end
934
+ return run_command_for_each_arg(id_list) do |arg|
935
+ _stop(arg, options)
936
+ end
937
+ end
938
+
939
+ def _stop(app_id, options)
940
+ app = find_app_by_name_or_id(app_id)
941
+ return 1 if app.nil?
942
+ tier_records = extract_app_tiers(app)
943
+ if options[:dry_run]
944
+ print_h1 "Dry Run"
945
+ end
946
+ tier_records.each do |tier_record|
947
+ tier_record[:instances].each do |instance|
948
+ stop_cmd = "instances stop #{instance['id']} -y"
949
+ if options[:dry_run]
950
+ puts stop_cmd
951
+ else
952
+ my_terminal.execute(stop_cmd)
953
+ end
695
954
  end
696
- @apps_interface.stop(app['id'])
697
- list([])
698
- rescue RestClient::Exception => e
699
- print_rest_exception(e, options)
700
- exit 1
701
955
  end
956
+ return 0
702
957
  end
703
958
 
704
959
  def start(args)
705
960
  options = {}
706
961
  optparse = Morpheus::Cli::OptionParser.new do |opts|
707
962
  opts.banner = subcommand_usage("[app]")
708
- build_common_options(opts, options, [:json, :dry_run])
963
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
964
+ opts.footer = "Start an app.\n" +
965
+ "[app] is required. This is the name or id of an app. Supports 1-N [app] arguments."
709
966
  end
710
967
  optparse.parse!(args)
711
- if args.count != 1
712
- print_error Morpheus::Terminal.angry_prompt
713
- puts_error "#{command_name} start expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
968
+ if args.count < 1
969
+ puts_error "[id] argument is required"
970
+ puts_error optparse
714
971
  return 1
715
972
  end
716
973
  connect(options)
717
- begin
718
- app = find_app_by_name_or_id(args[0])
719
- if options[:dry_run]
720
- print_dry_run @apps_interface.dry.start(app['id'])
721
- return
974
+ id_list = parse_id_list(args)
975
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to start #{id_list.size == 1 ? 'app' : 'apps'} #{anded_list(id_list)}?", options)
976
+ return 9, "aborted command"
977
+ end
978
+ return run_command_for_each_arg(id_list) do |arg|
979
+ _start(arg, options)
980
+ end
981
+ end
982
+
983
+ def _start(app_id, options)
984
+ app = find_app_by_name_or_id(app_id)
985
+ return 1 if app.nil?
986
+ tier_records = extract_app_tiers(app)
987
+ if options[:dry_run]
988
+ print_h1 "Dry Run"
989
+ end
990
+ tier_records.each do |tier_record|
991
+ tier_record[:instances].each do |instance|
992
+ start_cmd = "instances start #{instance['id']} -y"
993
+ if options[:dry_run]
994
+ puts start_cmd
995
+ else
996
+ my_terminal.execute(start_cmd)
997
+ end
722
998
  end
723
- @apps_interface.start(app['id'])
724
- list([])
725
- rescue RestClient::Exception => e
726
- print_rest_exception(e, options)
727
- exit 1
728
999
  end
1000
+ return 0
729
1001
  end
730
1002
 
731
1003
  def restart(args)
732
1004
  options = {}
733
1005
  optparse = Morpheus::Cli::OptionParser.new do |opts|
734
1006
  opts.banner = subcommand_usage("[app]")
735
- build_common_options(opts, options, [:json, :dry_run])
1007
+ build_common_options(opts, options, [:auto_confirm, :json, :dry_run, :quiet, :remote])
1008
+ opts.footer = "Restart an app.\n" +
1009
+ "[app] is required. This is the name or id of an app. Supports 1-N [app] arguments."
736
1010
  end
737
1011
  optparse.parse!(args)
738
- if args.count != 1
739
- print_error Morpheus::Terminal.angry_prompt
740
- puts_error "#{command_name} restart expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1012
+ if args.count < 1
1013
+ puts_error "[id] argument is required"
1014
+ puts_error optparse
741
1015
  return 1
742
1016
  end
743
1017
  connect(options)
744
- begin
745
- app = find_app_by_name_or_id(args[0])
746
- if options[:dry_run]
747
- print_dry_run @apps_interface.dry.restart(app['id'])
748
- return
1018
+ id_list = parse_id_list(args)
1019
+ unless options[:yes] || ::Morpheus::Cli::OptionTypes::confirm("Are you sure you would like to restart #{id_list.size == 1 ? 'app' : 'apps'} #{anded_list(id_list)}?", options)
1020
+ return 9, "aborted command"
1021
+ end
1022
+ return run_command_for_each_arg(id_list) do |arg|
1023
+ _restart(arg, options)
1024
+ end
1025
+ end
1026
+
1027
+ def _restart(app_id, options)
1028
+ app = find_app_by_name_or_id(app_id)
1029
+ return 1 if app.nil?
1030
+ tier_records = extract_app_tiers(app)
1031
+ if options[:dry_run]
1032
+ print_h1 "Dry Run"
1033
+ end
1034
+ tier_records.each do |tier_record|
1035
+ tier_record[:instances].each do |instance|
1036
+ restart_cmd = "instances restart #{instance['id']} -y"
1037
+ if options[:dry_run]
1038
+ puts restart_cmd
1039
+ else
1040
+ my_terminal.execute(restart_cmd)
1041
+ end
749
1042
  end
750
- @apps_interface.restart(app['id'])
751
- list([])
752
- rescue RestClient::Exception => e
753
- print_rest_exception(e, options)
754
- exit 1
755
1043
  end
1044
+ return 0
756
1045
  end
757
- =end
758
1046
 
759
1047
  def firewall_disable(args)
760
1048
  options = {}
@@ -765,7 +1053,7 @@ class Morpheus::Cli::Apps
765
1053
  optparse.parse!(args)
766
1054
  if args.count != 1
767
1055
  print_error Morpheus::Terminal.angry_prompt
768
- puts_error "#{command_name} firewall-disable expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1056
+ puts_error "#{command_name} firewall-disable expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
769
1057
  return 1
770
1058
  end
771
1059
  connect(options)
@@ -793,7 +1081,7 @@ class Morpheus::Cli::Apps
793
1081
  optparse.parse!(args)
794
1082
  if args.count != 1
795
1083
  print_error Morpheus::Terminal.angry_prompt
796
- puts_error "#{command_name} firewall-enable expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1084
+ puts_error "#{command_name} firewall-enable expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
797
1085
  return 1
798
1086
  end
799
1087
  connect(options)
@@ -821,7 +1109,7 @@ class Morpheus::Cli::Apps
821
1109
  optparse.parse!(args)
822
1110
  if args.count != 1
823
1111
  print_error Morpheus::Terminal.angry_prompt
824
- puts_error "#{command_name} security-groups expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1112
+ puts_error "#{command_name} security-groups expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
825
1113
  return 1
826
1114
  end
827
1115
  connect(options)
@@ -875,7 +1163,7 @@ class Morpheus::Cli::Apps
875
1163
  optparse.parse!(args)
876
1164
  if args.count != 1
877
1165
  print_error Morpheus::Terminal.angry_prompt
878
- puts_error "#{command_name} apply-security-groups expects 1 argument and received #{args.count}: #{args.join(' ')}\n#{optparse}"
1166
+ puts_error "#{command_name} apply-security-groups expects 1 argument and received #{args.count}: #{args}\n#{optparse}"
879
1167
  return 1
880
1168
  end
881
1169
  if !clear_or_secgroups_specified
@@ -1049,22 +1337,41 @@ class Morpheus::Cli::Apps
1049
1337
 
1050
1338
  private
1051
1339
 
1052
- def add_app_option_types(connected=true)
1053
- [
1054
- {'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'selectOptions' => (connected ? get_available_blueprints() : []), 'required' => true, 'defaultValue' => 'existing', 'description' => "The blueprint to use. The default value is 'existing' which means no template, for creating a blank app and adding existing instances."},
1055
- {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this app'},
1056
- {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false},
1057
- {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => (connected ? get_available_groups() : []), 'required' => true},
1058
- {'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'text', 'required' => false},
1059
- ]
1340
+ def extract_app_tiers(app)
1341
+ tier_rows = []
1342
+ begin
1343
+ app_tiers = app['appTiers'] || []
1344
+ sorted_app_tiers = app_tiers.sort {|a,b| a['bootSequence'] <=> b['bootSequence'] }
1345
+ sorted_app_tiers.each do |app_tier|
1346
+ tier_name = app_tier['tier']['name']
1347
+ boot_sequence = app_tier['bootSequence'] || 0
1348
+ instances = (app_tier['appInstances'] || []).collect {|it| it['instance']}
1349
+ row = {tier_name: tier_name, boot_sequence: boot_sequence, instances: instances}
1350
+ tier_rows << row
1351
+ end
1352
+ rescue => ex
1353
+ Morpheus::Logging::DarkPrinter.puts "Error extracting app instances: #{ex}" if Morpheus::Logging.debug?
1354
+ end
1355
+ return tier_rows
1060
1356
  end
1061
1357
 
1062
- def update_app_option_types(connected=true)
1063
- list = add_app_option_types(connected)
1064
- list = list.reject {|it| ["blueprint", "group"].include? it['fieldName'] }
1065
- list.each {|it| it['required'] = false }
1066
- list
1067
- end
1358
+ # def add_app_option_types(connected=true)
1359
+ # [
1360
+ # {'fieldName' => 'blueprint', 'fieldLabel' => 'Blueprint', 'type' => 'select', 'selectOptions' => (connected ? get_available_blueprints() : []), 'required' => true, 'defaultValue' => 'existing', 'description' => "The blueprint to use. The default value is 'existing' which means no template, for creating a blank app and adding existing instances."},
1361
+ # {'fieldName' => 'name', 'fieldLabel' => 'Name', 'type' => 'text', 'required' => true, 'description' => 'Enter a name for this app'},
1362
+ # {'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'required' => false},
1363
+ # {'fieldName' => 'group', 'fieldLabel' => 'Group', 'type' => 'select', 'selectOptions' => (connected ? get_available_groups() : []), 'required' => false},
1364
+ # {'fieldName' => 'cloud', 'fieldLabel' => 'Default Cloud', 'type' => 'select', 'selectOptions' => [], 'required' => true},
1365
+ # {'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'text', 'required' => false},
1366
+ # ]
1367
+ # end
1368
+
1369
+ # def update_app_option_types(connected=true)
1370
+ # list = add_app_option_types(connected)
1371
+ # list = list.reject {|it| ["blueprint", "group"].include? it['fieldName'] }
1372
+ # list.each {|it| it['required'] = false }
1373
+ # list
1374
+ # end
1068
1375
 
1069
1376
  def find_app_by_id(id)
1070
1377
  app_results = @apps_interface.get(id.to_i)
@@ -1107,6 +1414,7 @@ class Morpheus::Cli::Apps
1107
1414
  {
1108
1415
  id: app['id'],
1109
1416
  name: app['name'],
1417
+ group: app['group'] ? app['group']['name'] : app['siteId'],
1110
1418
  tiers: tiers_str,
1111
1419
  instances: instances_str,
1112
1420
  containers: containers_str,
@@ -1122,6 +1430,7 @@ class Morpheus::Cli::Apps
1122
1430
  columns = [
1123
1431
  :id,
1124
1432
  :name,
1433
+ :group,
1125
1434
  :tiers,
1126
1435
  :instances,
1127
1436
  :containers,
@@ -1154,6 +1463,8 @@ class Morpheus::Cli::Apps
1154
1463
  out << "#{white}EMPTY#{return_color}"
1155
1464
  elsif status_string == 'running'
1156
1465
  out << "#{green}#{status_string.upcase}#{return_color}"
1466
+ elsif status_string == 'provisioning'
1467
+ status_string = "#{cyan}#{status_string.upcase}#{cyan}"
1157
1468
  elsif status_string == 'stopped' or status_string == 'failed'
1158
1469
  out << "#{red}#{status_string.upcase}#{return_color}"
1159
1470
  elsif status_string == 'unknown'
@@ -1188,7 +1499,7 @@ class Morpheus::Cli::Apps
1188
1499
  @available_blueprints = results['data'].collect {|it|
1189
1500
  {"id" => it["value"], "name" => it["name"], "value" => it["value"]}
1190
1501
  }
1191
- default_option = {"id" => "existing", "name" => "Existing Instances", "value" => "existing"}
1502
+ default_option = {"id" => "existing", "name" => "Existing Instances", "value" => "existing", "type" => "morpheus"}
1192
1503
  @available_blueprints.unshift(default_option)
1193
1504
  end
1194
1505
  #puts "get_available_blueprints() rtn: #{@available_blueprints.inspect}"
@@ -1213,4 +1524,82 @@ class Morpheus::Cli::Apps
1213
1524
  return @available_environments
1214
1525
  end
1215
1526
 
1527
+ def find_blueprint_by_name_or_id(val)
1528
+ if val.to_s =~ /\A\d{1,}\Z/
1529
+ return find_blueprint_by_id(val)
1530
+ else
1531
+ return find_blueprint_by_name(val)
1532
+ end
1533
+ end
1534
+
1535
+ def find_blueprint_by_id(id)
1536
+ begin
1537
+ json_response = @blueprints_interface.get(id.to_i)
1538
+ return json_response['blueprint']
1539
+ rescue RestClient::Exception => e
1540
+ if e.response && e.response.code == 404
1541
+ print_red_alert "Blueprint not found by id #{id}"
1542
+ else
1543
+ raise e
1544
+ end
1545
+ end
1546
+ end
1547
+
1548
+ def find_blueprint_by_name(name)
1549
+ blueprints = @blueprints_interface.list({name: name.to_s})['blueprints']
1550
+ if blueprints.empty?
1551
+ print_red_alert "Blueprint not found by name #{name}"
1552
+ return nil
1553
+ elsif blueprints.size > 1
1554
+ print_red_alert "#{blueprints.size} blueprints found by name #{name}"
1555
+ # print_blueprints_table(blueprints, {color: red})
1556
+ rows = blueprints.collect { |it| {id: it['id'], name: it['name']} }
1557
+ print red
1558
+ print as_pretty_table(rows, [:id, :name], {color:red})
1559
+ print reset,"\n"
1560
+ return nil
1561
+ else
1562
+ return blueprints[0]
1563
+ end
1564
+ end
1565
+
1566
+ # lookup scoped instance config in a blueprint
1567
+ # this only finds one right now
1568
+ # def tmplCfg = getConfigMap(appTemplateConfig?.tiers?.getAt(tierName)?.instances?.getAt(index), opts.environment, opts.group, instanceOpts.instance.cloud?: opts?.defaultCloud?.name)
1569
+ def get_scoped_instance_config(instance_config, env_name, group_name, cloud_name)
1570
+ config = instance_config.clone
1571
+ if env_name && config['environments'] && config['environments'][env_name]
1572
+ config = config['environments'][env_name].clone
1573
+ end
1574
+ if group_name && config['groups'] && config['groups'][group_name]
1575
+ config = config['groups'][group_name].clone
1576
+ end
1577
+ if cloud_name && config['clouds'] && config['clouds'][cloud_name]
1578
+ config = config['clouds'][cloud_name].clone
1579
+ end
1580
+ config.delete('environments')
1581
+ config.delete('groups')
1582
+ config.delete('clouds')
1583
+ return config
1584
+ end
1585
+
1586
+ # def getConfigMap(instance, env, group, cloud) {
1587
+ # def configMap = instance
1588
+ # if(env && instance?.environments) {
1589
+ # def envName = (env instanceof String? env : env?.name)
1590
+ # configMap = instance?.environments?.getAt(envName) ?: instance
1591
+ # }
1592
+ # if(group && configMap?.groups) {
1593
+ # if (group instanceof String) {
1594
+ # configMap = configMap?.groups?.getAt(group) ?: configMap
1595
+ # }
1596
+ # else {
1597
+ # configMap = configMap?.groups?.getAt(group?.name) ?: configMap
1598
+ # }
1599
+ # }
1600
+ # if(cloud && configMap?.clouds) {
1601
+ # return configMap?.clouds?.getAt(cloud) ?: configMap
1602
+ # }
1603
+ # return configMap
1604
+ # }
1216
1605
  end