aptible-cli 0.14.1 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -1
- data/aptible-cli.gemspec +1 -0
- data/bin/aptible +9 -5
- data/lib/aptible/cli.rb +36 -0
- data/lib/aptible/cli/agent.rb +10 -6
- data/lib/aptible/cli/error.rb +6 -0
- data/lib/aptible/cli/formatter.rb +21 -0
- data/lib/aptible/cli/formatter/grouped_keyed_list.rb +54 -0
- data/lib/aptible/cli/formatter/keyed_list.rb +25 -0
- data/lib/aptible/cli/formatter/keyed_object.rb +16 -0
- data/lib/aptible/cli/formatter/list.rb +33 -0
- data/lib/aptible/cli/formatter/node.rb +8 -0
- data/lib/aptible/cli/formatter/object.rb +38 -0
- data/lib/aptible/cli/formatter/root.rb +46 -0
- data/lib/aptible/cli/formatter/value.rb +25 -0
- data/lib/aptible/cli/helpers/app.rb +1 -0
- data/lib/aptible/cli/helpers/database.rb +22 -6
- data/lib/aptible/cli/helpers/operation.rb +3 -2
- data/lib/aptible/cli/helpers/tunnel.rb +1 -3
- data/lib/aptible/cli/helpers/vhost.rb +9 -46
- data/lib/aptible/cli/renderer.rb +26 -0
- data/lib/aptible/cli/renderer/base.rb +8 -0
- data/lib/aptible/cli/renderer/json.rb +26 -0
- data/lib/aptible/cli/renderer/text.rb +99 -0
- data/lib/aptible/cli/resource_formatter.rb +136 -0
- data/lib/aptible/cli/subcommands/apps.rb +26 -14
- data/lib/aptible/cli/subcommands/backup.rb +22 -4
- data/lib/aptible/cli/subcommands/config.rb +15 -11
- data/lib/aptible/cli/subcommands/db.rb +82 -31
- data/lib/aptible/cli/subcommands/deploy.rb +1 -1
- data/lib/aptible/cli/subcommands/endpoints.rb +11 -8
- data/lib/aptible/cli/subcommands/operation.rb +2 -1
- data/lib/aptible/cli/subcommands/rebuild.rb +1 -1
- data/lib/aptible/cli/subcommands/restart.rb +1 -1
- data/lib/aptible/cli/subcommands/services.rb +8 -9
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/agent_spec.rb +11 -14
- data/spec/aptible/cli/formatter_spec.rb +4 -0
- data/spec/aptible/cli/renderer/json_spec.rb +63 -0
- data/spec/aptible/cli/renderer/text_spec.rb +150 -0
- data/spec/aptible/cli/resource_formatter_spec.rb +113 -0
- data/spec/aptible/cli/subcommands/apps_spec.rb +144 -28
- data/spec/aptible/cli/subcommands/backup_spec.rb +37 -16
- data/spec/aptible/cli/subcommands/config_spec.rb +95 -0
- data/spec/aptible/cli/subcommands/db_spec.rb +185 -93
- data/spec/aptible/cli/subcommands/endpoints_spec.rb +10 -8
- data/spec/aptible/cli/subcommands/operation_spec.rb +0 -1
- data/spec/aptible/cli/subcommands/rebuild_spec.rb +17 -0
- data/spec/aptible/cli/subcommands/services_spec.rb +8 -12
- data/spec/aptible/cli_spec.rb +31 -0
- data/spec/fabricators/account_fabricator.rb +11 -0
- data/spec/fabricators/app_fabricator.rb +15 -0
- data/spec/fabricators/configuration_fabricator.rb +8 -0
- data/spec/fabricators/database_image_fabricator.rb +17 -0
- data/spec/fabricators/operation_fabricator.rb +1 -0
- data/spec/fabricators/service_fabricator.rb +4 -0
- data/spec/spec_helper.rb +63 -1
- metadata +55 -4
- data/spec/aptible/cli/helpers/vhost_spec.rb +0 -105
@@ -11,12 +11,19 @@ module Aptible
|
|
11
11
|
desc 'apps', 'List all applications'
|
12
12
|
option :environment
|
13
13
|
def apps
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
Formatter.render(Renderer.current) do |root|
|
15
|
+
root.grouped_keyed_list(
|
16
|
+
{ 'environment' => 'handle' },
|
17
|
+
'handle'
|
18
|
+
) do |node|
|
19
|
+
scoped_environments(options).each do |account|
|
20
|
+
account.each_app do |app|
|
21
|
+
node.object do |n|
|
22
|
+
ResourceFormatter.inject_app(n, app, account)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
18
26
|
end
|
19
|
-
say ''
|
20
27
|
end
|
21
28
|
end
|
22
29
|
|
@@ -29,8 +36,13 @@ module Aptible
|
|
29
36
|
if app.errors.any?
|
30
37
|
raise Thor::Error, app.errors.full_messages.first
|
31
38
|
else
|
32
|
-
|
33
|
-
|
39
|
+
CLI.logger.info "App #{handle} created!"
|
40
|
+
|
41
|
+
Formatter.render(Renderer.current) do |root|
|
42
|
+
root.object do |o|
|
43
|
+
o.value('git_remote', app.git_repo)
|
44
|
+
end
|
45
|
+
end
|
34
46
|
end
|
35
47
|
end
|
36
48
|
|
@@ -56,9 +68,9 @@ module Aptible
|
|
56
68
|
# Noop
|
57
69
|
when 1
|
58
70
|
if container_count.nil?
|
59
|
-
m =
|
60
|
-
|
61
|
-
|
71
|
+
m = 'Passing container count as a positional ' \
|
72
|
+
'argument is deprecated, use --container-count'
|
73
|
+
CLI.logger.warn(m)
|
62
74
|
container_count = Integer(more.first)
|
63
75
|
else
|
64
76
|
raise Thor::Error, 'Container count was passed via both ' \
|
@@ -77,9 +89,9 @@ module Aptible
|
|
77
89
|
|
78
90
|
if options[:size]
|
79
91
|
if container_size.nil?
|
80
|
-
m =
|
81
|
-
|
82
|
-
|
92
|
+
m = 'Passing container size via the --size keyword ' \
|
93
|
+
'argument is deprecated, use --container-size'
|
94
|
+
CLI.logger.warn(m)
|
83
95
|
container_size = options[:size]
|
84
96
|
else
|
85
97
|
raise Thor::Error, 'Container size was passed via both ' \
|
@@ -106,7 +118,7 @@ module Aptible
|
|
106
118
|
app_options
|
107
119
|
define_method 'apps:deprovision' do
|
108
120
|
app = ensure_app(options)
|
109
|
-
|
121
|
+
CLI.logger.info "Deprovisioning #{app.handle}..."
|
110
122
|
app.create_operation!(type: 'deprovision')
|
111
123
|
end
|
112
124
|
end
|
@@ -40,8 +40,13 @@ module Aptible
|
|
40
40
|
}.delete_if { |_, v| v.nil? }
|
41
41
|
|
42
42
|
operation = backup.create_operation!(opts)
|
43
|
-
|
43
|
+
CLI.logger.info "Restoring backup into #{handle}"
|
44
44
|
attach_to_operation_logs(operation)
|
45
|
+
|
46
|
+
account = destination_account || backup.account
|
47
|
+
|
48
|
+
database = databases_from_handle(handle, account).first
|
49
|
+
render_database(database, account)
|
45
50
|
end
|
46
51
|
|
47
52
|
desc 'backup:list DB_HANDLE', 'List backups for a database'
|
@@ -55,9 +60,22 @@ module Aptible
|
|
55
60
|
min_created_at = Time.now - age
|
56
61
|
|
57
62
|
database = ensure_database(options.merge(db: handle))
|
58
|
-
|
59
|
-
|
60
|
-
|
63
|
+
|
64
|
+
Formatter.render(Renderer.current) do |root|
|
65
|
+
root.keyed_list('description') do |l|
|
66
|
+
database.each_backup do |backup|
|
67
|
+
break if backup.created_at < min_created_at
|
68
|
+
description = "#{backup.id}: #{backup.created_at}, " \
|
69
|
+
"#{backup.aws_region}"
|
70
|
+
|
71
|
+
l.object do |o|
|
72
|
+
o.value('id', backup.id)
|
73
|
+
o.value('description', description)
|
74
|
+
o.value('created_at', backup.created_at)
|
75
|
+
o.value('region', backup.aws_region)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
61
79
|
end
|
62
80
|
end
|
63
81
|
end
|
@@ -14,8 +14,19 @@ module Aptible
|
|
14
14
|
def config
|
15
15
|
app = ensure_app(options)
|
16
16
|
config = app.current_configuration
|
17
|
-
env = config ? config.env :
|
18
|
-
|
17
|
+
env = config ? config.env : {}
|
18
|
+
|
19
|
+
Formatter.render(Renderer.current) do |root|
|
20
|
+
root.keyed_list('shell_export') do |list|
|
21
|
+
env.each_pair do |k, v|
|
22
|
+
list.object do |node|
|
23
|
+
node.value('key', k)
|
24
|
+
node.value('value', v)
|
25
|
+
node.value('shell_export', "#{k}=#{Shellwords.escape(v)}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
19
30
|
end
|
20
31
|
|
21
32
|
desc 'config:add', 'Add an ENV variable to an app'
|
@@ -25,7 +36,7 @@ module Aptible
|
|
25
36
|
app = ensure_app(options)
|
26
37
|
env = extract_env(args)
|
27
38
|
operation = app.create_operation!(type: 'configure', env: env)
|
28
|
-
|
39
|
+
CLI.logger.info 'Updating configuration and restarting app...'
|
29
40
|
attach_to_operation_logs(operation)
|
30
41
|
end
|
31
42
|
|
@@ -45,7 +56,7 @@ module Aptible
|
|
45
56
|
[arg, '']
|
46
57
|
end]
|
47
58
|
operation = app.create_operation!(type: 'configure', env: env)
|
48
|
-
|
59
|
+
CLI.logger.info 'Updating configuration and restarting app...'
|
49
60
|
attach_to_operation_logs(operation)
|
50
61
|
end
|
51
62
|
|
@@ -54,13 +65,6 @@ module Aptible
|
|
54
65
|
define_method 'config:unset' do |*args|
|
55
66
|
send('config:rm', *args)
|
56
67
|
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
def formatted_config(env)
|
61
|
-
env = Hash[env.sort]
|
62
|
-
env.map { |k, v| "#{k}=#{Shellwords.escape(v)}" }.join("\n")
|
63
|
-
end
|
64
68
|
end
|
65
69
|
end
|
66
70
|
end
|
@@ -15,28 +15,73 @@ module Aptible
|
|
15
15
|
desc 'db:list', 'List all databases'
|
16
16
|
option :environment
|
17
17
|
define_method 'db:list' do
|
18
|
-
|
19
|
-
|
18
|
+
Formatter.render(Renderer.current) do |root|
|
19
|
+
root.grouped_keyed_list(
|
20
|
+
{ 'environment' => 'handle' },
|
21
|
+
'handle'
|
22
|
+
) do |node|
|
23
|
+
scoped_environments(options).each do |account|
|
24
|
+
account.each_database do |db|
|
25
|
+
node.object do |n|
|
26
|
+
ResourceFormatter.inject_database(n, db, account)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
20
31
|
end
|
21
32
|
end
|
22
33
|
|
23
|
-
desc 'db:
|
24
|
-
|
34
|
+
desc 'db:versions', 'List available database versions'
|
35
|
+
define_method 'db:versions' do
|
36
|
+
Formatter.render(Renderer.current) do |root|
|
37
|
+
root.grouped_keyed_list('type', 'version') do |node|
|
38
|
+
Aptible::Api::DatabaseImage.all(
|
39
|
+
token: fetch_token
|
40
|
+
).each do |database_image|
|
41
|
+
node.object do |n|
|
42
|
+
n.value('type', database_image.type)
|
43
|
+
n.value('version', database_image.version)
|
44
|
+
n.value('default', database_image.default)
|
45
|
+
n.value('description', database_image.description)
|
46
|
+
n.value('docker_repo', database_image.default)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'db:create HANDLE ' \
|
54
|
+
'[--type TYPE] [--version VERSION] ' \
|
55
|
+
'[--container-size SIZE_MB] [--size SIZE_GB]',
|
25
56
|
'Create a new database'
|
26
|
-
option :type,
|
57
|
+
option :type, type: :string
|
58
|
+
option :version, type: :string
|
27
59
|
option :container_size, type: :numeric
|
28
60
|
option :size, default: 10, type: :numeric
|
29
61
|
option :environment
|
30
62
|
define_method 'db:create' do |handle|
|
31
|
-
|
63
|
+
account = ensure_environment(options)
|
32
64
|
|
33
65
|
db_opts = {
|
34
66
|
handle: handle,
|
35
|
-
type: options[:type],
|
36
67
|
initial_container_size: options[:container_size],
|
37
68
|
initial_disk_size: options[:size]
|
38
69
|
}.delete_if { |_, v| v.nil? }
|
39
|
-
|
70
|
+
|
71
|
+
type = options[:type]
|
72
|
+
version = options[:version]
|
73
|
+
|
74
|
+
if version && type
|
75
|
+
image = find_database_image(type, version)
|
76
|
+
db_opts[:type] = image.type
|
77
|
+
db_opts[:database_image] = image
|
78
|
+
elsif version
|
79
|
+
raise Thor::Error, '--type is required when passing --version'
|
80
|
+
else
|
81
|
+
db_opts[:type] = type || 'postgresql'
|
82
|
+
end
|
83
|
+
|
84
|
+
database = account.create_database!(db_opts)
|
40
85
|
|
41
86
|
op_opts = {
|
42
87
|
type: 'provision',
|
@@ -55,15 +100,17 @@ module Aptible
|
|
55
100
|
end
|
56
101
|
|
57
102
|
attach_to_operation_logs(op)
|
58
|
-
|
103
|
+
|
104
|
+
render_database(database.reload, account)
|
59
105
|
end
|
60
106
|
|
61
107
|
desc 'db:clone SOURCE DEST', 'Clone a database to create a new one'
|
62
108
|
option :environment
|
63
109
|
define_method 'db:clone' do |source_handle, dest_handle|
|
110
|
+
# TODO: Deprecate + recommend backup
|
64
111
|
source = ensure_database(options.merge(db: source_handle))
|
65
|
-
|
66
|
-
|
112
|
+
database = clone_database(source, dest_handle)
|
113
|
+
render_database(database, database.account)
|
67
114
|
end
|
68
115
|
|
69
116
|
desc 'db:dump HANDLE', 'Dump a remote database to file'
|
@@ -72,7 +119,7 @@ module Aptible
|
|
72
119
|
database = ensure_database(options.merge(db: handle))
|
73
120
|
with_postgres_tunnel(database) do |url|
|
74
121
|
filename = "#{handle}.dump"
|
75
|
-
|
122
|
+
CLI.logger.info "Dumping to #{filename}"
|
76
123
|
`pg_dump #{url} > #{filename}`
|
77
124
|
end
|
78
125
|
end
|
@@ -82,7 +129,7 @@ module Aptible
|
|
82
129
|
define_method 'db:execute' do |handle, sql_path|
|
83
130
|
database = ensure_database(options.merge(db: handle))
|
84
131
|
with_postgres_tunnel(database) do |url|
|
85
|
-
|
132
|
+
CLI.logger.info "Executing #{sql_path} against #{handle}"
|
86
133
|
`psql #{url} < #{sql_path}`
|
87
134
|
end
|
88
135
|
end
|
@@ -97,37 +144,37 @@ module Aptible
|
|
97
144
|
|
98
145
|
credential = find_credential(database, options[:type])
|
99
146
|
|
100
|
-
|
101
|
-
|
147
|
+
m = "Creating #{credential.type} tunnel to #{database.handle}..."
|
148
|
+
CLI.logger.info m
|
102
149
|
|
103
150
|
if options[:type].nil?
|
104
151
|
types = database.database_credentials.map(&:type)
|
105
152
|
unless types.empty?
|
106
153
|
valid = types.join(', ')
|
107
|
-
|
108
|
-
|
154
|
+
CLI.logger.info 'Use --type TYPE to specify a tunnel type'
|
155
|
+
CLI.logger.info "Valid types for #{database.handle}: #{valid}"
|
109
156
|
end
|
110
157
|
end
|
111
158
|
|
112
159
|
with_local_tunnel(credential, desired_port) do |tunnel_helper|
|
113
160
|
port = tunnel_helper.port
|
114
|
-
|
161
|
+
CLI.logger.info "Connect at #{local_url(credential, port)}"
|
115
162
|
|
116
163
|
uri = URI(local_url(credential, port))
|
117
164
|
db = uri.path.gsub(%r{^/}, '')
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
165
|
+
CLI.logger.info 'Or, use the following arguments:'
|
166
|
+
CLI.logger.info "* Host: #{uri.host}"
|
167
|
+
CLI.logger.info "* Port: #{uri.port}"
|
168
|
+
CLI.logger.info "* Username: #{uri.user}" unless uri.user.empty?
|
169
|
+
CLI.logger.info "* Password: #{uri.password}"
|
170
|
+
CLI.logger.info "* Database: #{db}" unless db.empty?
|
124
171
|
|
125
|
-
|
172
|
+
CLI.logger.info 'Connected. Ctrl-C to close connection.'
|
126
173
|
|
127
174
|
begin
|
128
175
|
tunnel_helper.wait
|
129
176
|
rescue Interrupt
|
130
|
-
|
177
|
+
CLI.logger.warn 'Closing tunnel'
|
131
178
|
end
|
132
179
|
end
|
133
180
|
end
|
@@ -136,7 +183,7 @@ module Aptible
|
|
136
183
|
option :environment
|
137
184
|
define_method 'db:deprovision' do |handle|
|
138
185
|
database = ensure_database(options.merge(db: handle))
|
139
|
-
|
186
|
+
CLI.logger.info "Deprovisioning #{database.handle}..."
|
140
187
|
database.create_operation!(type: 'deprovision')
|
141
188
|
end
|
142
189
|
|
@@ -144,7 +191,7 @@ module Aptible
|
|
144
191
|
option :environment
|
145
192
|
define_method 'db:backup' do |handle|
|
146
193
|
database = ensure_database(options.merge(db: handle))
|
147
|
-
|
194
|
+
CLI.logger.info "Backing up #{database.handle}..."
|
148
195
|
op = database.create_operation!(type: 'backup')
|
149
196
|
attach_to_operation_logs(op)
|
150
197
|
end
|
@@ -153,7 +200,7 @@ module Aptible
|
|
153
200
|
option :environment
|
154
201
|
define_method 'db:reload' do |handle|
|
155
202
|
database = ensure_database(options.merge(db: handle))
|
156
|
-
|
203
|
+
CLI.logger.info "Reloading #{database.handle}..."
|
157
204
|
op = database.create_operation!(type: 'reload')
|
158
205
|
attach_to_operation_logs(op)
|
159
206
|
end
|
@@ -173,7 +220,7 @@ module Aptible
|
|
173
220
|
disk_size: options[:size]
|
174
221
|
}.delete_if { |_, v| v.nil? }
|
175
222
|
|
176
|
-
|
223
|
+
CLI.logger.info "Restarting #{database.handle}..."
|
177
224
|
op = database.create_operation!(opts)
|
178
225
|
attach_to_operation_logs(op)
|
179
226
|
end
|
@@ -185,7 +232,11 @@ module Aptible
|
|
185
232
|
database = ensure_database(options.merge(db: handle))
|
186
233
|
credential = find_credential(database, options[:type])
|
187
234
|
|
188
|
-
|
235
|
+
Formatter.render(Renderer.current) do |root|
|
236
|
+
root.keyed_object('connection_url') do |node|
|
237
|
+
node.value('connection_url', credential.connection_url)
|
238
|
+
end
|
239
|
+
end
|
189
240
|
end
|
190
241
|
end
|
191
242
|
end
|
@@ -127,12 +127,15 @@ module Aptible
|
|
127
127
|
define_method 'endpoints:list' do
|
128
128
|
resource = ensure_app_or_database(options)
|
129
129
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
130
|
+
Formatter.render(Renderer.current) do |root|
|
131
|
+
root.list do |list|
|
132
|
+
each_service(resource) do |service|
|
133
|
+
service.each_vhost do |vhost|
|
134
|
+
list.object do |node|
|
135
|
+
ResourceFormatter.inject_vhost(node, vhost, service)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
136
139
|
end
|
137
140
|
end
|
138
141
|
end
|
@@ -143,7 +146,7 @@ module Aptible
|
|
143
146
|
app_or_database_options
|
144
147
|
define_method 'endpoints:deprovision' do |hostname|
|
145
148
|
resource = ensure_app_or_database(options)
|
146
|
-
vhost = find_vhost(
|
149
|
+
vhost = find_vhost(each_service(resource), hostname)
|
147
150
|
op = vhost.create_operation!(type: 'deprovision')
|
148
151
|
attach_to_operation_logs(op)
|
149
152
|
end
|
@@ -170,7 +173,7 @@ module Aptible
|
|
170
173
|
|
171
174
|
def modify_app_vhost(flags, options, hostname)
|
172
175
|
app = ensure_app(options)
|
173
|
-
vhost = find_vhost(
|
176
|
+
vhost = find_vhost(each_service(app), hostname)
|
174
177
|
vhost.update!(**flags.prepare(vhost.service.account, options))
|
175
178
|
provision_vhost_and_explain(vhost.service, vhost)
|
176
179
|
end
|
@@ -12,7 +12,8 @@ module Aptible
|
|
12
12
|
o = Aptible::Api::Operation.find(operation_id, token: fetch_token)
|
13
13
|
raise "Operation ##{operation_id} not found" if o.nil?
|
14
14
|
|
15
|
-
|
15
|
+
m = "Requesting cancellation on #{prettify_operation(o)}..."
|
16
|
+
CLI.logger.info m
|
16
17
|
o.update!(cancelled: true)
|
17
18
|
end
|
18
19
|
end
|