aptible-cli 0.26.2 → 0.26.5

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -0
  3. data/Gemfile.lock +1 -1
  4. data/lib/aptible/cli/agent.rb +7 -4
  5. data/lib/aptible/cli/helpers/app.rb +29 -16
  6. data/lib/aptible/cli/helpers/database.rb +49 -32
  7. data/lib/aptible/cli/helpers/environment.rb +5 -4
  8. data/lib/aptible/cli/helpers/log_drain.rb +3 -1
  9. data/lib/aptible/cli/helpers/metric_drain.rb +3 -1
  10. data/lib/aptible/cli/resource_formatter.rb +14 -1
  11. data/lib/aptible/cli/subcommands/backup.rb +1 -1
  12. data/lib/aptible/cli/subcommands/config.rb +2 -2
  13. data/lib/aptible/cli/subcommands/db.rb +4 -3
  14. data/lib/aptible/cli/subcommands/log_drain.rb +2 -0
  15. data/lib/aptible/cli/subcommands/logs.rb +5 -5
  16. data/lib/aptible/cli/subcommands/metric_drain.rb +2 -0
  17. data/lib/aptible/cli/version.rb +1 -1
  18. data/lib/aptible/cli.rb +14 -0
  19. data/spec/aptible/cli/subcommands/apps_spec.rb +34 -21
  20. data/spec/aptible/cli/subcommands/backup_retention_policy_spec.rb +3 -1
  21. data/spec/aptible/cli/subcommands/backup_spec.rb +28 -3
  22. data/spec/aptible/cli/subcommands/config_spec.rb +11 -3
  23. data/spec/aptible/cli/subcommands/db_spec.rb +49 -95
  24. data/spec/aptible/cli/subcommands/deploy_spec.rb +8 -3
  25. data/spec/aptible/cli/subcommands/endpoints_spec.rb +20 -4
  26. data/spec/aptible/cli/subcommands/environment_spec.rb +4 -0
  27. data/spec/aptible/cli/subcommands/log_drain_spec.rb +15 -0
  28. data/spec/aptible/cli/subcommands/logs_spec.rb +16 -4
  29. data/spec/aptible/cli/subcommands/maintenance_spec.rb +7 -2
  30. data/spec/aptible/cli/subcommands/metric_drain_spec.rb +14 -2
  31. data/spec/aptible/cli/subcommands/services_spec.rb +3 -4
  32. data/spec/fabricators/account_fabricator.rb +9 -1
  33. data/spec/fabricators/app_external_aws_rds_connection_fabricator.rb +1 -1
  34. data/spec/fabricators/app_fabricator.rb +1 -1
  35. data/spec/fabricators/backup_fabricator.rb +1 -1
  36. data/spec/fabricators/backup_retention_policy_fabricator.rb +1 -1
  37. data/spec/fabricators/certificate_fabricator.rb +1 -1
  38. data/spec/fabricators/configuration_fabricator.rb +1 -1
  39. data/spec/fabricators/database_credential_fabricator.rb +1 -1
  40. data/spec/fabricators/database_disk_fabricator.rb +1 -1
  41. data/spec/fabricators/database_fabricator.rb +10 -1
  42. data/spec/fabricators/database_image_fabricator.rb +1 -1
  43. data/spec/fabricators/external_aws_account_fabricator.rb +1 -1
  44. data/spec/fabricators/external_aws_database_credential_fabricator.rb +1 -1
  45. data/spec/fabricators/external_aws_resource_fabricator.rb +1 -1
  46. data/spec/fabricators/log_drain_fabricator.rb +1 -1
  47. data/spec/fabricators/maintenance_app_fabricator.rb +1 -1
  48. data/spec/fabricators/maintenance_database_fabricator.rb +1 -1
  49. data/spec/fabricators/metric_drain_fabricator.rb +15 -1
  50. data/spec/fabricators/operation_fabricator.rb +1 -1
  51. data/spec/fabricators/service_fabricator.rb +1 -1
  52. data/spec/fabricators/service_sizing_policy_fabricator.rb +1 -1
  53. data/spec/fabricators/stack_fabricator.rb +1 -1
  54. data/spec/fabricators/vhost_fabricator.rb +1 -1
  55. data/spec/spec_helper.rb +7 -0
  56. data/spec/support/stub_aptible_resource.rb +25 -0
  57. metadata +5 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 800b50ac57828f37560b0a291a280fbf4e9bbf121b35fd812d662e45ef39bd45
