aptible-cli 0.19.3 → 0.19.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f20b71eb3388732ade1e6e53cb1f076761d40a06d460faf08fa3e468f239acc1
4
- data.tar.gz: 3e603d02a1dbe3e8168209f9a0f061d29851a48189d28dc549bff3a7d203a5e1
3
+ metadata.gz: f3f0904b2033596eb17ff98125288e5a2adec95ae6e379b95a5b8131b81bfe54
4
+ data.tar.gz: 9c002c17e22dd77ff79360671d0057455a70ff4ee2311d1466651a5c39b3c1a2
5
5
  SHA512:
6
- metadata.gz: e195096e2854788c488477ca6d42fd1beb15ce2b6044f0be32a774e6b3915c387694e4608c7546a94915dcb32b3f0844f4864544e4a64806dca23379c0383598
7
- data.tar.gz: b2fb1ef99eb836cac0e80419d362277b05877826e8cf4b8f8cb78f937168d4ab658b81f9fea6c7ea0559cd5fe6f926aec896869b7fa8737e4503536d39dfb161
6
+ metadata.gz: 0c1b0857f175135e6d7feb64a9a6a7b9633b73823f1f32100473f2f90c5b529a6dcf041b55bf4af47a92af08c425b24f4ce1abfacce3deea74b1e031e0bf5fa0
7
+ data.tar.gz: ccb65684ab24eb9055adee5e17a04d0a9dbf2cdc496ba1b71a9a6a229e83ee092ad43c2ee660f73b5da95b47b875bdf2d0212d6ede3d932455ff67188d4b47c1
data/README.md CHANGED
@@ -31,6 +31,7 @@ Commands:
31
31
  aptible apps # List all applications
32
32
  aptible apps:create HANDLE # Create a new application
33
33
  aptible apps:deprovision # Deprovision an app
34
+ aptible apps:rename OLD_HANDLE NEW_HANDLE [--environment ENVIRONMENT_HANDLE] # Rename an app handle. In order for the new app handle to appear in log drain and metric drain destinations, you must restart the app.
34
35
  aptible apps:scale SERVICE [--container-count COUNT] [--container-size SIZE_MB] # Scale a service
35
36
  aptible backup:list DB_HANDLE # List backups for a database
36
37
  aptible backup:orphaned # List backups associated with deprovisioned databases
@@ -50,6 +51,7 @@ Commands:
50
51
  aptible db:list # List all databases
51
52
  aptible db:modify HANDLE [--iops IOPS] [--volume-type [gp2, gp3]] # Modify a database disk
52
53
  aptible db:reload HANDLE # Reload a database
54
+ aptible db:rename OLD_HANDLE NEW_HANDLE [--environment ENVIRONMENT_HANDLE] # Rename a database handle. In order for the new database handle to appear in log drain and metric drain destinations, you must reload the database.
53
55
  aptible db:replicate HANDLE REPLICA_HANDLE [--container-size SIZE_MB] [--disk-size SIZE_GB] [--logical --version VERSION] [--key-arn KEY_ARN] # Create a replica/follower of a database
54
56
  aptible db:restart HANDLE [--container-size SIZE_MB] [--disk-size SIZE_GB] [--iops IOPS] [--volume-type [gp2, gp3]] # Restart a database
55
57
  aptible db:tunnel HANDLE # Create a local tunnel to a database
@@ -69,6 +71,7 @@ Commands:
69
71
  aptible endpoints:tls:modify [--app APP] ENDPOINT_HOSTNAME # Modify an App TLS Endpoint
70
72
  aptible environment:ca_cert # Retrieve the CA certificate associated with the environment
71
73
  aptible environment:list # List all environments
74
+ aptible environment:rename OLD_HANDLE NEW_HANDLE # Rename an environment handle. In order for the new environment handle to appear in log drain/metric destinations, you must restart the apps/databases in this environment.
72
75
  aptible help [COMMAND] # Describe available commands or one specific command
73
76
  aptible log_drain:create:datadog HANDLE --url DATADOG_URL --environment ENVIRONMENT [--drain-apps true/false] [--drain_databases true/false] [--drain_ephemeral_sessions true/false] [--drain_proxies true/false] # Create a Datadog Log Drain
74
77
  aptible log_drain:create:elasticsearch HANDLE --db DATABASE_HANDLE --environment ENVIRONMENT [--drain-apps true/false] [--drain_databases true/false] [--drain_ephemeral_sessions true/false] [--drain_proxies true/false] # Create an Elasticsearch Log Drain
