morpheus-cli 5.4.4.2 → 5.4.5
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/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])
|