4
- data.tar.gz: e0c70006be582d93f6e5648fae3894a481bde9cc9b43042ee399e745790aac95
3
+ metadata.gz: 13c94bbbfe4b853700fec4e895ab0eebd02f0c5ffcddf6cfd0a597c42f665d37
4
+ data.tar.gz: c495f4582ca15271799caa1b71a3922c3e5bbaafa692eda9476862465971556e
5
5
  SHA512:
6
- metadata.gz: 3653523bd6b9f944e5f88128a63c058aee80722288a44f5886393059f8b1ca2f7c506c45a7df06e0794ba75814574e36372ff02c73ee1efcd1c20ee803b1d0a1
7
- data.tar.gz: a47e8d8f71c9ac07d5001ad65616ac123884ac97ed5ab151d6d8895564bffffa623e1ef055b32fc3d4d0c661a9e71b72859b84480bde108f645f7ce33252e81f
6
+ metadata.gz: eb5b9e8adc78ba955f5125ca6c673894fea37fb8dd9a61775b4f44f87b9afc2c7fd9dbf8c645cd380af6fca7f754f03654a139539d9180060c6475544320b864
7
+ data.tar.gz: d86d87bb3c7d8dd6c57692c63c0592b99f950cd96b8b6bce1038678678a0c983c6b734ef380852b6edb8bb1e4e205fa6938b5045c8d5337f82fd4c40fb5aac2e
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ inherit_gem:
2
+ aptible-tasks: .rubocop.yml
3
+
4
+ Metrics/LineLength:
5
+ Max: 120
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- aptible-cli (0.26.2)
4
+ aptible-cli (0.26.5)
5
5
  activesupport (>= 4.0, < 6.0)
6
6
  aptible-api (~> 1.12)
7
7
  aptible-auth (~> 1.4)
@@ -92,10 +92,13 @@ module Aptible
92
92
  level = Logger::WARN
93
93
  debug_level = ENV['APTIBLE_DEBUG']
94
94
  level = debug_level if debug_level
95
- require 'httplog' if ENV['BUNDLER_VERSION'] && \
96
- ENV['APTIBLE_LOG_HTTP_REQUEST_RESPONSE'] && \
97
- !ENV['APTIBLE_LOG_HTTP_REQUEST_RESPONSE'] \
98
- .downcase.start_with?('f')
95
+ if ENV['BUNDLER_VERSION'] && \
96
+ ENV['APTIBLE_LOG_HTTP_REQUEST_RESPONSE'] && \
97
+ !ENV['APTIBLE_LOG_HTTP_REQUEST_RESPONSE'] \
98
+ .downcase.start_with?('f')
99
+ require 'httplog'
100
+ HttpLog.configure { |c| c.log_headers = true }
101
+ end
99
102
  conf.logger.tap { |l| l.level = level }
100
103
  end
101
104
  warn_sso_enforcement
@@ -110,12 +110,9 @@ module Aptible
110
110
  end
111
111
  end
112
112
 
113
- apps = apps_from_handle(s.app_handle, environment)
113
+ app = app_from_handle(s.app_handle, environment)
114
114
 
115
- case apps.count
116
- when 1
117
- return apps.first
118
- when 0
115
+ if app.nil?
119
116
  err_bits = ['Could not find app', s.app_handle]
120
117
  if environment
121
118
  err_bits << 'in environment'
@@ -125,11 +122,9 @@ module Aptible
125
122
  end
126
123
  err_bits << s.explain
127
124
  raise Thor::Error, err_bits.join(' ')
128
- else
129
- err = "Multiple apps named #{s.app_handle} exist, please specify " \
130
- 'with --environment'
131
- raise Thor::Error, err
132
125
  end
126
+
127
+ app
133
128
  end
