aptible-cli 0.26.1 → 0.26.4

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -0
  3. data/Gemfile.lock +2 -2
  4. data/Makefile +3 -0
  5. data/README.md +83 -83
  6. data/lib/aptible/cli/agent.rb +14 -4
  7. data/lib/aptible/cli/helpers/app.rb +11 -0
  8. data/lib/aptible/cli/helpers/database.rb +29 -13
  9. data/lib/aptible/cli/helpers/log_drain.rb +3 -1
  10. data/lib/aptible/cli/helpers/metric_drain.rb +3 -1
  11. data/lib/aptible/cli/resource_formatter.rb +16 -3
  12. data/lib/aptible/cli/subcommands/apps.rb +2 -2
  13. data/lib/aptible/cli/subcommands/config.rb +8 -8
  14. data/lib/aptible/cli/subcommands/db.rb +4 -3
  15. data/lib/aptible/cli/subcommands/deploy.rb +1 -1
  16. data/lib/aptible/cli/subcommands/log_drain.rb +2 -0
  17. data/lib/aptible/cli/subcommands/logs.rb +5 -5
  18. data/lib/aptible/cli/subcommands/metric_drain.rb +2 -0
  19. data/lib/aptible/cli/subcommands/rebuild.rb +2 -1
  20. data/lib/aptible/cli/subcommands/restart.rb +2 -1
  21. data/lib/aptible/cli/subcommands/services.rb +4 -4
  22. data/lib/aptible/cli/subcommands/ssh.rb +1 -1
  23. data/lib/aptible/cli/version.rb +1 -1
  24. data/lib/aptible/cli.rb +14 -0
  25. data/spec/aptible/cli/agent_spec.rb +70 -0
  26. data/spec/aptible/cli/subcommands/config_spec.rb +8 -0
  27. data/spec/aptible/cli/subcommands/db_spec.rb +3 -51
  28. data/spec/aptible/cli/subcommands/log_drain_spec.rb +6 -0
  29. data/spec/aptible/cli/subcommands/metric_drain_spec.rb +4 -0
  30. data/spec/fabricators/account_fabricator.rb +9 -1
  31. data/spec/fabricators/app_external_aws_rds_connection_fabricator.rb +1 -1
  32. data/spec/fabricators/app_fabricator.rb +1 -1
  33. data/spec/fabricators/backup_fabricator.rb +1 -1
  34. data/spec/fabricators/backup_retention_policy_fabricator.rb +1 -1
  35. data/spec/fabricators/certificate_fabricator.rb +1 -1
  36. data/spec/fabricators/configuration_fabricator.rb +1 -1
  37. data/spec/fabricators/database_credential_fabricator.rb +1 -1
  38. data/spec/fabricators/database_disk_fabricator.rb +1 -1
  39. data/spec/fabricators/database_fabricator.rb +10 -1
  40. data/spec/fabricators/database_image_fabricator.rb +1 -1
  41. data/spec/fabricators/external_aws_account_fabricator.rb +1 -1
  42. data/spec/fabricators/external_aws_database_credential_fabricator.rb +1 -1
  43. data/spec/fabricators/external_aws_resource_fabricator.rb +1 -1
  44. data/spec/fabricators/log_drain_fabricator.rb +1 -1
  45. data/spec/fabricators/maintenance_app_fabricator.rb +1 -1
  46. data/spec/fabricators/maintenance_database_fabricator.rb +1 -1
  47. data/spec/fabricators/metric_drain_fabricator.rb +15 -1
  48. data/spec/fabricators/operation_fabricator.rb +1 -1
  49. data/spec/fabricators/service_fabricator.rb +1 -1
  50. data/spec/fabricators/service_sizing_policy_fabricator.rb +1 -1
  51. data/spec/fabricators/stack_fabricator.rb +1 -1
  52. data/spec/fabricators/vhost_fabricator.rb +1 -1
  53. data/spec/spec_helper.rb +5 -0
  54. data/spec/support/stub_aptible_resource.rb +25 -0
  55. metadata +5 -2
