morpheus-cli 3.6.3 → 3.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|