134
129
 
135
130
  def ensure_service(options, type)
@@ -166,13 +161,20 @@ module Aptible
166
161
  )
167
162
  end
168
163
 
169
- def apps_from_handle(handle, environment)
170
- # TODO: This should probably use each_app for more efficiency.
171
- if environment
172
- environment.apps
173
- else
174
- apps_all
175
- end.select { |a| a.handle == handle }
164
+ def app_from_handle(handle, environment)
165
+ url = "/find/app?handle=#{handle}"
166
+ url += "&environment=#{environment.handle}" unless environment.nil?
167
+
168
+ Aptible::Api::App.find_by_url(
169
+ url,
170
+ token: fetch_token
171
+ )
172
+ rescue HyperResource::ClientError => e
173
+ raise unless e.body.is_a?(Hash) &&
174
+ e.body['error'] == 'multiple_resources_found'
175
+ raise Thor::Error,
176
+ "Multiple apps named #{handle} exist, please specify " \
177
+ 'with --environment'
176
178
  end
177
179
 
178
180
  def extract_env(args)
@@ -196,6 +198,17 @@ module Aptible
196
198
  raise Thor::Error, "Invalid argument: #{k}" if v.nil?
197
199
  end
198
200
 
201
+ def current_configuration(app)
202
+ conf_link = app.links['current_configuration']
203
+ return unless conf_link
204
+
205
+ Aptible::Api::Configuration.find_by_url(
206
+ conf_link.href,
207
+ token: fetch_token,
208
+ headers: { 'Prefer' => 'no_sensitive_extras=false' }
209
+ )
210
+ end
211
+
199
212
  private
200
213
 
201
214
  def handle_strategies
@@ -36,16 +36,10 @@ module Aptible
36
36
  raise Thor::Error,
37
37
  "Could not find environment #{environment_handle}"
38
38
  end
39
- databases = databases_from_handle(db_handle, environment)
40
- case databases.count
41
- when 1
42
- return databases.first
43
- when 0
44
- raise Thor::Error, "Could not find database #{db_handle}"
45
- else
46
- err = 'Multiple databases exist, please specify with --environment'
47
- raise Thor::Error, err
48
- end
39
+ db = database_from_handle(db_handle, environment)
40
+ raise Thor::Error, "Could not find database #{db_handle}" if db.nil?
41
+
42
+ db
49
43
  end
50
44
 
51
45
  def databases_href
@@ -140,20 +134,27 @@ module Aptible
140
134
  external_rds_databases_all.find { |a| a.handle == handle }
141
135
  end
142
136
 
143
- def databases_from_handle(handle, environment)
144
- databases = if environment
145
- environment.databases
146
- else
147
- databases_all
148
- end
149
- databases.select { |a| a.handle == handle }
137
+ def database_from_handle(handle, environment)
138
+ url = "/find/database?handle=#{handle}"
139
+ url += "&environment=#{environment.handle}" unless environment.nil?
140
+
141
+ Aptible::Api::Database.find_by_url(
142
+ url,
143
+ token: fetch_token
144
+ )
145
+ rescue HyperResource::ClientError => e
146
+ raise unless e.body.is_a?(Hash) &&
147
+ e.body['error'] == 'multiple_resources_found'
148
+ raise Thor::Error,
149
+ 'Multiple databases exist, please specify ' \
150
+ 'with --environment'
150
151
  end
151
152
 
152
153
  def clone_database(source, dest_handle)
153
154
  op = source.create_operation!(type: 'clone', handle: dest_handle)
154
155
  attach_to_operation_logs(op)
155
156
 
156
- databases_from_handle(dest_handle, source.account).first
157
+ database_from_handle(dest_handle, source.account)
157
158
  end
158
159
 
159
160
  def replicate_database(source, dest_handle, options)
@@ -177,7 +178,7 @@ module Aptible
177
178
  op = source.create_operation!(replication_params)
178
179
  attach_to_operation_logs(op)
179
180
 
180
- replica = databases_from_handle(dest_handle, source.account).first
181
+ replica = database_from_handle(dest_handle, source.account)
181
182
  attach_to_operation_logs(replica.operations.last)