@@ -65,7 +65,9 @@ module Aptible
65
65
  end
66
66
 
67
67
  def ensure_log_drain(account, handle)
68
- drains = account.log_drains.select { |d| d.handle == handle }
68
+ link = account.links['log_drains'].base_href
69
+ account_drains = Aptible::Api::LogDrain.all(href: link, token: fetch_token)
70
+ drains = account_drains.select { |d| d.handle == handle }
69
71
 
70
72
  if drains.empty?
71
73
  raise Thor::Error, "No drain found with handle #{handle}"
@@ -19,7 +19,9 @@ module Aptible
19
19
  end
20
20
 
21
21
  def ensure_metric_drain(account, handle)
22
- drains = account.metric_drains.select { |d| d.handle == handle }
22
+ link = account.links['metric_drains'].base_href
23
+ account_drains = Aptible::Api::MetricDrain.all(href: link, token: fetch_token)
24
+ drains = account_drains.select { |d| d.handle == handle }
23
25
 
24
26
  if drains.empty?
25
27
  raise Thor::Error, "No drain found with handle #{handle}"
@@ -128,6 +128,10 @@ module Aptible
128
128
  end
129
129
 
130
130
  def inject_database(node, database, account)
131
+ # Some callers pass a database object with sensitive attributes already, others do not.
132
+ # Avoid creating extra 'show' activity if we already have the needed info
133
+ database = with_sensitive(database) if database.objects[:database_credentials].nil?
134
+
131
135
  node.value('id', database.id)
132
136
  node.value('handle', database.handle)
133
137
  node.value('created_at', database.created_at)
@@ -203,8 +207,8 @@ module Aptible
203
207
  node.value('type', 'https')
204
208
  node.value('port', port)
205
209
  node.value('load_balancing_algorithm_type', vhost
206
- .load_balancing_algorithm_type)
207
- node.value('shared', vhost.shared)
210
+ .load_balancing_algorithm_type || 'round_robin')
211
+ node.value('shared', vhost.shared || 'false')
208
212
  end
209
213
 
210
214
  node.value('internal', vhost.internal)
@@ -243,6 +247,9 @@ module Aptible
243
247
  log_drain.drain_ephemeral_sessions)
244
248
  node.value('drain_proxies', log_drain.drain_proxies)
245
249
 
250
+ # These can be either optional for the drain type,
251
+ # or sensitive attributes we don't need to worry about
252
+ # in text output
246
253
  optional_attrs = %w(drain_username drain_host drain_port url)
247
254
  optional_attrs.each do |attr|
248
255
  value = log_drain.attributes[attr]
@@ -257,7 +264,13 @@ module Aptible
257
264
  node.value('handle', metric_drain.handle)
258
265
  node.value('drain_type', metric_drain.drain_type)
259
266
  node.value('created_at', metric_drain.created_at)
260
- node.value('drain_configuration', metric_drain.drain_configuration)
267
+
268
+ # Sensitive attributes we don't need to worry about being missing in text output
269
+ optional_attrs = %w(drain_configuration)
270
+ optional_attrs.each do |attr|
271
+ value = metric_drain.attributes[attr]
272
+ node.value(attr, value) unless value.nil?
273
+ end
261
274
 
262
275
  attach_account(node, account)
263
276
  end
@@ -65,7 +65,7 @@ module Aptible
65
65
  end
66
66
  end
67
67
 
68
- desc 'apps:scale SERVICE ' \
68
+ desc 'apps:scale [--app APP] SERVICE ' \
69
69
  '[--container-count COUNT] [--container-size SIZE_MB] ' \
70
70
  '[--container-profile PROFILE]',
71
71
  'Scale a service'
@@ -107,7 +107,7 @@ module Aptible
107
107
  attach_to_operation_logs(op)
