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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +27 -0
  3. data/.github/workflows/test.yml +1 -1
  4. data/Dockerfile +8 -5
  5. data/Gemfile.lock +28 -17
  6. data/Makefile +50 -2
  7. data/README.md +2 -1
  8. data/aptible-cli.gemspec +5 -2
  9. data/docker-compose.yml +5 -1
  10. data/lib/aptible/cli/agent.rb +9 -0
  11. data/lib/aptible/cli/helpers/aws_account.rb +158 -0
  12. data/lib/aptible/cli/helpers/database.rb +182 -2
  13. data/lib/aptible/cli/helpers/token.rb +14 -0
  14. data/lib/aptible/cli/helpers/vhost/option_set_builder.rb +8 -15
  15. data/lib/aptible/cli/renderer/text.rb +33 -2
  16. data/lib/aptible/cli/subcommands/aws_accounts.rb +252 -0
  17. data/lib/aptible/cli/subcommands/db.rb +67 -3
  18. data/lib/aptible/cli/subcommands/deploy.rb +45 -11
  19. data/lib/aptible/cli/subcommands/endpoints.rb +0 -2
  20. data/lib/aptible/cli/subcommands/organizations.rb +55 -0
  21. data/lib/aptible/cli/subcommands/services.rb +20 -8
  22. data/lib/aptible/cli/version.rb +1 -1
  23. data/spec/aptible/cli/helpers/database_spec.rb +118 -0
  24. data/spec/aptible/cli/helpers/token_spec.rb +70 -0
  25. data/spec/aptible/cli/subcommands/db_spec.rb +553 -0
  26. data/spec/aptible/cli/subcommands/deploy_spec.rb +42 -7
  27. data/spec/aptible/cli/subcommands/external_aws_accounts_spec.rb +737 -0
  28. data/spec/aptible/cli/subcommands/organizations_spec.rb +90 -0
  29. data/spec/aptible/cli/subcommands/services_spec.rb +77 -0
  30. data/spec/fabricators/app_external_aws_rds_connection_fabricator.rb +55 -0
  31. data/spec/fabricators/external_aws_account_fabricator.rb +49 -0
  32. data/spec/fabricators/external_aws_database_credential_fabricator.rb +46 -0
  33. data/spec/fabricators/external_aws_resource_fabricator.rb +72 -0
  34. 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(type: 'deploy', env: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' })
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
- env = {
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', env: env)
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', env: { 'FOO' => 'bar', 'BAR' => 'qux' })
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', env: { 'APTIBLE_DOCKER_IMAGE' => 'foobar' })
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