182
183
  replica
183
184
  end
@@ -185,6 +186,14 @@ module Aptible
185
186
  # Creates a local tunnel and yields the helper
186
187
 
187
188
  def with_local_tunnel(credential, port = 0, target_account = nil)
189
+ # Credential has the senstive header set, and for some reason
190
+ # credential.create_operation! _lists all operations_. This would
191
+ # generate a show activity for every previous tunnel operation.
192
+ # So, we strip the sensitive header first to prevent that from happening
193
+ # This will also strip the connection_url, but we don't need it from
194
+ # this point on.
195
+ credential = without_sensitive(credential)
196
+ # Twice by here??
188
197
  op = if target_account.nil?
189
198
  credential.create_operation!(
190
199
  type: 'tunnel',
@@ -284,7 +293,7 @@ module Aptible
284
293
  raise Thor::Error, 'This command only works for PostgreSQL'
285
294
  end
286
295
 
287
- credential = find_credential(database)
296
+ credential, _credentials = find_credential(database)
288
297
 
289
298
  with_local_tunnel(credential) do |tunnel_helper|
290
299
  yield local_url(credential, tunnel_helper.port)
@@ -304,7 +313,7 @@ module Aptible
304
313
  remote_url = credential.connection_url
305
314
 
306
315
  uri = URI.parse(remote_url)
307
- domain = credential.database.account.stack.internal_domain
316
+ domain = without_sensitive(credential).database.account.stack.internal_domain
308
317
  "#{uri.scheme}://#{uri.user}:#{uri.password}@" \
309
318
  "localhost.#{domain}:#{local_port}#{uri.path}"
310
319
  end
@@ -314,21 +323,25 @@ module Aptible
314
323
  raise Thor::Error, "Database #{database.handle} is not provisioned"
315
324
  end
316
325
 
326
+ # Get the database credentials, without going using `with_senstive(database)`, as that
327
+ # would get the embedded last_operation, and generate an extra show activity
328
+ creds_link = database.links['database_credentials']
329
+ database_credentials = Aptible::Api::DatabaseCredential.all(
330
+ href: creds_link.href,
331
+ token: fetch_token,
332
+ headers: { 'Prefer' => 'no_sensitive_extras=false' }
333
+ )
334
+
317
335
  finder = proc { |c| c.default }
318
336
  finder = proc { |c| c.type == type } if type
319
- credential = database.database_credentials.find(&finder)
337
+ credential = database_credentials.find(&finder)
320
338
 
321
- return credential if credential
339
+ # It may be weird to return the credential and all the credentials, but the db:tunnel
340
+ # command lists all the credential types if you do not provide one, and we want to avoid
341
+ # generating more show activity than needed
342
+ return credential, database_credentials if credential
322
343
 
323
- types = database.database_credentials.map(&:type)
324
-
325
- # On v1, we fallback to the DB. We make sure to make --type work, to
326
- # avoid a confusing experience for customers.
327
- if database.account.stack.version == 'v1'
328
- types << database.type
329
- types.uniq!
330
- return database if type.nil? || type == database.type
331
- end
344
+ types = database_credentials.map(&:type)
332
345
 
333
346
  valid = types.join(', ')
334
347
 
@@ -365,6 +378,10 @@ module Aptible
365
378
  end
366
379
 
367
380
  def render_database(database, account)
381
+ # Maybe reload with senstive data
382
+ # Definately don't load the embedded last_operation
383
+ database.href = database.href + '?no_embed=true'
384
+ database = with_sensitive(database) if database.connection_url.nil?
368
385
  Formatter.render(Renderer.current) do |root|
369
386
  root.keyed_object('connection_url') do |node|
370
387
  ResourceFormatter.inject_database(node, database, account)
@@ -42,10 +42,11 @@ module Aptible
42
42
 
43
43
  def environment_from_handle(handle)
44
44
  return nil unless handle
45
- href = environment_href
46
- Aptible::Api::Account.all(token: fetch_token, href: href).find do |a|
47
- a.handle == handle
48
- end
45
+
46
+ Aptible::Api::Account.find_by_url(
47
+ "/find/account?handle=#{handle}",
48
+ token: fetch_token
49
+ )
49
50
  end
50
51
 
51
52
  def environment_map(accounts)
@@ -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)
@@ -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
@@ -67,7 +67,7 @@ module Aptible
67
67
 