108
108
  end
109
109
 
110
- desc 'apps:deprovision', 'Deprovision an app'
110
+ desc 'apps:deprovision [--app APP]', 'Deprovision an app'
111
111
  app_options
112
112
  define_method 'apps:deprovision' do
113
113
  telemetry(__method__, options)
@@ -9,13 +9,13 @@ module Aptible
9
9
  include Helpers::App
10
10
  include Helpers::Telemetry
11
11
 
12
- desc 'config', "Print an app's current configuration"
12
+ desc 'config [--app APP]', "Print an app's current configuration"
13
13
  app_options
14
14
  def config
15
15
  telemetry(__method__, options)
16
16
 
17
17
  app = ensure_app(options)
18
- config = app.current_configuration
18
+ config = current_configuration(app)
19
19
  env = config ? config.env : {}
20
20
 
21
21
  Formatter.render(Renderer.current) do |root|
@@ -31,14 +31,14 @@ module Aptible
31
31
  end
32
32
  end
33
33
 
34
- desc 'config:get [VAR1]',
34
+ desc 'config:get [--app APP] [VAR1]',
35
35
  "Print a specific key within an app's current configuration"
36
36
  app_options
37
37
  define_method 'config:get' do |*args|
38
38
  telemetry(__method__, options)
39
39
 
40
40
  app = ensure_app(options)
41
- config = app.current_configuration
41
+ config = current_configuration(app)
42
42
  env = config ? config.env : {}
43
43
 
44
44
  Formatter.render(Renderer.current) do |root|
@@ -50,7 +50,7 @@ module Aptible
50
50
  end
51
51
  end
52
52
 
53
- desc 'config:add [VAR1=VAL1] [VAR2=VAL2] [...]',
53
+ desc 'config:add [--app APP] [VAR1=VAL1] [VAR2=VAL2] [...]',
54
54
  'Add an ENV variable to an app'
55
55
  app_options
56
56
  define_method 'config:add' do |*args|
@@ -64,7 +64,7 @@ module Aptible
64
64
  attach_to_operation_logs(operation)
65
65
  end
66
66
 
67
- desc 'config:set [VAR1=VAL1] [VAR2=VAL2] [...]',
67
+ desc 'config:set [--app APP] [VAR1=VAL1] [VAR2=VAL2] [...]',
68
68
  'Add an ENV variable to an app'
69
69
  app_options
70
70
  define_method 'config:set' do |*args|
@@ -72,7 +72,7 @@ module Aptible
72
72
  send('config:add', *args)
73
73
  end
74
74
 
75
- desc 'config:rm [VAR1] [VAR2] [...]',
75
+ desc 'config:rm [--app APP] [VAR1] [VAR2] [...]',
76
76
  'Remove an ENV variable from an app'
77
77
  app_options
78
78
  define_method 'config:rm' do |*args|
@@ -90,7 +90,7 @@ module Aptible
90
90
  attach_to_operation_logs(operation)
91
91
  end
92
92
 
93
- desc 'config:unset [VAR1] [VAR2] [...]',
93
+ desc 'config:unset [--app APP] [VAR1] [VAR2] [...]',
94
94
  'Remove an ENV variable from an app'
95
95
  app_options
96
96
  define_method 'config:unset' do |*args|
@@ -335,13 +335,13 @@ module Aptible
335
335
  return use_rds_tunnel(handle, desired_port) if aws_rds_db?(handle)
336
336
 
337
337
  database = ensure_database(options.merge(db: handle))
338
- credential = find_credential(database, options[:type])
338
+ credential, credentials = find_credential(database, options[:type])
339
339
 
340
340
  m = "Creating #{credential.type} tunnel to #{database.handle}..."
341
341
  CLI.logger.info m
342
342
 
343
343
  if options[:type].nil?
344
- types = database.database_credentials.map(&:type)
344
+ types = credentials.map(&:type)
345
345
  unless types.empty?
