aptible-cli 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +63 -50
  3. data/lib/aptible/cli/agent.rb +9 -3
  4. data/lib/aptible/cli/helpers/config_path.rb +11 -0
  5. data/lib/aptible/cli/helpers/log_drain.rb +85 -0
  6. data/lib/aptible/cli/helpers/metric_drain.rb +39 -0
  7. data/lib/aptible/cli/helpers/ssh.rb +5 -1
  8. data/lib/aptible/cli/helpers/token.rb +5 -1
  9. data/lib/aptible/cli/resource_formatter.rb +66 -3
  10. data/lib/aptible/cli/subcommands/apps.rb +5 -40
  11. data/lib/aptible/cli/subcommands/backup.rb +15 -7
  12. data/lib/aptible/cli/subcommands/db.rb +24 -21
  13. data/lib/aptible/cli/subcommands/log_drain.rb +159 -0
  14. data/lib/aptible/cli/subcommands/metric_drain.rb +137 -0
  15. data/lib/aptible/cli/version.rb +1 -1
  16. data/spec/aptible/cli/resource_formatter_spec.rb +1 -0
  17. data/spec/aptible/cli/subcommands/apps_spec.rb +10 -51
  18. data/spec/aptible/cli/subcommands/backup_spec.rb +1 -1
  19. data/spec/aptible/cli/subcommands/db_spec.rb +0 -26
  20. data/spec/aptible/cli/subcommands/environment_spec.rb +4 -2
  21. data/spec/aptible/cli/subcommands/log_drain_spec.rb +207 -0
  22. data/spec/aptible/cli/subcommands/metric_drain_spec.rb +183 -0
  23. data/spec/fabricators/account_fabricator.rb +3 -0
  24. data/spec/fabricators/app_fabricator.rb +1 -0
  25. data/spec/fabricators/database_disk_fabricator.rb +2 -1
  26. data/spec/fabricators/database_fabricator.rb +1 -0
  27. data/spec/fabricators/log_drain_fabricator.rb +21 -0
  28. data/spec/fabricators/metric_drain_fabricator.rb +8 -0
  29. data/spec/fabricators/service_fabricator.rb +1 -0
  30. data/spec/fabricators/vhost_fabricator.rb +1 -0
  31. data/spec/spec_helper.rb +4 -0
  32. metadata +15 -5
  33. data/lib/aptible/cli/subcommands/domains.rb +0 -40
  34. data/spec/aptible/cli/subcommands/domains_spec.rb +0 -76
@@ -1,5 +1,5 @@
1
1
  module Aptible
2
2
  module CLI
3
- VERSION = '0.18.0'.freeze
3
+ VERSION = '0.19.0'.freeze
4
4
  end
5
5
  end
@@ -27,6 +27,7 @@ describe Aptible::CLI::ResourceFormatter do
27
27
  'Id: 12',
28
28
  'Hostname: foo.io',
29
29
  'Status: provisioned',
30
+ "Created At: #{fmt_time(service.created_at)}",
30
31
  'Type: https',
31
32
  'Port: default',
32
33
  'Internal: false',
@@ -104,26 +104,30 @@ describe Aptible::CLI::Agent do
104
104
  {
105
105
  'environment' => {
106
106
  'id' => account.id,
107
- 'handle' => account.handle
107
+ 'handle' => account.handle,
108
+ 'created_at' => fmt_time(account.created_at)
108
109
  },
109
110
  'handle' => app.handle,
110
111
  'id' => app.id,
111
112
  'status' => app.status,
112
113
  'git_remote' => app.git_repo,
114
+ 'created_at' => fmt_time(app.created_at),
113
115
  'services' => [
114
116
  {
115
117
  'service' => s1.process_type,
116
118
  'id' => s1.id,
117
119
  'command' => s1.command,
118
120
  'container_count' => s1.container_count,
119
- 'container_size' => s1.container_memory_limit_mb
121
+ 'container_size' => s1.container_memory_limit_mb,
122
+ 'created_at' => fmt_time(s1.created_at)
120
123
  },
121
124
  {
122
125
  'service' => s2.process_type,
123
126
  'id' => s2.id,
124
127
  'command' => 'CMD',
125
128
  'container_count' => s2.container_count,
126
- 'container_size' => s2.container_memory_limit_mb
129
+ 'container_size' => s2.container_memory_limit_mb,
130
+ 'created_at' => fmt_time(s2.created_at)
127
131
  }
128
132
  ]
129
133
  }
