morpheus-cli 5.4.4.2 → 5.5.0
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/Dockerfile +1 -1
- data/lib/morpheus/api/apps_interface.rb +18 -4
- data/lib/morpheus/api/instances_interface.rb +16 -2
- data/lib/morpheus/api/prices_interface.rb +6 -0
- data/lib/morpheus/cli/cli_command.rb +16 -3
- data/lib/morpheus/cli/commands/apps.rb +366 -47
- data/lib/morpheus/cli/commands/clouds.rb +51 -27
- data/lib/morpheus/cli/commands/clusters.rb +70 -10
- data/lib/morpheus/cli/commands/cypher_command.rb +22 -23
- data/lib/morpheus/cli/commands/dashboard_command.rb +1 -1
- data/lib/morpheus/cli/commands/execution_request_command.rb +9 -4
- data/lib/morpheus/cli/commands/file_copy_request_command.rb +2 -1
- data/lib/morpheus/cli/commands/instances.rb +307 -24
- data/lib/morpheus/cli/commands/library_layouts_command.rb +15 -0
- data/lib/morpheus/cli/commands/library_option_lists_command.rb +18 -8
- data/lib/morpheus/cli/commands/prices_command.rb +25 -11
- data/lib/morpheus/cli/commands/provisioning_licenses_command.rb +1 -1
- data/lib/morpheus/cli/commands/tasks.rb +45 -10
- data/lib/morpheus/cli/mixins/execution_request_helper.rb +50 -0
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +5 -4
- data/lib/morpheus/cli/option_types.rb +32 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +12 -0
- data/lib/morpheus/routes.rb +3 -5
- metadata +3 -2
@@ -373,8 +373,15 @@ class Morpheus::Cli::Clusters
|
|
373
373
|
opts.on( '--resource-name NAME', "Resource Name" ) do |val|
|
374
374
|
options[:resourceName] = val.to_s
|
375
375
|
end
|
376
|
-
opts.on('--tags LIST', String, "
|
377
|
-
options[:
|
376
|
+
opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
377
|
+
options[:metadata] = val
|
378
|
+
end
|
379
|
+
opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
380
|
+
options[:metadata] = val
|
381
|
+
end
|
382
|
+
opts.add_hidden_option('--metadata')
|
383
|
+
opts.on('--labels LIST', String, "Tags") do |val|
|
384
|
+
options[:labels] = val
|
378
385
|
end
|
379
386
|
opts.on( '-g', '--group GROUP', "Group Name or ID" ) do |val|
|
380
387
|
options[:group] = val
|
@@ -510,13 +517,13 @@ class Morpheus::Cli::Clusters
|
|
510
517
|
description = options[:description] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'type' => 'text', 'fieldLabel' => 'Description', 'required' => false, 'description' => 'Resource Description.'}],options[:options],@api_client,{})['description']
|
511
518
|
cluster_payload['description'] = description if description
|
512
519
|
|
513
|
-
|
520
|
+
labels = options[:labels]
|
514
521
|
|
515
|
-
if !
|
516
|
-
|
522
|
+
if !labels && !options[:no_prompt]
|
523
|
+
labels = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'type' => 'text', 'fieldLabel' => 'Resource Labels', 'required' => false, 'description' => 'Resource Labels.'}],options[:options],@api_client,{})['labels']
|
517
524
|
end
|
518
525
|
|
519
|
-
server_payload['
|
526
|
+
server_payload['labels'] = labels if labels
|
520
527
|
|
521
528
|
# Cloud / Zone
|
522
529
|
cloud_id = nil
|
@@ -595,7 +602,7 @@ class Morpheus::Cli::Clusters
|
|
595
602
|
|
596
603
|
# Multi-disk / prompt for volumes
|
597
604
|
if provision_type['hasVolumes']
|
598
|
-
volumes = options[:volumes] || prompt_volumes(service_plan, provision_type, options
|
605
|
+
volumes = options[:volumes] || prompt_volumes(service_plan, provision_type, options, @api_client, api_params)
|
599
606
|
if !volumes.empty?
|
600
607
|
server_payload['volumes'] = volumes
|
601
608
|
end
|
@@ -605,7 +612,13 @@ class Morpheus::Cli::Clusters
|
|
605
612
|
option_type_list =
|
606
613
|
((controller_type.nil? ? [] : controller_type['optionTypes'].reject { |type| !type['enabled'] || type['fieldComponent'] } rescue []) +
|
607
614
|
layout['optionTypes'] +
|
608
|
-
(cluster_type['optionTypes'].reject { |type|
|
615
|
+
(cluster_type['optionTypes'].reject { |type|
|
616
|
+
!type['enabled'] || !type['creatable'] || type['fieldComponent']
|
617
|
+
} rescue []))
|
618
|
+
|
619
|
+
# remove metadata option_type , prompt manually for that field 'tags' instead of 'metadata'
|
620
|
+
metadata_option_type = option_type_list.find {|type| type['fieldName'] == 'metadata' }
|
621
|
+
option_type_list = option_type_list.reject {|type| type['fieldName'] == 'metadata' }
|
609
622
|
|
610
623
|
# KLUDGE: google zone required for network selection
|
611
624
|
if option_type = option_type_list.find {|type| type['code'] == 'computeServerType.googleLinux.googleZoneId'}
|
@@ -633,10 +646,23 @@ class Morpheus::Cli::Clusters
|
|
633
646
|
# Server options
|
634
647
|
server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options].deep_merge({:context_map => {'domain' => ''}}), @api_client, api_params, options[:no_prompt], true))
|
635
648
|
|
649
|
+
# Metadata Tags
|
650
|
+
if metadata_option_type
|
651
|
+
if options[:metadata]
|
652
|
+
metadata = parse_metadata(options[:metadata])
|
653
|
+
server_payload['tags'] = metadata if !metadata.empty?
|
654
|
+
else
|
655
|
+
metadata = prompt_metadata(options)
|
656
|
+
server_payload['tags'] = metadata if !metadata.empty?
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
636
660
|
# Worker count
|
637
661
|
if !['manual', 'external'].include?(provision_type['code'])
|
638
662
|
default_node_count = layout['computeServers'] ? (layout['computeServers'].find {|it| it['nodeType'] == 'worker'} || {'nodeCount' => 3})['nodeCount'] : 3
|
639
|
-
|
663
|
+
nodeCount = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => "config.nodeCount", 'type' => 'number', 'fieldLabel' => "#{['docker-cluster', 'kvm-cluster'].include?(cluster_type['code']) ? 'Host' : 'Worker'} Count", 'required' => true, 'defaultValue' => default_node_count > 0 ? default_node_count : 3}], options[:options], @api_client, api_params, options[:no_prompt])['config']['nodeCount']
|
664
|
+
server_payload['config']['nodeCount'] = nodeCount
|
665
|
+
server_payload['nodeCount'] = nodeCount
|
640
666
|
end
|
641
667
|
|
642
668
|
# Create User
|
@@ -1092,6 +1118,16 @@ class Morpheus::Cli::Clusters
|
|
1092
1118
|
opts.on("--description [TEXT]", String, "Description") do |val|
|
1093
1119
|
options[:description] = val.to_s
|
1094
1120
|
end
|
1121
|
+
opts.on('--tags LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
1122
|
+
options[:metadata] = val
|
1123
|
+
end
|
1124
|
+
opts.on('--metadata LIST', String, "Metadata tags in the format 'ping=pong,flash=bang'") do |val|
|
1125
|
+
options[:metadata] = val
|
1126
|
+
end
|
1127
|
+
opts.add_hidden_option('--metadata')
|
1128
|
+
opts.on('--labels LIST', String, "Tags") do |val|
|
1129
|
+
options[:labels] = val
|
1130
|
+
end
|
1095
1131
|
add_server_options(opts, options)
|
1096
1132
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :remote])
|
1097
1133
|
opts.footer = "Add worker to a cluster.\n" +
|
@@ -1153,6 +1189,14 @@ class Morpheus::Cli::Clusters
|
|
1153
1189
|
# Description
|
1154
1190
|
server_payload['description'] = options[:description] || Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'description', 'fieldLabel' => 'Description', 'type' => 'text', 'description' => 'Worker Description'}], options[:options], @api_client)['description']
|
1155
1191
|
|
1192
|
+
# Labels
|
1193
|
+
labels = options[:labels]
|
1194
|
+
if !labels && !options[:no_prompt]
|
1195
|
+
labels = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'type' => 'text', 'fieldLabel' => 'Resource Labels', 'required' => false, 'description' => 'Resource Labels.'}],options[:options],@api_client,{})['labels']
|
1196
|
+
end
|
1197
|
+
|
1198
|
+
server_payload['labels'] = labels if labels
|
1199
|
+
|
1156
1200
|
# Cloud
|
1157
1201
|
available_clouds = options_interface.options_for_source('clouds', {groupId: cluster['site']['id'], clusterId: cluster['id'], ownerOnly: true})['data']
|
1158
1202
|
cloud_id = nil
|
@@ -1192,7 +1236,7 @@ class Morpheus::Cli::Clusters
|
|
1192
1236
|
end
|
1193
1237
|
|
1194
1238
|
# Multi-disk / prompt for volumes
|
1195
|
-
volumes = options[:volumes] || prompt_volumes(service_plan, provision_type, options
|
1239
|
+
volumes = options[:volumes] || prompt_volumes(service_plan, provision_type, options, @api_client, {zoneId: cloud['id'], siteId: group['id']})
|
1196
1240
|
|
1197
1241
|
if !volumes.empty?
|
1198
1242
|
server_payload['volumes'] = volumes
|
@@ -1218,8 +1262,24 @@ class Morpheus::Cli::Clusters
|
|
1218
1262
|
(type['fieldContext'] == 'instance.networkDomain' && type['fieldName'] == 'id')
|
1219
1263
|
} rescue [])
|
1220
1264
|
|
1265
|
+
# remove metadata option_type , prompt manually for that field 'tags' instead of 'metadata'
|
1266
|
+
#metadata_option_type = option_type_list.find {|type| type['fieldName'] == 'metadata' }
|
1267
|
+
metadata_option_type = cluster_type['optionTypes'].find {|type| type['fieldName'] == 'metadata' }
|
1268
|
+
option_type_list = option_type_list.reject {|type| type['fieldName'] == 'metadata' }
|
1269
|
+
|
1221
1270
|
server_payload.deep_merge!(Morpheus::Cli::OptionTypes.prompt(option_type_list, options[:options], @api_client, {zoneId: cloud['id'], siteId: group['id'], layoutId: layout['id']}))
|
1222
1271
|
|
1272
|
+
# Metadata Tags
|
1273
|
+
if metadata_option_type
|
1274
|
+
if options[:metadata]
|
1275
|
+
metadata = parse_metadata(options[:metadata])
|
1276
|
+
server_payload['tags'] = metadata if !metadata.empty?
|
1277
|
+
else
|
1278
|
+
metadata = prompt_metadata(options)
|
1279
|
+
server_payload['tags'] = metadata if !metadata.empty?
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
|
1223
1283
|
# Create User
|
1224
1284
|
if !options[:createUser].nil?
|
1225
1285
|
server_payload['config']['createUser'] = options[:createUser]
|
@@ -185,30 +185,29 @@ class Morpheus::Cli::CypherCommand
|
|
185
185
|
# This response does contain cypher too though.
|
186
186
|
|
187
187
|
if cypher_item.empty?
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
188
|
+
puts "Cypher item not found in response"
|
189
|
+
else
|
190
|
+
description_cols = {
|
191
|
+
#"ID" => 'id',
|
192
|
+
"Key" => lambda {|it| it["itemKey"] },
|
193
|
+
"TTL" => lambda {|it|
|
194
|
+
format_expiration_ttl(it["expireDate"])
|
195
|
+
},
|
196
|
+
# "Type" => lambda {|it|
|
197
|
+
# data_type
|
198
|
+
# },
|
199
|
+
"Expiration" => lambda {|it|
|
200
|
+
format_expiration_date(it["expireDate"])
|
201
|
+
},
|
202
|
+
# "Date Created" => lambda {|it| format_local_dt(it["dateCreated"]) },
|
203
|
+
"Last Updated" => lambda {|it| format_local_dt(it["lastUpdated"]) },
|
204
|
+
"Last Accessed" => lambda {|it| format_local_dt(it["lastAccessed"]) }
|
205
|
+
}
|
206
|
+
if cypher_item["expireDate"].nil?
|
207
|
+
description_cols.delete("Expires")
|
208
|
+
end
|
209
|
+
print_description_list(description_cols, cypher_item)
|
209
210
|
end
|
210
|
-
print_description_list(description_cols, cypher_item)
|
211
|
-
|
212
211
|
# print_h2 "Value", options
|
213
212
|
# print_h2 "Data", options
|
214
213
|
print_h2 "Data (#{data_type})", options
|
@@ -135,7 +135,7 @@ This includes instance and backup counts, favorite instances, monitoring and rec
|
|
135
135
|
print as_pretty_table([json_response], monitoring_column_definitions.upcase_keys!, options)
|
136
136
|
|
137
137
|
|
138
|
-
if json_response['logStats']
|
138
|
+
if json_response['logStats'] && json_response['logStats']['data']
|
139
139
|
# todo: should come from monitoring.startMs-endMs
|
140
140
|
log_period_display = "7 Days"
|
141
141
|
print_h2 "Logs (#{log_period_display})", options
|
@@ -83,12 +83,13 @@ class Morpheus::Cli::ExecutionRequestCommand
|
|
83
83
|
if options[:refresh_interval].nil? || options[:refresh_interval].to_f < 0
|
84
84
|
options[:refresh_interval] = default_refresh_interval
|
85
85
|
end
|
86
|
-
if execution_request['
|
86
|
+
if !(execution_request['status'] == 'pending' || execution_request['status'] == 'new')
|
87
87
|
# it is finished
|
88
88
|
else
|
89
89
|
print cyan
|
90
|
-
|
91
|
-
|
90
|
+
refresh_display_seconds = options[:refresh_interval] % 1.0 == 0 ? options[:refresh_interval].to_i : options[:refresh_interval]
|
91
|
+
print "Execution request has not yet finished. Refreshing every #{refresh_display_seconds} seconds"
|
92
|
+
while execution_request['status'] == 'pending' || execution_request['status'] == 'new' do
|
92
93
|
sleep(options[:refresh_interval])
|
93
94
|
print cyan,".",reset
|
94
95
|
json_response = @execution_request_interface.get(execution_request_id, params)
|
@@ -114,9 +115,13 @@ class Morpheus::Cli::ExecutionRequestCommand
|
|
114
115
|
#"Created By" => lambda {|it| it['createdById'] },
|
115
116
|
#"Subdomain" => lambda {|it| it['subdomain'] },
|
116
117
|
}
|
118
|
+
description_cols.delete("Server ID") if execution_request['serverId'].nil?
|
119
|
+
description_cols.delete("Instance ID") if execution_request['instanceId'].nil?
|
120
|
+
description_cols.delete("Container ID") if execution_request['containerId'].nil?
|
121
|
+
description_cols.delete("Exit Code") if execution_request['exitCode'].nil?
|
117
122
|
print_description_list(description_cols, execution_request)
|
118
123
|
|
119
|
-
if execution_request['stdErr'] && execution_request['stdErr'] != "stdin: is not a tty\n"
|
124
|
+
if execution_request['stdErr'].to_s.strip != '' && execution_request['stdErr'] != "stdin: is not a tty\n"
|
120
125
|
print_h2 "Error"
|
121
126
|
puts execution_request['stdErr'].to_s.strip
|
122
127
|
end
|
@@ -80,7 +80,8 @@ class Morpheus::Cli::FileCopyRequestCommand
|
|
80
80
|
# it is finished
|
81
81
|
else
|
82
82
|
print cyan
|
83
|
-
|
83
|
+
refresh_display_seconds = options[:refresh_interval] % 1.0 == 0 ? options[:refresh_interval].to_i : options[:refresh_interval]
|
84
|
+
print "File copy request has not yet finished. Refreshing every #{refresh_display_seconds} seconds"
|
84
85
|
while !['complete','failed','expired'].include?(file_copy_request['status']) do
|
85
86
|
sleep(options[:refresh_interval])
|
86
87
|
print cyan,".",reset
|
@@ -8,6 +8,7 @@ class Morpheus::Cli::Instances
|
|
8
8
|
include Morpheus::Cli::DeploymentsHelper
|
9
9
|
include Morpheus::Cli::ProcessesHelper
|
10
10
|
include Morpheus::Cli::LogsHelper
|
11
|
+
include Morpheus::Cli::ExecutionRequestHelper
|
11
12
|
|
12
13
|
set_command_name :instances
|
13
14
|
set_command_description "View and manage instances."
|
@@ -24,12 +25,15 @@ class Morpheus::Cli::Instances
|
|
24
25
|
:wiki, :update_wiki,
|
25
26
|
{:exec => :execution_request},
|
26
27
|
:deploys,
|
27
|
-
:refresh, :apply
|
28
|
+
:refresh, :prepare_apply, :apply, :state
|
28
29
|
#register_subcommands :firewall_disable, :firewall_enable
|
29
30
|
# register_subcommands {:'lb-update' => :load_balancer_update}
|
30
31
|
alias_subcommand :details, :get
|
31
32
|
set_default_subcommand :list
|
32
33
|
|
34
|
+
# hide these for now
|
35
|
+
set_subcommands_hidden :prepare_apply
|
36
|
+
|
33
37
|
def initialize()
|
34
38
|
#@appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
35
39
|
end
|
@@ -492,15 +496,15 @@ class Morpheus::Cli::Instances
|
|
492
496
|
payload["zoneId"] = cloud["id"]
|
493
497
|
payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
|
494
498
|
end
|
495
|
-
if options[:cloud]
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
end
|
499
|
+
# if options[:cloud]
|
500
|
+
# group_id = group ? group["id"] : ((payload["instance"] && payload["instance"]["site"].is_a?(Hash)) ? payload["instance"]["site"]["id"] : nil)
|
501
|
+
# cloud = find_cloud_by_name_or_id_for_provisioning(group_id, options[:cloud])
|
502
|
+
# if cloud.nil?
|
503
|
+
# return 1, "cloud not found by #{options[:cloud]}"
|
504
|
+
# end
|
505
|
+
# payload["zoneId"] = cloud["id"]
|
506
|
+
# payload.deep_merge!({"instance" => {"cloud" => cloud["name"] } })
|
507
|
+
# end
|
504
508
|
if options[:instance_type_code]
|
505
509
|
# should just use find_instance_type_by_name_or_id
|
506
510
|
# note that the api actually will match name name or code
|
@@ -535,7 +539,7 @@ class Morpheus::Cli::Instances
|
|
535
539
|
end
|
536
540
|
end
|
537
541
|
end
|
538
|
-
|
542
|
+
|
539
543
|
payload['instance'] ||= {}
|
540
544
|
if options[:instance_name]
|
541
545
|
payload['instance']['name'] = options[:instance_name]
|
@@ -4549,19 +4553,22 @@ EOT
|
|
4549
4553
|
end
|
4550
4554
|
end
|
4551
4555
|
|
4552
|
-
def
|
4556
|
+
def prepare_apply(args)
|
4553
4557
|
params, payload, options = {}, {}, {}
|
4554
4558
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
4555
4559
|
opts.banner = subcommand_usage("[instance] [options]")
|
4556
4560
|
build_standard_update_options(opts, options, [:auto_confirm])
|
4557
4561
|
opts.footer = <<-EOT
|
4558
|
-
|
4562
|
+
Prepare to apply an instance.
|
4559
4563
|
[instance] is required. This is the name or id of an instance.
|
4564
|
+
Displays the current configuration data used by the apply command.
|
4560
4565
|
This is only supported by certain types of instances such as terraform.
|
4561
4566
|
EOT
|
4562
4567
|
end
|
4563
4568
|
optparse.parse!(args)
|
4564
|
-
|
4569
|
+
if args.count != 1
|
4570
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
4571
|
+
end
|
4565
4572
|
connect(options)
|
4566
4573
|
|
4567
4574
|
begin
|
@@ -4577,10 +4584,131 @@ EOT
|
|
4577
4584
|
payload.deep_merge!(parse_passed_options(options))
|
4578
4585
|
# raise_command_error "Specify at least one option to update.\n#{optparse}" if payload.empty?
|
4579
4586
|
end
|
4580
|
-
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to apply this instance: #{instance['name']}?")
|
4581
|
-
return 9, "aborted command"
|
4582
|
-
end
|
4583
4587
|
@instances_interface.setopts(options)
|
4588
|
+
if options[:dry_run]
|
4589
|
+
print_dry_run @instances_interface.dry.prepare_apply(instance["id"], params)
|
4590
|
+
return
|
4591
|
+
end
|
4592
|
+
json_response = @instances_interface.prepare_apply(instance["id"], params)
|
4593
|
+
render_result = render_with_format(json_response, options)
|
4594
|
+
return 0 if render_result
|
4595
|
+
# print_green_success "Prepared to apply instance: #{instance['name']}"
|
4596
|
+
print_h1 "Prepared Instance: #{instance['name']}"
|
4597
|
+
instance_config = json_response['data']
|
4598
|
+
# instance_config = json_response if instance_config.nil?
|
4599
|
+
puts as_yaml(instance_config, options)
|
4600
|
+
#return get([app['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
4601
|
+
print "\n", reset
|
4602
|
+
return 0
|
4603
|
+
rescue RestClient::Exception => e
|
4604
|
+
print_rest_exception(e, options)
|
4605
|
+
exit 1
|
4606
|
+
end
|
4607
|
+
end
|
4608
|
+
|
4609
|
+
def apply(args)
|
4610
|
+
default_refresh_interval = 15
|
4611
|
+
params, payload, options = {}, {}, {}
|
4612
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
4613
|
+
opts.banner = subcommand_usage("[instance] [options]")
|
4614
|
+
opts.on( '-p', '--parameter NAME=VALUE', "Template parameter name and value" ) do |val|
|
4615
|
+
k, v = val.split("=")
|
4616
|
+
options[:options]['templateParameter'] ||= {}
|
4617
|
+
options[:options]['templateParameter'][k] = v
|
4618
|
+
end
|
4619
|
+
opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
4620
|
+
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
4621
|
+
end
|
4622
|
+
opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
4623
|
+
options[:no_refresh] = true
|
4624
|
+
end
|
4625
|
+
opts.on(nil, '--no-validate', "Do not validate planned changes before apply" ) do
|
4626
|
+
options[:no_validate] = true
|
4627
|
+
end
|
4628
|
+
opts.on(nil, '--validate-only', "Only validate planned changes, do not execute the apply command." ) do
|
4629
|
+
options[:validate_only] = true
|
4630
|
+
end
|
4631
|
+
build_standard_update_options(opts, options, [:auto_confirm])
|
4632
|
+
opts.footer = <<-EOT
|
4633
|
+
Apply an instance.
|
4634
|
+
[instance] is required. This is the name or id of an instance.
|
4635
|
+
This is only supported by certain types of instances such as terraform.
|
4636
|
+
By default this executes two requests to validate and then apply the changes.
|
4637
|
+
The first request corresponds to the terraform plan command only.
|
4638
|
+
Use --no-validate to skip this step apply changes in one step.
|
4639
|
+
EOT
|
4640
|
+
end
|
4641
|
+
optparse.parse!(args)
|
4642
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
4643
|
+
connect(options)
|
4644
|
+
|
4645
|
+
instance = find_instance_by_name_or_id(args[0])
|
4646
|
+
return 1 if instance.nil?
|
4647
|
+
# construct request
|
4648
|
+
params.merge!(parse_query_options(options))
|
4649
|
+
payload = {}
|
4650
|
+
if options[:payload]
|
4651
|
+
payload = options[:payload]
|
4652
|
+
payload.deep_merge!(parse_passed_options(options))
|
4653
|
+
else
|
4654
|
+
payload.deep_merge!(parse_passed_options(options))
|
4655
|
+
# attempt to load prepare-apply to get templateParameter values and prompt for them
|
4656
|
+
# ok, actually use options/layoutParameters to get the list of parameters
|
4657
|
+
begin
|
4658
|
+
prepare_apply_json_response = @instances_interface.prepare_apply(instance["id"])
|
4659
|
+
config = prepare_apply_json_response['data']
|
4660
|
+
variable_map = config['templateParameter']
|
4661
|
+
api_params = {layoutId: instance['layout']['id'], instanceId: instance['id'], zoneId: instance['cloud']['id'], siteId: instance['group']['id']}
|
4662
|
+
layout_parameters = @options_interface.options_for_source('layoutParameters',api_params)['data']
|
4663
|
+
|
4664
|
+
if layout_parameters && !layout_parameters.empty?
|
4665
|
+
variable_option_types = []
|
4666
|
+
i = 0
|
4667
|
+
layout_parameters.each do |layout_parameter|
|
4668
|
+
var_label = layout_parameter['displayName'] || layout_parameter['name']
|
4669
|
+
var_name = layout_parameter['name']
|
4670
|
+
var_value = variable_map ? variable_map[var_name] : layout_parameter['defaultValue']
|
4671
|
+
if var_value.nil? && layout_parameter['defaultValue']
|
4672
|
+
var_value = layout_parameter['defaultValue']
|
4673
|
+
end
|
4674
|
+
var_type = (layout_parameter['passwordType'] || layout_parameter['sensitive']) ? 'password' : 'text'
|
4675
|
+
option_type = {'fieldContext' => 'templateParameter', 'fieldName' => var_name, 'fieldLabel' => var_label, 'type' => var_type, 'required' => true, 'defaultValue' => (var_value.to_s.empty? ? nil : var_value.to_s), 'displayOrder' => (i+1) }
|
4676
|
+
variable_option_types << option_type
|
4677
|
+
i+=1
|
4678
|
+
end
|
4679
|
+
blueprint_type_display = format_blueprint_type(instance['layout']['provisionTypeCode'])
|
4680
|
+
if blueprint_type_display == "terraform"
|
4681
|
+
blueprint_type_display = "Terraform"
|
4682
|
+
end
|
4683
|
+
print_h2 "#{blueprint_type_display} Variables"
|
4684
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(variable_option_types, options[:options], @api_client)
|
4685
|
+
v_prompt.deep_compact!
|
4686
|
+
payload.deep_merge!(v_prompt)
|
4687
|
+
end
|
4688
|
+
rescue RestClient::Exception => ex
|
4689
|
+
# if e.response && e.response.code == 404
|
4690
|
+
Morpheus::Logging::DarkPrinter.puts "Unable to load config for instance apply, skipping parameter prompting" if Morpheus::Logging.debug?
|
4691
|
+
# print_rest_exception(ex, options)
|
4692
|
+
# end
|
4693
|
+
end
|
4694
|
+
end
|
4695
|
+
|
4696
|
+
@instances_interface.setopts(options)
|
4697
|
+
if options[:validate_only]
|
4698
|
+
# validate only
|
4699
|
+
if options[:dry_run]
|
4700
|
+
print_dry_run @instances_interface.dry.validate_apply(instance["id"], params, payload)
|
4701
|
+
return
|
4702
|
+
end
|
4703
|
+
json_response = @instances_interface.validate_apply(instance["id"], params, payload)
|
4704
|
+
print_green_success "Validating instance #{instance['name']}"
|
4705
|
+
execution_id = json_response['executionId']
|
4706
|
+
if !options[:no_refresh]
|
4707
|
+
#Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
|
4708
|
+
validate_execution_request = wait_for_execution_request(execution_id, options)
|
4709
|
+
end
|
4710
|
+
elsif options[:no_validate]
|
4711
|
+
# skip validate, apply only
|
4584
4712
|
if options[:dry_run]
|
4585
4713
|
print_dry_run @instances_interface.dry.apply(instance["id"], params, payload)
|
4586
4714
|
return
|
@@ -4588,17 +4716,172 @@ EOT
|
|
4588
4716
|
json_response = @instances_interface.apply(instance["id"], params, payload)
|
4589
4717
|
render_response(json_response, options) do
|
4590
4718
|
print_green_success "Applying instance #{instance['name']}"
|
4591
|
-
|
4719
|
+
execution_id = json_response['executionId']
|
4720
|
+
if !options[:no_refresh]
|
4721
|
+
#Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
|
4722
|
+
apply_execution_request = wait_for_execution_request(execution_id, options)
|
4723
|
+
end
|
4724
|
+
end
|
4725
|
+
else
|
4726
|
+
# validate and then apply
|
4727
|
+
if options[:dry_run]
|
4728
|
+
print_dry_run @instances_interface.dry.validate_apply(instance["id"], params, payload)
|
4729
|
+
print_dry_run @instances_interface.dry.apply(instance["id"], params, payload)
|
4730
|
+
return
|
4731
|
+
end
|
4732
|
+
json_response = @instances_interface.validate_apply(instance["id"], params, payload)
|
4733
|
+
print_green_success "Validating instance #{instance['name']}"
|
4734
|
+
execution_id = json_response['executionId']
|
4735
|
+
validate_execution_request = wait_for_execution_request(execution_id, options)
|
4736
|
+
if validate_execution_request['status'] != 'complete'
|
4737
|
+
print_red_alert "Validation failed. Changes will not be applied."
|
4738
|
+
return 1, "Validation failed. Changes will not be applied."
|
4739
|
+
else
|
4740
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to apply these changes?")
|
4741
|
+
return 9, "aborted command"
|
4742
|
+
end
|
4743
|
+
json_response = @instances_interface.apply(instance["id"], params, payload)
|
4744
|
+
render_response(json_response, options) do
|
4745
|
+
print_green_success "Applying instance #{instance['name']}"
|
4746
|
+
execution_id = json_response['executionId']
|
4747
|
+
if !options[:no_refresh]
|
4748
|
+
#Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
|
4749
|
+
apply_execution_request = wait_for_execution_request(execution_id, options)
|
4750
|
+
end
|
4751
|
+
end
|
4592
4752
|
end
|
4593
|
-
return 0, nil
|
4594
|
-
rescue RestClient::Exception => e
|
4595
|
-
print_rest_exception(e, options)
|
4596
|
-
exit 1
|
4597
4753
|
end
|
4754
|
+
return 0, nil
|
4598
4755
|
end
|
4599
4756
|
|
4600
|
-
|
4601
|
-
|
4757
|
+
def state(args)
|
4758
|
+
params, payload, options = {}, {}, {}
|
4759
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
4760
|
+
opts.banner = subcommand_usage("[instance] [options]")
|
4761
|
+
opts.on('--data', "Display State Data") do
|
4762
|
+
options[:include_state_data] = true
|
4763
|
+
end
|
4764
|
+
opts.on('--specs', "Display Spec Templates") do
|
4765
|
+
options[:include_spec_templates] = true
|
4766
|
+
end
|
4767
|
+
opts.on('--plan', "Display Plan Data") do
|
4768
|
+
options[:include_plan_data] = true
|
4769
|
+
end
|
4770
|
+
opts.on('--input', "Display Input") do
|
4771
|
+
options[:include_input] = true
|
4772
|
+
end
|
4773
|
+
opts.on('--output', "Display Output") do
|
4774
|
+
options[:include_output] = true
|
4775
|
+
end
|
4776
|
+
opts.on('-a','--all', "Display All Details") do
|
4777
|
+
options[:include_state_data] = true
|
4778
|
+
options[:include_spec_templates] = true
|
4779
|
+
options[:include_plan_data] = true
|
4780
|
+
options[:include_input] = true
|
4781
|
+
options[:include_output] = true
|
4782
|
+
options[:details] = true
|
4783
|
+
end
|
4784
|
+
build_standard_get_options(opts, options)
|
4785
|
+
opts.footer = <<-EOT
|
4786
|
+
View state of an instance.
|
4787
|
+
[instance] is required. This is the name or id of an instance.
|
4788
|
+
This is only supported by certain types of apps such as terraform.
|
4789
|
+
EOT
|
4790
|
+
end
|
4791
|
+
optparse.parse!(args)
|
4792
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
4793
|
+
connect(options)
|
4794
|
+
instance = find_instance_by_name_or_id(args[0])
|
4795
|
+
return 1 if instance.nil?
|
4796
|
+
# construct request
|
4797
|
+
params.merge!(parse_query_options(options))
|
4798
|
+
@instances_interface.setopts(options)
|
4799
|
+
if options[:dry_run]
|
4800
|
+
print_dry_run @instances_interface.dry.state(instance["id"], params)
|
4801
|
+
return
|
4802
|
+
end
|
4803
|
+
json_response = @instances_interface.state(instance["id"], params)
|
4804
|
+
render_result = render_with_format(json_response, options)
|
4805
|
+
return 0 if render_result
|
4806
|
+
print_h1 "Instance State: #{instance['name']}", options
|
4807
|
+
# print_h2 "Workloads", options
|
4808
|
+
if json_response['workloads'] && !json_response['workloads'].empty?
|
4809
|
+
workload_columns = {
|
4810
|
+
"Name" => lambda {|it| it['subRefName'].to_s.empty? ? "#{it['refName']}" : "#{it['refName']} - #{it['subRefName']}" },
|
4811
|
+
"Last Check" => lambda {|it| format_local_dt(it['stateDate']) },
|
4812
|
+
"Status" => lambda {|it| format_ok_status(it['status'] || 'ok') },
|
4813
|
+
"Drift Status" => lambda {|it| it['iacDrift'] ? "Drift" : "No Drift" }
|
4814
|
+
}
|
4815
|
+
print as_pretty_table(json_response['workloads'], workload_columns.upcase_keys!, options)
|
4816
|
+
else
|
4817
|
+
print cyan,"No workloads found.",reset,"\n"
|
4818
|
+
end
|
4819
|
+
if options[:include_state_data]
|
4820
|
+
print_h2 "State Data", options
|
4821
|
+
puts json_response['stateData']
|
4822
|
+
end
|
4823
|
+
if options[:include_spec_templates]
|
4824
|
+
print_h2 "Spec Templates", options
|
4825
|
+
spec_templates_columns = {
|
4826
|
+
"Resource Spec" => lambda {|it| it['name'] || (it['template'] ? it['template']['name'] : nil) },
|
4827
|
+
"Attached to Source Template" => lambda {|it| format_boolean(!it['isolated']) },
|
4828
|
+
"Source Spec Template" => lambda {|it| (it['template'] ? it['template']['name'] : nil) || it['name'] }
|
4829
|
+
}
|
4830
|
+
print as_pretty_table(json_response['specs'], spec_templates_columns.upcase_keys!, options)
|
4831
|
+
# print "\n", reset
|
4832
|
+
end
|
4833
|
+
if options[:include_plan_data]
|
4834
|
+
# print_h2 "Plan Data", options
|
4835
|
+
if instance['type'] == 'terraform' || instance['layout']['provisionTypeCode'] == 'terraform'
|
4836
|
+
print_h2 "Terraform Plan", options
|
4837
|
+
else
|
4838
|
+
print_h2 "Plan Data", options
|
4839
|
+
end
|
4840
|
+
puts json_response['planData']
|
4841
|
+
# print "\n", reset
|
4842
|
+
end
|
4843
|
+
if options[:include_input]
|
4844
|
+
# print_h2 "Input"
|
4845
|
+
if json_response['input'] && json_response['input']['variables']
|
4846
|
+
print_h2 "VARIABLES", options
|
4847
|
+
input_variable_columns = {
|
4848
|
+
"Name" => lambda {|it| it['name'] },
|
4849
|
+
"Value" => lambda {|it| it['value'] }
|
4850
|
+
}
|
4851
|
+
print as_pretty_table(json_response['input']['variables'], input_variable_columns.upcase_keys!, options)
|
4852
|
+
end
|
4853
|
+
if json_response['input'] && json_response['input']['providers']
|
4854
|
+
print_h2 "PROVIDERS", options
|
4855
|
+
input_provider_columns = {
|
4856
|
+
"Name" => lambda {|it| it['name'] }
|
4857
|
+
}
|
4858
|
+
print as_pretty_table(json_response['input']['providers'], input_provider_columns.upcase_keys!, options)
|
4859
|
+
end
|
4860
|
+
if json_response['input'] && json_response['input']['data']
|
4861
|
+
print_h2 "DATA", options
|
4862
|
+
input_data_columns = {
|
4863
|
+
"Type" => lambda {|it| it['type'] },
|
4864
|
+
"Key" => lambda {|it| it['key'] },
|
4865
|
+
"Name" => lambda {|it| it['name'] }
|
4866
|
+
}
|
4867
|
+
print as_pretty_table(json_response['input']['data'], input_data_columns.upcase_keys!, options)
|
4868
|
+
end
|
4869
|
+
# print "\n", reset
|
4870
|
+
end
|
4871
|
+
if options[:include_output]
|
4872
|
+
# print_h2 "Output", options
|
4873
|
+
if json_response['output'] && json_response['output']['outputs']
|
4874
|
+
print_h2 "OUTPUTS", options
|
4875
|
+
input_variable_columns = {
|
4876
|
+
"Name" => lambda {|it| it['name'] },
|
4877
|
+
"Value" => lambda {|it| it['value'] }
|
4878
|
+
}
|
4879
|
+
print as_pretty_table(json_response['output']['outputs'], input_variable_columns.upcase_keys!, options)
|
4880
|
+
end
|
4881
|
+
# print "\n", reset
|
4882
|
+
end
|
4883
|
+
print "\n", reset
|
4884
|
+
return 0
|
4602
4885
|
end
|
4603
4886
|
|
4604
4887
|
private
|