aptible-cli 0.18.0 → 0.19.0

Sign up to get free protection for your applications and to get access to all the features.
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