346
346
  valid = types.join(', ')
347
347
  CLI.logger.info 'Use --type TYPE to specify a tunnel type'
@@ -481,7 +481,8 @@ module Aptible
481
481
  telemetry(__method__, options.merge(handle: handle))
482
482
 
483
483
  database = ensure_database(options.merge(db: handle))
484
- credential = find_credential(database, options[:type])
484
+
485
+ credential, _credentials = find_credential(database, options[:type])
485
486
 
486
487
  Formatter.render(Renderer.current) do |root|
487
488
  root.keyed_object('connection_url') do |node|
@@ -19,7 +19,7 @@ module Aptible
19
19
  include Helpers::App
20
20
  include Helpers::Telemetry
21
21
 
22
- desc 'deploy [OPTIONS] [VAR1=VAL1] [VAR2=VAL2] [...]',
22
+ desc 'deploy [--app APP] [OPTIONS] [VAR1=VAL1] [VAR2=VAL2] [...]',
23
23
  'Deploy an app'
24
24
  option :git_commitish,
25
25
  desc: 'Deploy a specific git commit or branch: the ' \
@@ -44,6 +44,8 @@ module Aptible
44
44
  account = acc_map[drain.links.account.href]
45
45
  next if account.nil?
46
46
 
47
+ # JSON output format we potentially show sensitive attributes
48
+ drain = with_sensitive(drain) if Renderer.format == 'json'
47
49
  node.object do |n|
48
50
  ResourceFormatter.inject_log_drain(n, drain, account)
49
51
  end
@@ -134,11 +134,11 @@ module Aptible
134
134
  options[:string_matches]
135
135
  )
136
136
  elsif id_options.any?