@@ -87,6 +90,8 @@ Commands:
87
90
  aptible metric_drain:deprovision HANDLE --environment ENVIRONMENT # Deprovisions a Metric Drain
88
91
  aptible metric_drain:list # List all Metric Drains
89
92
  aptible operation:cancel OPERATION_ID # Cancel a running operation
93
+ aptible operation:follow OPERATION_ID # Follow logs of a running operation
94
+ aptible operation:logs OPERATION_ID # View logs for given operation
90
95
  aptible rebuild # Rebuild an app, and restart its services
91
96
  aptible restart # Restart all services associated with an app
92
97
  aptible services # List Services for an App
@@ -154,6 +154,19 @@ module Aptible
154
154
  raise Thor::Error, err
155
155
  end
156
156
 
157
+ def validate_image_type(type)
158
+ available_types = []
159
+
160
+ Aptible::Api::DatabaseImage.all(token: fetch_token).each do |i|
161
+ return true if i.type == type
162
+ available_types << i.type
163
+ end
164
+
165
+ err = "No Database Image of type \"#{type}\""
166
+ err = "#{err}, valid types: #{available_types.uniq.join(', ')}"
167
+ raise Thor::Error, err
168
+ end
169
+
157
170
  def render_database(database, account)
158
171
  Formatter.render(Renderer.current) do |root|
159
172
  root.keyed_object('connection_url') do |node|
@@ -1,4 +1,5 @@
1
1
  require 'aptible/api'
2
+ require 'net/http'
2
3
 
3
4
  module Aptible
4
5
  module CLI
@@ -48,6 +49,15 @@ module Aptible
48
49
  operation.update!(cancelled: true)
49
50
  end
50
51
 
52
+ def operation_logs(operation)
53
+ res = get_operation_logs_redirect(operation)
54
+ s3_file_request = get_operation_logs_s3_file(res.body)
55
+
56
+ m = "Printing out results of operation logs for #{operation.id}"
57
+ CLI.logger.info m
58
+ puts s3_file_request.body
59
+ end
60
+
51
61
  def prettify_operation(o)
52
62
  bits = [o.status, o.type, "##{o.id}"]
53
63
  if o.resource.respond_to?(:handle)
@@ -55,6 +65,37 @@ module Aptible
55
65
  end
56
66
  bits.join ' '
57
67
  end
68
+
69
+ private
70
+
71
+ def get_operation_logs_redirect(operation)
72
+ uri = URI(operation.logs_url)
73
+ headers = { 'Authorization' => "Bearer #{fetch_token}" }
74
+ http = Net::HTTP.new(uri.host, uri.port)
75
+ http.use_ssl = true
76
+ res = http.request(Net::HTTP::Get.new(uri.request_uri, headers))
77
+ # note: res body with a 200 is target redirect location for download
78
+ if !res || res.code != '200' || res.body.nil?
79
+ raise Thor::Error, 'Unable to retrieve the operation\'s logs. '\
80
+ 'If the issue persists please contact support for assistance.'
81
+ end
82
+ res
83
+ end
84
+
85
+ def get_operation_logs_s3_file(location)
86
+ s3_uri = URI(location)
87
+ http = Net::HTTP.new(s3_uri.host, s3_uri.port)
88
+ http.use_ssl = true
89
+
90
+ # follow the link with redirect and retrieve it from s3 directly
91
+ res = http.request(Net::HTTP::Get.new(s3_uri.request_uri))
92
+ if !res || res.code != '200'
93
+ raise Thor::Error, 'Unable to retrieve operation logs, '\
94
+ "S3 returned response code #{res.code}. "\
95
+ 'If the issue persists please contact support for assistance.'
96
+ end
97
+ res
98
+ end
58
99
  end
59
100
  end
60
101
  end
@@ -94,6 +94,29 @@ module Aptible
94
94
  raise if e.response.status != 404
95
95
  end
96
96
  end
