aptible-cli 0.25.0 → 0.26.2
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 +53 -2
- data/README.md +83 -82
- data/aptible-cli.gemspec +5 -2
- data/docker-compose.yml +5 -1
- data/lib/aptible/cli/agent.rb +16 -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/renderer/text.rb +33 -2
- data/lib/aptible/cli/resource_formatter.rb +2 -2
- data/lib/aptible/cli/subcommands/apps.rb +2 -2
- data/lib/aptible/cli/subcommands/aws_accounts.rb +252 -0
- data/lib/aptible/cli/subcommands/config.rb +6 -6
- data/lib/aptible/cli/subcommands/db.rb +67 -3
- data/lib/aptible/cli/subcommands/deploy.rb +46 -12
- data/lib/aptible/cli/subcommands/organizations.rb +55 -0
- data/lib/aptible/cli/subcommands/rebuild.rb +2 -1
- data/lib/aptible/cli/subcommands/restart.rb +2 -1
- data/lib/aptible/cli/subcommands/services.rb +24 -12
- data/lib/aptible/cli/subcommands/ssh.rb +1 -1
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/agent_spec.rb +70 -0
- 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 +65 -7
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aptible/api'
|
|
4
|
+
|
|
5
|
+
module Aptible
|
|
6
|
+
module CLI
|
|
7
|
+
module Helpers
|
|
8
|
+
module AwsAccount
|
|
9
|
+
include Helpers::Token
|
|
10
|
+
|
|
11
|
+
def aws_accounts_href
|
|
12
|
+
if Renderer.format == 'json'
|
|
13
|
+
'/external_aws_accounts'
|
|
14
|
+
else
|
|
15
|
+
'/external_aws_accounts?per_page=5000&no_embed=true'
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def aws_accounts_all
|
|
20
|
+
Aptible::Api::ExternalAwsAccount.all(
|
|
21
|
+
token: fetch_token,
|
|
22
|
+
href: aws_accounts_href
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def aws_account_from_id(id)
|
|
27
|
+
Aptible::Api::ExternalAwsAccount.find(id.to_s, token: fetch_token)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def ensure_external_aws_account(id)
|
|
31
|
+
acct = aws_account_from_id(id)
|
|
32
|
+
if acct.nil?
|
|
33
|
+
raise Thor::Error, "External AWS account not found: #{id}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
acct
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def fetch_organization_id
|
|
40
|
+
orgs = Aptible::Auth::Organization.all(token: fetch_token)
|
|
41
|
+
raise Thor::Error, 'No organizations found, specify one with ' \
|
|
42
|
+
'--organization-id=ORG_ID' if orgs.empty?
|
|
43
|
+
raise Thor::Error, 'Multiple organizations found, indicate which ' \
|
|
44
|
+
'one to use with --organization-id=ORG_ID ' \
|
|
45
|
+
"\n\tFound organization ids:" \
|
|
46
|
+
"\n\t\t#{orgs.map do |o|
|
|
47
|
+
"#{o.id} (#{o.name})"
|
|
48
|
+
end.join("\n\t\t")}" \
|
|
49
|
+
if orgs.count > 1
|
|
50
|
+
|
|
51
|
+
orgs.first.id
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def organization_id_from_opts_or_auth(options)
|
|
55
|
+
return options[:organization_id] if options.key? :organization_id
|
|
56
|
+
|
|
57
|
+
fetch_organization_id
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def build_external_aws_account_attrs(options)
|
|
61
|
+
discovery_role_arn = if options[:remove_discovery_role_arn]
|
|
62
|
+
''
|
|
63
|
+
else
|
|
64
|
+
options[:discovery_role_arn]
|
|
65
|
+
end
|
|
66
|
+
discovery_enabled = if options.key?(:discovery_enabled)
|
|
67
|
+
options[:discovery_enabled]
|
|
68
|
+
end
|
|
69
|
+
attrs = {
|
|
70
|
+
account_name: options[:account_name] || options[:name],
|
|
71
|
+
aws_account_id: options[:aws_account_id],
|
|
72
|
+
aws_region_primary: options[:aws_region_primary],
|
|
73
|
+
status: options[:status],
|
|
74
|
+
discovery_enabled: discovery_enabled,
|
|
75
|
+
discovery_role_arn: discovery_role_arn,
|
|
76
|
+
discovery_frequency: options[:discovery_frequency]
|
|
77
|
+
}
|
|
78
|
+
attrs.reject { |_, v| v.nil? }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def create_external_aws_account!(options)
|
|
82
|
+
attrs = build_external_aws_account_attrs(options)
|
|
83
|
+
attrs[:organization_id] = organization_id_from_opts_or_auth(options)
|
|
84
|
+
begin
|
|
85
|
+
resource = Aptible::Api::ExternalAwsAccount.create(
|
|
86
|
+
token: fetch_token,
|
|
87
|
+
**attrs
|
|
88
|
+
)
|
|
89
|
+
if resource.errors.any?
|
|
90
|
+
raise Thor::Error, resource.errors.full_messages.first
|
|
91
|
+
end
|
|
92
|
+
resource
|
|
93
|
+
rescue HyperResource::ClientError => e
|
|
94
|
+
raise Thor::Error, e.message
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def update_external_aws_account!(id, options)
|
|
99
|
+
ext = ensure_external_aws_account(id)
|
|
100
|
+
attrs = build_external_aws_account_attrs(options)
|
|
101
|
+
begin
|
|
102
|
+
unless attrs.empty?
|
|
103
|
+
ext.update!(**attrs)
|
|
104
|
+
if ext.errors.any?
|
|
105
|
+
raise Thor::Error, ext.errors.full_messages.first
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
ext
|
|
109
|
+
rescue HyperResource::ClientError => e
|
|
110
|
+
raise Thor::Error, e.message
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def delete_external_aws_account!(id)
|
|
115
|
+
ext = ensure_external_aws_account(id)
|
|
116
|
+
begin
|
|
117
|
+
if ext.respond_to?(:destroy!)
|
|
118
|
+
ext.destroy!
|
|
119
|
+
elsif ext.respond_to?(:destroy)
|
|
120
|
+
ext.destroy
|
|
121
|
+
elsif ext.respond_to?(:delete!)
|
|
122
|
+
ext.delete!
|
|
123
|
+
elsif ext.respond_to?(:delete)
|
|
124
|
+
ext.delete
|
|
125
|
+
else
|
|
126
|
+
raise Thor::Error, 'Delete is not supported for this resource'
|
|
127
|
+
end
|
|
128
|
+
rescue HyperResource::ClientError => e
|
|
129
|
+
raise Thor::Error, e.message
|
|
130
|
+
end
|
|
131
|
+
true
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def check_external_aws_account!(id)
|
|
135
|
+
ext = ensure_external_aws_account(id)
|
|
136
|
+
begin
|
|
137
|
+
ext.check!
|
|
138
|
+
rescue HyperResource::ClientError => e
|
|
139
|
+
raise Thor::Error, e.message
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def format_check_state(state)
|
|
144
|
+
case state
|
|
145
|
+
when 'success'
|
|
146
|
+
'✅ success'
|
|
147
|
+
when 'failed'
|
|
148
|
+
'❌ failed'
|
|
149
|
+
when 'not_run'
|
|
150
|
+
'⏭️ not_run'
|
|
151
|
+
else
|
|
152
|
+
state
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -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
|
|
@@ -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.
|
|
@@ -203,8 +203,8 @@ module Aptible
|
|
|
203
203
|
node.value('type', 'https')
|
|
204
204
|
node.value('port', port)
|
|
205
205
|
node.value('load_balancing_algorithm_type', vhost
|
|
206
|
-
.load_balancing_algorithm_type)
|
|
207
|
-
node.value('shared', vhost.shared)
|
|
206
|
+
.load_balancing_algorithm_type || 'round_robin')
|
|
207
|
+
node.value('shared', vhost.shared || 'false')
|
|
208
208
|
end
|
|
209
209
|
|
|
210
210
|
node.value('internal', vhost.internal)
|
|
@@ -65,7 +65,7 @@ module Aptible
|
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
desc 'apps:scale SERVICE ' \
|
|
68
|
+
desc 'apps:scale [--app APP] SERVICE ' \
|
|
69
69
|
'[--container-count COUNT] [--container-size SIZE_MB] ' \
|
|
70
70
|
'[--container-profile PROFILE]',
|
|
71
71
|
'Scale a service'
|
|
@@ -107,7 +107,7 @@ module Aptible
|
|
|
107
107
|
attach_to_operation_logs(op)
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
desc 'apps:deprovision', 'Deprovision an app'
|
|
110
|
+
desc 'apps:deprovision [--app APP]', 'Deprovision an app'
|
|
111
111
|
app_options
|
|
112
112
|
define_method 'apps:deprovision' do
|
|
113
113
|
telemetry(__method__, options)
|