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 +4 -4
- data/lib/morpheus/cli/apps.rb +605 -216
- data/lib/morpheus/cli/blueprints_command.rb +139 -187
- data/lib/morpheus/cli/cli_command.rb +106 -6
- data/lib/morpheus/cli/cloud_datastores_command.rb +1 -1
- data/lib/morpheus/cli/commands/standard/cat_command.rb +45 -0
- data/lib/morpheus/cli/containers_command.rb +7 -9
- data/lib/morpheus/cli/echo_command.rb +3 -1
- data/lib/morpheus/cli/execution_request_command.rb +4 -4
- data/lib/morpheus/cli/expression_parser.rb +4 -0
- data/lib/morpheus/cli/file_copy_request_command.rb +4 -4
- data/lib/morpheus/cli/hosts.rb +11 -13
- data/lib/morpheus/cli/instances.rb +109 -41
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +115 -79
- data/lib/morpheus/cli/monitoring_checks_command.rb +2 -2
- data/lib/morpheus/cli/option_types.rb +30 -3
- data/lib/morpheus/cli/remote.rb +8 -4
- data/lib/morpheus/cli/shell.rb +4 -3
- data/lib/morpheus/cli/source_command.rb +1 -0
- data/lib/morpheus/cli/storage_providers_command.rb +10 -0
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +3 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ebb3ffe2cbabe89c6f9ba8148cf1e2d2a7bfd51893a71f69acf05c406013454d
|
4
|
+
data.tar.gz: 298427126d4530f189b3ffbc09a9c06baeaf91018412a9b5633dda1ed76e5571
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0bcc8b841e8a23e6c90e81c724dcc5fecd967eee7294c97069708f849db2e98880c2f3832188a5740c4dd3aa81267682b7cd5b8bd84af0d229df8807ad6f9c33
|
7
|
+
data.tar.gz: 6b94513b1484b9caa2d04ced8c24f143f5e41f4746e3bbeb92c074aa0c2dacaf9166f4b5d409c390d25ec11965fa58678d0d9568e6d4dd9b27c66e33aac3107c
|
data/lib/morpheus/cli/apps.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
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
|
-
#
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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('--
|
116
|
-
options[:
|
106
|
+
opts.on( '--name VALUE', String, "Name" ) do |val|
|
107
|
+
options[:name] = val
|
117
108
|
end
|
118
|
-
opts.on('--
|
119
|
-
options[:
|
109
|
+
opts.on( '--description VALUE', String, "Description" ) do |val|
|
110
|
+
options[:description] = val
|
120
111
|
end
|
121
|
-
opts.on('
|
122
|
-
options[:
|
112
|
+
opts.on( '-e', '--environment VALUE', "Environment Name" ) do |val|
|
113
|
+
options[:environment] = val.to_s == 'null' ? nil : val
|
123
114
|
end
|
124
|
-
|
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
|
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
|
-
|
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[:
|
147
|
-
|
148
|
-
payload =
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
-
|
157
|
-
|
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
|
-
|
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
|
-
|
162
|
-
|
163
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
356
|
+
print_dry_run @apps_interface.dry.validate(payload)
|
182
357
|
end
|
183
|
-
|
358
|
+
return 0
|
184
359
|
end
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
-
|
219
|
-
|
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
|
-
|
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 [
|
254
|
-
|
255
|
-
|
256
|
-
|
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-
|
261
|
-
options[:
|
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
|
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
|
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(
|
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
|
-
|
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']}
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
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
|
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
|
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
|
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
|
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
|
685
|
-
|
686
|
-
puts_error
|
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
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
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
|
712
|
-
|
713
|
-
puts_error
|
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
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
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
|
739
|
-
|
740
|
-
puts_error
|
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
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
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
|
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
|
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
|
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
|
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
|
1053
|
-
[
|
1054
|
-
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
|
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
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
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
|