97
+
98
+ desc 'apps:rename OLD_HANDLE NEW_HANDLE [--environment'\
99
+ ' ENVIRONMENT_HANDLE]', 'Rename an app handle. In order'\
100
+ ' for the new app handle to appear in log drain and metric'\
101
+ ' drain destinations, you must restart the app.'
102
+ option :environment
103
+ define_method 'apps:rename' do |old_handle, new_handle|
104
+ env = ensure_environment(options)
105
+ app = ensure_app(options.merge(app: old_handle))
106
+ app.update!(handle: new_handle)
107
+ m1 = "In order for the new app name (#{new_handle}) to appear"\
108
+ ' in log drain and metric drain destinations, you must'\
109
+ ' restart the app.'
110
+ m2 = 'You can restart your app with this command: "aptible '\
111
+ "restart --app #{new_handle} --environment #{env.handle}\""
112
+ m3 = 'Warning - Git remote addresses must be updated to match'\
113
+ ' the new handle, if using Dockerfile deploy. '\
114
+ "(git@beta.aptible.com:#{app.account.handle}"\
115
+ "/#{new_handle}.git)"
116
+ CLI.logger.warn m1
117
+ CLI.logger.info m2
118
+ CLI.logger.warn m3
119
+ end
97
120
  end
98
121
  end
99
122
  end
@@ -84,6 +84,7 @@ module Aptible
84
84
  version = options[:version]
85
85
 
86
86
  if version && type
87
+ validate_image_type(type)
87
88
  image = find_database_image(type, version)
88
89
  db_opts[:type] = image.type
89
90
  db_opts[:database_image] = image
@@ -91,6 +92,7 @@ module Aptible
91
92
  raise Thor::Error, '--type is required when passing --version'
92
93
  else
93
94
  db_opts[:type] = type || 'postgresql'
95
+ validate_image_type(db_opts[:type])
94
96
  end
95
97
 
96
98
  database = account.create_database!(db_opts)
@@ -346,6 +348,24 @@ module Aptible
346
348
  end
347
349
  end
348
350
  end
351
+
352
+ desc 'db:rename OLD_HANDLE NEW_HANDLE [--environment'\
353
+ ' ENVIRONMENT_HANDLE]', 'Rename a database handle. In order'\
354
+ ' for the new database handle to appear in log drain and'\
355
+ ' metric drain destinations, you must reload the database.'
356
+ option :environment
357
+ define_method 'db:rename' do |old_handle, new_handle|
358
+ env = ensure_environment(options)
359
+ db = ensure_database(options.merge(db: old_handle))
360
+ db.update!(handle: new_handle)
361
+ m1 = "In order for the new database name (#{new_handle}) to"\
362
+ ' appear in log drain and metric drain destinations,'\
363
+ ' you must reload the database.'
364
+ m2 = 'You can reload your database with this command: "aptible'\
365
+ " db:reload #{new_handle} --environment #{env.handle}\""
366
+ CLI.logger.warn m1
367
+ CLI.logger.info m2
368
+ end
349
369
  end
350
370
  end
351
371
  end
@@ -41,6 +41,25 @@ module Aptible
41
41
  end
42
42
  end
43
43
  end
44
+
45
+ desc 'environment:rename OLD_HANDLE NEW_HANDLE',
46
+ 'Rename an environment handle. In order for the new'\
47
+ ' environment handle to appear in log drain/metric'\
48
+ ' destinations, you must restart the apps/databases in'\
49
+ ' this environment.'
50
+ define_method 'environment:rename' do |old_handle, new_handle|
51
+ env = ensure_environment(options.merge(environment: old_handle))
52
+ env.update!(handle: new_handle)
53
+ m1 = "In order for the new environment handle (#{new_handle})"\
54
+ ' to appear in log drain and metric drain destinations,'\
55
+ ' you must restart the apps and databases in this'\
56
+ ' environment. Also be aware of the following resources'\
57
+ ' that may need names adjusted:'
58
+ m2 = "* Git remote URLs (ex: git@beta.aptible.com:#{new_handle}"\
59
+ '/APP_HANDLE.git)'
60
+ m3 = '* Your own external scripts (e.g. for CI/CD)'
61
+ [m1, m2, m3].each { |val| CLI.logger.info val }
62
+ end
44
63
  end
45
64
  end
46
65
  end
@@ -8,6 +8,7 @@ module Aptible
8
8
  'EU1' => 'https://app.datadoghq.eu',
9
9
  'US1-FED' => 'https://app.ddog-gov.com'
10
10
  }.freeze
11
+ PATH = '/api/v1/series'.freeze
11
12
 
12
13
  def self.included(thor)
13
14
  thor.class_eval do
@@ -106,7 +107,7 @@ module Aptible
106
107
  "Valid options are #{sites}"
107
108
  end
108
109
 
109
- config[:series_url] = site
110
+ config[:series_url] = site + PATH
110
111
  end
