aptible-cli 0.21.0 → 0.23.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.
- 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
|