137
- if options[:container_id]
138
- search_attrs = { container_id: options[:container_id] }
139
- else
140
- search_attrs = { type: r_type, id: id_options.compact.first }
141
- end
137
+ search_attrs = if options[:container_id]
138
+ { container_id: options[:container_id] }
139
+ else
140
+ { type: r_type, id: id_options.compact.first }
141
+ end
142
142
  files = find_s3_files_by_attrs(
143
143
  options[:region],
144
144
  options[:bucket],
@@ -38,6 +38,8 @@ module Aptible
38
38
  account = acc_map[drain.links.account.href]
39
39
  next if account.nil?
40
40
 
41
+ # JSON output format we potentially show sensitive attributes
42
+ drain = with_sensitive(drain) if Renderer.format == 'json'
41
43
  node.object do |n|
42
44
  ResourceFormatter.inject_metric_drain(n, drain, account)
43
45
  end
@@ -8,7 +8,8 @@ module Aptible
8
8
  include Helpers::App
9
9
  include Helpers::Telemetry
10
10
 
11
- desc 'rebuild', 'Rebuild an app, and restart its services'
11
+ desc 'rebuild [--app APP]',
12
+ 'Rebuild an app, and restart its services'
12
13
  app_options
13
14
  def rebuild
14
15
  telemetry(__method__, options)
@@ -8,7 +8,8 @@ module Aptible
8
8
  include Helpers::App
9
9
  include Helpers::Telemetry
10
10
 
11
- desc 'restart', 'Restart all services associated with an app'
11
+ desc 'restart [--app APP]',
12
+ 'Restart all services associated with an app'
12
13
  option :simulate_oom,
13
14
  type: :boolean,
14
15
  desc: 'Add this flag to simulate an OOM restart and test ' \
@@ -7,7 +7,7 @@ module Aptible
7
7
  include Helpers::App
8
8
  include Helpers::Telemetry
9
9
 
10
- desc 'services', 'List Services for an App'
10
+ desc 'services [--app APP]', 'List Services for an App'
11
11
  app_options
12
12
  def services
13
13
  telemetry(__method__, options)
@@ -25,7 +25,7 @@ module Aptible
25
25
  end
26
26
  end
27
27
 
28
- desc 'services:settings SERVICE'\
28
+ desc 'services:settings [--app APP] SERVICE'\
29
29
  ' [--force-zero-downtime|--no-force-zero-downtime]'\
30
30
  ' [--simple-health-check|--no-simple-health-check]'\
31
31
  ' [--restart-free-scaling|--no-restart-free-scaling]'\
@@ -69,7 +69,7 @@ module Aptible
69
69
  service.update!(**updates) if updates.any?
70
70
  end
71
71
 
72
- desc 'services:autoscaling_policy SERVICE',
72
+ desc 'services:autoscaling_policy [--app APP] SERVICE',
73
73
  'Returns the associated sizing policy, if any'
74
74
  app_options
75
75
  define_method 'services:autoscaling_policy' do |service|
@@ -99,7 +99,7 @@ module Aptible
99
99
 
100
100
  map 'services:sizing_policy' => 'services:autoscaling_policy'
101
101
 
102
- desc 'services:autoscaling_policy:set SERVICE '\
102
+ desc 'services:autoscaling_policy:set [--app APP] SERVICE '\
103
103
  '--autoscaling-type (horizontal|vertical) '\
104
104
  '[--metric-lookback-seconds SECONDS] '\
105
105
  '[--percentile PERCENTILE] '\
@@ -10,7 +10,7 @@ module Aptible
10
10
  include Helpers::App
11
11
  include Helpers::Telemetry
12
12
 
13
- desc 'ssh [COMMAND]', 'Run a command against an app'
13
+ desc 'ssh [--app APP] [COMMAND]', 'Run a command against an app'
14
14
  long_desc <<-LONGDESC
15
15
  Runs an interactive command against a remote Aptible app
16
16
 
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.26.1'.freeze
3
+ VERSION = '0.26.4'.freeze
4
4
  end
5
5
  end
data/lib/aptible/cli.rb CHANGED
@@ -7,6 +7,20 @@ require 'aptible/cli/formatter'
7
7
  require 'aptible/cli/renderer'
8
8
  require 'aptible/cli/resource_formatter'
9
9
 
10
+ # Set no_sensitive_extras=true as the default for all API resources.
11
+ # This avoids returning sensitive embedded data unless explicitly requested.
12
+ Aptible::Api::Resource.headers = { 'Prefer' => 'no_sensitive_extras=true' }
13
+
14
+ def with_sensitive(resource)
15
+ resource.headers['Prefer'] = 'no_sensitive_extras=false'
16
+ resource.find_by_url(resource.href)
17
+ end
18
+
19
+ def without_sensitive(resource)
20
+ resource.headers['Prefer'] = 'no_sensitive_extras=true'
21
+ resource.find_by_url(resource.href)
22
+ end
23
+
10
24
  module Aptible
11
25
  module CLI
12
26
  class TtyLogFormatter
@@ -428,6 +428,76 @@ describe Aptible::CLI::Agent do
428
428
  end
429
429
  end
430
430
 
431
+ context 'failed_login_attempt_id handling' do
432
+ before do
433
+ allow(subject).to receive(:options)
434
+ .and_return(email: email, password: password)
435
+ expect(subject).to receive(:ask).with('2FA Token: ')
436
+ .once
437
+ .and_return(token)
438
+ end
439
+
440
+ it 'should extract failed_login_attempt_id when present' do
441
+ e = make_oauth2_error(
442
+ 'otp_token_required',
443
+ 'failed_login_attempt_id' => 'attempt-123'
444
+ )
445
+
446
+ expect(Aptible::Auth::Token).to receive(:create)
447
+ .with(email: email, password: password, expires_in: 1.week.seconds)
448
+ .once
449
+ .and_raise(e)
450
+
451
+ expect(Aptible::Auth::Token).to receive(:create)
452
+ .with(email: email, password: password, otp_token: token,
453
+ previous_login_attempt_id: 'attempt-123',
454
+ expires_in: 12.hours.seconds)
455
+ .once
456
+ .and_return(token)
457
+
458
+ subject.login
459
+ end
460
+
461
+ it 'should handle nil exception_context gracefully' do
462
+ parsed = { 'error' => 'otp_token_required' }
463
+ response = double('response',
464
+ parsed: parsed,
465
+ body: 'error otp_token_required')
466
+ allow(response).to receive(:error=)
467
+ e = OAuth2::Error.new(response)
468
+
469
+ expect(Aptible::Auth::Token).to receive(:create)
470
+ .with(email: email, password: password, expires_in: 1.week.seconds)
471
+ .once
472
+ .and_raise(e)
473
+
474
+ expect(Aptible::Auth::Token).to receive(:create)
475
+ .with(email: email, password: password, otp_token: token,
476
+ expires_in: 12.hours.seconds)
477
+ .once
478
+ .and_return(token)
479
+
480
+ subject.login
481
+ end
482
+
483
+ it 'should handle missing failed_login_attempt_id gracefully' do
484
+ e = make_oauth2_error('otp_token_required', {})
485
+
486
+ expect(Aptible::Auth::Token).to receive(:create)
487
+ .with(email: email, password: password, expires_in: 1.week.seconds)
488
+ .once
489
+ .and_raise(e)
490
+
491
+ expect(Aptible::Auth::Token).to receive(:create)
492
+ .with(email: email, password: password, otp_token: token,
493
+ expires_in: 12.hours.seconds)
494
+ .once
495
+ .and_return(token)
496
+
497
+ subject.login
498
+ end
499
+ end
500
+
431
501
  context 'SSO logins' do
432
502
  let(:token) { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9.eyJpZCI6I' }
433
503
 
@@ -17,6 +17,9 @@ describe Aptible::CLI::Agent do
17
17
  end
18
18
 
19
19
  before { allow(subject).to receive(:options) { { app: app.handle } } }
20
+ before do
21
+ allow(subject).to receive(:current_configuration, &:current_configuration)
22
+ end
20
23
  let(:operation) { Fabricate(:operation, resource: app) }
21
24
 
22
25
  describe '#config' do
@@ -60,6 +63,11 @@ describe Aptible::CLI::Agent do
60
63
  end
61
64
 
62
65
  describe '#config:get' do
66
+ it 'shows nothing for an unconfigured app' do
67
+ subject.send('config:get', 'FOO')
68
+ expect(captured_output_text).to eq('')
69
+ end
70
+
63
71
  it 'should show single environment variable specified' do
64
72
  app.current_configuration = Fabricate(
65
73
  :configuration, app: app, env: { 'FOO' => 'BAR', 'QUX' => 'two words' }
@@ -10,6 +10,9 @@ describe Aptible::CLI::Agent do
10
10
  allow(subject).to receive(:ask)
11
11
  allow(subject).to receive(:save_token)
12
12
  allow(subject).to receive(:fetch_token) { token }
13
+ allow(Aptible::Api::DatabaseCredential).to receive(:all) do
14
+ database.database_credentials
15
+ end
13
16
  end
14
17
 
15
18
  let(:handle) { 'foobar' }
@@ -226,41 +229,6 @@ describe Aptible::CLI::Agent do
226
229
  expect { subject.send('db:tunnel', handle) }
227
230
  .to raise_error(/foobar is not provisioned/im)
228
231
  end
229
-
230
- context 'v1 stack' do
231
- before do
232
- allow(database.account.stack).to receive(:version) { 'v1' }
233
- end
234
-
235
- it 'falls back to the database itself if no type is given' do
236
- expect(subject).to receive(:with_local_tunnel).with(database, 0)
237
- subject.send('db:tunnel', handle)
238
- end
239
-
240
- it 'falls back to the database itself if type matches' do
241
- subject.options = { type: 'bar' }
242
- allow(database).to receive(:type) { 'bar' }
243
-
244
- expect(subject).to receive(:with_local_tunnel).with(database, 0)
245
- subject.send('db:tunnel', handle)
246
- end
247
-
248
- it 'does not fall back to the database itself if type mismatches' do
249
- subject.options = { type: 'bar' }
250
- allow(database).to receive(:type) { 'foo' }
251
-
252
- expect { subject.send('db:tunnel', handle) }
253
- .to raise_error(/no credential with type bar/im)
254
- end
255
-
256
- it 'does not suggest other types that do not exist' do
257
- expect(subject).to receive(:with_local_tunnel).with(database, 0)
258
-
259
- subject.send('db:tunnel', handle)
260
-
261
- expect(captured_logs).not_to match(/use --type type/i)
262
- end
263
- end
264
232
  end
265
233
  end
266
234
 
@@ -845,22 +813,6 @@ describe Aptible::CLI::Agent do
845
813
  expect { subject.send('db:url', handle) }
846
814
  .to raise_error(/Multiple databases/)
847
815
  end
848
-
849
- context 'v1 stack' do
850
- before do
851
- allow(database.account.stack).to receive(:version) { 'v1' }
852
- end
853
-
854
- it 'returns the URL of a specified DB' do
855
- connection_url = 'postgresql://aptible-v1:password@lega.cy:4242/db'
856
- expect(database).to receive(:connection_url)
857
- .and_return(connection_url)
858
-
859
- subject.send('db:url', handle)
860
-
861
- expect(captured_output_text.chomp).to eq(connection_url)
862
- end
863
- end
864
816
  end
865
817
  end
866
818
 
@@ -14,9 +14,15 @@ describe Aptible::CLI::Agent do
14
14
  .with(token: token, href: '/log_drains?per_page=5000')
15
15
  .and_return([log_drain])
16
16
 
17
+ allow(Aptible::Api::LogDrain).to receive(:all)
18
+ .with(token: token, href: "/accounts/#{account.id}/log_drains")
19
+ .and_return([log_drain])
20
+
17
21
  allow(Aptible::Api::Account).to receive(:all)
18
22
  .with(token: token, href: '/accounts?per_page=5000&no_embed=true')
19
23
  .and_return([account])
24
+
25
+ allow(account).to receive(:reload).and_return(account)
20
26
  end
21
27
 
22
28
  describe '#log_drain:list' do
@@ -14,6 +14,10 @@ describe Aptible::CLI::Agent do
14
14
  .with(token: token, href: '/metric_drains?per_page=5000')
15
15
  .and_return([metric_drain])
16
16
 
17
+ allow(Aptible::Api::MetricDrain).to receive(:all)
18
+ .with(token: token, href: "/accounts/#{account.id}/metric_drains")
19
+ .and_return([metric_drain])
20
+
17
21
  allow(Aptible::Api::Account).to receive(:all)
18
22
  .with(token: token, href: '/accounts?per_page=5000&no_embed=true')
19
23
  .and_return([account])
@@ -1,4 +1,4 @@
1
- class StubAccount < OpenStruct
1
+ class StubAccount < StubAptibleResource
2
2
  def each_app(&block)
3
3
  return enum_for(:each_app) if block.nil?
4
4
  apps.each(&block)
@@ -34,6 +34,14 @@ Fabricator(:account, from: :stub_account) do
34
34
  hash = {
35
35
  self: OpenStruct.new(
36
36
  href: "/accounts/#{attrs[:id]}"
37
+ ),
38
+ metric_drains: OpenStruct.new(
39
+ base_href: "/accounts/#{attrs[:id]}/metric_drains",
40
+ href: "/accounts/#{attrs[:id]}/metric_drains"
41
+ ),
42
+ log_drains: OpenStruct.new(
43
+ base_href: "/accounts/#{attrs[:id]}/log_drains",
44
+ href: "/accounts/#{attrs[:id]}/log_drains"
37
45
  )
38
46
  }
39
47
  OpenStruct.new(hash)
@@ -1,4 +1,4 @@
1
- class StubAppExternalAwsRdsConnection < OpenStruct
1
+ class StubAppExternalAwsRdsConnection < StubAptibleResource
2
2
  def attributes
3
3
  {
4
4
  'id' => id,
@@ -1,4 +1,4 @@
1
- class StubApp < OpenStruct
1
+ class StubApp < StubAptibleResource
2
2
  def vhosts
3
3
  services.map(&:vhosts).flatten
4
4
  end
@@ -1,4 +1,4 @@
1
- class StubBackup < OpenStruct; end
1
+ class StubBackup < StubAptibleResource; end
2
2
 
3
3
  Fabricator(:backup, from: :stub_backup) do
4
4
  id { sequence(:backup_id) }
@@ -1,4 +1,4 @@
1
- class StubBackupRetentionPolicy < OpenStruct
1
+ class StubBackupRetentionPolicy < StubAptibleResource
2
2
  def reload
3
3
  self
4
4
  end
@@ -1,4 +1,4 @@
1
- class StubCertificate < OpenStruct; end
1
+ class StubCertificate < StubAptibleResource; end
2
2
 
3
3
  Fabricator(:certificate, from: :stub_certificate) do
4
4
  account
@@ -1,4 +1,4 @@
1
- class StubConfiguration < OpenStruct
1
+ class StubConfiguration < StubAptibleResource
2
2
  end
3
3
 
4
4
  Fabricator(:configuration, from: :stub_configuration) do
@@ -1,4 +1,4 @@
1
- class StubDatabaseCredential < OpenStruct; end
1
+ class StubDatabaseCredential < StubAptibleResource; end
2
2
 
3
3
  Fabricator(:database_credential, from: :stub_database_credential) do
4
4
  database
@@ -1,4 +1,4 @@
1
- class StubDatabaseDisk < OpenStruct
1
+ class StubDatabaseDisk < StubAptibleResource
2
2
  end
3
3
 
4
4
  Fabricator(:database_disk, from: :stub_database_disk) do
@@ -1,7 +1,13 @@
1
- class StubDatabase < OpenStruct
1
+ class StubDatabase < StubAptibleResource
2
2
  def provisioned?
3
3
  status == 'provisioned'
4
4
  end
5
+
6
+ def objects
7
+ {
8
+ 'database_credentials' => database_credentials
9
+ }
10
+ end
5
11
  end
6
12
 
7
13
  Fabricator(:database, from: :stub_database) do
@@ -23,6 +29,9 @@ Fabricator(:database, from: :stub_database) do
23
29
  hash = {
24
30
  account: OpenStruct.new(
25
31
  href: "/accounts/#{attrs[:account].id}"
32
+ ),
33
+ database_credentials: OpenStruct.new(
34
+ href: "/databases/#{attrs[:handle]}/database_credentials"
26
35
  )
27
36
  }
28
37
  OpenStruct.new(hash)
@@ -1,4 +1,4 @@
1
- class StubDatabaseImage < OpenStruct
1
+ class StubDatabaseImage < StubAptibleResource
2
2
  end
3
3
 
4
4
  Fabricator(:database_image, from: :stub_database_image) do
@@ -1,4 +1,4 @@
1
- class StubExternalAwsAccount < OpenStruct
1
+ class StubExternalAwsAccount < StubAptibleResource
2
2
  def attributes
3
3
  {
4
4
  'aws_account_id' => aws_account_id,
@@ -1,4 +1,4 @@
1
- class StubExternalAwsDatabaseCredential < OpenStruct
1
+ class StubExternalAwsDatabaseCredential < StubAptibleResource
2
2
  def attributes
3
3
  {
4
4
  'id' => id,