111
112
  opts = {
112
113
  handle: handle,
@@ -16,6 +16,39 @@ module Aptible
16
16
  CLI.logger.info m
17
17
  o.update!(cancelled: true)
18
18
  end
19
+
20
+ desc 'operation:follow OPERATION_ID',
21
+ 'Follow logs of a running operation'
22
+ define_method 'operation:follow' do |operation_id|
23
+ o = Aptible::Api::Operation.find(operation_id, token: fetch_token)
24
+ raise "Operation ##{operation_id} not found" if o.nil?
25
+
26
+ if %w(failed succeeded).include? o.status
27
+ raise Thor::Error, "This operation has already #{o.status}. " \
28
+ 'Run the following command to retrieve ' \
29
+ "the operation's logs:\n" \
30
+ "aptible operation:logs #{o.id}"
31
+ end
32
+
33
+ CLI.logger.info "Streaming logs for #{prettify_operation(o)}..."
34
+
35
+ attach_to_operation_logs(o)
36
+ end
37
+
38
+ desc 'operation:logs OPERATION_ID', 'View logs for given operation'
39
+ define_method 'operation:logs' do |operation_id|
40
+ o = Aptible::Api::Operation.find(operation_id, token: fetch_token)
41
+ raise "Operation ##{operation_id} not found" if o.nil?
42
+
43
+ unless %w(succeeded failed).include? o.status
44
+ e = 'Error - You can view the logs when operation is complete.'
45
+ raise Thor::Error, e
46
+ end
47
+
48
+ m = "Requesting operation logs for #{prettify_operation(o)}..."
49
+ CLI.logger.info m
50
+ operation_logs(o)
51
+ end
19
52
  end
20
53
  end
21
54
  end
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.19.3'.freeze
3
+ VERSION = '0.19.4'.freeze
4
4
  end
5
5
  end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aptible::CLI::Helpers::Database do
4
+ subject { Class.new.send(:include, described_class).new }
5
+
6
+ describe '#validate_image_type' do
7
+ let(:pg) do
8
+ Fabricate(:database_image, type: 'postgresql', version: '10')
9
+ end
10
+
11
+ let(:redis) do
12
+ Fabricate(:database_image, type: 'redis', version: '9.4')
13
+ end
14
+
15
+ let(:token) { 'some-token' }
16
+
17
+ before do
18
+ allow(subject).to receive(:fetch_token).and_return(token)
19
+ allow(Aptible::Api::DatabaseImage).to receive(:all)
20
+ .and_return([pg, redis])
21
+ end
22
+
23
+ it 'Raises an error if provided an invalid type' do
24
+ bad_type = 'cassandra'
25
+ err = "No Database Image of type \"#{bad_type}\", " \
26
+ "valid types: #{pg.type}, #{redis.type}"
27
+ expect do
28
+ subject.validate_image_type(bad_type)
29
+ end.to raise_error(Thor::Error, err)
30
+ end
31
+
32
+ it 'Retruns true when provided a valid type' do
33
+ expect(subject.validate_image_type(pg.type)).to be(true)
34
+ end
35
+ end
36
+ end
@@ -197,6 +197,47 @@ describe Aptible::CLI::Agent do
197
197
  end
198
198
  end
199
199
 
200
+ describe '#apps:rename' do
201
+ before do
202
+ allow(Aptible::Api::App).to receive(:all) { [app] }
203
+ allow(Aptible::Api::Account).to receive(:all) { [account] }
204
+ end
205
+
206
+ before(:each) do
207
+ allow(subject).to receive(:options)
208
+ .and_return(environment: account.handle)
209
+ end
210
+
211
+ context 'with environment and app' do
212
+ it 'should rename properly' do
213
+ expect(app).to receive(:update!)
214
+ .with(handle: 'hello2').and_return(app)
215
+ subject.send('apps:rename', 'hello', 'hello2')
216
+ expect(captured_logs).to include(
217
+ 'In order for the new app name (hello2) to appear in log drain and '\
218
+ 'metric drain destinations, you must restart the app.'
219
+ )
220
+ expect(captured_logs).to include(
221
+ "(git@beta.aptible.com:#{account.handle}/hello2.git)"
222
+ )
223
+ end
224
+ it 'should fail if app does not exist' do
225
+ expect { subject.send('apps:rename', 'hello2', 'hello3') }
226
+ .to raise_error(/Could not find app hello/)
227
+ end
228
+ it 'should raise error if update fails' do
229
+ response = Faraday::Response.new(status: 422)
230
+ error = HyperResource::ClientError.new('ActiveRecord::RecordInvalid:'\
231
+ ' Validation failed: Handle has already been taken, Handle has already'\
232
+ ' been taken', response: response)
233
+ expect(app).to receive(:update!)
234
+ .with(handle: 'hello2').and_raise(error)
235
+ expect { subject.send('apps:rename', 'hello', 'hello2') }
236
+ .to raise_error(HyperResource::ClientError)
237
+ end
238
+ end
239
+ end
240
+
200
241
  describe '#apps:scale' do
201
242
  before do
202
243
  allow(Aptible::Api::App).to receive(:all) { [app] }
@@ -22,6 +22,9 @@ describe Aptible::CLI::Agent do
22
22
  before do
23
23
  allow(Aptible::Api::Account).to receive(:all).and_return([account])
24
24
  end
25
+ before do
26
+ subject.stub(:validate_image_type) { true }
27
+ end
25
28
 
26
29
  def expect_provision_database(create_opts, provision_opts = {})
27
30
  db = Fabricate(:database)
@@ -876,4 +879,39 @@ describe Aptible::CLI::Agent do
876
879
  expect(captured_output_text).to eq(expected)
877
880
  end
878
881
  end
882
+
883
+ describe '#db:rename' do
884
+ before do
885
+ allow(subject).to receive(:options)
886
+ .and_return(environment: account.handle)
887
+ allow(Aptible::Api::Account).to receive(:all) { [account] }
888
+ end
889
+ context 'with environment and db' do
890
+ it 'should rename properly' do
891
+ expect(database).to receive(:update!)
892
+ .with(handle: 'foo2').and_return(database)
893
+ subject.send('db:rename', database.handle, 'foo2')
894
+ expect(captured_logs).to include(
895
+ 'In order for the new database name (foo2) to appear in log drain '\
896
+ 'and metric drain destinations, you must reload the database.'
897
+ )
898
+ end
899
+ it 'should fail if db does not exist' do
900
+ expect { subject.send('db:rename', 'foo2', 'foo3') }
901
+ .to raise_error(/Could not find database foo2/)
902
+ end
903
+ it 'should raise error if update fails' do
904
+ response = Faraday::Response.new(status: 500)
905
+ error = HyperResource::ClientError.new(
906
+ 'An error occurred: Validation failed: Handle has '\
907
+ 'already been taken, Handle has already been taken',
908
+ response: response
909
+ )
910
+ expect(database).to receive(:update!)
911
+ .with(handle: 'foo2').and_raise(error)
912
+ expect { subject.send('db:rename', database.handle, 'foo2') }
913
+ .to raise_error(HyperResource::ClientError)
914
+ end
915
+ end
916
+ end
879
917
  end
@@ -10,47 +10,82 @@ describe Aptible::CLI::Agent do
10
10
 
11
11
  let(:token) { double 'token' }
12
12
 
13
- before do
13
+ before(:each) do
14
14
  allow(subject).to receive(:fetch_token) { token }
15
15
  allow(Aptible::Api::Account).to receive(:all).with(token: token)
16
16
  .and_return([a1, a2])
17
17
  end
18
18
 
19
- it 'lists avaliable environments' do
20
- subject.send('environment:list')
19
+ describe('#environment:list') do
20
+ it 'lists avaliable environments' do
21
+ subject.send('environment:list')
21
22
 
22
- expect(captured_output_text.split("\n")).to include('foo')
23
- expect(captured_output_text.split("\n")).to include('bar')
23
+ expect(captured_output_text.split("\n")).to include('foo')
24
+ expect(captured_output_text.split("\n")).to include('bar')
25
+ end
24
26
  end
25
27
 
26
- it 'fetches certs for all avaliable environments' do
27
- subject.send('environment:ca_cert')
28
-
29
- expect(captured_output_text.split("\n")).to include('account 1 cert')
30
- expect(captured_output_text.split("\n")).to include('--account 2 cert--')
31
-
32
- expected_accounts = [
33
- {
34
- 'handle' => 'foo',
35
- 'ca_body' => 'account 1 cert',
36
- 'created_at' => fmt_time(a1.created_at)
37
- },
38
- {
39
- 'handle' => 'bar',
40
- 'ca_body' => '--account 2 cert--',
41
- 'created_at' => fmt_time(a2.created_at)
42
- }
43
- ]
44
- expect(captured_output_json.map! { |account| account.except('id') })
45
- .to eq(expected_accounts)
46
- end
28
+ describe('#environment:ca_cert') do
29
+ it 'fetches certs for all avaliable environments' do
30
+ subject.send('environment:ca_cert')
31
+
32
+ expect(captured_output_text.split("\n")).to include('account 1 cert')
33
+ expect(captured_output_text.split("\n")).to include('--account 2 cert--')
34
+
35
+ expected_accounts = [
36
+ {
37
+ 'handle' => 'foo',
38
+ 'ca_body' => 'account 1 cert',
39
+ 'created_at' => fmt_time(a1.created_at)
40
+ },
41
+ {
42
+ 'handle' => 'bar',
43
+ 'ca_body' => '--account 2 cert--',
44
+ 'created_at' => fmt_time(a2.created_at)
45
+ }
46
+ ]
47
+ expect(captured_output_json.map! { |account| account.except('id') })
48
+ .to eq(expected_accounts)
49
+ end
47
50
 
48
- it 'fetches certs for specified environment' do
49
- subject.options = { environment: 'foo' }
50
- subject.send('environment:ca_cert')
51
+ it 'fetches certs for specified environment' do
52
+ subject.options = { environment: 'foo' }
53
+ subject.send('environment:ca_cert')
54
+
55
+ expect(captured_output_text.split("\n")).to include('account 1 cert')
56
+ expect(captured_output_text.split("\n"))
57
+ .to_not include('--account 2 cert--')
58
+ end
59
+ end
51
60
 
52
- expect(captured_output_text.split("\n")).to include('account 1 cert')
53
- expect(captured_output_text.split("\n"))
54
- .to_not include('--account 2 cert--')
61
+ describe('#environment:rename') do
62
+ it 'should rename properly' do
63
+ expect(a1).to receive(:update!)
64
+ .with(handle: 'foo-renamed').and_return(a1)
65
+ subject.send('environment:rename', 'foo', 'foo-renamed')
66
+ expect(captured_logs).to include(
67
+ 'In order for the new environment handle (foo-renamed)'
68
+ )
69
+ expect(captured_logs).to include(
70
+ '* Your own external scripts (e.g. for CI/CD)'
71
+ )
72
+ expect(captured_logs).to include(
73
+ '* Git remote URLs (ex: git@beta.aptible.com:foo-renamed'
74
+ )
75
+ end
76
+ it 'should fail if env does not exist' do
77
+ expect { subject.send('environment:rename', 'foo1', 'foo2') }
78
+ .to raise_error(/Could not find environment foo1/)
79
+ end
80
+ it 'should raise error if update fails' do
81
+ response = Faraday::Response.new(status: 422)
82
+ error = HyperResource::ClientError.new('ActiveRecord::RecordInvalid:'\
83
+ ' Validation failed: Handle has already been taken, Handle has already'\
84
+ ' been taken', response: response)
85
+ expect(a1).to receive(:update!)
86
+ .with(handle: 'bar').and_raise(error)
87
+ expect { subject.send('environment:rename', 'foo', 'bar') }
88
+ .to raise_error(HyperResource::ClientError)
89
+ end
55
90
  end
56
91
  end
@@ -145,7 +145,7 @@ describe Aptible::CLI::Agent do
145
145
  drain_type: :datadog,
146
146
  drain_configuration: {
147
147
  api_key: 'foobar',
148
- series_url: 'https://app.datadoghq.eu'
148
+ series_url: 'https://app.datadoghq.eu/api/v1/series'
149
149
  }
150
150
  }