68
68
  account = destination_account || backup.account
69
69
 
70
- database = databases_from_handle(handle, account).first
70
+ database = database_from_handle(handle, account)
71
71
  render_database(database, account)
72
72
  end
73
73
 
@@ -15,7 +15,7 @@ module Aptible
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|
@@ -38,7 +38,7 @@ module Aptible
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|
@@ -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|
@@ -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
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.26.2'.freeze
3
+ VERSION = '0.26.5'.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
@@ -20,13 +20,17 @@ describe Aptible::CLI::Agent do
20
20
  before do
21
21
  allow(subject).to receive(:save_token)
22
22
  allow(subject).to receive(:attach_to_operation_logs)
23
- allow(subject).to receive(:fetch_token) { double 'token' }
23
+ allow(subject).to receive(:fetch_token) { token }
24
+ allow(Aptible::Api::Account).to receive(:find_by_url)
25
+ .with("/find/account?handle=#{account.handle}", token: token)
26
+ .and_return(account)
24
27
  end
25
28
 
26
29
  let!(:account) { Fabricate(:account) }
27
30
  let!(:app) { Fabricate(:app, handle: 'hello', account: account) }
28
31
  let!(:service) { Fabricate(:service, app: app, process_type: 'web') }
29
32
  let(:op) { Fabricate(:operation, status: 'succeeded', resource: app) }
33
+ let(:token) { double 'token' }
30
34
 
31
35
  describe '#apps' do
32
36
  it 'lists an app in an account' do
@@ -83,13 +87,14 @@ describe Aptible::CLI::Agent do
83
87
 
84
88
  it 'lists filters down to one account' do
85
89
  account2 = Fabricate(:account, handle: 'account2')
90
+ allow(Aptible::Api::Account).to receive(:find_by_url)
91
+ .with("/find/account?handle=#{account2.handle}", token: token)
92
+ .and_return(account2)
86
93
  app2 = Fabricate(:app, account: account2, handle: 'app2')
87
94
  allow(subject).to receive(:options)
88
95
  .and_return(environment: account2.handle)
89
96
  allow(Aptible::Api::App).to receive(:all).and_return([app2])
90
97
 
91
- allow(Aptible::Api::Account).to receive(:all)
92
- .and_return([account, account2])
93
98
  subject.send('apps')
94
99
 
95
100
  expect(captured_output_text)
@@ -215,8 +220,11 @@ describe Aptible::CLI::Agent do
215
220
 
216
221
  describe '#apps:rename' do
217
222
  before do
218
- allow(Aptible::Api::App).to receive(:all) { [app] }
219
- allow(Aptible::Api::Account).to receive(:all) { [account] }
223
+ allow(Aptible::Api::App).to receive(:find_by_url)
224
+ .and_return(nil)
225
+ allow(Aptible::Api::App).to receive(:find_by_url)
226
+ .with("/find/app?handle=#{app.handle}&environment=#{account.handle}", token: token)
227
+ .and_return(app)
220
228
  end
221
229
 
222
230
  before(:each) do
@@ -256,7 +264,6 @@ describe Aptible::CLI::Agent do
256
264
 
257
265
  describe '#apps:scale' do
258
266
  before do
259
- allow(Aptible::Api::App).to receive(:all) { [app] }
260
267
  allow(Aptible::Api::Account).to receive(:all) { [account] }
261
268
  end
262
269
 
@@ -268,9 +275,9 @@ describe Aptible::CLI::Agent do
268
275
  .with('foobar')
269
276
  .and_return(account)
270
277
 
271
- expect(subject).to receive(:apps_from_handle)
278
+ expect(subject).to receive(:app_from_handle)
272
279
  .with('hello', account)
273
- .and_return([app])
280
+ .and_return(app)
274
281
  end
