aptible-cli 0.16.2 → 0.16.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +48 -45
- data/aptible-cli.gemspec +2 -2
- data/lib/aptible/cli/agent.rb +78 -9
- data/lib/aptible/cli/helpers/database.rb +23 -0
- data/lib/aptible/cli/helpers/security_key.rb +136 -0
- data/lib/aptible/cli/helpers/system.rb +26 -0
- data/lib/aptible/cli/resource_formatter.rb +36 -1
- data/lib/aptible/cli/subcommands/backup.rb +56 -12
- data/lib/aptible/cli/subcommands/db.rb +66 -6
- data/lib/aptible/cli/subcommands/inspect.rb +1 -1
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/agent_spec.rb +131 -9
- data/spec/aptible/cli/subcommands/backup_spec.rb +79 -2
- data/spec/aptible/cli/subcommands/db_spec.rb +147 -2
- metadata +9 -8
@@ -2,14 +2,17 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Aptible::CLI::Agent do
|
4
4
|
let(:token) { 'some-token' }
|
5
|
-
let(:account) { Fabricate(:account, handle: 'test') }
|
5
|
+
let(:account) { Fabricate(:account, handle: 'test', id: 1) }
|
6
6
|
let(:alt_account) { Fabricate(:account, handle: 'alt') }
|
7
7
|
let(:database) { Fabricate(:database, account: account, handle: 'some-db') }
|
8
8
|
let!(:backup) do
|
9
9
|
# created_at: 2016-06-14 13:24:11 +0000
|
10
10
|
Fabricate(
|
11
11
|
:backup,
|
12
|
-
|
12
|
+
database_with_deleted: database,
|
13
|
+
created_at: Time.at(1465910651),
|
14
|
+
account: account,
|
15
|
+
id: 1
|
13
16
|
)
|
14
17
|
end
|
15
18
|
|
@@ -178,4 +181,78 @@ describe Aptible::CLI::Agent do
|
|
178
181
|
.to raise_error(Thor::Error, 'Could not find database nope')
|
179
182
|
end
|
180
183
|
end
|
184
|
+
|
185
|
+
describe '#backup:orphaned' do
|
186
|
+
before { allow(Aptible::Api::Account).to receive(:all) { [account] } }
|
187
|
+
before do
|
188
|
+
m = allow(account).to receive(:each_orphaned_backup)
|
189
|
+
ages = [
|
190
|
+
1.day, 2.days, 3.days, 4.days,
|
191
|
+
5.days, 2.weeks, 3.weeks, 1.month,
|
192
|
+
1.year
|
193
|
+
]
|
194
|
+
ages.each do |age|
|
195
|
+
b = Fabricate(:backup, database: database, created_at: age.ago,
|
196
|
+
account: account)
|
197
|
+
allow(b).to receive(:database_with_deleted).and_return(database)
|
198
|
+
m.and_yield(b)
|
199
|
+
b
|
200
|
+
end
|
201
|
+
end
|
202
|
+
before { subject.options = { max_age: '1w' } }
|
203
|
+
|
204
|
+
it 'can show a subset of backups' do
|
205
|
+
subject.send('backup:orphaned')
|
206
|
+
puts captured_output_text
|
207
|
+
expect(captured_output_text.split("\n").size).to eq(5)
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'allows scoping via environment' do
|
211
|
+
subject.options = { max_age: '1w', environment: database.account.handle }
|
212
|
+
subject.send('backup:orphaned')
|
213
|
+
expect(captured_output_text.split("\n").size).to eq(5)
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'shows more backups if requested' do
|
217
|
+
subject.options = { max_age: '2y' }
|
218
|
+
subject.send('backup:orphaned')
|
219
|
+
expect(captured_output_text.split("\n").size).to eq(9)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'errors out if max_age is invalid' do
|
223
|
+
subject.options = { max_age: 'foobar' }
|
224
|
+
expect { subject.send('backup:orphaned') }
|
225
|
+
.to raise_error(Thor::Error, 'Invalid age: foobar')
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe '#backup:purge' do
|
230
|
+
it 'fails if the backup cannot be found' do
|
231
|
+
expect(Aptible::Api::Backup).to receive(:find)
|
232
|
+
.with(1, token: token).and_return(nil)
|
233
|
+
|
234
|
+
expect { subject.send('backup:purge', 1) }
|
235
|
+
.to raise_error('Backup #1 not found')
|
236
|
+
end
|
237
|
+
|
238
|
+
context 'successful purge' do
|
239
|
+
let(:op) { Fabricate(:operation, resource: backup) }
|
240
|
+
|
241
|
+
before do
|
242
|
+
expect(Aptible::Api::Backup).to receive(:find)
|
243
|
+
.with(1, token: token).and_return(backup)
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'creates a purge operation on the backup' do
|
247
|
+
expect(backup).to receive(:create_operation!) do |options|
|
248
|
+
expect(options[:type]).to eq('purge')
|
249
|
+
op
|
250
|
+
end
|
251
|
+
|
252
|
+
expect(subject).to receive(:attach_to_operation_logs).with(op)
|
253
|
+
|
254
|
+
subject.send('backup:purge', 1)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
181
258
|
end
|
@@ -55,7 +55,7 @@ describe Aptible::CLI::Agent do
|
|
55
55
|
subject.send('db:create', 'foo')
|
56
56
|
end
|
57
57
|
|
58
|
-
it 'creates a new DB with a disk size' do
|
58
|
+
it 'creates a new DB with a (implicitly) disk size' do
|
59
59
|
expect_provision_database(
|
60
60
|
{ handle: 'foo', type: 'postgresql', initial_disk_size: 200 },
|
61
61
|
{ disk_size: 200 }
|
@@ -65,6 +65,16 @@ describe Aptible::CLI::Agent do
|
|
65
65
|
subject.send('db:create', 'foo')
|
66
66
|
end
|
67
67
|
|
68
|
+
it 'creates a new DB with a disk-size' do
|
69
|
+
expect_provision_database(
|
70
|
+
{ handle: 'foo', type: 'postgresql', initial_disk_size: 200 },
|
71
|
+
{ disk_size: 200 }
|
72
|
+
)
|
73
|
+
|
74
|
+
subject.options = { type: 'postgresql', disk_size: 200 }
|
75
|
+
subject.send('db:create', 'foo')
|
76
|
+
end
|
77
|
+
|
68
78
|
it 'deprovisions the database if the operation cannot be created' do
|
69
79
|
db = Fabricate(:database)
|
70
80
|
|
@@ -377,7 +387,7 @@ describe Aptible::CLI::Agent do
|
|
377
387
|
expect(captured_logs).to match(/restarting foobar/i)
|
378
388
|
end
|
379
389
|
|
380
|
-
it 'allows restarting a database with
|
390
|
+
it 'allows restarting a database with (implicitly disk) size' do
|
381
391
|
expect(database).to receive(:create_operation!)
|
382
392
|
.with(type: 'restart', disk_size: 40).and_return(op)
|
383
393
|
|
@@ -389,6 +399,18 @@ describe Aptible::CLI::Agent do
|
|
389
399
|
expect(captured_logs).to match(/restarting foobar/i)
|
390
400
|
end
|
391
401
|
|
402
|
+
it 'allows restarting a database with a disk-size' do
|
403
|
+
expect(database).to receive(:create_operation!)
|
404
|
+
.with(type: 'restart', disk_size: 40).and_return(op)
|
405
|
+
|
406
|
+
expect(subject).to receive(:attach_to_operation_logs).with(op)
|
407
|
+
|
408
|
+
subject.options = { disk_size: 40 }
|
409
|
+
subject.send('db:restart', handle)
|
410
|
+
|
411
|
+
expect(captured_logs).to match(/restarting foobar/i)
|
412
|
+
end
|
413
|
+
|
392
414
|
it 'fails if the DB is not found' do
|
393
415
|
expect { subject.send('db:restart', 'nope') }
|
394
416
|
.to raise_error(Thor::Error, 'Could not find database nope')
|
@@ -439,6 +461,129 @@ describe Aptible::CLI::Agent do
|
|
439
461
|
end
|
440
462
|
end
|
441
463
|
|
464
|
+
describe '#db:replicate' do
|
465
|
+
let(:databases) { [] }
|
466
|
+
before { allow(Aptible::Api::Database).to receive(:all) { databases } }
|
467
|
+
|
468
|
+
def expect_replicate_database(opts = {})
|
469
|
+
master = Fabricate(:database, handle: 'master')
|
470
|
+
databases << master
|
471
|
+
replica = Fabricate(:database,
|
472
|
+
account: master.account,
|
473
|
+
handle: 'replica')
|
474
|
+
|
475
|
+
op = Fabricate(:operation)
|
476
|
+
|
477
|
+
params = { type: 'replicate', handle: 'replica' }.merge(opts)
|
478
|
+
params[:disk_size] = params.delete(:size) if params[:size]
|
479
|
+
expect(master).to receive(:create_operation!)
|
480
|
+
.with(**params).and_return(op)
|
481
|
+
|
482
|
+
expect(subject).to receive(:attach_to_operation_logs).with(op) do
|
483
|
+
databases << replica
|
484
|
+
replica
|
485
|
+
end
|
486
|
+
|
487
|
+
provision = Fabricate(:operation)
|
488
|
+
|
489
|
+
expect(replica).to receive_message_chain(:operations, :last)
|
490
|
+
.and_return(provision)
|
491
|
+
|
492
|
+
expect(subject).to receive(:attach_to_operation_logs).with(provision)
|
493
|
+
|
494
|
+
expect(replica).to receive(:reload).and_return(replica)
|
495
|
+
|
496
|
+
subject.options = opts
|
497
|
+
subject.send('db:replicate', 'master', 'replica')
|
498
|
+
|
499
|
+
expect(captured_logs).to match(/replicating master/i)
|
500
|
+
end
|
501
|
+
|
502
|
+
it 'allows replicating an existing database' do
|
503
|
+
expect_replicate_database
|
504
|
+
end
|
505
|
+
|
506
|
+
it 'allows replicating a database with a container size' do
|
507
|
+
expect_replicate_database(container_size: 40)
|
508
|
+
end
|
509
|
+
|
510
|
+
it 'allows replicating a database with an (implicitly) disk size option' do
|
511
|
+
expect_replicate_database(size: 40)
|
512
|
+
end
|
513
|
+
|
514
|
+
it 'allows replicating a database with a disk-size option' do
|
515
|
+
expect_replicate_database(disk_size: 40)
|
516
|
+
end
|
517
|
+
|
518
|
+
it 'fails if the DB is not found' do
|
519
|
+
expect { subject.send('db:replicate', 'nope', 'replica') }
|
520
|
+
.to raise_error(Thor::Error, 'Could not find database nope')
|
521
|
+
end
|
522
|
+
|
523
|
+
it 'allows logical replication of a database with --version set' do
|
524
|
+
master = Fabricate(:database, handle: 'master')
|
525
|
+
databases << master
|
526
|
+
replica = Fabricate(:database,
|
527
|
+
account: master.account,
|
528
|
+
handle: 'replica')
|
529
|
+
|
530
|
+
dbimg = Fabricate(:database_image,
|
531
|
+
type: 'postgresql',
|
532
|
+
version: 10,
|
533
|
+
docker_repo: 'aptible/postgresql:10')
|
534
|
+
|
535
|
+
expect(subject).to receive(:find_database_image).with('postgresql', 10)
|
536
|
+
.and_return(dbimg)
|
537
|
+
|
538
|
+
op = Fabricate(:operation)
|
539
|
+
|
540
|
+
params = { type: 'replicate_logical', handle: 'replica',
|
541
|
+
docker_ref: dbimg.docker_repo }
|
542
|
+
expect(master).to receive(:create_operation!)
|
543
|
+
.with(**params).and_return(op)
|
544
|
+
|
545
|
+
expect(subject).to receive(:attach_to_operation_logs).with(op) do
|
546
|
+
databases << replica
|
547
|
+
replica
|
548
|
+
end
|
549
|
+
|
550
|
+
provision = Fabricate(:operation)
|
551
|
+
|
552
|
+
expect(replica).to receive_message_chain(:operations, :last)
|
553
|
+
.and_return(provision)
|
554
|
+
|
555
|
+
expect(subject).to receive(:attach_to_operation_logs).with(provision)
|
556
|
+
|
557
|
+
expect(replica).to receive(:reload).and_return(replica)
|
558
|
+
|
559
|
+
subject.options = { logical: true, version: 10 }
|
560
|
+
subject.send('db:replicate', 'master', 'replica')
|
561
|
+
|
562
|
+
expect(captured_logs).to match(/replicating master/i)
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'fails if logical replication requested without --version' do
|
566
|
+
master = Fabricate(:database, handle: 'master', type: 'postgresql')
|
567
|
+
databases << master
|
568
|
+
|
569
|
+
subject.options = { type: 'replicate', handle: 'replica', logical: true }
|
570
|
+
expect { subject.send('db:replicate', 'master', 'replica') }
|
571
|
+
.to raise_error(Thor::Error, '--version is required for logical ' \
|
572
|
+
'replication')
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'fails if logical replication requested for non-postgres db' do
|
576
|
+
master = Fabricate(:database, handle: 'master', type: 'mysql')
|
577
|
+
databases << master
|
578
|
+
|
579
|
+
subject.options = { type: 'replicate', handle: 'replica',
|
580
|
+
logical: true, version: 10 }
|
581
|
+
expect { subject.send('db:replicate', 'master', 'replica') }
|
582
|
+
.to raise_error(Thor::Error, 'Logical replication only works for ' \
|
583
|
+
'PostgreSQL')
|
584
|
+
end
|
585
|
+
end
|
586
|
+
|
442
587
|
describe '#db:dump' do
|
443
588
|
it 'should fail if database is non-existent' do
|
444
589
|
allow(Aptible::Api::Database).to receive(:all) { [] }
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aptible-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.
|
4
|
+
version: 0.16.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frank Macreery
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aptible-resource
|
@@ -30,28 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '1.
|
33
|
+
version: '1.2'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '1.
|
40
|
+
version: '1.2'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: aptible-auth
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 1.1.0
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 1.1.0
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: aptible-billing
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -277,7 +277,9 @@ files:
|
|
277
277
|
- lib/aptible/cli/helpers/database.rb
|
278
278
|
- lib/aptible/cli/helpers/environment.rb
|
279
279
|
- lib/aptible/cli/helpers/operation.rb
|
280
|
+
- lib/aptible/cli/helpers/security_key.rb
|
280
281
|
- lib/aptible/cli/helpers/ssh.rb
|
282
|
+
- lib/aptible/cli/helpers/system.rb
|
281
283
|
- lib/aptible/cli/helpers/token.rb
|
282
284
|
- lib/aptible/cli/helpers/tunnel.rb
|
283
285
|
- lib/aptible/cli/helpers/vhost.rb
|
@@ -372,8 +374,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
372
374
|
- !ruby/object:Gem::Version
|
373
375
|
version: '0'
|
374
376
|
requirements: []
|
375
|
-
|
376
|
-
rubygems_version: 2.7.6
|
377
|
+
rubygems_version: 3.0.3
|
377
378
|
signing_key:
|
378
379
|
specification_version: 4
|
379
380
|
summary: Command-line interface for Aptible services
|