151
151
  expect_provision_metric_drain(opts)
@@ -3,6 +3,8 @@ require 'spec_helper'
3
3
  describe Aptible::CLI::Agent do
4
4
  let(:token) { 'some-token' }
5
5
  let(:operation) { Fabricate(:operation) }
6
+ let(:net_http_double) { double('Net::HTTP') }
7
+ let(:net_http_get_double) { double('Net::HTTP::Get') }
6
8
 
7
9
  before do
8
10
  allow(subject).to receive(:fetch_token).and_return(token)
@@ -26,4 +28,174 @@ describe Aptible::CLI::Agent do
26
28
  subject.send('operation:cancel', 1)
27
29
  end
28
30
  end
31
+
32
+ describe '#operation:follow' do
33
+ it 'fails if the operation cannot be found' do
34
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
35
+ .and_return(nil)
36
+
37
+ expect { subject.send('operation:follow', 1) }
38
+ .to raise_error('Operation #1 not found')
39
+ end
40
+
41
+ it 'connects to a running operation' do
42
+ op = Fabricate(:operation, status: 'running', type: 'restart')
43
+ expect(Aptible::Api::Operation).to receive(:find)
44
+ .with(op.id.to_s, token: token).and_return(op)
45
+
46
+ expect(subject).to receive(:attach_to_operation_logs).with(op)
47
+ subject.send('operation:follow', op.id.to_s)
48
+ end
49
+
50
+ it 'connects to a queued operation' do
51
+ op = Fabricate(:operation, status: 'queued', type: 'restart')
52
+ expect(Aptible::Api::Operation).to receive(:find)
53
+ .with(op.id.to_s, token: token).and_return(op)
54
+
55
+ expect(subject).to receive(:attach_to_operation_logs).with(op)
56
+ subject.send('operation:follow', op.id.to_s)
57
+ end
58
+
59
+ it 'does not connect to a failed operation' do
60
+ id = 34
61
+ status = 'failed'
62
+ op = Fabricate(:operation, id: id, status: status)
63
+ expect(Aptible::Api::Operation).to receive(:find)
64
+ .with(op.id.to_s, token: token).and_return(op)
65
+
66
+ expect { subject.send('operation:follow', op.id.to_s) }
67
+ .to raise_error(Thor::Error, /aptible operation:logs #{id}/)
68
+ end
69
+
70
+ it 'does not connect to a succeeded operation' do
71
+ id = 43
72
+ status = 'succeeded'
73
+ op = Fabricate(:operation, id: id, status: status)
74
+ expect(Aptible::Api::Operation).to receive(:find)
75
+ .with(op.id.to_s, token: token).and_return(op)
76
+
77
+ expect { subject.send('operation:follow', op.id.to_s) }
78
+ .to raise_error(Thor::Error, /aptible operation:logs #{id}/)
79
+ end
80
+ end
81
+
82
+ describe '#operation:logs' do
83
+ it 'sends operation logs request when subcommand sent' do
84
+ operation_id = SecureRandom.uuid
85
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
86
+ .and_return(Fabricate(
87
+ :operation, status: 'succeeded', id: operation_id
88
+ ))
89
+
90
+ # stub out operations call
91
+ response = instance_double(Net::HTTPResponse, body: 'https://s3.aptible.com/not-real/s3')
92
+
93
+ # stub out s3 call
94
+ s3_response = instance_double(Net::HTTPResponse, body: 'Mock logs')
95
+
96
+ allow(Net::HTTP).to receive(:new).twice do |_, _, _|
97
+ net_http_double
98
+ end
99
+ expect(response).to receive(:code).and_return('200')
100
+ expect(s3_response).to receive(:code).and_return('200')
101
+ expect(net_http_double).to receive(:use_ssl=).twice
102
+ expect(net_http_double).to receive(:request).twice do |request|
103
+ if request.path == "/operations/#{operation_id}/logs"
104
+ response
105
+ elsif request.path == '/not-real/s3'
106
+ s3_response
107
+ end
108
+ end
109
+
110
+ subject.send('operation:logs', 1)
111
+ end
112
+
113
+ it 'errors when operation is not found' do
114
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
115
+ .and_return(nil)
116
+
117
+ expect { subject.send('operation:logs', 1) }
118
+ .to raise_error('Operation #1 not found')
119
+ end
120
+ it 'errors when operation is not status expected' do
121
+ operation_id = SecureRandom.uuid
122
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
123
+ .and_return(Fabricate(:operation, status: 'queued', id: operation_id))
124
+
125
+ expect { subject.send('operation:logs', 1) }
126
+ .to raise_error('Error - You can view the logs when operation '\
127
+ 'is complete.')
128
+ end
129
+ it 'errors when operation logs are not found' do
130
+ operation_id = SecureRandom.uuid
131
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
132
+ .and_return(
133
+ Fabricate(:operation, status: 'succeeded', id: operation_id)
134
+ )
135
+
136
+ # stub out operations call
137
+ response = Net::HTTPSuccess.new(1.0, '404', 'Not Found')
138
+ expect_any_instance_of(Net::HTTP)
139
+ .to receive(:request)
140
+ .with(an_instance_of(Net::HTTP::Get))
141
+ .and_return(response)
142
+
143
+ expect { subject.send('operation:logs', 1) }
144
+ .to raise_error('Unable to retrieve the operation\'s logs. '\
145
+ 'If the issue persists please contact support for assistance.')
146
+ end
147
+ it 'errors when body is empty' do
148
+ operation_id = SecureRandom.uuid
149
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
150
+ .and_return(Fabricate(
151
+ :operation, status: 'succeeded', id: operation_id
152
+ ))
153
+
154
+ # stub out operations call
155
+ response = instance_double(Net::HTTPResponse, body: nil)
156
+
157
+ allow(Net::HTTP).to receive(:new) do |_, _, _|
158
+ net_http_double
159
+ end
160
+ expect(response).to receive(:code).and_return('200')
161
+ expect(net_http_double).to receive(:use_ssl=)
162
+ expect(net_http_double).to receive(:request).and_return(response)
163
+
164
+ expect { subject.send('operation:logs', 1) }
165
+ .to raise_error('Unable to retrieve the operation\'s logs. '\
166
+ 'If the issue persists please contact support for assistance.')
167
+ end
168
+ it 'errors when s3 itself returns an error code' do
169
+ operation_id = SecureRandom.uuid
170
+ expect(Aptible::Api::Operation).to receive(:find).with(1, token: token)
171
+ .and_return(Fabricate(
172
+ :operation, status: 'succeeded', id: operation_id
173
+ ))
174
+
175
+ # stub out operations call
176
+ response = instance_double(Net::HTTPResponse, body: 'https://s3.aptible.com/not-real/s3')
177
+
178
+ # stub out s3 call (to fail)
179
+ expect(response).to receive(:code).and_return('200')
180
+ s3_response = Net::HTTPSuccess.new(1.0, '404', 'Not Found')
181
+
182
+ allow(Net::HTTP).to receive(:new).twice do |_, _, _|
183
+ net_http_double
184
+ end
185
+ expect(net_http_double).to receive(:use_ssl=).twice
186
+ expect(net_http_double).to receive(:request).twice do |request|
187
+ if request.path == "/operations/#{operation_id}/logs"
188
+ response
189
+ elsif request.path == '/not-real/s3'
190
+ s3_response
191
+ end
192
+ end
193
+
194
+ expect { subject.send('operation:logs', 1) }
195
+ .to raise_error('Unable to retrieve operation logs, '\
196
+ 'S3 returned response code 404. '\
197
+ 'If the issue persists please contact support for '\
198
+ 'assistance.')
199
+ end
200
+ end
29
201
  end
@@ -1,7 +1,12 @@
1
1
  class StubOperation < OpenStruct; end
2
2
 
3
+ def mock_logs_url(id)
4
+ "https://api.aptible.com/operations/#{id}/logs"
5
+ end
6
+
3
7
  Fabricator(:operation, from: :stub_operation) do
4
8
  status 'queued'
5
- resource { Fabricate(:app) }
6
9
  errors { Aptible::Resource::Errors.new }
10
+ resource { Fabricate(:app) }
11
+ after_save { |operation| operation.logs_url = mock_logs_url(operation.id) }
7
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aptible-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.3
4
+ version: 0.19.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Macreery
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-01 00:00:00.000000000 Z
11
+ date: 2022-09-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aptible-resource
@@ -326,6 +326,7 @@ files:
326
326
  - script/sync-readme-usage
327
327
  - spec/aptible/cli/agent_spec.rb
328
328
  - spec/aptible/cli/formatter_spec.rb
329
+ - spec/aptible/cli/helpers/database_spec.rb
329
330
  - spec/aptible/cli/helpers/git_remote_handle_strategy_spec.rb
330
331
  - spec/aptible/cli/helpers/handle_from_git_remote_spec.rb
331
332
  - spec/aptible/cli/helpers/operation_spec.rb
@@ -382,7 +383,7 @@ homepage: https://github.com/aptible/aptible-cli
382
383
  licenses:
383
384
  - MIT
384
385
  metadata: {}
385
- post_install_message:
386
+ post_install_message:
386
387
  rdoc_options: []
387
388
  require_paths:
388
389
  - lib
@@ -398,12 +399,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
398
399
  version: '0'
399
400
  requirements: []
400
401
  rubygems_version: 3.0.3.1
401
- signing_key:
402
+ signing_key:
402
403
  specification_version: 4
403
404
  summary: Command-line interface for Aptible services
404
405
  test_files:
405
406
  - spec/aptible/cli/agent_spec.rb
406
407
  - spec/aptible/cli/formatter_spec.rb
408
+ - spec/aptible/cli/helpers/database_spec.rb
407
409
  - spec/aptible/cli/helpers/git_remote_handle_strategy_spec.rb
408
410
  - spec/aptible/cli/helpers/handle_from_git_remote_spec.rb
409
411
  - spec/aptible/cli/helpers/operation_spec.rb