morpheus-cli 3.6.3 → 3.6.4

Sign up to get free protection for your applications and to get access to all the features.
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