aptible-cli 0.24.10 → 0.26.1
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/.github/workflows/release.yml +27 -0
- data/.github/workflows/test.yml +1 -1
- data/Dockerfile +8 -5
- data/Gemfile.lock +28 -17
- data/Makefile +50 -2
- data/README.md +2 -1
- data/aptible-cli.gemspec +5 -2
- data/docker-compose.yml +5 -1
- data/lib/aptible/cli/agent.rb +9 -0
- data/lib/aptible/cli/helpers/aws_account.rb +158 -0
- data/lib/aptible/cli/helpers/database.rb +182 -2
- data/lib/aptible/cli/helpers/token.rb +14 -0
- data/lib/aptible/cli/helpers/vhost/option_set_builder.rb +8 -15
- data/lib/aptible/cli/renderer/text.rb +33 -2
- data/lib/aptible/cli/subcommands/aws_accounts.rb +252 -0
- data/lib/aptible/cli/subcommands/db.rb +67 -3
- data/lib/aptible/cli/subcommands/deploy.rb +45 -11
- data/lib/aptible/cli/subcommands/endpoints.rb +0 -2
- data/lib/aptible/cli/subcommands/organizations.rb +55 -0
- data/lib/aptible/cli/subcommands/services.rb +20 -8
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/helpers/database_spec.rb +118 -0
- data/spec/aptible/cli/helpers/token_spec.rb +70 -0
- data/spec/aptible/cli/subcommands/db_spec.rb +553 -0
- data/spec/aptible/cli/subcommands/deploy_spec.rb +42 -7
- data/spec/aptible/cli/subcommands/external_aws_accounts_spec.rb +737 -0
- data/spec/aptible/cli/subcommands/organizations_spec.rb +90 -0
- data/spec/aptible/cli/subcommands/services_spec.rb +77 -0
- data/spec/fabricators/app_external_aws_rds_connection_fabricator.rb +55 -0
- data/spec/fabricators/external_aws_account_fabricator.rb +49 -0
- data/spec/fabricators/external_aws_database_credential_fabricator.rb +46 -0
- data/spec/fabricators/external_aws_resource_fabricator.rb +72 -0
- metadata +64 -6
|
@@ -8,6 +8,23 @@ module Aptible
|
|
|
8
8
|
include Helpers::Environment
|
|
9
9
|
include Helpers::Ssh
|
|
10
10
|
|
|
11
|
+
# RdsDatabase is a translation struct so the same renderer can be
|
|
12
|
+
# used for external_aws_resource as those for databases
|
|
13
|
+
RdsDatabase = Struct.new(:handle, :id, :created_at, :raw)
|
|
14
|
+
# MockRdsDatabaseAccountShell - there is no direct 1:1 mapping
|
|
15
|
+
# between accounts and external_aws_resources. Since this is
|
|
16
|
+
# coerced via app_external_aws_rds_connections, we use this
|
|
17
|
+
# struct to stub out those that are not found to be attached to
|
|
18
|
+
# any apps.
|
|
19
|
+
MockRdsDatabaseAccountShell = Struct.new(
|
|
20
|
+
:handle,
|
|
21
|
+
:id,
|
|
22
|
+
:created_at
|
|
23
|
+
)
|
|
24
|
+
# using an ID that cannot be hit for visual segregation of
|
|
25
|
+
# unattached databases
|
|
26
|
+
UNATTACHED_RDS_ACCOUNT_ID = -9999
|
|
27
|
+
|
|
11
28
|
def ensure_database(options = {})
|
|
12
29
|
db_handle = options[:db]
|
|
13
30
|
environment_handle = options[:environment]
|
|
@@ -46,6 +63,83 @@ module Aptible
|
|
|
46
63
|
)
|
|
47
64
|
end
|
|
48
65
|
|
|
66
|
+
def aws_rds_db?(handle)
|
|
67
|
+
handle.start_with? 'aws:rds::'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def external_rds_databases_map
|
|
71
|
+
external_rds_databases_all.map { |rds| [rds[:id], rds] }.to_h
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fetch_rds_databases_with_accounts
|
|
75
|
+
rds_map = external_rds_databases_map
|
|
76
|
+
accts_rds_map = accounts_external_rds_databases_map(rds_map)
|
|
77
|
+
[rds_map, accts_rds_map]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def accounts_external_rds_databases_map(rds_map)
|
|
81
|
+
return {} if rds_map.empty?
|
|
82
|
+
|
|
83
|
+
map_of_accounts_to_rds(rds_map)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def map_of_accounts_to_rds(rds_map)
|
|
87
|
+
# one rds db can be on multiple accounts
|
|
88
|
+
accts_rds_map = {}
|
|
89
|
+
rds_map.each_value do |db|
|
|
90
|
+
account = derive_account_from_conns(db)
|
|
91
|
+
next if account.nil?
|
|
92
|
+
|
|
93
|
+
accts_rds_map[account.id] = [] if accts_rds_map[account.id].nil?
|
|
94
|
+
accts_rds_map[account.id] << db
|
|
95
|
+
end
|
|
96
|
+
accts_rds_map
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def rds_shell_account
|
|
100
|
+
MockRdsDatabaseAccountShell.new(
|
|
101
|
+
'unattached rds databases',
|
|
102
|
+
UNATTACHED_RDS_ACCOUNT_ID
|
|
103
|
+
)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def external_rds_databases_all
|
|
107
|
+
Aptible::Api::ExternalAwsResource
|
|
108
|
+
.all(
|
|
109
|
+
token: fetch_token
|
|
110
|
+
)
|
|
111
|
+
.select { |db| db.resource_type == 'aws_rds_db_instance' }
|
|
112
|
+
.map do |db|
|
|
113
|
+
RdsDatabase.new(
|
|
114
|
+
"aws:rds::#{db.resource_name}",
|
|
115
|
+
db.id,
|
|
116
|
+
db.created_at,
|
|
117
|
+
db
|
|
118
|
+
)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def derive_account_from_conns(db, preferred_acct = nil)
|
|
123
|
+
conns = db.raw.app_external_aws_rds_connections
|
|
124
|
+
return nil if conns.empty?
|
|
125
|
+
|
|
126
|
+
if preferred_acct.present?
|
|
127
|
+
valid_conns = conns.find do |conn|
|
|
128
|
+
conn.present? && conn.app.account.id == preferred_acct.id
|
|
129
|
+
end
|
|
130
|
+
return nil if valid_conns.nil?
|
|
131
|
+
return valid_conns.app.account
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
first_present_conn = conns.find(&:present?)
|
|
135
|
+
return nil if first_present_conn.nil?
|
|
136
|
+
first_present_conn.app.account
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def external_rds_database_from_handle(handle)
|
|
140
|
+
external_rds_databases_all.find { |a| a.handle == handle }
|
|
141
|
+
end
|
|
142
|
+
|
|
49
143
|
def databases_from_handle(handle, environment)
|
|
50
144
|
databases = if environment
|
|
51
145
|
environment.databases
|
|
@@ -90,8 +184,19 @@ module Aptible
|
|
|
90
184
|
|
|
91
185
|
# Creates a local tunnel and yields the helper
|
|
92
186
|
|
|
93
|
-
def with_local_tunnel(credential, port = 0)
|
|
94
|
-
op =
|
|
187
|
+
def with_local_tunnel(credential, port = 0, target_account = nil)
|
|
188
|
+
op = if target_account.nil?
|
|
189
|
+
credential.create_operation!(
|
|
190
|
+
type: 'tunnel',
|
|
191
|
+
status: 'succeeded'
|
|
192
|
+
)
|
|
193
|
+
else
|
|
194
|
+
credential.create_operation!(
|
|
195
|
+
type: 'tunnel',
|
|
196
|
+
status: 'succeeded',
|
|
197
|
+
destination_account: target_account.id
|
|
198
|
+
)
|
|
199
|
+
end
|
|
95
200
|
|
|
96
201
|
with_ssh_cmd(op) do |base_ssh_cmd, ssh_credential|
|
|
97
202
|
ssh_cmd = base_ssh_cmd + ['-o', 'SendEnv=ACCESS_TOKEN']
|
|
@@ -106,6 +211,72 @@ module Aptible
|
|
|
106
211
|
end
|
|
107
212
|
end
|
|
108
213
|
|
|
214
|
+
def with_rds_tunnel(handle, port = 0)
|
|
215
|
+
external_rds = external_rds_database_from_handle(handle)
|
|
216
|
+
if external_rds.nil?
|
|
217
|
+
raise Thor::Error, "No rds db found with handle #{handle}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
credential = external_rds.raw.external_aws_database_credentials.first
|
|
221
|
+
if credential.nil?
|
|
222
|
+
raise Thor::Error, 'No rds credential found with handle ' \
|
|
223
|
+
"#{handle}. Check to see if you have run " \
|
|
224
|
+
'db:attach or a scan has properly completed.'
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
target_account = derive_account_from_conns(external_rds)
|
|
228
|
+
if target_account.nil?
|
|
229
|
+
raise Thor::Error,
|
|
230
|
+
"No env for rds found with handle #{handle}. Check to see " \
|
|
231
|
+
'if you have run db:attach or a scan has properly completed.'
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
with_local_tunnel(credential, port, target_account) do |tunnel_helper|
|
|
235
|
+
url = local_rds_url(credential, tunnel_helper.port, target_account)
|
|
236
|
+
yield url, tunnel_helper
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def use_rds_tunnel(handle, port)
|
|
241
|
+
with_rds_tunnel(handle, port) do |url, tunnel_helper|
|
|
242
|
+
CLI.logger.info "Connect at #{url}"
|
|
243
|
+
|
|
244
|
+
uri = URI(url)
|
|
245
|
+
db = uri.path.gsub(%r{^/}, '')
|
|
246
|
+
CLI.logger.info 'Or, use the following arguments:'
|
|
247
|
+
CLI.logger.info "* Host: #{uri.host}"
|
|
248
|
+
CLI.logger.info "* Port: #{uri.port}"
|
|
249
|
+
CLI.logger.info "* Username: #{uri.user}" unless uri.user.empty?
|
|
250
|
+
CLI.logger.info "* Password: #{uri.password}"
|
|
251
|
+
CLI.logger.info "* Database: #{db}" unless db.empty?
|
|
252
|
+
|
|
253
|
+
CLI.logger.info 'Connected. Ctrl-C to close connection.'
|
|
254
|
+
|
|
255
|
+
begin
|
|
256
|
+
tunnel_helper.wait
|
|
257
|
+
rescue Interrupt
|
|
258
|
+
CLI.logger.warn 'Closing tunnel'
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def use_rds_dump(handle, filename, dump_options)
|
|
264
|
+
with_rds_tunnel(handle) do |url|
|
|
265
|
+
CLI.logger.info "Dumping to #{filename}"
|
|
266
|
+
`pg_dump #{url} #{dump_options.shelljoin} > #{filename}`
|
|
267
|
+
exit $CHILD_STATUS.exitstatus unless $CHILD_STATUS.success?
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def use_rds_execute(handle, sql_path, options)
|
|
272
|
+
with_rds_tunnel(handle) do |url|
|
|
273
|
+
CLI.logger.info "Executing #{sql_path} against #{handle}"
|
|
274
|
+
args = options[:on_error_stop] ? '-v ON_ERROR_STOP=true ' : ''
|
|
275
|
+
`psql #{args}#{url} < #{sql_path}`
|
|
276
|
+
exit $CHILD_STATUS.exitstatus unless $CHILD_STATUS.success?
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
109
280
|
# Creates a local PG tunnel and yields the url to it
|
|
110
281
|
|
|
111
282
|
def with_postgres_tunnel(database)
|
|
@@ -120,6 +291,15 @@ module Aptible
|
|
|
120
291
|
end
|
|
121
292
|
end
|
|
122
293
|
|
|
294
|
+
def local_rds_url(credential, local_port, forced_account)
|
|
295
|
+
remote_url = credential.connection_url
|
|
296
|
+
|
|
297
|
+
uri = URI.parse(remote_url)
|
|
298
|
+
domain = forced_account.stack.internal_domain
|
|
299
|
+
"#{uri.scheme}://#{uri.user}:#{uri.password}@" \
|
|
300
|
+
"localhost.#{domain}:#{local_port}#{uri.path}"
|
|
301
|
+
end
|
|
302
|
+
|
|
123
303
|
def local_url(credential, local_port)
|
|
124
304
|
remote_url = credential.connection_url
|
|
125
305
|
|
|
@@ -52,6 +52,20 @@ module Aptible
|
|
|
52
52
|
tok = fetch_token
|
|
53
53
|
JWT.decode(tok, nil, false)
|
|
54
54
|
end
|
|
55
|
+
|
|
56
|
+
# Instance of Aptible::Auth::Token from current token
|
|
57
|
+
def current_token
|
|
58
|
+
Aptible::Auth::Token.current_token(token: fetch_token)
|
|
59
|
+
rescue HyperResource::ClientError => e
|
|
60
|
+
raise Thor::Error, e.message
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Instance of Aptible::Auth::User associated with current token
|
|
64
|
+
def whoami
|
|
65
|
+
current_token.user
|
|
66
|
+
rescue HyperResource::ClientError => e
|
|
67
|
+
raise Thor::Error, e.message
|
|
68
|
+
end
|
|
55
69
|
end
|
|
56
70
|
end
|
|
57
71
|
end
|
|
@@ -12,7 +12,6 @@ module Aptible
|
|
|
12
12
|
ports
|
|
13
13
|
port
|
|
14
14
|
alb
|
|
15
|
-
shared
|
|
16
15
|
).freeze
|
|
17
16
|
|
|
18
17
|
def initialize(&block)
|
|
@@ -63,6 +62,13 @@ module Aptible
|
|
|
63
62
|
'least_outstanding_requests, and ' \
|
|
64
63
|
'weighted_random'
|
|
65
64
|
)
|
|
65
|
+
|
|
66
|
+
option(
|
|
67
|
+
:shared,
|
|
68
|
+
type: :boolean,
|
|
69
|
+
desc: "Share this Endpoint's load balancer with other " \
|
|
70
|
+
'Endpoints'
|
|
71
|
+
)
|
|
66
72
|
end
|
|
67
73
|
end
|
|
68
74
|
|
|
@@ -123,15 +129,6 @@ module Aptible
|
|
|
123
129
|
'on this Endpoint'
|
|
124
130
|
)
|
|
125
131
|
end
|
|
126
|
-
|
|
127
|
-
if builder.shared?
|
|
128
|
-
option(
|
|
129
|
-
:shared,
|
|
130
|
-
type: :boolean,
|
|
131
|
-
desc: "Share this Endpoint's load balancer with other " \
|
|
132
|
-
'Endpoints'
|
|
133
|
-
)
|
|
134
|
-
end
|
|
135
132
|
end
|
|
136
133
|
end
|
|
137
134
|
|
|
@@ -203,11 +200,7 @@ module Aptible
|
|
|
203
200
|
params[:load_balancing_algorithm_type] = lba_type
|
|
204
201
|
end
|
|
205
202
|
|
|
206
|
-
|
|
207
|
-
params[:shared] = options.delete(:shared) do
|
|
208
|
-
create? ? false : nil
|
|
209
|
-
end
|
|
210
|
-
end
|
|
203
|
+
params[:shared] = options.delete(:shared)
|
|
211
204
|
end
|
|
212
205
|
|
|
213
206
|
options.delete(:environment)
|
|
@@ -32,8 +32,14 @@ module Aptible
|
|
|
32
32
|
# children are KeyedObject instances so they can render properly,
|
|
33
33
|
# but we need to warn in tests that this is required.
|
|
34
34
|
node.children.each_pair do |k, c|
|
|
35
|
-
io.print "#{format_key(k)}:
|
|
36
|
-
|
|
35
|
+
io.print "#{format_key(k)}:"
|
|
36
|
+
if c.is_a?(Formatter::List)
|
|
37
|
+
io.puts
|
|
38
|
+
visit_indented(c, io, ' ')
|
|
39
|
+
else
|
|
40
|
+
io.print ' '
|
|
41
|
+
visit(c, io)
|
|
42
|
+
end
|
|
37
43
|
end
|
|
38
44
|
when Formatter::GroupedKeyedList
|
|
39
45
|
enum = spacer_enumerator
|
|
@@ -65,6 +71,31 @@ module Aptible
|
|
|
65
71
|
|
|
66
72
|
private
|
|
67
73
|
|
|
74
|
+
def visit_indented(node, io, indent)
|
|
75
|
+
return unless node.is_a?(Formatter::List)
|
|
76
|
+
|
|
77
|
+
node.children.each do |child|
|
|
78
|
+
case child
|
|
79
|
+
when Formatter::Object
|
|
80
|
+
child.children.each_pair do |k, c|
|
|
81
|
+
io.print "#{indent}#{format_key(k)}:"
|
|
82
|
+
if c.is_a?(Formatter::List)
|
|
83
|
+
io.puts
|
|
84
|
+
visit_indented(c, io, indent + ' ')
|
|
85
|
+
else
|
|
86
|
+
io.print ' '
|
|
87
|
+
visit(c, io)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
io.puts unless child == node.children.last
|
|
91
|
+
when Formatter::Value
|
|
92
|
+
io.puts "#{indent}#{child.value}"
|
|
93
|
+
else
|
|
94
|
+
visit(child, io)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
68
99
|
def output_list(nodes, io)
|
|
69
100
|
if nodes.all? { |v| v.is_a?(Formatter::Value) }
|
|
70
101
|
# All nodes are single values, so we render one per line.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aptible
|
|
4
|
+
module CLI
|
|
5
|
+
module Subcommands
|
|
6
|
+
module AwsAccounts
|
|
7
|
+
def self.included(thor)
|
|
8
|
+
thor.class_eval do
|
|
9
|
+
include Helpers::Token
|
|
10
|
+
include Helpers::AwsAccount
|
|
11
|
+
include Helpers::Telemetry
|
|
12
|
+
|
|
13
|
+
desc 'aws_accounts', 'List external AWS accounts', hide: true
|
|
14
|
+
option :organization_id, aliases: '--org-id',
|
|
15
|
+
type: :string,
|
|
16
|
+
default: nil,
|
|
17
|
+
desc: 'Organization ID'
|
|
18
|
+
def aws_accounts
|
|
19
|
+
telemetry(__method__, options)
|
|
20
|
+
|
|
21
|
+
accounts = aws_accounts_all
|
|
22
|
+
|
|
23
|
+
Formatter.render(Renderer.current) do |root|
|
|
24
|
+
root.list do |list|
|
|
25
|
+
accounts.each do |ext|
|
|
26
|
+
list.object do |node|
|
|
27
|
+
node.value('id', ext.id) if ext.respond_to?(:id)
|
|
28
|
+
attrs = ext.respond_to?(:attributes) ? ext.attributes : {}
|
|
29
|
+
%w(
|
|
30
|
+
aws_account_id
|
|
31
|
+
account_name
|
|
32
|
+
aws_region_primary
|
|
33
|
+
status
|
|
34
|
+
discovery_enabled
|
|
35
|
+
discovery_role_arn
|
|
36
|
+
discovery_frequency
|
|
37
|
+
account_id
|
|
38
|
+
created_at
|
|
39
|
+
updated_at
|
|
40
|
+
).each do |k|
|
|
41
|
+
v = attrs[k]
|
|
42
|
+
node.value(k, v) unless v.nil?
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc 'aws_accounts:add ' \
|
|
51
|
+
'[--account-name ACCOUNT_NAME] ' \
|
|
52
|
+
'[--aws-account-id AWS_ACCOUNT_ID] ' \
|
|
53
|
+
'[--org-id ORGANIZATION_ID] '\
|
|
54
|
+
'[--aws-region-primary AWS_REGION] ' \
|
|
55
|
+
'[--discovery-enabled|--no-discovery-enabled] ' \
|
|
56
|
+
'[--discovery-role-arn DISCOVERY_ROLE_ARN] ' \
|
|
57
|
+
'[--discovery-frequency FREQ]', \
|
|
58
|
+
'Add a new external AWS account', hide: true
|
|
59
|
+
option :account_name, type: :string, desc: 'Display name'
|
|
60
|
+
option :aws_account_id, type: :string, desc: 'AWS Account ID'
|
|
61
|
+
option :organization_id, aliases: '--org-id',
|
|
62
|
+
type: :string,
|
|
63
|
+
default: nil,
|
|
64
|
+
desc: 'Organization ID'
|
|
65
|
+
option :aws_region_primary, type: :string,
|
|
66
|
+
desc: 'Primary AWS region'
|
|
67
|
+
option :discovery_enabled, type: :boolean,
|
|
68
|
+
desc: 'Enable resource discovery'
|
|
69
|
+
option :discovery_role_arn, type: :string,
|
|
70
|
+
desc: 'IAM Role ARN that Aptible ' \
|
|
71
|
+
'will assume to discover ' \
|
|
72
|
+
'resources in your AWS account'
|
|
73
|
+
option :discovery_frequency,
|
|
74
|
+
type: :string,
|
|
75
|
+
desc: 'Discovery frequency (e.g., daily)'
|
|
76
|
+
define_method 'aws_accounts:add' do
|
|
77
|
+
telemetry(__method__, options)
|
|
78
|
+
|
|
79
|
+
resource = create_external_aws_account!(options)
|
|
80
|
+
|
|
81
|
+
Formatter.render(Renderer.current) do |root|
|
|
82
|
+
root.object do |node|
|
|
83
|
+
node.value('id', resource.id) if resource.respond_to?(:id)
|
|
84
|
+
rattrs =
|
|
85
|
+
if resource.respond_to?(:attributes)
|
|
86
|
+
resource.attributes
|
|
87
|
+
else
|
|
88
|
+
{}
|
|
89
|
+
end
|
|
90
|
+
%w(
|
|
91
|
+
aws_account_id
|
|
92
|
+
account_name
|
|
93
|
+
aws_region_primary
|
|
94
|
+
discovery_enabled
|
|
95
|
+
discovery_role_arn
|
|
96
|
+
discovery_frequency
|
|
97
|
+
account_id
|
|
98
|
+
created_at
|
|
99
|
+
updated_at
|
|
100
|
+
).each do |k|
|
|
101
|
+
v = rattrs[k]
|
|
102
|
+
node.value(k, v) unless v.nil?
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
desc 'aws_accounts:show ID',
|
|
109
|
+
'Show an external AWS account', \
|
|
110
|
+
hide: true
|
|
111
|
+
define_method 'aws_accounts:show' do |id|
|
|
112
|
+
telemetry(__method__, options.merge(id: id))
|
|
113
|
+
ext = ensure_external_aws_account(id)
|
|
114
|
+
Formatter.render(Renderer.current) do |root|
|
|
115
|
+
root.object do |node|
|
|
116
|
+
node.value('id', ext.id)
|
|
117
|
+
rattrs =
|
|
118
|
+
if ext.respond_to?(:attributes)
|
|
119
|
+
ext.attributes
|
|
120
|
+
else
|
|
121
|
+
{}
|
|
122
|
+
end
|
|
123
|
+
%w(
|
|
124
|
+
aws_account_id
|
|
125
|
+
account_name
|
|
126
|
+
aws_region_primary
|
|
127
|
+
discovery_enabled
|
|
128
|
+
discovery_role_arn
|
|
129
|
+
discovery_frequency
|
|
130
|
+
account_id
|
|
131
|
+
created_at
|
|
132
|
+
updated_at
|
|
133
|
+
).each do |k|
|
|
134
|
+
v = rattrs[k]
|
|
135
|
+
node.value(k, v) unless v.nil?
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
desc 'aws_accounts:delete ID',
|
|
142
|
+
'Delete an external AWS account', \
|
|
143
|
+
hide: true
|
|
144
|
+
define_method 'aws_accounts:delete' do |id|
|
|
145
|
+
telemetry(__method__, options.merge(id: id))
|
|
146
|
+
|
|
147
|
+
delete_external_aws_account!(id)
|
|
148
|
+
|
|
149
|
+
Formatter.render(Renderer.current) do |root|
|
|
150
|
+
root.object do |node|
|
|
151
|
+
node.value('id', id)
|
|
152
|
+
node.value('deleted', true)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
desc 'aws_accounts:update ID ' \
|
|
158
|
+
'[--account-name ACCOUNT_NAME] ' \
|
|
159
|
+
'[--aws-account-id AWS_ACCOUNT_ID] ' \
|
|
160
|
+
'[--aws-region-primary AWS_REGION] ' \
|
|
161
|
+
'[--discovery-enabled|--no-discovery-enabled] ' \
|
|
162
|
+
'[--discovery-role-arn DISCOVERY_ROLE_ARN] ' \
|
|
163
|
+
'[--remove-discovery-role-arn] ' \
|
|
164
|
+
'[--discovery-frequency FREQ]', \
|
|
165
|
+
'Update an external AWS account', hide: true
|
|
166
|
+
option :account_name, type: :string, desc: 'New display name'
|
|
167
|
+
option :aws_account_id, type: :string, desc: 'AWS Account ID'
|
|
168
|
+
option :aws_region_primary, type: :string,
|
|
169
|
+
desc: 'Primary AWS region'
|
|
170
|
+
option :discovery_enabled, type: :boolean,
|
|
171
|
+
desc: 'Enable resource discovery'
|
|
172
|
+
option :discovery_role_arn, type: :string,
|
|
173
|
+
desc: 'IAM Role ARN that Aptible ' \
|
|
174
|
+
'will assume to discover ' \
|
|
175
|
+
'resources in your AWS account'
|
|
176
|
+
option :remove_discovery_role_arn, type: :boolean,
|
|
177
|
+
desc: 'Remove the discovery ' \
|
|
178
|
+
'role ARN from this ' \
|
|
179
|
+
'account'
|
|
180
|
+
option :discovery_frequency,
|
|
181
|
+
type: :string,
|
|
182
|
+
desc: 'Discovery frequency (e.g., daily)'
|
|
183
|
+
define_method 'aws_accounts:update' do |id|
|
|
184
|
+
telemetry(__method__, options.merge(id: id))
|
|
185
|
+
|
|
186
|
+
ext = update_external_aws_account!(id, options)
|
|
187
|
+
|
|
188
|
+
Formatter.render(Renderer.current) do |root|
|
|
189
|
+
root.object do |node|
|
|
190
|
+
node.value('id', ext.id)
|
|
191
|
+
rattrs =
|
|
192
|
+
if ext.respond_to?(:attributes)
|
|
193
|
+
ext.attributes
|
|
194
|
+
else
|
|
195
|
+
{}
|
|
196
|
+
end
|
|
197
|
+
%w(
|
|
198
|
+
aws_account_id
|
|
199
|
+
account_name
|
|
200
|
+
aws_region_primary
|
|
201
|
+
discovery_enabled
|
|
202
|
+
discovery_role_arn
|
|
203
|
+
discovery_frequency
|
|
204
|
+
account_id
|
|
205
|
+
created_at
|
|
206
|
+
updated_at
|
|
207
|
+
).each do |k|
|
|
208
|
+
v = rattrs[k]
|
|
209
|
+
node.value(k, v) unless v.nil?
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
desc 'aws_accounts:check ID',
|
|
216
|
+
'Check the connection for an external AWS account', \
|
|
217
|
+
hide: true
|
|
218
|
+
define_method 'aws_accounts:check' do |id|
|
|
219
|
+
telemetry(__method__, options.merge(id: id))
|
|
220
|
+
|
|
221
|
+
response = check_external_aws_account!(id)
|
|
222
|
+
|
|
223
|
+
fmt_state = lambda do |state|
|
|
224
|
+
Renderer.format == 'json' ? state : format_check_state(state)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
Formatter.render(Renderer.current) do |root|
|
|
228
|
+
root.object do |node|
|
|
229
|
+
node.value('state', fmt_state.call(response.state))
|
|
230
|
+
node.list('checks') do |check_list|
|
|
231
|
+
response.checks.each do |check|
|
|
232
|
+
check_list.object do |check_node|
|
|
233
|
+
check_node.value('name', check.check_name)
|
|
234
|
+
check_node.value('state', fmt_state.call(check.state))
|
|
235
|
+
check_node.value('details', check.details) \
|
|
236
|
+
unless check.details.nil?
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
unless response.state == 'success'
|
|
244
|
+
raise Thor::Error, 'AWS account check failed'
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|