@@ -145,12 +149,14 @@ describe Aptible::CLI::Agent do
145
149
  {
146
150
  'environment' => {
147
151
  'id' => account.id,
148
- 'handle' => account.handle
152
+ 'handle' => account.handle,
153
+ 'created_at' => fmt_time(account.created_at)
149
154
  },
150
155
  'handle' => app.handle,
151
156
  'id' => app.id,
152
157
  'status' => app.status,
153
158
  'git_remote' => app.git_repo,
159
+ 'created_at' => fmt_time(app.created_at),
154
160
  'last_deploy_operation' =>
155
161
  {
156
162
  'id' => op.id,
@@ -247,42 +253,6 @@ describe Aptible::CLI::Agent do
247
253
  .to raise_error(/provide at least/im)
248
254
  end
249
255
 
250
- it 'should scale container count (legacy)' do
251
- stub_options
252
- expect(service).to receive(:create_operation!)
253
- .with(type: 'scale', container_count: 3)
254
- .and_return(op)
255
- subject.send('apps:scale', 'web', '3')
256
- expect(captured_logs).to match(/deprecated/i)
257
- end
258
-
259
- it 'should scale container size (legacy)' do
260
- stub_options(size: 90210)
261
- expect(service).to receive(:create_operation!)
262
- .with(type: 'scale', container_size: 90210)
263
- .and_return(op)
264
- subject.send('apps:scale', 'web')
265
- expect(captured_logs).to match(/deprecated/i)
266
- end
267
-
268
- it 'should fail when using both current and legacy count' do
269
- stub_options(container_count: 2)
270
- expect { subject.send('apps:scale', 'web', '3') }
271
- .to raise_error(/count was passed via both/im)
272
- end
273
-
274
- it 'should fail when using both current and legacy size' do
275
- stub_options(container_size: 1024, size: 512)
276
- expect { subject.send('apps:scale', 'web') }
277
- .to raise_error(/size was passed via both/im)
278
- end
279
-
280
- it 'should fail when using too many arguments' do
281
- stub_options
282
- expect { subject.send('apps:scale', 'web', '3', '4') }
283
- .to raise_error(/usage:.*apps:scale/im)
284
- end
285
-
286
256
  it 'should fail if the service does not exist' do
287
257
  stub_options(container_count: 2)
288
258
 
@@ -317,17 +287,6 @@ describe Aptible::CLI::Agent do
317
287
  subject.send('apps:scale', 'web')
318
288
  end.to raise_error(Thor::Error)
319
289
  end
320
-
321
- it 'should fail if number is not a valid number (legacy)' do
322
- allow(subject).to receive(:options) { { app: 'hello' } }
323
- allow(service).to receive(:create_operation) { op }
324
-
325
- expect do
326
- subject.send('apps:scale', 'web', 'potato')
327
- end.to raise_error(ArgumentError)
328
-
329
- expect(captured_logs).to match(/deprecated/i)
330
- end
331
290
  end
332
291
 
333
292
  describe '#apps:deprovision' do
@@ -112,7 +112,7 @@ describe Aptible::CLI::Agent do
112
112
  Fabricate(:database, account: account, handle: default_handle)
113
113
  end
114
114
 
115
- subject.options = { size: s }
115
+ subject.options = { disk_size: s }
116
116
  subject.send('backup:restore', 1)
117
117
  end
118
118
 
@@ -57,16 +57,6 @@ describe Aptible::CLI::Agent do
57
57
  subject.send('db:create', 'foo')
58
58
  end
59
59
 
60
- it 'creates a new DB with a (implicitly) disk size' do
61
- expect_provision_database(
62
- { handle: 'foo', type: 'postgresql', initial_disk_size: 200 },
63
- { disk_size: 200 }
64
- )
65
-
66
- subject.options = { type: 'postgresql', size: 200 }
67
- subject.send('db:create', 'foo')
68
- end
69
-
70
60
  it 'creates a new DB with a disk-size' do
71
61
  expect_provision_database(
72
62
  { handle: 'foo', type: 'postgresql', initial_disk_size: 200 },
@@ -414,18 +404,6 @@ describe Aptible::CLI::Agent do
414
404
  expect(captured_logs).to match(/restarting foobar/i)
415
405
  end
416
406
 
417
- it 'allows restarting a database with (implicitly disk) size' do
418
- expect(database).to receive(:create_operation!)
419
- .with(type: 'restart', disk_size: 40).and_return(op)
420
-
421
- expect(subject).to receive(:attach_to_operation_logs).with(op)
422
-
423
- subject.options = { size: 40 }
424
- subject.send('db:restart', handle)
425
-
426
- expect(captured_logs).to match(/restarting foobar/i)
427
- end
428
-
429
407
  it 'allows restarting a database with a disk-size' do
430
408
  expect(database).to receive(:create_operation!)
431
409
  .with(type: 'restart', disk_size: 40).and_return(op)
@@ -581,10 +559,6 @@ describe Aptible::CLI::Agent do
581
559
  expect_replicate_database(container_size: 40)
582
560
  end
583
561
 
584
- it 'allows replicating a database with an (implicitly) disk size option' do
585
- expect_replicate_database(size: 40)
586
- end
587
-
588
562
  it 'allows replicating a database with a disk-size option' do
589
563
  expect_replicate_database(disk_size: 40)
590
564
  end
@@ -32,11 +32,13 @@ describe Aptible::CLI::Agent do
32
32
  expected_accounts = [
33
33
  {
34
34
  'handle' => 'foo',
35
- 'ca_body' => 'account 1 cert'
35
+ 'ca_body' => 'account 1 cert',
36
+ 'created_at' => fmt_time(a1.created_at)
36
37
  },
37
38
  {
38
39
  'handle' => 'bar',
39
- 'ca_body' => '--account 2 cert--'
40
+ 'ca_body' => '--account 2 cert--',
41
+ 'created_at' => fmt_time(a2.created_at)
40
42
  }
41
43
  ]
42
44
  expect(captured_output_json.map! { |account| account.except('id') })
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aptible::CLI::Agent do
4
+ let(:account) { Fabricate(:account) }
5
+ let!(:log_drain) do
6
+ Fabricate(:log_drain, handle: 'test', account: account)
7
+ end
8
+
9
+ let(:token) { double('token') }
10
+ before { allow(subject).to receive(:fetch_token).and_return(token) }
11
+
12
+ before do
13
+ allow(Aptible::Api::Account).to receive(:all)
14
+ .with(token: token).and_return([account])
15
+ end
16
+
17
+ describe '#log_drain:list' do
18
+ it 'lists a log drains for an account' do
19
+ subject.send('log_drain:list')
20
+
21
+ out = "=== aptible\n" \
22
+ "test\n"
23
+ expect(captured_output_text).to eq(out)
24
+ end
25
+
26
+ it 'lists log drains across multiple accounts' do
27
+ other_account = Fabricate(:account)
28
+ Fabricate(:log_drain, handle: 'test2', account: other_account)
29
+ accounts = [account, other_account]
30
+ allow(Aptible::Api::Account).to receive(:all).and_return(accounts)
31
+
32
+ subject.send('log_drain:list')
33
+
34
+ out = "=== aptible\n" \
35
+ "test\n" \
36
+ "test2\n"
37
+ expect(captured_output_text).to eq(out)
38
+ end
39
+
40
+ it 'lists log drains for a single account when --environment is included' do
41
+ other_account = Fabricate(:account)
42
+ Fabricate(:log_drain, handle: 'test2', account: other_account)
43
+ accounts = [account, other_account]
44
+ allow(Aptible::Api::Account).to receive(:all).and_return(accounts)
45
+
46
+ subject.options = { environment: account.handle }
47
+ subject.send('log_drain:list')
48
+
49
+ out = "=== aptible\n" \
50
+ "test\n"
51
+ expect(captured_output_text).to eq(out)
52
+ end
53
+ end
54
+
55
+ describe '#log_drain:create' do
56
+ def expect_provision_log_drain(create_opts, provision_opts = {})
57
+ log_drain = Fabricate(:log_drain, account: account)
58
+ op = Fabricate(:operation)
59
+
60
+ expect(account).to receive(:create_log_drain!)
61
+ .with(**create_opts).and_return(log_drain)
62
+
63
+ expect(log_drain).to receive(:create_operation)
64
+ .with(type: :provision, **provision_opts).and_return(op)
65
+
66
+ expect(subject).to receive(:attach_to_operation_logs).with(op)
67
+ end
68
+
69
+ context 'elasticsearch' do
70
+ let(:db) { Fabricate(:database, account: account, id: 5) }
71
+
72
+ it 'creates a new Elasticsearch log drain' do
73
+ opts = {
74
+ handle: 'test',
75
+ drain_apps: nil,
76
+ drain_databases: nil,
77
+ drain_ephemeral_sessions: nil,
78
+ drain_proxies: nil,
79
+ drain_type: :elasticsearch_database,
80
+ logging_token: nil,
81
+ database_id: db.id
82
+ }
83
+ expect_provision_log_drain(opts)
84
+
85
+ subject.options = {
86
+ db: db.handle,
87
+ environment: account.handle
88
+ }
89
+ subject.send('log_drain:create:elasticsearch', 'test')
90
+ end
91
+
92
+ it 'creates a new Elasticsearch log drain with a pipeline' do
93
+ opts = {
94
+ handle: 'test-es',
95
+ drain_apps: nil,
96
+ drain_databases: nil,
97
+ drain_ephemeral_sessions: nil,
98
+ drain_proxies: nil,
99
+ drain_type: :elasticsearch_database,
100
+ logging_token: 'test-pipeline',
101
+ database_id: db.id
102
+ }
103
+ expect_provision_log_drain(opts)
104
+
105
+ subject.options = {
106
+ db: db.handle,
107
+ environment: account.handle,
108
+ pipeline: 'test-pipeline'
109
+ }
110
+ subject.send('log_drain:create:elasticsearch', 'test-es')
111
+ end
112
+ end
113
+
114
+ # HTTPS, Datadog, Sumologic, and LogDNA are all similar enough
115
+ # that they're not tested individually
116
+ context 'https' do
117
+ it 'creates a new HTTPS log drain' do
118
+ opts = {
119
+ handle: 'test-https',
120
+ drain_apps: nil,
121
+ drain_databases: nil,
122
+ drain_ephemeral_sessions: nil,
123
+ drain_proxies: nil,
124
+ drain_type: :https_post,
125
+ url: 'https://test.foo.com'
126
+ }
127
+ expect_provision_log_drain(opts)
128
+
129
+ subject.options = {
130
+ environment: account.handle,
131
+ url: 'https://test.foo.com'
132
+ }
133
+ subject.send('log_drain:create:https', 'test-https')
134
+ end
135
+ end
136
+
137
+ # Syslog and Papertrail are similar enough that they're
138
+ # not tested individually
139
+ context 'syslog' do
140
+ it 'creates a new syslog log drain' do
141
+ opts = {
142
+ handle: 'test-syslog',
143
+ drain_host: 'test.foo.com',
144
+ drain_port: 2468,
145
+ logging_token: nil,
146
+ drain_apps: nil,
147
+ drain_databases: nil,
148
+ drain_ephemeral_sessions: nil,
149
+ drain_proxies: nil,
150
+ drain_type: :syslog_tls_tcp
151
+ }
152
+ expect_provision_log_drain(opts)
153
+
154
+ subject.options = {
155
+ environment: account.handle,
156
+ host: 'test.foo.com',
157
+ port: 2468
158
+ }
159
+ subject.send('log_drain:create:syslog', 'test-syslog')
160
+ end
161
+
162
+ it 'creates a new syslog log drain with a logging token' do
163
+ opts = {
164
+ handle: 'test-syslog',
165
+ drain_host: 'test.foo.com',
166
+ drain_port: 2468,
167
+ logging_token: 'test-token',
168
+ drain_apps: nil,
169
+ drain_databases: nil,
170
+ drain_ephemeral_sessions: nil,
171
+ drain_proxies: nil,
172
+ drain_type: :syslog_tls_tcp
173
+ }
174
+ expect_provision_log_drain(opts)
175
+
176
+ subject.options = {
177
+ environment: account.handle,
178
+ host: 'test.foo.com',
179
+ port: 2468,
180
+ token: 'test-token'
181
+ }
182
+ subject.send('log_drain:create:syslog', 'test-syslog')
183
+ end
184
+ end
185
+ end
186
+
187
+ describe '#log_drain:deprovision' do
188
+ let(:operation) { Fabricate(:operation, resource: log_drain) }
189
+
190
+ it 'deprovisions a log drain' do
191
+ expect(log_drain).to receive(:create_operation)
192
+ .with(type: :deprovision).and_return(operation)
193
+ expect(subject).to receive(:attach_to_operation_logs).with(operation)
194
+ subject.send('log_drain:deprovision', log_drain.handle)
195
+ end
196
+
197
+ it 'does not fail if the operation cannot be found' do
198
+ expect(log_drain).to receive(:create_operation)
199
+ .with(type: :deprovision).and_return(operation)
200
+ response = Faraday::Response.new(status: 404)
201
+ error = HyperResource::ClientError.new('Not Found', response: response)
202
+ expect(subject).to receive(:attach_to_operation_logs).with(operation)
203
+ .and_raise(error)
204
+ subject.send('log_drain:deprovision', log_drain.handle)
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ describe Aptible::CLI::Agent do
4
+ let(:account) { Fabricate(:account) }
5
+ let!(:metric_drain) do
6
+ Fabricate(:metric_drain, handle: 'test', account: account)
7
+ end
8
+
9
+ let(:token) { double('token') }
10
+ before { allow(subject).to receive(:fetch_token).and_return(token) }
11
+
12
+ before do
13
+ allow(Aptible::Api::Account).to receive(:all)
14
+ .with(token: token).and_return([account])
15
+ end
16
+
17
+ describe '#metric_drain:list' do
18
+ it 'lists a metric drains for an account' do
19
+ subject.send('metric_drain:list')
20
+
21
+ out = "=== aptible\n" \
22
+ "test\n"
23
+ expect(captured_output_text).to eq(out)
24
+ end
25
+
26
+ it 'lists metric drains across multiple accounts' do
27
+ other_account = Fabricate(:account)
28
+ Fabricate(:metric_drain, handle: 'test2', account: other_account)
29
+ accounts = [account, other_account]
30
+ allow(Aptible::Api::Account).to receive(:all).and_return(accounts)
31
+
32
+ subject.send('metric_drain:list')
33
+
34
+ out = "=== aptible\n" \
35
+ "test\n" \
36
+ "test2\n"
37
+ expect(captured_output_text).to eq(out)
38
+ end
39
+
40
+ it 'lists metric drains for a single account with --environment' do
41
+ other_account = Fabricate(:account)
42
+ Fabricate(:metric_drain, handle: 'test2', account: other_account)
43
+ accounts = [account, other_account]
44
+ allow(Aptible::Api::Account).to receive(:all).and_return(accounts)
45
+
46
+ subject.options = { environment: account.handle }
47
+ subject.send('metric_drain:list')
48
+
49
+ out = "=== aptible\n" \
50
+ "test\n"
51
+ expect(captured_output_text).to eq(out)
52
+ end
53
+ end
54
+
55
+ describe '#metric_drain:create' do
56
+ def expect_provision_metric_drain(create_opts, provision_opts = {})
57
+ metric_drain = Fabricate(:metric_drain, account: account)
58
+ op = Fabricate(:operation)
59
+
60
+ expect(account).to receive(:create_metric_drain!)
61
+ .with(**create_opts).and_return(metric_drain)
62
+
63
+ expect(metric_drain).to receive(:create_operation)
64
+ .with(type: :provision, **provision_opts).and_return(op)
65
+
66
+ expect(subject).to receive(:attach_to_operation_logs).with(op)
67
+ end
68
+
69
+ context 'influxdb' do
70
+ let(:db) { Fabricate(:database, account: account, id: 5) }
71
+
72
+ it 'creates a new InfluxDB metric drain' do
73
+ opts = {
74
+ handle: 'test-influxdb',
75
+ drain_type: :influxdb_database,
76
+ database_id: db.id
77
+ }
78
+ expect_provision_metric_drain(opts)
79
+
80
+ subject.options = {
81
+ db: db.handle,
82
+ environment: account.handle
83
+ }
84
+ subject.send('metric_drain:create:influxdb', 'test-influxdb')
85
+ end
86
+ end
87
+
88
+ context 'influxdb:custom' do
89
+ it 'creates a new InfluxDB metric drain' do
90
+ opts = {
91
+ handle: 'test-influxdb-custom',
92
+ drain_type: :influxdb,
93
+ drain_configuration: {
94
+ address: 'https://test.foo.com:443',
95
+ database: 'foobar',
96
+ password: 'bar',
97
+ username: 'foo'
98
+ }
99
+ }
100
+ expect_provision_metric_drain(opts)
101
+
102
+ subject.options = {
103
+ environment: account.handle,
104
+ username: 'foo',
105
+ password: 'bar',
106
+ db: 'foobar',
107
+ url: 'https://test.foo.com:443'
108
+ }
109
+ subject.send('metric_drain:create:influxdb:custom',
110
+ 'test-influxdb-custom')
111
+ end
112
+ end
113
+
114
+ context 'datadog' do
115
+ it 'creates a new Datadog metric drain' do
116
+ opts = {
117
+ handle: 'test-datadog',
118
+ drain_type: :datadog,
119
+ drain_configuration: {
120
+ api_key: 'foobar'
121
+ }
122
+ }
123
+ expect_provision_metric_drain(opts)
124
+
125
+ subject.options = {
126
+ environment: account.handle,
127
+ api_key: 'foobar'
128
+ }
129
+ subject.send('metric_drain:create:datadog', 'test-datadog')
130
+ end
131
+
132
+ it 'raises an error when the Datadog site is invalid' do
133
+ subject.options = {
134
+ environment: account.handle,
135
+ api_key: 'foobar',
136
+ site: 'BAD'
137
+ }
138
+ expect { subject.send('metric_drain:create:datadog', 'test-datadog') }
139
+ .to raise_error(Thor::Error, /Invalid Datadog site/i)
140
+ end
141
+
142
+ it 'creates a new Datadog metric drain with a Datadog site defined' do
143
+ opts = {
144
+ handle: 'test-datadog',
145
+ drain_type: :datadog,
146
+ drain_configuration: {
147
+ api_key: 'foobar',
148
+ series_url: 'https://app.datadoghq.eu'
149
+ }
150
+ }
151
+ expect_provision_metric_drain(opts)
152
+
153
+ subject.options = {
154
+ environment: account.handle,
155
+ api_key: 'foobar',
156
+ site: 'EU1'
157
+ }
158
+ subject.send('metric_drain:create:datadog', 'test-datadog')
159
+ end
160
+ end
161
+ end
162
+
163
+ describe '#metric_drain:deprovision' do
164
+ let(:operation) { Fabricate(:operation, resource: metric_drain) }
165
+
166
+ it 'deprovisions a log drain' do
167
+ expect(metric_drain).to receive(:create_operation)
168
+ .with(type: :deprovision).and_return(operation)
169
+ expect(subject).to receive(:attach_to_operation_logs).with(operation)
170
+ subject.send('metric_drain:deprovision', metric_drain.handle)
171
+ end
172
+
173
+ it 'does not fail if the operation cannot be found' do
174
+ expect(metric_drain).to receive(:create_operation)
175
+ .with(type: :deprovision).and_return(operation)
176
+ response = Faraday::Response.new(status: 404)
177
+ error = HyperResource::ClientError.new('Not Found', response: response)
178
+ expect(subject).to receive(:attach_to_operation_logs).with(operation)
179
+ .and_raise(error)
180
+ subject.send('metric_drain:deprovision', metric_drain.handle)
181
+ end
182
+ end
183
+ end