morpheus-cli 5.4.4.2 → 5.4.5
Sign up to get free protection for your applications and to get access to all the features.
- 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/cli/commands/apps.rb +366 -47
- data/lib/morpheus/cli/commands/clusters.rb +70 -10
- 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 +297 -14
- data/lib/morpheus/cli/commands/library_layouts_command.rb +15 -0
- data/lib/morpheus/cli/commands/provisioning_licenses_command.rb +1 -1
- data/lib/morpheus/cli/mixins/execution_request_helper.rb +50 -0
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +4 -4
- data/lib/morpheus/cli/option_types.rb +7 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +12 -0
- 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: 5644f86d51c4b7199d89f570fc4f558dfc300581a358cf6c432782920d47063d
|
4
|
+
data.tar.gz: 1e772e9ff9884067b9f76241c83886785d7e219030818ebfd724a285e8ff885c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 276cfc3c70d0cde328a0e5aac930580a53158d868d0716753badfe13617a92e7e372ca29edf06dc392aad28b3a98b08f0d6480eb819d6f0f33b99075174939e6
|
7
|
+
data.tar.gz: 2ed27747e8fda3675cbb05e38ac5846496894430570531269e4d6e8954d9fd050022928e52ba66702628370011d470dfaa992198b8965d0b208e30bbbba539e6
|
data/Dockerfile
CHANGED
@@ -57,20 +57,34 @@ class Morpheus::AppsInterface < Morpheus::APIClient
|
|
57
57
|
execute(opts)
|
58
58
|
end
|
59
59
|
|
60
|
-
def
|
61
|
-
url = "#{@base_url}/api/apps/#{app_id}/apply"
|
60
|
+
def prepare_apply(app_id, params={})
|
61
|
+
url = "#{@base_url}/api/apps/#{app_id}/prepare-apply"
|
62
|
+
headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
63
|
+
opts = {method: :get, url: url, headers: headers}
|
64
|
+
execute(opts)
|
65
|
+
end
|
66
|
+
|
67
|
+
def validate_apply(app_id, params, payload)
|
68
|
+
url = "#{@base_url}/api/apps/#{app_id}/validate-apply"
|
62
69
|
headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
63
70
|
opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
|
64
71
|
execute(opts)
|
65
72
|
end
|
66
73
|
|
67
|
-
def
|
68
|
-
url = "#{@base_url}/api/apps/#{app_id}/
|
74
|
+
def apply(app_id, params, payload)
|
75
|
+
url = "#{@base_url}/api/apps/#{app_id}/apply"
|
69
76
|
headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
70
77
|
opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
|
71
78
|
execute(opts)
|
72
79
|
end
|
73
80
|
|
81
|
+
def state(id, params={})
|
82
|
+
url = "#{@base_url}/api/apps/#{id}/state"
|
83
|
+
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
84
|
+
opts = {method: :get, url: url, headers: headers}
|
85
|
+
execute(opts)
|
86
|
+
end
|
87
|
+
|
74
88
|
def add_instance(app_id, payload)
|
75
89
|
url = "#{@base_url}/api/apps/#{app_id}/add-instance"
|
76
90
|
headers = { :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
@@ -379,6 +379,20 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
|
|
379
379
|
execute(opts)
|
380
380
|
end
|
381
381
|
|
382
|
+
def prepare_apply(id, params={})
|
383
|
+
url = "#{@base_url}/api/instances/#{id}/prepare-apply"
|
384
|
+
headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
385
|
+
opts = {method: :get, url: url, headers: headers}
|
386
|
+
execute(opts)
|
387
|
+
end
|
388
|
+
|
389
|
+
def validate_apply(id, params, payload)
|
390
|
+
url = "#{@base_url}/api/instances/#{id}/validate-apply"
|
391
|
+
headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
392
|
+
opts = {method: :post, url: url, headers: headers, payload: payload.to_json}
|
393
|
+
execute(opts)
|
394
|
+
end
|
395
|
+
|
382
396
|
def apply(id, params, payload)
|
383
397
|
url = "#{@base_url}/api/instances/#{id}/apply"
|
384
398
|
headers = {:params => params, :authorization => "Bearer #{@access_token}", 'Content-Type' => 'application/json' }
|
@@ -386,8 +400,8 @@ class Morpheus::InstancesInterface < Morpheus::APIClient
|
|
386
400
|
execute(opts)
|
387
401
|
end
|
388
402
|
|
389
|
-
def
|
390
|
-
url = "#{@base_url}/api/instances/#{id}/state
|
403
|
+
def state(id, params={})
|
404
|
+
url = "#{@base_url}/api/instances/#{id}/state"
|
391
405
|
headers = { params: params, authorization: "Bearer #{@access_token}" }
|
392
406
|
opts = {method: :get, url: url, headers: headers}
|
393
407
|
execute(opts)
|
@@ -7,10 +7,12 @@ class Morpheus::Cli::Apps
|
|
7
7
|
include Morpheus::Cli::ProvisioningHelper
|
8
8
|
include Morpheus::Cli::ProcessesHelper
|
9
9
|
include Morpheus::Cli::LogsHelper
|
10
|
+
include Morpheus::Cli::ExecutionRequestHelper
|
11
|
+
|
10
12
|
set_command_name :apps
|
11
13
|
set_command_description "View and manage apps."
|
12
14
|
register_subcommands :list, :count, :get, :view, :add, :update, :remove, :cancel_removal, :add_instance, :remove_instance, :logs, :security_groups, :apply_security_groups, :history
|
13
|
-
register_subcommands :refresh, :apply
|
15
|
+
register_subcommands :refresh, :prepare_apply, :plan, :apply, :state
|
14
16
|
register_subcommands :stop, :start, :restart
|
15
17
|
register_subcommands :wiki, :update_wiki
|
16
18
|
#register_subcommands :firewall_disable, :firewall_enable
|
@@ -18,6 +20,9 @@ class Morpheus::Cli::Apps
|
|
18
20
|
alias_subcommand :details, :get
|
19
21
|
set_default_subcommand :list
|
20
22
|
|
23
|
+
# hide these for now
|
24
|
+
set_subcommands_hidden :prepare_apply
|
25
|
+
|
21
26
|
def initialize()
|
22
27
|
# @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
23
28
|
end
|
@@ -656,7 +661,22 @@ class Morpheus::Cli::Apps
|
|
656
661
|
json_response = @apps_interface.get(id.to_i)
|
657
662
|
render_response(json_response, options, 'app') do
|
658
663
|
app = json_response['app']
|
659
|
-
|
664
|
+
# API used to only return apps.appTiers including instances with lots of detail
|
665
|
+
# now returns simplified instance list as "instances" for terraform, etc with only id,name
|
666
|
+
# so load the instance details if needed
|
667
|
+
app_tiers = []
|
668
|
+
instances = []
|
669
|
+
if app['appTiers'] && !app['appTiers'].empty?
|
670
|
+
# appTiers contains instances with lots of detail
|
671
|
+
app_tiers = app['appTiers']
|
672
|
+
app_tiers.each do |app_tier|
|
673
|
+
instances += (app_tier['appInstances'] || []).collect {|it| it['instance']}.flatten().compact
|
674
|
+
end
|
675
|
+
elsif app['instances'] && !app['instances'].empty?
|
676
|
+
# need to load instance details which are not returned in this simple list
|
677
|
+
instance_ids = app['instances'].collect {|it| it['id'] }
|
678
|
+
instances = @instances_interface.list({id: instance_ids})['instances']
|
679
|
+
end
|
660
680
|
print_h1 "App Details", [], options
|
661
681
|
print cyan
|
662
682
|
description_cols = {
|
@@ -678,7 +698,6 @@ class Morpheus::Cli::Apps
|
|
678
698
|
"Tiers" => lambda {|it|
|
679
699
|
# it['instanceCount']
|
680
700
|
tiers = []
|
681
|
-
app_tiers = it['appTiers'] || []
|
682
701
|
app_tiers.each do |app_tier|
|
683
702
|
tiers << app_tier['tier']
|
684
703
|
end
|
@@ -686,20 +705,13 @@ class Morpheus::Cli::Apps
|
|
686
705
|
},
|
687
706
|
"Instances" => lambda {|it|
|
688
707
|
# it['instanceCount']
|
689
|
-
instances = []
|
690
|
-
app_tiers = it['appTiers'] || []
|
691
|
-
app_tiers.each do |app_tier|
|
692
|
-
instances += (app_tier['appInstances'] || []).collect {|it| it['instance']}.flatten().compact
|
693
|
-
end
|
694
|
-
#"(#{instances.count})"
|
695
708
|
"(#{instances.count}) #{instances.collect {|it| it['name'] }.join(',')}"
|
696
709
|
},
|
697
710
|
"Containers" => lambda {|it|
|
698
711
|
#it['containerCount']
|
699
712
|
containers = []
|
700
|
-
|
701
|
-
|
702
|
-
containers += (app_tier['appInstances'] || []).collect {|it| it['instance']['containers']}.flatten().compact
|
713
|
+
instances.each do |instance|
|
714
|
+
containers += (instance['containers'] || [])
|
703
715
|
end
|
704
716
|
#"(#{containers.count})"
|
705
717
|
"(#{containers.count}) #{containers.collect {|it| it }.join(',')}"
|
@@ -724,36 +736,40 @@ class Morpheus::Cli::Apps
|
|
724
736
|
end
|
725
737
|
|
726
738
|
if app_tiers.empty?
|
727
|
-
#puts yellow, "This app is empty", reset
|
728
739
|
print reset,"\n"
|
740
|
+
if instances.empty?
|
741
|
+
print cyan, "This app is empty", reset, "\n\n"
|
742
|
+
else
|
743
|
+
print_h2 "Instances", options
|
744
|
+
instances_rows = instances.collect do |instance|
|
745
|
+
connection_string = ''
|
746
|
+
if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
|
747
|
+
connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
|
748
|
+
end
|
749
|
+
{id: instance['id'], name: instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: (instance['containers'] || []).count, status: format_instance_status(instance), type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
|
750
|
+
end
|
751
|
+
instances_rows = instances_rows.sort {|x,y| x[:id] <=> y[:id] } #oldest to newest..
|
752
|
+
print cyan
|
753
|
+
print as_pretty_table(instances_rows, [:id, :name, :cloud, :type, :environment, :nodes, :connection, :status], {border_style: options[:border_style]})
|
754
|
+
print reset
|
755
|
+
print "\n"
|
756
|
+
end
|
729
757
|
else
|
730
758
|
app_tiers.each do |app_tier|
|
731
759
|
# print_h2 "Tier: #{app_tier['tier']['name']}", options
|
732
760
|
print_h2 "#{app_tier['tier']['name']}", options
|
733
761
|
print cyan
|
734
|
-
|
762
|
+
tier_instances = (app_tier['appInstances'] || []).collect {|it| it['instance']}
|
763
|
+
instances = tier_instances.collect { |tier_instance| instances.find { |i| i['id'] == tier_instance['id'] } }
|
735
764
|
if instances.empty?
|
736
765
|
puts yellow, "This tier is empty", reset
|
737
766
|
else
|
738
767
|
instances_rows = instances.collect do |instance|
|
739
|
-
# JD: fix bug here, status is not returned because withStats: false !?
|
740
|
-
status_string = instance['status'].to_s
|
741
|
-
if status_string == 'running'
|
742
|
-
status_string = "#{green}#{status_string.upcase}#{cyan}"
|
743
|
-
elsif status_string == 'provisioning'
|
744
|
-
status_string = "#{cyan}#{status_string.upcase}#{cyan}"
|
745
|
-
elsif status_string == 'stopped' or status_string == 'failed'
|
746
|
-
status_string = "#{red}#{status_string.upcase}#{cyan}"
|
747
|
-
elsif status_string == 'unknown'
|
748
|
-
status_string = "#{white}#{status_string.upcase}#{cyan}"
|
749
|
-
else
|
750
|
-
status_string = "#{yellow}#{status_string.upcase}#{cyan}"
|
751
|
-
end
|
752
768
|
connection_string = ''
|
753
769
|
if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
|
754
770
|
connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
|
755
771
|
end
|
756
|
-
{id: instance['id'], name: instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: instance['containers'].count, status:
|
772
|
+
{id: instance['id'], name: instance['name'], connection: connection_string, environment: instance['instanceContext'], nodes: (instance['containers'] || []).count, status: format_instance_status(instance), type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
|
757
773
|
end
|
758
774
|
instances_rows = instances_rows.sort {|x,y| x[:id] <=> y[:id] } #oldest to newest..
|
759
775
|
print cyan
|
@@ -926,19 +942,22 @@ EOT
|
|
926
942
|
end
|
927
943
|
end
|
928
944
|
|
929
|
-
def
|
945
|
+
def prepare_apply(args)
|
930
946
|
params, payload, options = {}, {}, {}
|
931
947
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
932
948
|
opts.banner = subcommand_usage("[app] [options]")
|
933
949
|
build_standard_update_options(opts, options, [:auto_confirm])
|
934
950
|
opts.footer = <<-EOT
|
935
|
-
|
951
|
+
Prepare to apply an app.
|
936
952
|
[app] is required. This is the name or id of an app.
|
953
|
+
Displays the current configuration data used by the apply command.
|
937
954
|
This is only supported by certain types of apps such as terraform.
|
938
955
|
EOT
|
939
956
|
end
|
940
957
|
optparse.parse!(args)
|
941
|
-
|
958
|
+
if args.count != 1
|
959
|
+
raise_command_error "wrong number of arguments, expected 1 and got (#{args.count}) #{args.join(', ')}\n#{optparse}"
|
960
|
+
end
|
942
961
|
connect(options)
|
943
962
|
|
944
963
|
begin
|
@@ -954,10 +973,135 @@ EOT
|
|
954
973
|
payload.deep_merge!(parse_passed_options(options))
|
955
974
|
# raise_command_error "Specify at least one option to update.\n#{optparse}" if payload.empty?
|
956
975
|
end
|
957
|
-
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to apply this app: #{app['name']}?")
|
958
|
-
return 9, "aborted command"
|
959
|
-
end
|
960
976
|
@apps_interface.setopts(options)
|
977
|
+
if options[:dry_run]
|
978
|
+
print_dry_run @apps_interface.dry.prepare_apply(app["id"], params)
|
979
|
+
return
|
980
|
+
end
|
981
|
+
json_response = @apps_interface.prepare_apply(app["id"], params)
|
982
|
+
render_result = render_with_format(json_response, options)
|
983
|
+
return 0 if render_result
|
984
|
+
# print_green_success "Prepared to apply app: #{app['name']}"
|
985
|
+
print_h1 "Prepared App: #{app['name']}"
|
986
|
+
app_config = json_response['data']
|
987
|
+
# app_config = json_response if app_config.nil?
|
988
|
+
puts as_yaml(app_config, options)
|
989
|
+
#return get([app['id']] + (options[:remote] ? ["-r",options[:remote]] : []))
|
990
|
+
print "\n", reset
|
991
|
+
return 0
|
992
|
+
rescue RestClient::Exception => e
|
993
|
+
print_rest_exception(e, options)
|
994
|
+
exit 1
|
995
|
+
end
|
996
|
+
end
|
997
|
+
|
998
|
+
def apply(args)
|
999
|
+
default_refresh_interval = 15
|
1000
|
+
params, payload, options = {}, {}, {}
|
1001
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1002
|
+
opts.banner = subcommand_usage("[app] [options]")
|
1003
|
+
opts.on( '-p', '--parameter NAME=VALUE', "Template parameter name and value" ) do |val|
|
1004
|
+
k, v = val.split("=")
|
1005
|
+
options[:options]['templateParameter'] ||= {}
|
1006
|
+
options[:options]['templateParameter'][k] = v
|
1007
|
+
end
|
1008
|
+
opts.on('--refresh [SECONDS]', String, "Refresh until execution is complete. Default interval is #{default_refresh_interval} seconds.") do |val|
|
1009
|
+
options[:refresh_interval] = val.to_s.empty? ? default_refresh_interval : val.to_f
|
1010
|
+
end
|
1011
|
+
opts.on(nil, '--no-refresh', "Do not refresh" ) do
|
1012
|
+
options[:no_refresh] = true
|
1013
|
+
end
|
1014
|
+
opts.on(nil, '--no-validate', "Do not validate planned changes before apply" ) do
|
1015
|
+
options[:no_validate] = true
|
1016
|
+
end
|
1017
|
+
opts.on(nil, '--validate-only', "Only validate planned changes, do not execute the apply command." ) do
|
1018
|
+
options[:validate_only] = true
|
1019
|
+
end
|
1020
|
+
build_standard_update_options(opts, options, [:auto_confirm])
|
1021
|
+
opts.footer = <<-EOT
|
1022
|
+
Apply an app.
|
1023
|
+
[app] is required. This is the name or id of an app.
|
1024
|
+
This is only supported by certain types of apps such as terraform.
|
1025
|
+
By default this executes two requests to validate and then apply the changes.
|
1026
|
+
The first request corresponds to the terraform plan command only.
|
1027
|
+
Use --no-validate to skip this step apply changes in one step.
|
1028
|
+
EOT
|
1029
|
+
end
|
1030
|
+
optparse.parse!(args)
|
1031
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
1032
|
+
connect(options)
|
1033
|
+
|
1034
|
+
app = find_app_by_name_or_id(args[0])
|
1035
|
+
return 1 if app.nil?
|
1036
|
+
# construct request
|
1037
|
+
params.merge!(parse_query_options(options))
|
1038
|
+
payload = {}
|
1039
|
+
if options[:payload]
|
1040
|
+
payload = options[:payload]
|
1041
|
+
payload.deep_merge!(parse_passed_options(options))
|
1042
|
+
else
|
1043
|
+
payload.deep_merge!(parse_passed_options(options))
|
1044
|
+
# attempt to load prepare-apply to get templateParameter values and prompt for them
|
1045
|
+
# ok, actually use options/layoutParameters to get the list of parameters
|
1046
|
+
begin
|
1047
|
+
prepare_apply_json_response = @apps_interface.prepare_apply(app["id"])
|
1048
|
+
config = prepare_apply_json_response['data']
|
1049
|
+
variable_map = config['templateParameter']
|
1050
|
+
# need to load the instance details to get the app cloud...ugh
|
1051
|
+
first_instance = app['instances'][0]
|
1052
|
+
instance = first_instance ? find_instance_by_name_or_id(first_instance['id']) : nil
|
1053
|
+
zone_id = instance ? instance['cloud']['id'] : nil
|
1054
|
+
api_params = {templateId: app['blueprint']['id'], appId: app['id'], zoneId: zone_id, siteId: app['group']['id']}
|
1055
|
+
layout_parameters = @options_interface.options_for_source('templateParameters',api_params)['data']
|
1056
|
+
|
1057
|
+
if layout_parameters && !layout_parameters.empty?
|
1058
|
+
variable_option_types = []
|
1059
|
+
i = 0
|
1060
|
+
layout_parameters.each do |layout_parameter|
|
1061
|
+
var_label = layout_parameter['displayName'] || layout_parameter['name']
|
1062
|
+
var_name = layout_parameter['name']
|
1063
|
+
var_value = variable_map ? variable_map[var_name] : layout_parameter['defaultValue']
|
1064
|
+
if var_value.nil? && layout_parameter['defaultValue']
|
1065
|
+
var_value = layout_parameter['defaultValue']
|
1066
|
+
end
|
1067
|
+
var_type = (layout_parameter['passwordType'] || layout_parameter['sensitive']) ? 'password' : 'text'
|
1068
|
+
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) }
|
1069
|
+
variable_option_types << option_type
|
1070
|
+
i+=1
|
1071
|
+
end
|
1072
|
+
blueprint_type_display = format_blueprint_type(config['type'])
|
1073
|
+
if blueprint_type_display == "terraform"
|
1074
|
+
blueprint_type_display = "Terraform"
|
1075
|
+
end
|
1076
|
+
print_h2 "#{blueprint_type_display} Variables"
|
1077
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt(variable_option_types, options[:options], @api_client)
|
1078
|
+
v_prompt.deep_compact!
|
1079
|
+
payload.deep_merge!(v_prompt)
|
1080
|
+
end
|
1081
|
+
rescue RestClient::Exception => ex
|
1082
|
+
# if e.response && e.response.code == 404
|
1083
|
+
Morpheus::Logging::DarkPrinter.puts "Unable to load config for app apply, skipping parameter prompting" if Morpheus::Logging.debug?
|
1084
|
+
# print_rest_exception(ex, options)
|
1085
|
+
# end
|
1086
|
+
end
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
@apps_interface.setopts(options)
|
1090
|
+
if options[:validate_only]
|
1091
|
+
# validate only
|
1092
|
+
if options[:dry_run]
|
1093
|
+
print_dry_run @apps_interface.dry.validate_apply(app["id"], params, payload)
|
1094
|
+
return
|
1095
|
+
end
|
1096
|
+
json_response = @apps_interface.validate_apply(app["id"], params, payload)
|
1097
|
+
print_green_success "Validating app #{app['name']}"
|
1098
|
+
execution_id = json_response['executionId']
|
1099
|
+
if !options[:no_refresh]
|
1100
|
+
#Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
|
1101
|
+
validate_execution_request = wait_for_execution_request(execution_id, options)
|
1102
|
+
end
|
1103
|
+
elsif options[:no_validate]
|
1104
|
+
# skip validate, apply only
|
961
1105
|
if options[:dry_run]
|
962
1106
|
print_dry_run @apps_interface.dry.apply(app["id"], params, payload)
|
963
1107
|
return
|
@@ -965,13 +1109,172 @@ EOT
|
|
965
1109
|
json_response = @apps_interface.apply(app["id"], params, payload)
|
966
1110
|
render_response(json_response, options) do
|
967
1111
|
print_green_success "Applying app #{app['name']}"
|
968
|
-
|
1112
|
+
execution_id = json_response['executionId']
|
1113
|
+
if !options[:no_refresh]
|
1114
|
+
#Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
|
1115
|
+
apply_execution_request = wait_for_execution_request(execution_id, options)
|
1116
|
+
end
|
1117
|
+
end
|
1118
|
+
else
|
1119
|
+
# validate and then apply
|
1120
|
+
if options[:dry_run]
|
1121
|
+
print_dry_run @apps_interface.dry.validate_apply(app["id"], params, payload)
|
1122
|
+
print_dry_run @apps_interface.dry.apply(app["id"], params, payload)
|
1123
|
+
return
|
1124
|
+
end
|
1125
|
+
json_response = @apps_interface.validate_apply(app["id"], params, payload)
|
1126
|
+
print_green_success "Validating app #{app['name']}"
|
1127
|
+
execution_id = json_response['executionId']
|
1128
|
+
validate_execution_request = wait_for_execution_request(execution_id, options)
|
1129
|
+
if validate_execution_request['status'] != 'complete'
|
1130
|
+
print_red_alert "Validation failed. Changes will not be applied."
|
1131
|
+
return 1, "Validation failed. Changes will not be applied."
|
1132
|
+
else
|
1133
|
+
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to apply these changes?")
|
1134
|
+
return 9, "aborted command"
|
1135
|
+
end
|
1136
|
+
json_response = @apps_interface.apply(app["id"], params, payload)
|
1137
|
+
render_response(json_response, options) do
|
1138
|
+
print_green_success "Applying app #{app['name']}"
|
1139
|
+
execution_id = json_response['executionId']
|
1140
|
+
if !options[:no_refresh]
|
1141
|
+
#Morpheus::Cli::ExecutionRequestCommand.new.handle(["get", execution_id, "--refresh", options[:refresh_interval].to_s]+ (options[:remote] ? ["-r",options[:remote]] : []))
|
1142
|
+
apply_execution_request = wait_for_execution_request(execution_id, options)
|
1143
|
+
end
|
1144
|
+
end
|
969
1145
|
end
|
970
|
-
return 0, nil
|
971
|
-
rescue RestClient::Exception => e
|
972
|
-
print_rest_exception(e, options)
|
973
|
-
exit 1
|
974
1146
|
end
|
1147
|
+
return 0, nil
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
def state(args)
|
1151
|
+
params, payload, options = {}, {}, {}
|
1152
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
1153
|
+
opts.banner = subcommand_usage("[app] [options]")
|
1154
|
+
opts.on('--data', "Display State Data") do
|
1155
|
+
options[:include_state_data] = true
|
1156
|
+
end
|
1157
|
+
opts.on('--specs', "Display Spec Templates") do
|
1158
|
+
options[:include_spec_templates] = true
|
1159
|
+
end
|
1160
|
+
opts.on('--plan', "Display Plan Data") do
|
1161
|
+
options[:include_plan_data] = true
|
1162
|
+
end
|
1163
|
+
opts.on('--input', "Display Input") do
|
1164
|
+
options[:include_input] = true
|
1165
|
+
end
|
1166
|
+
opts.on('--output', "Display Output") do
|
1167
|
+
options[:include_output] = true
|
1168
|
+
end
|
1169
|
+
opts.on('-a','--all', "Display All Details") do
|
1170
|
+
options[:include_state_data] = true
|
1171
|
+
options[:include_spec_templates] = true
|
1172
|
+
options[:include_plan_data] = true
|
1173
|
+
options[:include_input] = true
|
1174
|
+
options[:include_output] = true
|
1175
|
+
options[:details] = true
|
1176
|
+
end
|
1177
|
+
build_standard_get_options(opts, options)
|
1178
|
+
opts.footer = <<-EOT
|
1179
|
+
View state of an app.
|
1180
|
+
[app] is required. This is the name or id of an app.
|
1181
|
+
This is only supported by certain types of apps such as terraform.
|
1182
|
+
EOT
|
1183
|
+
end
|
1184
|
+
optparse.parse!(args)
|
1185
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
1186
|
+
connect(options)
|
1187
|
+
app = find_app_by_name_or_id(args[0])
|
1188
|
+
return 1 if app.nil?
|
1189
|
+
# construct request
|
1190
|
+
params.merge!(parse_query_options(options))
|
1191
|
+
@apps_interface.setopts(options)
|
1192
|
+
if options[:dry_run]
|
1193
|
+
print_dry_run @apps_interface.dry.state(app["id"], params)
|
1194
|
+
return
|
1195
|
+
end
|
1196
|
+
json_response = @apps_interface.state(app["id"], params)
|
1197
|
+
render_result = render_with_format(json_response, options)
|
1198
|
+
return 0 if render_result
|
1199
|
+
print_h1 "App State: #{app['name']}", options
|
1200
|
+
# print_h2 "Workloads", options
|
1201
|
+
if json_response['workloads'] && !json_response['workloads'].empty?
|
1202
|
+
workload_columns = {
|
1203
|
+
"Name" => lambda {|it| it['subRefName'].to_s.empty? ? "#{it['refName']}" : "#{it['refName']} - #{it['subRefName']}" },
|
1204
|
+
"Last Check" => lambda {|it| format_local_dt(it['stateDate']) },
|
1205
|
+
"Status" => lambda {|it| format_ok_status(it['status'] || 'ok') },
|
1206
|
+
"Drift Status" => lambda {|it| it['iacDrift'] ? "Drift" : "No Drift" }
|
1207
|
+
}
|
1208
|
+
print as_pretty_table(json_response['workloads'], workload_columns.upcase_keys!, options)
|
1209
|
+
else
|
1210
|
+
print cyan,"No workloads found.",reset,"\n"
|
1211
|
+
end
|
1212
|
+
if options[:include_state_data]
|
1213
|
+
print_h2 "State Data", options
|
1214
|
+
puts json_response['stateData']
|
1215
|
+
end
|
1216
|
+
if options[:include_spec_templates]
|
1217
|
+
print_h2 "Spec Templates", options
|
1218
|
+
spec_templates_columns = {
|
1219
|
+
"Resource Spec" => lambda {|it| it['name'] || (it['template'] ? it['template']['name'] : nil) },
|
1220
|
+
"Attached to Source Template" => lambda {|it| format_boolean(!it['isolated']) },
|
1221
|
+
"Source Spec Template" => lambda {|it| (it['template'] ? it['template']['name'] : nil) || it['name'] }
|
1222
|
+
}
|
1223
|
+
print as_pretty_table(json_response['specs'], spec_templates_columns.upcase_keys!, options)
|
1224
|
+
# print "\n", reset
|
1225
|
+
end
|
1226
|
+
if options[:include_plan_data]
|
1227
|
+
# print_h2 "Plan Data", options
|
1228
|
+
if app['type'] == 'terraform'
|
1229
|
+
print_h2 "Terraform Plan", options
|
1230
|
+
else
|
1231
|
+
print_h2 "Plan Data", options
|
1232
|
+
end
|
1233
|
+
puts json_response['planData']
|
1234
|
+
# print "\n", reset
|
1235
|
+
end
|
1236
|
+
if options[:include_input]
|
1237
|
+
# print_h2 "Input", options
|
1238
|
+
if json_response['input'] && json_response['input']['variables']
|
1239
|
+
print_h2 "VARIABLES", options
|
1240
|
+
input_variable_columns = {
|
1241
|
+
"Name" => lambda {|it| it['name'] },
|
1242
|
+
"Value" => lambda {|it| it['value'] }
|
1243
|
+
}
|
1244
|
+
print as_pretty_table(json_response['input']['variables'], input_variable_columns.upcase_keys!, options)
|
1245
|
+
end
|
1246
|
+
if json_response['input'] && json_response['input']['providers']
|
1247
|
+
print_h2 "PROVIDERS", options
|
1248
|
+
input_provider_columns = {
|
1249
|
+
"Name" => lambda {|it| it['name'] }
|
1250
|
+
}
|
1251
|
+
print as_pretty_table(json_response['input']['providers'], input_provider_columns.upcase_keys!, options)
|
1252
|
+
end
|
1253
|
+
if json_response['input'] && json_response['input']['data']
|
1254
|
+
print_h2 "DATA", options
|
1255
|
+
input_data_columns = {
|
1256
|
+
"Type" => lambda {|it| it['type'] },
|
1257
|
+
"Key" => lambda {|it| it['key'] },
|
1258
|
+
"Name" => lambda {|it| it['name'] }
|
1259
|
+
}
|
1260
|
+
print as_pretty_table(json_response['input']['data'], input_data_columns.upcase_keys!, options)
|
1261
|
+
end
|
1262
|
+
# print "\n", reset
|
1263
|
+
end
|
1264
|
+
if options[:include_output]
|
1265
|
+
# print_h2 "Output", options
|
1266
|
+
if json_response['output'] && json_response['output']['outputs']
|
1267
|
+
print_h2 "OUTPUTS", options
|
1268
|
+
input_variable_columns = {
|
1269
|
+
"Name" => lambda {|it| it['name'] },
|
1270
|
+
"Value" => lambda {|it| it['value'] }
|
1271
|
+
}
|
1272
|
+
print as_pretty_table(json_response['output']['outputs'], input_variable_columns.upcase_keys!, options)
|
1273
|
+
end
|
1274
|
+
# print "\n", reset
|
1275
|
+
end
|
1276
|
+
print "\n", reset
|
1277
|
+
return 0
|
975
1278
|
end
|
976
1279
|
|
977
1280
|
def add_instance(args)
|
@@ -1242,11 +1545,19 @@ EOT
|
|
1242
1545
|
begin
|
1243
1546
|
app = find_app_by_name_or_id(args[0])
|
1244
1547
|
container_ids = []
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1548
|
+
# API used to only return apps.appTiers
|
1549
|
+
# now returns detailed instance list as 'instances'
|
1550
|
+
app_tiers = app['appTiers'] || []
|
1551
|
+
instances = app['instances']
|
1552
|
+
if instances.nil?
|
1553
|
+
instances = []
|
1554
|
+
app_tiers.each do |app_tier|
|
1555
|
+
instances += (app_tier['appInstances'] || []).collect {|it| it['instance']}.flatten().compact
|
1556
|
+
end
|
1557
|
+
end
|
1558
|
+
instances.each do |instance|
|
1559
|
+
container_ids += instance['containers']
|
1560
|
+
end
|
1250
1561
|
if container_ids.empty?
|
1251
1562
|
print cyan,"app is empty",reset,"\n"
|
1252
1563
|
return 0
|
@@ -1636,11 +1947,19 @@ EOT
|
|
1636
1947
|
app = find_app_by_name_or_id(args[0])
|
1637
1948
|
|
1638
1949
|
instance_ids = []
|
1639
|
-
|
1640
|
-
|
1641
|
-
|
1950
|
+
# API used to only return apps.appTiers
|
1951
|
+
# now returns detailed instance list as "instances"
|
1952
|
+
app_tiers = app['appTiers'] || []
|
1953
|
+
instances = app['instances']
|
1954
|
+
if instances.nil?
|
1955
|
+
instances = []
|
1956
|
+
app_tiers.each do |app_tier|
|
1957
|
+
instances += (app_tier['appInstances'] || []).collect {|it| it['instance']}.flatten().compact
|
1642
1958
|
end
|
1643
1959
|
end
|
1960
|
+
instances.each do |instance|
|
1961
|
+
instance_ids << instance['id']
|
1962
|
+
end
|
1644
1963
|
|
1645
1964
|
# container_ids = instance['containers']
|
1646
1965
|
# if options[:node_id] && container_ids.include?(options[:node_id])
|