275
282
 
276
283
  def stub_options(**opts)
@@ -330,7 +337,7 @@ describe Aptible::CLI::Agent do
330
337
  allow(subject).to receive(:options) do
331
338
  { environment: 'foo', app: 'web', container_count: 2 }
332
339
  end
333
- allow(Aptible::Api::Account).to receive(:all) { [] }
340
+ allow(Aptible::Api::Account).to receive(:find_by_url).and_return(nil)
334
341
  allow(service).to receive(:create_operation!) { op }
335
342
 
336
343
  expect do
@@ -394,7 +401,14 @@ describe Aptible::CLI::Agent do
394
401
 
395
402
  before do
396
403
  account.apps = apps
397
- allow(Aptible::Api::App).to receive(:all).and_return(apps)
404
+ allow(Aptible::Api::App).to receive(:find_by_url)
405
+ .and_return(nil)
406
+ allow(Aptible::Api::App).to receive(:find_by_url)
407
+ .with("/find/app?handle=#{app.handle}&environment=#{account.handle}", token: token)
408
+ .and_return(app)
409
+ allow(Aptible::Api::App).to receive(:find_by_url)
410
+ .with("/find/app?handle=#{app.handle}", token: token)
411
+ .and_return(app)
398
412
  end
399
413
 
400
414
  it 'scopes the app search to an environment if provided' do
@@ -415,27 +429,21 @@ describe Aptible::CLI::Agent do
415
429
  end
416
430
 
417
431
  it 'fails if no app is found' do
418
- apps.pop
419
-
420
- strategies = [dummy_strategy_factory('hello', nil, true)]
432
+ strategies = [dummy_strategy_factory('goodbye', nil, true)]
421
433
  allow(subject).to receive(:handle_strategies) { strategies }
422
434
 
423
- expect { subject.ensure_app }.to raise_error(/not find app hello/)
435
+ expect { subject.ensure_app }.to raise_error(/not find app goodbye/)
424
436
  end
425
437
 
426
438
  it 'explains the strategy when it fails' do
427
- apps.pop
428
-
429
- strategies = [dummy_strategy_factory('hello', nil, true)]
439
+ strategies = [dummy_strategy_factory('goodbye', nil, true)]
430
440
  allow(subject).to receive(:handle_strategies) { strategies }
431
441
 
432
442
  expect { subject.ensure_app }.to raise_error(/from dummy/)
433
443
  end
434
444
 
435
445
  it 'indicates the environment when the app search was scoped' do
436
- apps.pop
437
-
438
- strategies = [dummy_strategy_factory('hello', 'aptible', true)]
446
+ strategies = [dummy_strategy_factory('goodbye', 'aptible', true)]
439
447
  allow(subject).to receive(:handle_strategies) { strategies }
440
448
 
441
449
  expect(subject).to receive(:environment_from_handle).with('aptible')
@@ -445,7 +453,12 @@ describe Aptible::CLI::Agent do
445
453
  end
446
454
 
447
455
  it 'fails if multiple apps are found' do
448
- apps << Fabricate(:app, handle: 'hello')
456
+ error = HyperResource::ClientError.new('multiple resources found')
457
+ allow(error).to receive(:body)
458
+ .and_return('error' => 'multiple_resources_found')
459
+ allow(Aptible::Api::App).to receive(:find_by_url)
460
+ .with('/find/app?handle=hello', token: token)
461
+ .and_raise(error)
449
462
 
450
463
  strategies = [dummy_strategy_factory('hello', nil, true)]
451
464
  allow(subject).to receive(:handle_strategies) { strategies }
@@ -13,7 +13,9 @@ describe Aptible::CLI::Agent do
13
13
 
14
14
  before do
15
15
  allow(subject).to receive(:fetch_token).and_return(token)
16
- allow(Aptible::Api::Account).to receive(:all) { [account] }
16
+ allow(Aptible::Api::Account).to receive(:find_by_url)
17
+ .with("/find/account?handle=#{account.handle}", token: token)
18
+ .and_return(account)
17
19
  end
18
20
 
19
21
  describe '#backup_retention_policy' do