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
|
@@ -264,6 +264,120 @@ describe Aptible::CLI::Agent do
|
|
|
264
264
|
end
|
|
265
265
|
end
|
|
266
266
|
|
|
267
|
+
describe '#db:tunnel (rds databases)' do
|
|
268
|
+
let(:rds_handle) { 'aws:rds::test-rds-db' }
|
|
269
|
+
let(:stack) { Fabricate(:stack, internal_domain: 'aptible.in') }
|
|
270
|
+
let(:account) { Fabricate(:account, stack: stack) }
|
|
271
|
+
let(:app) { Fabricate(:app, account: account) }
|
|
272
|
+
|
|
273
|
+
let(:raw_rds_resource) do
|
|
274
|
+
Fabricate(:external_aws_resource,
|
|
275
|
+
resource_name: 'test-rds-db',
|
|
276
|
+
resource_type: 'aws_rds_db_instance')
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
let(:rds_db) do
|
|
280
|
+
Aptible::CLI::Helpers::Database::RdsDatabase.new(
|
|
281
|
+
rds_handle,
|
|
282
|
+
raw_rds_resource.id,
|
|
283
|
+
raw_rds_resource.created_at,
|
|
284
|
+
raw_rds_resource
|
|
285
|
+
)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
let(:rds_credential) do
|
|
289
|
+
url = 'postgres://user:pass@host.com:5432/dbname'
|
|
290
|
+
cred = Fabricate(:external_aws_database_credential,
|
|
291
|
+
external_aws_resource: raw_rds_resource,
|
|
292
|
+
connection_url: url)
|
|
293
|
+
cred
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
let(:rds_connection) do
|
|
297
|
+
double('rds_connection', present?: true, app: app)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
before do
|
|
301
|
+
allow(app).to receive(:account).and_return(account)
|
|
302
|
+
allow(subject).to receive(:external_rds_database_from_handle)
|
|
303
|
+
.with(rds_handle).and_return(rds_db)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it 'fails if RDS database is non-existent' do
|
|
307
|
+
allow(subject).to receive(:external_rds_database_from_handle)
|
|
308
|
+
.with(rds_handle).and_return(nil)
|
|
309
|
+
|
|
310
|
+
expect do
|
|
311
|
+
subject.send('db:tunnel', rds_handle)
|
|
312
|
+
end.to raise_error(Thor::Error, /no rds db found/i)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
context 'valid RDS database' do
|
|
316
|
+
before do
|
|
317
|
+
raw_rds_resource.instance_variable_set(
|
|
318
|
+
:@external_aws_database_credentials,
|
|
319
|
+
[rds_credential]
|
|
320
|
+
)
|
|
321
|
+
raw_rds_resource.instance_variable_set(
|
|
322
|
+
:@app_external_aws_rds_connections,
|
|
323
|
+
[rds_connection]
|
|
324
|
+
)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it 'prints a message explaining how to connect' do
|
|
328
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
329
|
+
.with(rds_credential, 0, account)
|
|
330
|
+
.and_yield(socat_helper)
|
|
331
|
+
|
|
332
|
+
subject.send('db:tunnel', rds_handle)
|
|
333
|
+
|
|
334
|
+
local_url = 'postgres://user:pass@localhost.aptible.in:4242/dbname'
|
|
335
|
+
|
|
336
|
+
expect(captured_logs)
|
|
337
|
+
.to match(/connect at #{Regexp.escape(local_url)}/i)
|
|
338
|
+
expect(captured_logs).to match(/host: localhost\.aptible\.in/i)
|
|
339
|
+
expect(captured_logs).to match(/port: 4242/i)
|
|
340
|
+
expect(captured_logs).to match(/username: user/i)
|
|
341
|
+
expect(captured_logs).to match(/password: pass/i)
|
|
342
|
+
expect(captured_logs).to match(/database: dbname/i)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
it 'supports custom port option' do
|
|
346
|
+
subject.options = { port: 5555 }
|
|
347
|
+
|
|
348
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
349
|
+
.with(rds_credential, 5555, account)
|
|
350
|
+
.and_yield(socat_helper)
|
|
351
|
+
|
|
352
|
+
subject.send('db:tunnel', rds_handle)
|
|
353
|
+
|
|
354
|
+
expect(captured_logs).to match(/connected\. ctrl-c to close/i)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
it 'fails when no credential is found' do
|
|
358
|
+
raw_rds_resource.instance_variable_set(
|
|
359
|
+
:@external_aws_database_credentials,
|
|
360
|
+
[]
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
expect do
|
|
364
|
+
subject.send('db:tunnel', rds_handle)
|
|
365
|
+
end.to raise_error(Thor::Error, /no rds credential found/i)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
it 'fails when no account connection is found' do
|
|
369
|
+
raw_rds_resource.instance_variable_set(
|
|
370
|
+
:@app_external_aws_rds_connections,
|
|
371
|
+
[]
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
expect do
|
|
375
|
+
subject.send('db:tunnel', rds_handle)
|
|
376
|
+
end.to raise_error(Thor::Error, /no env for rds found/i)
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
267
381
|
describe '#db:list' do
|
|
268
382
|
before do
|
|
269
383
|
staging = Fabricate(:account, handle: 'staging')
|
|
@@ -289,6 +403,9 @@ describe Aptible::CLI::Agent do
|
|
|
289
403
|
allow(Aptible::Api::Account).to receive(:all)
|
|
290
404
|
.with(token: token, href: '/accounts?per_page=5000&no_embed=true')
|
|
291
405
|
.and_return([staging, prod])
|
|
406
|
+
allow(Aptible::Api::ExternalAwsResource).to receive(:all)
|
|
407
|
+
.with(token: token)
|
|
408
|
+
.and_return([])
|
|
292
409
|
end
|
|
293
410
|
|
|
294
411
|
context 'when no account is specified' do
|
|
@@ -329,6 +446,217 @@ describe Aptible::CLI::Agent do
|
|
|
329
446
|
end
|
|
330
447
|
end
|
|
331
448
|
|
|
449
|
+
describe '#db:list (rds databases)' do
|
|
450
|
+
let(:stack) { Fabricate(:stack, internal_domain: 'aptible.in') }
|
|
451
|
+
let(:staging) { Fabricate(:account, handle: 'staging', stack: stack) }
|
|
452
|
+
let(:prod) { Fabricate(:account, handle: 'production', stack: stack) }
|
|
453
|
+
let(:token) { 'the-token' }
|
|
454
|
+
|
|
455
|
+
# Create apps for each account
|
|
456
|
+
let(:staging_app) do
|
|
457
|
+
Fabricate(:app, handle: 'staging-app', account: staging)
|
|
458
|
+
end
|
|
459
|
+
let(:prod_app) { Fabricate(:app, handle: 'prod-app', account: prod) }
|
|
460
|
+
|
|
461
|
+
# Create RDS resources
|
|
462
|
+
let(:staging_rds) do
|
|
463
|
+
Fabricate(
|
|
464
|
+
:external_aws_resource,
|
|
465
|
+
resource_name: 'staging-rds-db',
|
|
466
|
+
resource_type: 'aws_rds_db_instance'
|
|
467
|
+
)
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
let(:prod_rds) do
|
|
471
|
+
Fabricate(
|
|
472
|
+
:external_aws_resource,
|
|
473
|
+
resource_name: 'prod-rds-db',
|
|
474
|
+
resource_type: 'aws_rds_db_instance'
|
|
475
|
+
)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
let(:unattached_rds) do
|
|
479
|
+
Fabricate(
|
|
480
|
+
:external_aws_resource,
|
|
481
|
+
resource_name: 'unattached-rds-db',
|
|
482
|
+
resource_type: 'aws_rds_db_instance'
|
|
483
|
+
)
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
# Create credentials
|
|
487
|
+
let(:staging_cred) do
|
|
488
|
+
Fabricate(
|
|
489
|
+
:external_aws_database_credential,
|
|
490
|
+
external_aws_resource: staging_rds
|
|
491
|
+
)
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
let(:prod_cred) do
|
|
495
|
+
Fabricate(
|
|
496
|
+
:external_aws_database_credential,
|
|
497
|
+
external_aws_resource: prod_rds
|
|
498
|
+
)
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
let(:unattached_cred) do
|
|
502
|
+
Fabricate(
|
|
503
|
+
:external_aws_database_credential,
|
|
504
|
+
external_aws_resource: unattached_rds
|
|
505
|
+
)
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
# Create connection doubles
|
|
509
|
+
let(:staging_conn) do
|
|
510
|
+
double('staging_conn', present?: true, app: staging_app)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
let(:prod_conn) do
|
|
514
|
+
double('prod_conn', present?: true, app: prod_app)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
before do
|
|
518
|
+
# Force lazy evaluation of all let blocks in the correct order
|
|
519
|
+
staging_app
|
|
520
|
+
prod_app
|
|
521
|
+
staging_conn
|
|
522
|
+
prod_conn
|
|
523
|
+
|
|
524
|
+
# Create stub regular databases to establish account sections
|
|
525
|
+
# (RDS databases piggyback on regular database account sections)
|
|
526
|
+
staging_regular_db = Fabricate(
|
|
527
|
+
:database,
|
|
528
|
+
account: staging,
|
|
529
|
+
handle: 'staging-regular-db'
|
|
530
|
+
)
|
|
531
|
+
prod_regular_db = Fabricate(
|
|
532
|
+
:database,
|
|
533
|
+
account: prod,
|
|
534
|
+
handle: 'prod-regular-db'
|
|
535
|
+
)
|
|
536
|
+
Fabricate(:database_credential, database: staging_regular_db)
|
|
537
|
+
Fabricate(:database_credential, database: prod_regular_db)
|
|
538
|
+
|
|
539
|
+
# Ensure apps properly return their accounts
|
|
540
|
+
allow(staging_app).to receive(:account).and_return(staging)
|
|
541
|
+
allow(prod_app).to receive(:account).and_return(prod)
|
|
542
|
+
|
|
543
|
+
# Set the connections directly on the RDS resources
|
|
544
|
+
staging_rds.instance_variable_set(
|
|
545
|
+
:@app_external_aws_rds_connections,
|
|
546
|
+
[staging_conn]
|
|
547
|
+
)
|
|
548
|
+
prod_rds.instance_variable_set(
|
|
549
|
+
:@app_external_aws_rds_connections,
|
|
550
|
+
[prod_conn]
|
|
551
|
+
)
|
|
552
|
+
unattached_rds.instance_variable_set(
|
|
553
|
+
:@app_external_aws_rds_connections,
|
|
554
|
+
[]
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
# Set the credentials directly on the RDS resources
|
|
558
|
+
staging_rds.instance_variable_set(
|
|
559
|
+
:@external_aws_database_credentials,
|
|
560
|
+
[staging_cred]
|
|
561
|
+
)
|
|
562
|
+
prod_rds.instance_variable_set(
|
|
563
|
+
:@external_aws_database_credentials,
|
|
564
|
+
[prod_cred]
|
|
565
|
+
)
|
|
566
|
+
unattached_rds.instance_variable_set(
|
|
567
|
+
:@external_aws_database_credentials,
|
|
568
|
+
[unattached_cred]
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# Setup API mocks
|
|
572
|
+
allow(subject).to receive(:fetch_token).and_return(token)
|
|
573
|
+
allow(Aptible::Api::Database).to receive(:all)
|
|
574
|
+
.with(token: token, href: '/databases?per_page=5000&no_embed=true')
|
|
575
|
+
.and_return([staging_regular_db, prod_regular_db])
|
|
576
|
+
allow(Aptible::Api::Account).to receive(:all)
|
|
577
|
+
.with(token: token, href: '/accounts?per_page=5000&no_embed=true')
|
|
578
|
+
.and_return([staging, prod])
|
|
579
|
+
allow(Aptible::Api::ExternalAwsResource).to receive(:all)
|
|
580
|
+
.with(token: token)
|
|
581
|
+
.and_return([staging_rds, prod_rds, unattached_rds])
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
context 'when no account is specified' do
|
|
585
|
+
it 'prints out RDS databases grouped by account' do
|
|
586
|
+
subject.send('db:list')
|
|
587
|
+
|
|
588
|
+
expect(captured_output_text).to include('=== staging')
|
|
589
|
+
expect(captured_output_text).to include('aws:rds::staging-rds-db')
|
|
590
|
+
|
|
591
|
+
expect(captured_output_text).to include('=== production')
|
|
592
|
+
expect(captured_output_text).to include('aws:rds::prod-rds-db')
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
it 'prints out unattached RDS databases' do
|
|
596
|
+
subject.send('db:list')
|
|
597
|
+
|
|
598
|
+
expect(captured_output_text)
|
|
599
|
+
.to include('=== unattached rds databases')
|
|
600
|
+
expect(captured_output_text).to include('aws:rds::unattached-rds-db')
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
context 'when a valid account is specified' do
|
|
605
|
+
it 'prints out RDS databases for that account' do
|
|
606
|
+
subject.options = { environment: 'staging' }
|
|
607
|
+
subject.send('db:list')
|
|
608
|
+
|
|
609
|
+
expect(captured_output_text).to include('=== staging')
|
|
610
|
+
expect(captured_output_text).to include('aws:rds::staging-rds-db')
|
|
611
|
+
expect(captured_output_text).to include('staging-regular-db')
|
|
612
|
+
|
|
613
|
+
expect(captured_output_text).not_to include('=== production')
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
it 'does not show unattached RDS databases when filtering' do
|
|
617
|
+
subject.options = { environment: 'staging' }
|
|
618
|
+
subject.send('db:list')
|
|
619
|
+
|
|
620
|
+
# When filtering by environment, unattached RDS databases
|
|
621
|
+
# are not shown at all
|
|
622
|
+
expect(captured_output_text)
|
|
623
|
+
.not_to include('=== unattached rds databases')
|
|
624
|
+
expect(captured_output_text)
|
|
625
|
+
.not_to include('aws:rds::unattached-rds-db')
|
|
626
|
+
|
|
627
|
+
# RDS databases attached to other filtered-out accounts don't show
|
|
628
|
+
expect(captured_output_text).not_to include('aws:rds::prod-rds-db')
|
|
629
|
+
|
|
630
|
+
# Only staging databases appear
|
|
631
|
+
expect(captured_output_text).to include('=== staging')
|
|
632
|
+
expect(captured_output_text).to include('aws:rds::staging-rds-db')
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
context 'with both regular databases and RDS databases' do
|
|
637
|
+
before do
|
|
638
|
+
staging_db = Fabricate(
|
|
639
|
+
:database,
|
|
640
|
+
handle: 'staging-postgres-db',
|
|
641
|
+
account: staging
|
|
642
|
+
)
|
|
643
|
+
Fabricate(:database_credential, database: staging_db)
|
|
644
|
+
|
|
645
|
+
allow(Aptible::Api::Database).to receive(:all)
|
|
646
|
+
.with(token: token, href: '/databases?per_page=5000&no_embed=true')
|
|
647
|
+
.and_return([staging_db])
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
it 'prints both regular and RDS databases' do
|
|
651
|
+
subject.send('db:list')
|
|
652
|
+
|
|
653
|
+
expect(captured_output_text).to include('=== staging')
|
|
654
|
+
expect(captured_output_text).to include('staging-postgres-db')
|
|
655
|
+
expect(captured_output_text).to include('aws:rds::staging-rds-db')
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
|
|
332
660
|
describe '#db:backup' do
|
|
333
661
|
before { allow(Aptible::Api::Account).to receive(:all) { [account] } }
|
|
334
662
|
before { allow(Aptible::Api::Database).to receive(:all) { [database] } }
|
|
@@ -750,6 +1078,105 @@ describe Aptible::CLI::Agent do
|
|
|
750
1078
|
end
|
|
751
1079
|
end
|
|
752
1080
|
|
|
1081
|
+
describe '#db:dump (rds databases)' do
|
|
1082
|
+
let(:rds_handle) { 'aws:rds::test-rds-db' }
|
|
1083
|
+
let(:stack) { Fabricate(:stack, internal_domain: 'aptible.in') }
|
|
1084
|
+
let(:account) { Fabricate(:account, stack: stack) }
|
|
1085
|
+
let(:app) { Fabricate(:app, account: account) }
|
|
1086
|
+
|
|
1087
|
+
let(:raw_rds_resource) do
|
|
1088
|
+
Fabricate(:external_aws_resource,
|
|
1089
|
+
resource_name: 'test-rds-db',
|
|
1090
|
+
resource_type: 'aws_rds_db_instance')
|
|
1091
|
+
end
|
|
1092
|
+
|
|
1093
|
+
let(:rds_db) do
|
|
1094
|
+
Aptible::CLI::Helpers::Database::RdsDatabase.new(
|
|
1095
|
+
rds_handle,
|
|
1096
|
+
raw_rds_resource.id,
|
|
1097
|
+
raw_rds_resource.created_at,
|
|
1098
|
+
raw_rds_resource
|
|
1099
|
+
)
|
|
1100
|
+
end
|
|
1101
|
+
|
|
1102
|
+
let(:rds_credential) do
|
|
1103
|
+
url = 'postgres://user:pass@host.com:5432/dbname'
|
|
1104
|
+
Fabricate(:external_aws_database_credential,
|
|
1105
|
+
external_aws_resource: raw_rds_resource,
|
|
1106
|
+
connection_url: url)
|
|
1107
|
+
end
|
|
1108
|
+
|
|
1109
|
+
let(:rds_connection) do
|
|
1110
|
+
double('rds_connection', present?: true, app: app)
|
|
1111
|
+
end
|
|
1112
|
+
|
|
1113
|
+
before do
|
|
1114
|
+
allow(app).to receive(:account).and_return(account)
|
|
1115
|
+
allow(subject).to receive(:external_rds_database_from_handle)
|
|
1116
|
+
.with(rds_handle).and_return(rds_db)
|
|
1117
|
+
|
|
1118
|
+
raw_rds_resource.instance_variable_set(
|
|
1119
|
+
:@external_aws_database_credentials,
|
|
1120
|
+
[rds_credential]
|
|
1121
|
+
)
|
|
1122
|
+
raw_rds_resource.instance_variable_set(
|
|
1123
|
+
:@app_external_aws_rds_connections,
|
|
1124
|
+
[rds_connection]
|
|
1125
|
+
)
|
|
1126
|
+
end
|
|
1127
|
+
|
|
1128
|
+
it 'exits with the same code as pg_dump' do
|
|
1129
|
+
exit_status = 123
|
|
1130
|
+
|
|
1131
|
+
allow(subject).to receive(:`).with(/pg_dump/) do
|
|
1132
|
+
`exit #{exit_status}`
|
|
1133
|
+
end
|
|
1134
|
+
|
|
1135
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
1136
|
+
.with(rds_credential, 0, account)
|
|
1137
|
+
.and_yield(socat_helper)
|
|
1138
|
+
|
|
1139
|
+
expect do
|
|
1140
|
+
subject.send('db:dump', rds_handle)
|
|
1141
|
+
end.to raise_error { |error|
|
|
1142
|
+
expect(error).to be_a SystemExit
|
|
1143
|
+
expect(error.status).to eq exit_status
|
|
1144
|
+
}
|
|
1145
|
+
end
|
|
1146
|
+
|
|
1147
|
+
context 'successful dump' do
|
|
1148
|
+
before do
|
|
1149
|
+
allow(subject).to receive(:`).with(/pg_dump .*/) do
|
|
1150
|
+
`exit 0`
|
|
1151
|
+
end
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
it 'prints a message indicating the dump is happening' do
|
|
1155
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
1156
|
+
.with(rds_credential, 0, account)
|
|
1157
|
+
.and_yield(socat_helper)
|
|
1158
|
+
|
|
1159
|
+
subject.send('db:dump', rds_handle)
|
|
1160
|
+
|
|
1161
|
+
expect(captured_logs)
|
|
1162
|
+
.to match(/Dumping to aws:rds::test-rds-db.dump/i)
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
it 'invokes pg_dump with the tunnel url' do
|
|
1166
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
1167
|
+
.with(rds_credential, 0, account)
|
|
1168
|
+
.and_yield(socat_helper)
|
|
1169
|
+
|
|
1170
|
+
local_url = 'postgres://user:pass@localhost.aptible.in:4242/dbname'
|
|
1171
|
+
|
|
1172
|
+
expect(subject).to receive(:`)
|
|
1173
|
+
.with(/pg_dump #{Regexp.escape(local_url)} > #{rds_handle}.dump/)
|
|
1174
|
+
|
|
1175
|
+
subject.send('db:dump', rds_handle)
|
|
1176
|
+
end
|
|
1177
|
+
end
|
|
1178
|
+
end
|
|
1179
|
+
|
|
753
1180
|
describe '#db:execute' do
|
|
754
1181
|
sql_path = 'file.sql'
|
|
755
1182
|
it 'should fail if database is non-existent' do
|
|
@@ -832,6 +1259,132 @@ describe Aptible::CLI::Agent do
|
|
|
832
1259
|
end
|
|
833
1260
|
end
|
|
834
1261
|
|
|
1262
|
+
describe '#db:execute (rds databases)' do
|
|
1263
|
+
let(:sql_path) { 'file.sql' }
|
|
1264
|
+
let(:rds_handle) { 'aws:rds::test-rds-db' }
|
|
1265
|
+
let(:stack) { Fabricate(:stack, internal_domain: 'aptible.in') }
|
|
1266
|
+
let(:account) { Fabricate(:account, stack: stack) }
|
|
1267
|
+
let(:app) { Fabricate(:app, account: account) }
|
|
1268
|
+
|
|
1269
|
+
let(:raw_rds_resource) do
|
|
1270
|
+
Fabricate(:external_aws_resource,
|
|
1271
|
+
resource_name: 'test-rds-db',
|
|
1272
|
+
resource_type: 'aws_rds_db_instance')
|
|
1273
|
+
end
|
|
1274
|
+
|
|
1275
|
+
let(:rds_db) do
|
|
1276
|
+
Aptible::CLI::Helpers::Database::RdsDatabase.new(
|
|
1277
|
+
rds_handle,
|
|
1278
|
+
raw_rds_resource.id,
|
|
1279
|
+
raw_rds_resource.created_at,
|
|
1280
|
+
raw_rds_resource
|
|
1281
|
+
)
|
|
1282
|
+
end
|
|
1283
|
+
|
|
1284
|
+
let(:rds_credential) do
|
|
1285
|
+
url = 'postgres://user:pass@host.com:5432/dbname'
|
|
1286
|
+
Fabricate(:external_aws_database_credential,
|
|
1287
|
+
external_aws_resource: raw_rds_resource,
|
|
1288
|
+
connection_url: url)
|
|
1289
|
+
end
|
|
1290
|
+
|
|
1291
|
+
let(:rds_connection) do
|
|
1292
|
+
double('rds_connection', present?: true, app: app)
|
|
1293
|
+
end
|
|
1294
|
+
|
|
1295
|
+
it 'should fail if database is non-existent' do
|
|
1296
|
+
allow(subject).to receive(:external_rds_database_from_handle)
|
|
1297
|
+
.with(rds_handle).and_return(nil)
|
|
1298
|
+
|
|
1299
|
+
expect do
|
|
1300
|
+
subject.send('db:execute', rds_handle, sql_path)
|
|
1301
|
+
end.to raise_error(Thor::Error, /no rds db found/i)
|
|
1302
|
+
end
|
|
1303
|
+
|
|
1304
|
+
context 'valid database' do
|
|
1305
|
+
before do
|
|
1306
|
+
allow(app).to receive(:account).and_return(account)
|
|
1307
|
+
allow(subject).to receive(:external_rds_database_from_handle)
|
|
1308
|
+
.with(rds_handle).and_return(rds_db)
|
|
1309
|
+
allow(subject).to receive(:`).with(/psql .*/) { `exit 0` }
|
|
1310
|
+
|
|
1311
|
+
raw_rds_resource.instance_variable_set(
|
|
1312
|
+
:@external_aws_database_credentials,
|
|
1313
|
+
[rds_credential]
|
|
1314
|
+
)
|
|
1315
|
+
raw_rds_resource.instance_variable_set(
|
|
1316
|
+
:@app_external_aws_rds_connections,
|
|
1317
|
+
[rds_connection]
|
|
1318
|
+
)
|
|
1319
|
+
end
|
|
1320
|
+
|
|
1321
|
+
it 'executes the file against the URL' do
|
|
1322
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
1323
|
+
.with(rds_credential, 0, account)
|
|
1324
|
+
.and_yield(socat_helper)
|
|
1325
|
+
|
|
1326
|
+
local_url = 'postgres://user:pass@localhost.aptible.in:4242/dbname'
|
|
1327
|
+
|
|
1328
|
+
expect(subject).to receive(:`)
|
|
1329
|
+
.with(/psql #{Regexp.escape(local_url)} < #{sql_path}/)
|
|
1330
|
+
|
|
1331
|
+
subject.send('db:execute', rds_handle, sql_path)
|
|
1332
|
+
|
|
1333
|
+
expect(captured_logs)
|
|
1334
|
+
.to match(/Executing #{sql_path} against #{rds_handle}/)
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
it 'fails when no credential is found' do
|
|
1338
|
+
raw_rds_resource.instance_variable_set(
|
|
1339
|
+
:@external_aws_database_credentials,
|
|
1340
|
+
[]
|
|
1341
|
+
)
|
|
1342
|
+
|
|
1343
|
+
expect do
|
|
1344
|
+
subject.send('db:execute', rds_handle, sql_path)
|
|
1345
|
+
end.to raise_error(Thor::Error, /no rds credential found/i)
|
|
1346
|
+
end
|
|
1347
|
+
|
|
1348
|
+
context 'on error stop' do
|
|
1349
|
+
before do
|
|
1350
|
+
subject.options = { on_error_stop: true }
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
it 'adds the ON_ERROR_STOP argument' do
|
|
1354
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
1355
|
+
.with(rds_credential, 0, account)
|
|
1356
|
+
.and_yield(socat_helper)
|
|
1357
|
+
|
|
1358
|
+
local_url = 'postgres://user:pass@localhost.aptible.in:4242/dbname'
|
|
1359
|
+
escaped_url = Regexp.escape(local_url)
|
|
1360
|
+
|
|
1361
|
+
expect(subject).to receive(:`)
|
|
1362
|
+
.with(/psql -v ON_ERROR_STOP=true #{escaped_url} < #{sql_path}/)
|
|
1363
|
+
|
|
1364
|
+
subject.send('db:execute', rds_handle, sql_path)
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
it 'exits with the same code as psql' do
|
|
1368
|
+
exit_status = 123
|
|
1369
|
+
allow(subject).to receive(:`).with(/psql .*/) do
|
|
1370
|
+
`exit #{exit_status}`
|
|
1371
|
+
end
|
|
1372
|
+
|
|
1373
|
+
expect(subject).to receive(:with_local_tunnel)
|
|
1374
|
+
.with(rds_credential, 0, account)
|
|
1375
|
+
.and_yield(socat_helper)
|
|
1376
|
+
|
|
1377
|
+
expect do
|
|
1378
|
+
subject.send('db:execute', rds_handle, sql_path)
|
|
1379
|
+
end.to raise_error { |error|
|
|
1380
|
+
expect(error).to be_a SystemExit
|
|
1381
|
+
expect(error.status).to eq exit_status
|
|
1382
|
+
}
|
|
1383
|
+
end
|
|
1384
|
+
end
|
|
1385
|
+
end
|
|
1386
|
+
end
|
|
1387
|
+
|
|
835
1388
|
describe '#db:deprovision' do
|
|
836
1389
|
before { expect(Aptible::Api::Database).to receive(:all) { [database] } }
|
|
837
1390
|
|
|
@@ -45,7 +45,10 @@ describe Aptible::CLI::Agent do
|
|
|
45
45
|
stub_options(docker_image: 'foobar')
|
|
46
46
|
|
|
47
47
|
expect(app).to receive(:create_operation!)
|
|
48
|
-
.with(
|
|
48
|
+
.with(
|
|
49
|
+
type: 'deploy',
|
|
50
|
+
settings: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' }
|
|
51
|
+
)
|
|
49
52
|
.and_return(operation)
|
|
50
53
|
expect(subject).to receive(:attach_to_operation_logs)
|
|
51
54
|
.with(operation)
|
|
@@ -60,14 +63,13 @@ describe Aptible::CLI::Agent do
|
|
|
60
63
|
private_registry_password: 'qux'
|
|
61
64
|
)
|
|
62
65
|
|
|
63
|
-
|
|
64
|
-
'APTIBLE_PRIVATE_REGISTRY_EMAIL' => 'foo',
|
|
66
|
+
sensitive_settings = {
|
|
65
67
|
'APTIBLE_PRIVATE_REGISTRY_USERNAME' => 'bar',
|
|
66
68
|
'APTIBLE_PRIVATE_REGISTRY_PASSWORD' => 'qux'
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
expect(app).to receive(:create_operation!)
|
|
70
|
-
.with(type: 'deploy',
|
|
72
|
+
.with(type: 'deploy', sensitive_settings: sensitive_settings)
|
|
71
73
|
.and_return(operation)
|
|
72
74
|
expect(subject).to receive(:attach_to_operation_logs)
|
|
73
75
|
.with(operation)
|
|
@@ -96,10 +98,12 @@ describe Aptible::CLI::Agent do
|
|
|
96
98
|
end
|
|
97
99
|
|
|
98
100
|
it 'allows setting configuration variables' do
|
|
99
|
-
stub_options
|
|
101
|
+
stub_options(docker_image: 'foobar')
|
|
100
102
|
|
|
101
103
|
expect(app).to receive(:create_operation!)
|
|
102
|
-
.with(type: 'deploy',
|
|
104
|
+
.with(type: 'deploy',
|
|
105
|
+
env: { 'FOO' => 'bar', 'BAR' => 'qux' },
|
|
106
|
+
settings: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' })
|
|
103
107
|
.and_return(operation)
|
|
104
108
|
expect(subject).to receive(:attach_to_operation_logs)
|
|
105
109
|
.with(operation)
|
|
@@ -137,7 +141,9 @@ describe Aptible::CLI::Agent do
|
|
|
137
141
|
stub_options(docker_image: 'foobar')
|
|
138
142
|
|
|
139
143
|
expect(app).to receive(:create_operation!)
|
|
140
|
-
.with(type: 'deploy',
|
|
144
|
+
.with(type: 'deploy',
|
|
145
|
+
env: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' },
|
|
146
|
+
settings: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' })
|
|
141
147
|
.and_return(operation)
|
|
142
148
|
expect(subject).to receive(:attach_to_operation_logs)
|
|
143
149
|
.with(operation)
|
|
@@ -145,6 +151,35 @@ describe Aptible::CLI::Agent do
|
|
|
145
151
|
subject.deploy('APTIBLE_DOCKER_IMAGE=foobar')
|
|
146
152
|
end
|
|
147
153
|
|
|
154
|
+
context 'dasherized option for image' do
|
|
155
|
+
it 'deploys via operation.settings' do
|
|
156
|
+
stub_options(docker_image: 'foobar')
|
|
157
|
+
|
|
158
|
+
expect(app).to receive(:create_operation!)
|
|
159
|
+
.with(type: 'deploy',
|
|
160
|
+
settings: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' })
|
|
161
|
+
.and_return(operation)
|
|
162
|
+
expect(subject).to receive(:attach_to_operation_logs)
|
|
163
|
+
.with(operation)
|
|
164
|
+
|
|
165
|
+
subject.deploy
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
context '(deprecated) environment variable style for image' do
|
|
170
|
+
it 'deploys via operation.env' do
|
|
171
|
+
stub_options
|
|
172
|
+
|
|
173
|
+
expect(app).to receive(:create_operation!)
|
|
174
|
+
.with(type: 'deploy', env: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' })
|
|
175
|
+
.and_return(operation)
|
|
176
|
+
expect(subject).to receive(:attach_to_operation_logs)
|
|
177
|
+
.with(operation)
|
|
178
|
+
|
|
179
|
+
subject.deploy('APTIBLE_DOCKER_IMAGE=foobar')
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
148
183
|
it 'reject contradictory command line argumnts' do
|
|
149
184
|
stub_options(docker_image: 'foobar')
|
|
150
185
|
|