aptible-cli 0.16.2 → 0.16.7
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/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
|