aptible-cli 0.21.0 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +50 -0
- data/Gemfile +0 -4
- data/Gemfile.lock +17 -22
- data/README.md +81 -76
- data/aptible-cli.gemspec +12 -9
- data/lib/aptible/cli/resource_formatter.rb +29 -0
- data/lib/aptible/cli/subcommands/endpoints.rb +30 -0
- data/lib/aptible/cli/subcommands/services.rb +171 -0
- data/lib/aptible/cli/version.rb +1 -1
- data/spec/aptible/cli/subcommands/endpoints_spec.rb +38 -0
- data/spec/aptible/cli/subcommands/services_spec.rb +143 -26
- data/spec/fabricators/service_sizing_policy_fabricator.rb +22 -0
- metadata +86 -42
- data/.travis.yml +0 -20
@@ -21,6 +21,177 @@ module Aptible
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
desc 'services:settings SERVICE'\
|
26
|
+
' [--force-zero-downtime|--no-force-zero-downtime]'\
|
27
|
+
' [--simple-health-check|--no-simple-health-check]',
|
28
|
+
'Modifies the zero-downtime deploy setting for a service'
|
29
|
+
app_options
|
30
|
+
option :force_zero_downtime,
|
31
|
+
type: :boolean, default: false,
|
32
|
+
desc: 'Force zero downtime deployments.'\
|
33
|
+
' Has no effect if service has an associated Endpoint'
|
34
|
+
option :simple_health_check,
|
35
|
+
type: :boolean, default: false,
|
36
|
+
desc: 'Use a simple uptime healthcheck during deployments'
|
37
|
+
define_method 'services:settings' do |service|
|
38
|
+
service = ensure_service(options, service)
|
39
|
+
updates = {}
|
40
|
+
updates[:force_zero_downtime] =
|
41
|
+
options[:force_zero_downtime] if options[:force_zero_downtime]
|
42
|
+
updates[:naive_health_check] =
|
43
|
+
options[:simple_health_check] if options[:simple_health_check]
|
44
|
+
|
45
|
+
service.update!(**updates) if updates.any?
|
46
|
+
end
|
47
|
+
|
48
|
+
desc 'services:sizing_policy SERVICE',
|
49
|
+
'Returns the associated sizing policy, if any'
|
50
|
+
app_options
|
51
|
+
define_method 'services:sizing_policy' do |service|
|
52
|
+
service = ensure_service(options, service)
|
53
|
+
policy = service.service_sizing_policy
|
54
|
+
|
55
|
+
unless policy
|
56
|
+
raise Thor::Error, "Service #{service} does not have a " \
|
57
|
+
'service sizing policy set'
|
58
|
+
end
|
59
|
+
|
60
|
+
Formatter.render(Renderer.current) do |root|
|
61
|
+
root.object do |node|
|
62
|
+
ResourceFormatter.inject_service_sizing_policy(
|
63
|
+
node, policy, service
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
desc 'services:sizing_policy:set SERVICE '\
|
70
|
+
'--autoscaling-type (horizontal|vertical) '\
|
71
|
+
'[--metric-lookback-seconds SECONDS] '\
|
72
|
+
'[--percentile PERCENTILE] '\
|
73
|
+
'[--post-scale-up-cooldown-seconds SECONDS] '\
|
74
|
+
'[--post-scale-down-cooldown-seconds SECONDS] '\
|
75
|
+
'[--post-release-cooldown-seconds SECONDS] '\
|
76
|
+
'[--mem-cpu-ratio-r-threshold RATIO] '\
|
77
|
+
'[--mem-cpu-ratio-c-threshold RATIO] '\
|
78
|
+
'[--mem-scale-up-threshold THRESHOLD] '\
|
79
|
+
'[--mem-scale-down-threshold THRESHOLD] '\
|
80
|
+
'[--minimum-memory MEMORY] '\
|
81
|
+
'[--maximum-memory MEMORY] '\
|
82
|
+
'[--min-cpu-threshold THRESHOLD] '\
|
83
|
+
'[--max-cpu-threshold THRESHOLD] '\
|
84
|
+
'[--min-containers CONTAINERS] '\
|
85
|
+
'[--max-containers CONTAINERS] '\
|
86
|
+
'[--scale-up-step STEPS] '\
|
87
|
+
'[--scale-down-step STEPS] ',
|
88
|
+
'Sets the sizing (autoscaling) policy for a service.'\
|
89
|
+
' This is not incremental, all arguments must be sent'\
|
90
|
+
' at once or they will be set to defaults.'
|
91
|
+
app_options
|
92
|
+
option :autoscaling_type,
|
93
|
+
type: :string,
|
94
|
+
desc: 'The type of autoscaling. Must be either '\
|
95
|
+
'"horizontal" or "vertical"'
|
96
|
+
option :metric_lookback_seconds,
|
97
|
+
type: :numeric,
|
98
|
+
desc: '(Default: 1800) The duration in seconds for '\
|
99
|
+
'retrieving past performance metrics.'
|
100
|
+
option :percentile,
|
101
|
+
type: :numeric,
|
102
|
+
desc: '(Default: 99) The percentile for evaluating metrics.'
|
103
|
+
option :post_scale_up_cooldown_seconds,
|
104
|
+
type: :numeric,
|
105
|
+
desc: '(Default: 60) The waiting period in seconds after '\
|
106
|
+
'an automated scale-up before another scaling action can '\
|
107
|
+
'be considered.'
|
108
|
+
option :post_scale_down_cooldown_seconds,
|
109
|
+
type: :numeric,
|
110
|
+
desc: '(Default: 300) The waiting period in seconds after '\
|
111
|
+
'an automated scale-down before another scaling action can '\
|
112
|
+
'be considered.'
|
113
|
+
option :post_release_cooldown_seconds,
|
114
|
+
type: :numeric,
|
115
|
+
desc: '(Default: 300) The time in seconds to wait '\
|
116
|
+
'following a deploy before another scaling action can '\
|
117
|
+
'be considered.'
|
118
|
+
option :mem_cpu_ratio_r_threshold,
|
119
|
+
type: :numeric,
|
120
|
+
desc: '(Default: 4.0) Establishes the ratio of Memory '\
|
121
|
+
'(in GB) to CPU (in CPUs) at which values exceeding the '\
|
122
|
+
'threshold prompt a shift to an R (Memory Optimized) '\
|
123
|
+
'profile.'
|
124
|
+
option :mem_cpu_ratio_c_threshold,
|
125
|
+
type: :numeric,
|
126
|
+
desc: '(Default: 2.0) Sets the Memory-to-CPU ratio '\
|
127
|
+
'threshold, below which the service is transitioned to a '\
|
128
|
+
'C (Compute Optimized) profile.'
|
129
|
+
option :mem_scale_up_threshold,
|
130
|
+
type: :numeric,
|
131
|
+
desc: '(Default: 0.9) Vertical autoscaling only - '\
|
132
|
+
'Specifies the percentage of the current memory limit '\
|
133
|
+
'at which the service’s memory usage triggers an '\
|
134
|
+
'up-scaling action.'
|
135
|
+
option :mem_scale_down_threshold,
|
136
|
+
type: :numeric,
|
137
|
+
desc: '(Default: 0.75) Vertical autoscaling only - '\
|
138
|
+
'Specifies the percentage of the current memory limit at '\
|
139
|
+
'which the service’s memory usage triggers a '\
|
140
|
+
'down-scaling action.'
|
141
|
+
option :minimum_memory,
|
142
|
+
type: :numeric,
|
143
|
+
desc: '(Default: 2048) Vertical autoscaling only - Sets '\
|
144
|
+
'the lowest memory limit to which the service can be '\
|
145
|
+
'scaled down by Autoscaler.'
|
146
|
+
option :maximum_memory,
|
147
|
+
type: :numeric,
|
148
|
+
desc: 'Vertical autoscaling only - Defines the upper '\
|
149
|
+
'memory threshold, capping the maximum memory allocation'\
|
150
|
+
' possible through Autoscaler. If blank, the container can'\
|
151
|
+
' scale to the largest size available.'
|
152
|
+
option :min_cpu_threshold,
|
153
|
+
type: :numeric,
|
154
|
+
desc: 'Horizontal autoscaling only - Specifies the '\
|
155
|
+
'percentage of the current CPU usage at which a '\
|
156
|
+
'down-scaling action is triggered.'
|
157
|
+
option :max_cpu_threshold,
|
158
|
+
type: :numeric,
|
159
|
+
desc: 'Horizontal autoscaling only - Specifies the '\
|
160
|
+
'percentage of the current CPU usage at which an '\
|
161
|
+
'up-scaling action is triggered.'
|
162
|
+
option :min_containers,
|
163
|
+
type: :numeric,
|
164
|
+
desc: 'Horizontal autoscaling only - Sets the lowest'\
|
165
|
+
' container count to which the service can be scaled down.'
|
166
|
+
option :max_containers,
|
167
|
+
type: :numeric,
|
168
|
+
desc: 'Horizontal autoscaling only - Sets the highest '\
|
169
|
+
'container count to which the service can be scaled up to.'
|
170
|
+
option :scale_up_step,
|
171
|
+
type: :numeric,
|
172
|
+
desc: '(Default: 1) Horizontal autoscaling only - Sets '\
|
173
|
+
'the amount of containers to add when autoscaling (ex: a '\
|
174
|
+
'value of 2 will go from 1->3->5). Container count will '\
|
175
|
+
'never exceed the configured maximum.'
|
176
|
+
option :scale_down_step,
|
177
|
+
type: :numeric,
|
178
|
+
desc: '(Default: 1) Horizontal autoscaling only - Sets '\
|
179
|
+
'the amount of containers to remove when autoscaling (ex:'\
|
180
|
+
' a value of 2 will go from 4->2->1). Container count '\
|
181
|
+
'will never exceed the configured minimum.'
|
182
|
+
define_method 'services:sizing_policy:set' do |service|
|
183
|
+
service = ensure_service(options, service)
|
184
|
+
ignored_attrs = %i(autoscaling_type app environment remote)
|
185
|
+
args = options.except(*ignored_attrs)
|
186
|
+
args[:autoscaling] = options[:autoscaling_type]
|
187
|
+
|
188
|
+
sizing_policy = service.service_sizing_policy
|
189
|
+
if sizing_policy
|
190
|
+
sizing_policy.update!(**args)
|
191
|
+
else
|
192
|
+
service.create_service_sizing_policy!(**args)
|
193
|
+
end
|
194
|
+
end
|
24
195
|
end
|
25
196
|
end
|
26
197
|
end
|
data/lib/aptible/cli/version.rb
CHANGED
@@ -452,6 +452,30 @@ describe Aptible::CLI::Agent do
|
|
452
452
|
end
|
453
453
|
end
|
454
454
|
|
455
|
+
describe 'endpoints:grpc:create' do
|
456
|
+
m = 'endpoints:grpc:create'
|
457
|
+
include_examples 'shared create app vhost examples', m
|
458
|
+
include_examples 'shared create tls vhost examples', m
|
459
|
+
|
460
|
+
it 'creates a gRPC Endpoint' do
|
461
|
+
expect_create_vhost(
|
462
|
+
service,
|
463
|
+
type: 'grpc',
|
464
|
+
platform: 'elb',
|
465
|
+
internal: false,
|
466
|
+
default: false,
|
467
|
+
ip_whitelist: []
|
468
|
+
)
|
469
|
+
subject.send(m, 'web')
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'creates an Endpoint with a container Port' do
|
473
|
+
expect_create_vhost(service, container_port: 10)
|
474
|
+
stub_options(port: 10)
|
475
|
+
subject.send(m, 'web')
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
455
479
|
shared_examples 'shared modify app vhost examples' do |m|
|
456
480
|
it 'does not change anything if no options are passed' do
|
457
481
|
v = Fabricate(:vhost, service: service)
|
@@ -579,6 +603,20 @@ describe Aptible::CLI::Agent do
|
|
579
603
|
end
|
580
604
|
end
|
581
605
|
|
606
|
+
describe 'endpoints:grpc:modify' do
|
607
|
+
m = 'endpoints:grpc:modify'
|
608
|
+
include_examples 'shared modify app vhost examples', m
|
609
|
+
include_examples 'shared modify tls vhost examples', m
|
610
|
+
|
611
|
+
it 'allows updating the Container Port' do
|
612
|
+
v = Fabricate(:vhost, service: service)
|
613
|
+
expect_modify_vhost(v, container_port: 10)
|
614
|
+
|
615
|
+
stub_options(port: 10)
|
616
|
+
subject.send(m, v.external_host)
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
582
620
|
describe 'endpoints:list' do
|
583
621
|
it 'lists Endpoints across services' do
|
584
622
|
s1 = Fabricate(:service, app: app)
|
@@ -8,45 +8,162 @@ describe Aptible::CLI::Agent do
|
|
8
8
|
allow(subject).to receive(:fetch_token) { token }
|
9
9
|
allow(Aptible::Api::App).to receive(:all).with(token: token)
|
10
10
|
.and_return([app])
|
11
|
-
allow(subject).to receive(:options).and_return(app: app.handle)
|
12
11
|
end
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
describe '#services' do
|
14
|
+
before do
|
15
|
+
allow(subject).to receive(:options).and_return(app: app.handle)
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
it 'lists a CMD service' do
|
19
|
+
Fabricate(:service, app: app, process_type: 'cmd', command: nil)
|
20
|
+
subject.send('services')
|
21
|
+
|
22
|
+
expect(captured_output_text.split("\n")).to include('Service: cmd')
|
23
|
+
expect(captured_output_text.split("\n")).to include('Command: CMD')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'lists a service with command' do
|
27
|
+
Fabricate(:service, app: app, process_type: 'cmd', command: 'foobar')
|
28
|
+
subject.send('services')
|
29
|
+
|
30
|
+
expect(captured_output_text.split("\n")).to include('Service: cmd')
|
31
|
+
expect(captured_output_text.split("\n")).to include('Command: foobar')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'lists container size' do
|
35
|
+
Fabricate(:service, app: app, container_memory_limit_mb: 1024)
|
36
|
+
subject.send('services')
|
37
|
+
|
38
|
+
expect(captured_output_text.split("\n"))
|
39
|
+
.to include('Container Size: 1024')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'lists container count' do
|
43
|
+
Fabricate(:service, app: app, container_count: 3)
|
44
|
+
subject.send('services')
|
21
45
|
|
22
|
-
|
23
|
-
|
24
|
-
subject.send('services')
|
46
|
+
expect(captured_output_text.split("\n")).to include('Container Count: 3')
|
47
|
+
end
|
25
48
|
|
26
|
-
|
27
|
-
|
49
|
+
it 'lists multiple services' do
|
50
|
+
Fabricate(:service, app: app, process_type: 'foo')
|
51
|
+
Fabricate(:service, app: app, process_type: 'bar')
|
52
|
+
subject.send('services')
|
53
|
+
|
54
|
+
expect(captured_output_text.split("\n")).to include('Service: foo')
|
55
|
+
expect(captured_output_text.split("\n")).to include('Service: bar')
|
56
|
+
end
|
28
57
|
end
|
29
58
|
|
30
|
-
|
31
|
-
|
32
|
-
|
59
|
+
describe '#services:settings' do
|
60
|
+
let(:base_options) { { app: app.handle } }
|
61
|
+
|
62
|
+
it 'allows changing zero_downtime_deployment settings' do
|
63
|
+
stub_options(force_zero_downtime: true, simple_health_check: true)
|
64
|
+
service = Fabricate(:service, app: app, process_type: 'foo')
|
65
|
+
|
66
|
+
expect(service).to receive(:update!)
|
67
|
+
.with(force_zero_downtime: true, naive_health_check: true)
|
68
|
+
|
69
|
+
subject.send('services:settings', 'foo')
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'allows changing only one of the options' do
|
73
|
+
stub_options(simple_health_check: true)
|
74
|
+
service = Fabricate(:service, app: app, process_type: 'foo')
|
75
|
+
|
76
|
+
expect(service).to receive(:update!).with(naive_health_check: true)
|
33
77
|
|
34
|
-
|
78
|
+
subject.send('services:settings', 'foo')
|
79
|
+
end
|
35
80
|
end
|
36
81
|
|
37
|
-
|
38
|
-
|
39
|
-
subject.send('services')
|
82
|
+
describe '#services:sizing_policy' do
|
83
|
+
let(:base_options) { { app: app.handle } }
|
40
84
|
|
41
|
-
|
85
|
+
it 'returns the sizing policy' do
|
86
|
+
stub_options
|
87
|
+
service = Fabricate(:service, app: app, process_type: 'foo')
|
88
|
+
sizing_policy = Fabricate(:service_sizing_policy)
|
89
|
+
expect(service).to receive(:service_sizing_policy)
|
90
|
+
.and_return(sizing_policy)
|
91
|
+
|
92
|
+
subject.send('services:sizing_policy', 'foo')
|
93
|
+
|
94
|
+
out = captured_output_text
|
95
|
+
expect(out).to match(/Autoscaling Type: vertical/i)
|
96
|
+
expect(out).to match(/Metric Lookback Seconds: 1800/i)
|
97
|
+
expect(out).to match(/Percentile: 99.0/i)
|
98
|
+
expect(out).to match(/Post Scale Up Cooldown Seconds: 60/i)
|
99
|
+
expect(out).to match(/Post Scale Down Cooldown Seconds: 300/i)
|
100
|
+
expect(out).to match(/Post Release Cooldown Seconds: 300/i)
|
101
|
+
expect(out).to match(/Mem Cpu Ratio R Threshold: 4/i)
|
102
|
+
expect(out).to match(/Mem Cpu Ratio C Threshold: 2/i)
|
103
|
+
expect(out).to match(/Mem Scale Up Threshold: 0.9/i)
|
104
|
+
expect(out).to match(/Mem Scale Down Threshold: 0.75/i)
|
105
|
+
expect(out).to match(/Minimum Memory: 2048/i)
|
106
|
+
expect(out).to match(/Maximum Memory:/i)
|
107
|
+
expect(out).to match(/Min Cpu Threshold: 0.4/i)
|
108
|
+
expect(out).to match(/Max Cpu Threshold: 0.9/i)
|
109
|
+
expect(out).to match(/Min Containers: 2/i)
|
110
|
+
expect(out).to match(/Max Containers: 5/i)
|
111
|
+
expect(out).to match(/Scale Up Step: 1/i)
|
112
|
+
expect(out).to match(/Scale Down Step: 1/i)
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'raises an error if the environment has no policy' do
|
116
|
+
stub_options
|
117
|
+
service = Fabricate(:service, app: app, process_type: 'foo')
|
118
|
+
expect(service).to receive(:service_sizing_policy).and_return(nil)
|
119
|
+
expect { subject.send('services:sizing_policy', 'foo') }
|
120
|
+
.to raise_error(/does not have a service sizing policy set/)
|
121
|
+
end
|
42
122
|
end
|
43
123
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
124
|
+
describe '#services:sizing_policy:set' do
|
125
|
+
let(:base_options) { { app: app.handle } }
|
126
|
+
let(:args) do
|
127
|
+
{
|
128
|
+
autoscaling_type: 'vertical',
|
129
|
+
mem_scale_down_threshold: 0.5,
|
130
|
+
scale_up_step: 2,
|
131
|
+
post_scale_down_cooldown_seconds: 3,
|
132
|
+
percentile: 93.2
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'updates existing sizing policy' do
|
137
|
+
stub_options(**args)
|
138
|
+
service = Fabricate(:service, app: app, process_type: 'foo')
|
139
|
+
sizing_policy = double(sizing_policy)
|
140
|
+
expect(service).to receive(:service_sizing_policy)
|
141
|
+
.and_return(sizing_policy)
|
142
|
+
|
143
|
+
api_args = args.except(:autoscaling_type)
|
144
|
+
api_args[:autoscaling] = args[:autoscaling_type]
|
145
|
+
|
146
|
+
expect(sizing_policy).to receive(:update!)
|
147
|
+
.with(**api_args)
|
148
|
+
|
149
|
+
subject.send('services:sizing_policy:set', 'foo')
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'creates a new sizing policy if necessary' do
|
153
|
+
stub_options(**args)
|
154
|
+
service = Fabricate(:service, app: app, process_type: 'foo')
|
155
|
+
|
156
|
+
api_args = args.except(:autoscaling_type)
|
157
|
+
api_args[:autoscaling] = args[:autoscaling_type]
|
158
|
+
|
159
|
+
expect(service).to receive(:create_service_sizing_policy!)
|
160
|
+
.with(**api_args)
|
161
|
+
|
162
|
+
subject.send('services:sizing_policy:set', 'foo')
|
163
|
+
end
|
164
|
+
end
|
48
165
|
|
49
|
-
|
50
|
-
|
166
|
+
def stub_options(**opts)
|
167
|
+
allow(subject).to receive(:options).and_return(base_options.merge(opts))
|
51
168
|
end
|
52
169
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class StubServiceSizingPolicy < OpenStruct; end
|
2
|
+
|
3
|
+
Fabricator(:service_sizing_policy, from: :stub_service_sizing_policy) do
|
4
|
+
autoscaling 'vertical'
|
5
|
+
metric_lookback_seconds 1800
|
6
|
+
percentile 99.0
|
7
|
+
post_scale_up_cooldown_seconds 60
|
8
|
+
post_scale_down_cooldown_seconds 300
|
9
|
+
post_release_cooldown_seconds 300
|
10
|
+
mem_cpu_ratio_r_threshold 4
|
11
|
+
mem_cpu_ratio_c_threshold 2
|
12
|
+
mem_scale_up_threshold 0.9
|
13
|
+
mem_scale_down_threshold 0.75
|
14
|
+
minimum_memory 2048
|
15
|
+
maximum_memory nil
|
16
|
+
min_cpu_threshold 0.4
|
17
|
+
max_cpu_threshold 0.9
|
18
|
+
min_containers 2
|
19
|
+
max_containers 5
|
20
|
+
scale_up_step 1
|
21
|
+
scale_down_step 1
|
22
|
+
end
|