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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 035407b70b8d466e8167c8c82dcc564c1b1ec3890411efc1f0cada46f29b5fb7
4
- data.tar.gz: 1af6373e32fd55e17031919e958987c3928f3cc9dd1537bdd03fa245f5d41c63
3
+ metadata.gz: 5644f86d51c4b7199d89f570fc4f558dfc300581a358cf6c432782920d47063d
4
+ data.tar.gz: 1e772e9ff9884067b9f76241c83886785d7e219030818ebfd724a285e8ff885c
5
5
  SHA512:
6
- metadata.gz: 5635c593c0a181fab9635275458448336b593f87462e53586b5caf6d255710d3fcb2e749772b6a03ff68e3362a73c7e764e4df408dd3b8b7cd7a5bc0082e3e85
7
- data.tar.gz: 10eb5b76228b348d8734c48014687a91adb2fece61d63905bdea17699b3d0a2751e3d509f12fe77e78871cf954c3bfe22443d48feb43d8af8da698d77a037f24
6
+ metadata.gz: 276cfc3c70d0cde328a0e5aac930580a53158d868d0716753badfe13617a92e7e372ca29edf06dc392aad28b3a98b08f0d6480eb819d6f0f33b99075174939e6
7
+ data.tar.gz: 2ed27747e8fda3675cbb05e38ac5846496894430570531269e4d6e8954d9fd050022928e52ba66702628370011d470dfaa992198b8965d0b208e30bbbba539e6
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM ruby:2.5.1
2
2
 
3
- RUN gem install morpheus-cli -v 5.4.4.1
3
+ RUN gem install morpheus-cli -v 5.4.5
4
4
 
5
5
  ENTRYPOINT ["morpheus"]
@@ -57,20 +57,34 @@ class Morpheus::AppsInterface < Morpheus::APIClient
57
57
  execute(opts)
58
58
  end
59
59
 
60
- def apply(app_id, params, payload)
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 prepare_apply(app_id, params, payload)
68
- url = "#{@base_url}/api/apps/#{app_id}/prepare-apply"
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 state_summary(id, params={})
390
- url = "#{@base_url}/api/instances/#{id}/state-summary"
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
- app_tiers = app['appTiers']
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
- app_tiers = it['appTiers'] || []
701
- app_tiers.each do |app_tier|
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
- instances = (app_tier['appInstances'] || []).collect {|it| it['instance']}
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: status_string, type: instance['instanceType']['name'], group: !instance['group'].nil? ? instance['group']['name'] : nil, cloud: !instance['cloud'].nil? ? instance['cloud']['name'] : nil}
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 apply(args)
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
- Apply an app.
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
- verify_args!(args:args, optparse:optparse, count:1)
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
- # return _get(app['id'], options)
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
- app['appTiers'].each do |app_tier|
1246
- app_tier['appInstances'].each do |app_instance|
1247
- container_ids += app_instance['instance']['containers']
1248
- end if app_tier['appInstances']
1249
- end if app['appTiers']
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
- app['appTiers'].each do |app_tier|
1640
- app_tier['appInstances'].each do |app_instance|
1641
- instance_ids << app_instance['instance']['id']
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])