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 +4 -4
- data/README.md +5 -0
- data/lib/aptible/cli/helpers/database.rb +13 -0
- data/lib/aptible/cli/helpers/operation.rb +41 -0
- data/lib/aptible/cli/subcommands/apps.rb +23 -0
- data/lib/aptible/cli/subcommands/db.rb +20 -0
- data/lib/aptible/cli/subcommands/environment.rb +19 -0
- data/lib/aptible/cli/subcommands/metric_drain.rb +2 -1
- data/lib/aptible/cli/subcommands/operation.rb +33 -0
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/helpers/database_spec.rb +36 -0
- data/spec/aptible/cli/subcommands/apps_spec.rb +41 -0
- data/spec/aptible/cli/subcommands/db_spec.rb +38 -0
- data/spec/aptible/cli/subcommands/environment_spec.rb +67 -32
- data/spec/aptible/cli/subcommands/metric_drain_spec.rb +1 -1
- data/spec/aptible/cli/subcommands/operation_spec.rb +172 -0
- data/spec/fabricators/operation_fabricator.rb +6 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f3f0904b2033596eb17ff98125288e5a2adec95ae6e379b95a5b8131b81bfe54
|
4
|
+
data.tar.gz: 9c002c17e22dd77ff79360671d0057455a70ff4ee2311d1466651a5c39b3c1a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/aptible/cli/version.rb
CHANGED
@@ -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
|
-
|
20
|
-
|
19
|
+
describe('#environment:list') do
|
20
|
+
it 'lists avaliable environments' do
|
21
|
+
subject.send('environment:list')
|
21
22
|
|
22
|
-
|
23
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
.
|
46
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
53
|
-
|
54
|
-
.
|
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.
|
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-
|
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
|