capistrano-asg-rolling 0.4.0 → 0.5.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 +1 -1
- data/.rubocop.yml +7 -0
- data/Gemfile +5 -4
- data/Gemfile.lock +74 -70
- data/README.md +59 -15
- data/capistrano-asg-rolling.gemspec +1 -1
- data/lib/capistrano/asg/rolling/autoscale_group.rb +39 -6
- data/lib/capistrano/asg/rolling/exception.rb +15 -1
- data/lib/capistrano/asg/rolling/instance.rb +6 -4
- data/lib/capistrano/asg/rolling/parallel.rb +2 -3
- data/lib/capistrano/asg/rolling/tags.rb +42 -0
- data/lib/capistrano/asg/rolling/version.rb +1 -1
- data/lib/capistrano/asg/rolling.rb +1 -0
- data/lib/capistrano/asg/tasks/rolling.rake +34 -26
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f61a7cc3d942cd53ef2efb22eadcfb60cced1deab57b0ef19398bc7efe78242
|
4
|
+
data.tar.gz: 258611f43a7123defb8aabf8a05dd732bc79cd9fdd4443003b81f1db19969b88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab36e35b6afcd985cb4d23d4b08f5486c85a74f006b5c9e80cce9cca7b80fb8cb172333c3a750391ec4d57807d60943c2b1b6d7e685cb97730f517958b506e2e
|
7
|
+
data.tar.gz: bb22aef2c396ebfa35c5843caa3929e1ba2c03aba7284de2334bf631e864432083636b1963ea7a5355a89e20b272497272842acc72e0fcee7087354a2bdaf949
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
@@ -2,6 +2,7 @@ require:
|
|
2
2
|
- rubocop-performance
|
3
3
|
- rubocop-rake
|
4
4
|
- rubocop-rspec
|
5
|
+
- rubocop-rspec_rails
|
5
6
|
|
6
7
|
AllCops:
|
7
8
|
NewCops: enable
|
@@ -21,6 +22,9 @@ Metrics/AbcSize:
|
|
21
22
|
Metrics/BlockLength:
|
22
23
|
Enabled: false
|
23
24
|
|
25
|
+
Metrics/BlockNesting:
|
26
|
+
Max: 4
|
27
|
+
|
24
28
|
Metrics/ClassLength:
|
25
29
|
Enabled: false
|
26
30
|
|
@@ -35,3 +39,6 @@ RSpec/IndexedLet:
|
|
35
39
|
|
36
40
|
RSpec/MultipleExpectations:
|
37
41
|
Max: 5
|
42
|
+
|
43
|
+
Style/GuardClause:
|
44
|
+
Enabled: false
|
data/Gemfile
CHANGED
@@ -7,7 +7,8 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
|
7
7
|
# Specify your gem's dependencies in capistrano-asg-rolling.gemspec
|
8
8
|
gemspec
|
9
9
|
|
10
|
-
gem 'rubocop', '~> 1.
|
11
|
-
gem 'rubocop-performance', '~> 1.
|
12
|
-
gem 'rubocop-rake', '~> 0.6
|
13
|
-
gem 'rubocop-rspec', '~>
|
10
|
+
gem 'rubocop', '~> 1.58'
|
11
|
+
gem 'rubocop-performance', '~> 1.20'
|
12
|
+
gem 'rubocop-rake', '~> 0.6'
|
13
|
+
gem 'rubocop-rspec', '~> 3.0'
|
14
|
+
gem 'rubocop-rspec_rails', '~> 2.30'
|
data/Gemfile.lock
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
capistrano-asg-rolling (0.
|
5
|
-
aws-sdk-autoscaling (~> 1, >= 1.
|
4
|
+
capistrano-asg-rolling (0.5.0)
|
5
|
+
aws-sdk-autoscaling (~> 1, >= 1.100.0)
|
6
6
|
aws-sdk-ec2 (~> 1)
|
7
7
|
capistrano (~> 3)
|
8
8
|
concurrent-ruby (~> 1)
|
@@ -10,101 +10,104 @@ PATH
|
|
10
10
|
GEM
|
11
11
|
remote: https://rubygems.org/
|
12
12
|
specs:
|
13
|
-
addressable (2.8.
|
14
|
-
public_suffix (>= 2.0.2, <
|
15
|
-
airbrussh (1.5.
|
13
|
+
addressable (2.8.7)
|
14
|
+
public_suffix (>= 2.0.2, < 7.0)
|
15
|
+
airbrussh (1.5.3)
|
16
16
|
sshkit (>= 1.6.1, != 1.7.0)
|
17
17
|
ast (2.4.2)
|
18
|
-
aws-eventstream (1.
|
19
|
-
aws-partitions (1.
|
20
|
-
aws-sdk-autoscaling (1.
|
21
|
-
aws-sdk-core (~> 3, >= 3.
|
22
|
-
aws-sigv4 (~> 1.1)
|
23
|
-
aws-sdk-core (3.184.0)
|
24
|
-
aws-eventstream (~> 1, >= 1.0.2)
|
25
|
-
aws-partitions (~> 1, >= 1.651.0)
|
18
|
+
aws-eventstream (1.3.0)
|
19
|
+
aws-partitions (1.989.0)
|
20
|
+
aws-sdk-autoscaling (1.121.0)
|
21
|
+
aws-sdk-core (~> 3, >= 3.207.0)
|
26
22
|
aws-sigv4 (~> 1.5)
|
23
|
+
aws-sdk-core (3.209.1)
|
24
|
+
aws-eventstream (~> 1, >= 1.3.0)
|
25
|
+
aws-partitions (~> 1, >= 1.651.0)
|
26
|
+
aws-sigv4 (~> 1.9)
|
27
27
|
jmespath (~> 1, >= 1.6.1)
|
28
|
-
aws-sdk-ec2 (1.
|
29
|
-
aws-sdk-core (~> 3, >= 3.
|
30
|
-
aws-sigv4 (~> 1.
|
31
|
-
aws-sigv4 (1.
|
28
|
+
aws-sdk-ec2 (1.481.0)
|
29
|
+
aws-sdk-core (~> 3, >= 3.207.0)
|
30
|
+
aws-sigv4 (~> 1.5)
|
31
|
+
aws-sigv4 (1.10.0)
|
32
32
|
aws-eventstream (~> 1, >= 1.0.2)
|
33
|
-
base64 (0.
|
34
|
-
|
33
|
+
base64 (0.2.0)
|
34
|
+
bigdecimal (3.1.8)
|
35
|
+
capistrano (3.19.1)
|
35
36
|
airbrussh (>= 1.0.0)
|
36
37
|
i18n
|
37
38
|
rake (>= 10.0.0)
|
38
39
|
sshkit (>= 1.9.0)
|
39
|
-
concurrent-ruby (1.
|
40
|
-
crack (0.
|
40
|
+
concurrent-ruby (1.3.4)
|
41
|
+
crack (1.0.0)
|
42
|
+
bigdecimal
|
41
43
|
rexml
|
42
|
-
diff-lcs (1.5.
|
43
|
-
hashdiff (1.
|
44
|
-
i18n (1.14.
|
44
|
+
diff-lcs (1.5.1)
|
45
|
+
hashdiff (1.1.1)
|
46
|
+
i18n (1.14.6)
|
45
47
|
concurrent-ruby (~> 1.0)
|
46
48
|
jmespath (1.6.2)
|
47
|
-
json (2.
|
49
|
+
json (2.7.2)
|
48
50
|
language_server-protocol (3.17.0.3)
|
49
51
|
net-scp (4.0.0)
|
50
52
|
net-ssh (>= 2.6.5, < 8.0.0)
|
51
|
-
net-
|
52
|
-
|
53
|
-
|
53
|
+
net-sftp (4.0.0)
|
54
|
+
net-ssh (>= 5.0.0, < 8.0.0)
|
55
|
+
net-ssh (7.3.0)
|
56
|
+
ostruct (0.6.0)
|
57
|
+
parallel (1.26.3)
|
58
|
+
parser (3.3.5.0)
|
54
59
|
ast (~> 2.4.1)
|
55
60
|
racc
|
56
|
-
public_suffix (5.
|
57
|
-
racc (1.
|
61
|
+
public_suffix (5.1.1)
|
62
|
+
racc (1.8.1)
|
58
63
|
rainbow (3.1.1)
|
59
|
-
rake (13.
|
60
|
-
regexp_parser (2.
|
61
|
-
rexml (3.
|
62
|
-
rspec (3.
|
63
|
-
rspec-core (~> 3.
|
64
|
-
rspec-expectations (~> 3.
|
65
|
-
rspec-mocks (~> 3.
|
66
|
-
rspec-core (3.
|
67
|
-
rspec-support (~> 3.
|
68
|
-
rspec-expectations (3.
|
64
|
+
rake (13.2.1)
|
65
|
+
regexp_parser (2.9.2)
|
66
|
+
rexml (3.3.8)
|
67
|
+
rspec (3.13.0)
|
68
|
+
rspec-core (~> 3.13.0)
|
69
|
+
rspec-expectations (~> 3.13.0)
|
70
|
+
rspec-mocks (~> 3.13.0)
|
71
|
+
rspec-core (3.13.1)
|
72
|
+
rspec-support (~> 3.13.0)
|
73
|
+
rspec-expectations (3.13.3)
|
69
74
|
diff-lcs (>= 1.2.0, < 2.0)
|
70
|
-
rspec-support (~> 3.
|
71
|
-
rspec-mocks (3.
|
75
|
+
rspec-support (~> 3.13.0)
|
76
|
+
rspec-mocks (3.13.2)
|
72
77
|
diff-lcs (>= 1.2.0, < 2.0)
|
73
|
-
rspec-support (~> 3.
|
74
|
-
rspec-support (3.
|
75
|
-
rubocop (1.
|
76
|
-
base64 (~> 0.1.1)
|
78
|
+
rspec-support (~> 3.13.0)
|
79
|
+
rspec-support (3.13.1)
|
80
|
+
rubocop (1.66.1)
|
77
81
|
json (~> 2.3)
|
78
82
|
language_server-protocol (>= 3.17.0)
|
79
83
|
parallel (~> 1.10)
|
80
|
-
parser (>= 3.
|
84
|
+
parser (>= 3.3.0.2)
|
81
85
|
rainbow (>= 2.2.2, < 4.0)
|
82
|
-
regexp_parser (>=
|
83
|
-
|
84
|
-
rubocop-ast (>= 1.28.1, < 2.0)
|
86
|
+
regexp_parser (>= 2.4, < 3.0)
|
87
|
+
rubocop-ast (>= 1.32.2, < 2.0)
|
85
88
|
ruby-progressbar (~> 1.7)
|
86
89
|
unicode-display_width (>= 2.4.0, < 3.0)
|
87
|
-
rubocop-ast (1.
|
88
|
-
parser (>= 3.
|
89
|
-
rubocop-
|
90
|
-
rubocop (
|
91
|
-
|
92
|
-
rubocop (~> 1.33)
|
93
|
-
rubocop-performance (1.19.1)
|
94
|
-
rubocop (>= 1.7.0, < 2.0)
|
95
|
-
rubocop-ast (>= 0.4.0)
|
90
|
+
rubocop-ast (1.32.3)
|
91
|
+
parser (>= 3.3.1.0)
|
92
|
+
rubocop-performance (1.22.1)
|
93
|
+
rubocop (>= 1.48.1, < 2.0)
|
94
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
96
95
|
rubocop-rake (0.6.0)
|
97
96
|
rubocop (~> 1.0)
|
98
|
-
rubocop-rspec (
|
99
|
-
rubocop (~> 1.
|
100
|
-
|
101
|
-
rubocop
|
97
|
+
rubocop-rspec (3.1.0)
|
98
|
+
rubocop (~> 1.61)
|
99
|
+
rubocop-rspec_rails (2.30.0)
|
100
|
+
rubocop (~> 1.61)
|
101
|
+
rubocop-rspec (~> 3, >= 3.0.1)
|
102
102
|
ruby-progressbar (1.13.0)
|
103
|
-
sshkit (1.
|
103
|
+
sshkit (1.23.1)
|
104
|
+
base64
|
104
105
|
net-scp (>= 1.1.2)
|
106
|
+
net-sftp (>= 2.1.2)
|
105
107
|
net-ssh (>= 2.8.0)
|
106
|
-
|
107
|
-
|
108
|
+
ostruct
|
109
|
+
unicode-display_width (2.6.0)
|
110
|
+
webmock (3.24.0)
|
108
111
|
addressable (>= 2.8.0)
|
109
112
|
crack (>= 0.3.2)
|
110
113
|
hashdiff (>= 0.4.0, < 2.0.0)
|
@@ -117,10 +120,11 @@ DEPENDENCIES
|
|
117
120
|
capistrano-asg-rolling!
|
118
121
|
rake (~> 13.0)
|
119
122
|
rspec (~> 3.0)
|
120
|
-
rubocop (~> 1.
|
121
|
-
rubocop-performance (~> 1.
|
122
|
-
rubocop-rake (~> 0.6
|
123
|
-
rubocop-rspec (~>
|
123
|
+
rubocop (~> 1.58)
|
124
|
+
rubocop-performance (~> 1.20)
|
125
|
+
rubocop-rake (~> 0.6)
|
126
|
+
rubocop-rspec (~> 3.0)
|
127
|
+
rubocop-rspec_rails (~> 2.30)
|
124
128
|
webmock (~> 3.11)
|
125
129
|
|
126
130
|
BUNDLED WITH
|
data/README.md
CHANGED
@@ -16,25 +16,22 @@ Instead of deploying to live servers, capistrano-asg-rolling will create a tempo
|
|
16
16
|
- Delete any outdated Launch Template versions, AMIs and snapshots created by previous deployments.
|
17
17
|
- Terminate the no longer needed instances.
|
18
18
|
|
19
|
-
##
|
19
|
+
## Important
|
20
20
|
|
21
|
-
|
21
|
+
### Instance refresh
|
22
22
|
|
23
|
-
|
23
|
+
To better understand how Instance Refresh replaces instances, make sure to read the documentation:
|
24
|
+
https://docs.aws.amazon.com/autoscaling/ec2/userguide/instance-refresh-overview.html
|
24
25
|
|
25
|
-
|
26
|
+
### Launch Templates
|
26
27
|
|
27
28
|
This gem depends on Auto Scaling Groups with Launch Templates. Using an Auto Scaling Group with a Launch Configuration is not supported, and will raise an `Capistrano::ASG::Rolling::NoLaunchTemplate`.
|
28
29
|
|
29
30
|
Instance refresh uses the desired configuration to update the Launch Template version of the Auto Scaling Group after a succesful deployment. Setting the Launch Template version to `Latest` on the Auto Scaling Group is not needed.
|
30
31
|
|
31
|
-
|
32
|
+
### Experimental
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
The configuration options are not considered stable and might be changed or removed in future releases.
|
36
|
-
|
37
|
-
The gem could have a better / fancier name.
|
34
|
+
Please note that this gem works well for our configuration / use case, but it might not fit yours. Any feedback using GitHub issues / pull requests, is much appreciated.
|
38
35
|
|
39
36
|
## Installation
|
40
37
|
|
@@ -133,16 +130,21 @@ autoscale 'app-autoscale-group', rolling: true # default: use rolling deploym
|
|
133
130
|
autoscale 'web-autoscale-group', rolling: false # override: use normal deployment
|
134
131
|
```
|
135
132
|
|
136
|
-
### Deploy with
|
133
|
+
### Deploy with custom percentage of minimum/maximum healthy instances during the instance refresh
|
134
|
+
|
135
|
+
The instance refresh is triggered without specifying a value for the minimum / maximum healthy percentages. This means that either the default
|
136
|
+
values will be used (minimum: 90%, maximum: 100%) or the percentages set in the instance maintenance policy for the Auto Scaling Group.
|
137
|
+
You can tune both the minimum and maximum values to have more control about the desired capacity that must be healthy to proceed with replacing instances.
|
137
138
|
|
138
|
-
|
139
|
+
For example: reducing the minimum healthy percentage allows more instances to be terminated and new instances to be brought up at once during the instance refresh. eg. a value of 0 would terminate all instances in the autoscaling group and replace them at once.
|
139
140
|
|
140
|
-
You can configure the minimum healthy percentage per autoscaling group using the `
|
141
|
+
You can configure the minimum healthy percentage per autoscaling group using the `min_healthy_percentage` option, and the maximum healthy percentage using the `max_healthy_percentage` option. Please note that if you specify `max_healthy_percentage`, you must also specify `min_healthy_percentage`.
|
141
142
|
|
142
143
|
```ruby
|
143
144
|
# config/deploy/<stage>.rb
|
144
|
-
autoscale 'app-autoscale-group',
|
145
|
-
autoscale 'web-autoscale-group',
|
145
|
+
autoscale 'app-autoscale-group', # use default percentages or percentages set in the instance maintenance policy
|
146
|
+
autoscale 'web-autoscale-group', min_healthy_percentage: 75 # allow 25% of instances to be terminated and replaced at once
|
147
|
+
autoscale 'web-autoscale-group', min_healthy_percentage: 100, max_healthy_percentage: 110 # allow for 10% above desired capacity during instance refresh
|
146
148
|
```
|
147
149
|
|
148
150
|
### Custom stage
|
@@ -172,6 +174,46 @@ autoscale 'web-autoscale-group', rolling: true, user: 'deployer'
|
|
172
174
|
|
173
175
|
With these two stages, you can run any tasks with `cap production <task name>` and rolling deployments with `cap production_rolling deploy`.
|
174
176
|
|
177
|
+
## Useful commands
|
178
|
+
|
179
|
+
### Test deployment
|
180
|
+
|
181
|
+
Do a test deployment: run the deploy task, but do not trigger the update ASG task and do not automatically terminate instances.
|
182
|
+
|
183
|
+
```bash
|
184
|
+
cap <stage> rolling:deploy_test
|
185
|
+
```
|
186
|
+
|
187
|
+
### Launch instances
|
188
|
+
|
189
|
+
Just launch instance(s) defined in the Auto Scale Group(s) launch template.
|
190
|
+
This instance is not attached to the Auto Scale Group and needs to be terminated manually.
|
191
|
+
|
192
|
+
```bash
|
193
|
+
cap <stage> rolling:launch_instances
|
194
|
+
```
|
195
|
+
|
196
|
+
### Create AMI of instance in ASG
|
197
|
+
|
198
|
+
Pick an instance in the Auto Scale Group(s), put it into standby and stop it.
|
199
|
+
Then create an AMI and a new launch template. Then start the instance and put it into service again.
|
200
|
+
|
201
|
+
```bash
|
202
|
+
cap <stage> rolling:create_ami
|
203
|
+
```
|
204
|
+
|
205
|
+
## Filtering
|
206
|
+
|
207
|
+
You can filter any command to run on a specific Auto Scale Group by using the parameter `asg_name`.
|
208
|
+
|
209
|
+
For example, the command:
|
210
|
+
|
211
|
+
```bash
|
212
|
+
cap <stage> deploy asg_name=app-autoscale-group
|
213
|
+
```
|
214
|
+
|
215
|
+
Will do a deployment only on the Auto Scale Group with the name `app-autoscale-group`.
|
216
|
+
|
175
217
|
## IAM policy
|
176
218
|
|
177
219
|
The following IAM permissions are required:
|
@@ -185,6 +227,7 @@ The following IAM permissions are required:
|
|
185
227
|
"Action": [
|
186
228
|
"autoscaling:DescribeAutoScalingGroups",
|
187
229
|
"autoscaling:DescribeAutoScalingInstances",
|
230
|
+
"autoscaling:DescribeInstanceRefreshes",
|
188
231
|
"autoscaling:EnterStandby",
|
189
232
|
"autoscaling:ExitStandby",
|
190
233
|
"autoscaling:StartInstanceRefresh",
|
@@ -198,6 +241,7 @@ The following IAM permissions are required:
|
|
198
241
|
"ec2:DescribeInstances",
|
199
242
|
"ec2:DescribeLaunchTemplateVersions",
|
200
243
|
"ec2:RunInstances",
|
244
|
+
"ec2:StartInstances",
|
201
245
|
"ec2:StopInstances",
|
202
246
|
"ec2:TerminateInstances"
|
203
247
|
],
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
34
34
|
spec.add_development_dependency 'webmock', '~> 3.11'
|
35
35
|
|
36
|
-
spec.add_dependency 'aws-sdk-autoscaling', '~> 1', '>= 1.
|
36
|
+
spec.add_dependency 'aws-sdk-autoscaling', '~> 1', '>= 1.100.0'
|
37
37
|
spec.add_dependency 'aws-sdk-ec2', '~> 1'
|
38
38
|
spec.add_dependency 'capistrano', '~> 3'
|
39
39
|
spec.add_dependency 'concurrent-ruby', '~> 1'
|
@@ -13,12 +13,21 @@ module Capistrano
|
|
13
13
|
LIFECYCLE_STATE_STANDBY = 'Standby'
|
14
14
|
|
15
15
|
COMPLETED_REFRESH_STATUSES = %w[Successful Failed Cancelled RollbackSuccessful RollbackFailed].freeze
|
16
|
+
FAILED_REFRESH_STATUS = 'Failed'
|
16
17
|
|
17
18
|
attr_reader :name, :properties, :refresh_id
|
18
19
|
|
19
20
|
def initialize(name, properties = {})
|
20
21
|
@name = name
|
21
22
|
@properties = properties
|
23
|
+
|
24
|
+
if properties[:healthy_percentage]
|
25
|
+
properties[:min_healthy_percentage] = properties.delete(:healthy_percentage)
|
26
|
+
|
27
|
+
Kernel.warn('WARNING: the property `healthy_percentage` is deprecated and will be removed in a future release. Please update to `min_healthy_percentage`.')
|
28
|
+
end
|
29
|
+
|
30
|
+
validate_properties!
|
22
31
|
end
|
23
32
|
|
24
33
|
def exists?
|
@@ -42,8 +51,12 @@ module Capistrano
|
|
42
51
|
aws_autoscaling_group.health_check_grace_period
|
43
52
|
end
|
44
53
|
|
45
|
-
def
|
46
|
-
properties.fetch(:
|
54
|
+
def min_healthy_percentage
|
55
|
+
properties.fetch(:min_healthy_percentage, nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
def max_healthy_percentage
|
59
|
+
properties.fetch(:max_healthy_percentage, nil)
|
47
60
|
end
|
48
61
|
|
49
62
|
def start_instance_refresh(launch_template)
|
@@ -58,18 +71,23 @@ module Capistrano
|
|
58
71
|
},
|
59
72
|
preferences: {
|
60
73
|
instance_warmup: instance_warmup_time,
|
61
|
-
|
62
|
-
|
63
|
-
|
74
|
+
skip_matching: true,
|
75
|
+
min_healthy_percentage: min_healthy_percentage,
|
76
|
+
max_healthy_percentage: max_healthy_percentage
|
77
|
+
}.compact
|
64
78
|
).instance_refresh_id
|
65
79
|
rescue Aws::AutoScaling::Errors::InstanceRefreshInProgress => e
|
66
|
-
raise Capistrano::ASG::Rolling::
|
80
|
+
raise Capistrano::ASG::Rolling::StartInstanceRefreshError, e
|
67
81
|
end
|
68
82
|
|
69
83
|
InstanceRefreshStatus = Struct.new(:status, :percentage_complete) do
|
70
84
|
def completed?
|
71
85
|
COMPLETED_REFRESH_STATUSES.include?(status)
|
72
86
|
end
|
87
|
+
|
88
|
+
def failed?
|
89
|
+
status == FAILED_REFRESH_STATUS
|
90
|
+
end
|
73
91
|
end
|
74
92
|
|
75
93
|
def latest_instance_refresh
|
@@ -136,6 +154,21 @@ module Capistrano
|
|
136
154
|
def aws_autoscaling_group
|
137
155
|
@aws_autoscaling_group ||= ::Aws::AutoScaling::AutoScalingGroup.new(name: name, client: aws_autoscaling_client)
|
138
156
|
end
|
157
|
+
|
158
|
+
def validate_properties!
|
159
|
+
raise ArgumentError, 'Property `min_healthy_percentage` must be between 0-100.' if min_healthy_percentage && !(0..100).cover?(min_healthy_percentage)
|
160
|
+
|
161
|
+
if max_healthy_percentage
|
162
|
+
raise ArgumentError, 'Property `max_healthy_percentage` must be between 100-200.' unless (100..200).cover?(max_healthy_percentage)
|
163
|
+
|
164
|
+
if min_healthy_percentage
|
165
|
+
diff = max_healthy_percentage - min_healthy_percentage
|
166
|
+
raise ArgumentError, 'The difference between `min_healthy_percentage` and `max_healthy_percentage` must not be greater than 100.' if diff > 100
|
167
|
+
else
|
168
|
+
raise ArgumentError, 'Property `min_healthy_percentage` must be specified when using `max_healthy_percentage`.'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
139
172
|
end
|
140
173
|
end
|
141
174
|
end
|
@@ -13,10 +13,17 @@ module Capistrano
|
|
13
13
|
class NoLaunchTemplate < Capistrano::ASG::Rolling::Exception
|
14
14
|
end
|
15
15
|
|
16
|
+
class StartInstanceRefreshError < Capistrano::ASG::Rolling::Exception
|
17
|
+
end
|
18
|
+
|
19
|
+
# Exception when the instance refresh failed on one of the ASGs.
|
16
20
|
class InstanceRefreshFailed < Capistrano::ASG::Rolling::Exception
|
21
|
+
def initialize
|
22
|
+
super('Failed to update Auto Scaling Group(s)')
|
23
|
+
end
|
17
24
|
end
|
18
25
|
|
19
|
-
# Exception when instance terminate
|
26
|
+
# Exception when instance terminate has failed.
|
20
27
|
class InstanceTerminateFailed < Capistrano::ASG::Rolling::Exception
|
21
28
|
attr_reader :instance
|
22
29
|
|
@@ -25,6 +32,13 @@ module Capistrano
|
|
25
32
|
super(exception)
|
26
33
|
end
|
27
34
|
end
|
35
|
+
|
36
|
+
# Exception when no instances could be launched.
|
37
|
+
class NoInstancesLaunched < Capistrano::ASG::Rolling::Exception
|
38
|
+
def initialize
|
39
|
+
super('No instances have been launched. Are you using a configuration with rolling deployments?')
|
40
|
+
end
|
41
|
+
end
|
28
42
|
end
|
29
43
|
end
|
30
44
|
end
|
@@ -81,6 +81,11 @@ module Capistrano
|
|
81
81
|
Configuration.use_private_ip_address? ? private_ip_address : public_ip_address
|
82
82
|
end
|
83
83
|
|
84
|
+
def start
|
85
|
+
aws_ec2_client.start_instances(instance_ids: [id])
|
86
|
+
aws_ec2_client.wait_until(:instance_running, instance_ids: [id])
|
87
|
+
end
|
88
|
+
|
84
89
|
def stop
|
85
90
|
aws_ec2_client.stop_instances(instance_ids: [id])
|
86
91
|
aws_ec2_client.wait_until(:instance_stopped, instance_ids: [id])
|
@@ -96,10 +101,7 @@ module Capistrano
|
|
96
101
|
end
|
97
102
|
|
98
103
|
def create_ami(name: nil, description: nil, tags: nil)
|
99
|
-
ami_tags = {
|
100
|
-
'Name' => autoscale_group.name_tag,
|
101
|
-
'capistrano-asg-rolling:version' => Capistrano::ASG::Rolling::VERSION
|
102
|
-
}
|
104
|
+
ami_tags = { 'Name' => autoscale_group.name_tag }
|
103
105
|
ami_tags.merge!(tags) if tags
|
104
106
|
|
105
107
|
AMI.create(instance: self, name: name || ami_name, description: description, tags: ami_tags)
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module ASG
|
5
|
+
module Rolling
|
6
|
+
# Helper for creating tags from Capistrano specific variables.
|
7
|
+
module Tags
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# The tags to add to an AMI.
|
11
|
+
def ami_tags
|
12
|
+
application_tags.merge(deployment_tags).merge(gem_tags)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Tags related to the current application / stage.
|
16
|
+
def application_tags
|
17
|
+
{
|
18
|
+
application: fetch(:application),
|
19
|
+
stage: fetch(:stage)
|
20
|
+
}.compact.transform_keys { |tag| "capistrano-asg-rolling:#{tag}" }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Tags related to the current deployment, such as git revisions.
|
24
|
+
def deployment_tags
|
25
|
+
{
|
26
|
+
branch: fetch(:branch),
|
27
|
+
user: fetch(:local_user),
|
28
|
+
revision: fetch(:current_revision),
|
29
|
+
release: fetch(:release_timestamp)
|
30
|
+
}.compact.transform_keys { |tag| "capistrano-asg-rolling:deployment-#{tag}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
# Tags related to the current gem version.
|
34
|
+
def gem_tags
|
35
|
+
{
|
36
|
+
'capistrano-asg-rolling:gem-version' => Capistrano::ASG::Rolling::VERSION
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -50,7 +50,7 @@ namespace :rolling do
|
|
50
50
|
config.instances.stop
|
51
51
|
|
52
52
|
logger.info 'Creating AMI(s)...'
|
53
|
-
amis = config.instances.create_ami(description: revision_log_message)
|
53
|
+
amis = config.instances.create_ami(description: revision_log_message, tags: Capistrano::ASG::Rolling::Tags.ami_tags)
|
54
54
|
|
55
55
|
logger.info 'Updating Launch Template(s) with the new AMI(s)...'
|
56
56
|
launch_templates = config.autoscale_groups.launch_templates
|
@@ -62,7 +62,7 @@ namespace :rolling do
|
|
62
62
|
group.start_instance_refresh(launch_template)
|
63
63
|
|
64
64
|
logger.verbose "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
|
65
|
-
rescue Capistrano::ASG::Rolling::
|
65
|
+
rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
|
66
66
|
logger.info "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
|
67
67
|
end
|
68
68
|
end
|
@@ -91,7 +91,7 @@ namespace :rolling do
|
|
91
91
|
end
|
92
92
|
|
93
93
|
# Only clean up when AMI was tagged by us.
|
94
|
-
next if exists && !ami.tag?('capistrano-asg-rolling:version')
|
94
|
+
next if exists && (!ami.tag?('capistrano-asg-rolling:version') || !ami.tag?('capistrano-asg-rolling:gem-version'))
|
95
95
|
|
96
96
|
logger.verbose "Deleting Launch Template **#{version.name}** version **#{version.version}**..."
|
97
97
|
version.delete
|
@@ -124,8 +124,7 @@ namespace :rolling do
|
|
124
124
|
instance.auto_terminate = false
|
125
125
|
end
|
126
126
|
else
|
127
|
-
|
128
|
-
exit 1
|
127
|
+
raise Capistrano::ASG::Rolling::NoInstancesLaunched
|
129
128
|
end
|
130
129
|
end
|
131
130
|
|
@@ -138,8 +137,7 @@ namespace :rolling do
|
|
138
137
|
instance.auto_terminate = false
|
139
138
|
end
|
140
139
|
else
|
141
|
-
|
142
|
-
exit 1
|
140
|
+
raise Capistrano::ASG::Rolling::NoInstancesLaunched
|
143
141
|
end
|
144
142
|
|
145
143
|
invoke 'deploy'
|
@@ -156,8 +154,14 @@ namespace :rolling do
|
|
156
154
|
logger.info "Instance **#{instance.id}** entering standby state..."
|
157
155
|
group.enter_standby(instance)
|
158
156
|
|
157
|
+
logger.info 'Stopping instance...'
|
158
|
+
instance.stop
|
159
|
+
|
159
160
|
logger.info 'Creating AMI...'
|
160
|
-
ami = instance.create_ami(description: revision_log_message)
|
161
|
+
ami = instance.create_ami(description: revision_log_message, tags: Capistrano::ASG::Rolling::Tags.ami_tags)
|
162
|
+
|
163
|
+
logger.info 'Starting instance...'
|
164
|
+
instance.start
|
161
165
|
|
162
166
|
logger.info "Instance **#{instance.id}** exiting standby state..."
|
163
167
|
group.exit_standby(instance)
|
@@ -175,27 +179,31 @@ namespace :rolling do
|
|
175
179
|
|
176
180
|
desc 'Get status of instance refresh'
|
177
181
|
task :instance_refresh_status do
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
182
|
+
if config.wait_for_instance_refresh?
|
183
|
+
groups = config.autoscale_groups.to_h { |group| [group.name, group] }
|
184
|
+
completed_groups = []
|
185
|
+
|
186
|
+
while groups.any?
|
187
|
+
groups.each do |name, group|
|
188
|
+
refresh = group.latest_instance_refresh
|
189
|
+
if refresh.nil? || refresh.completed?
|
190
|
+
logger.info "Auto Scaling Group: **#{name}**, completed with status '#{refresh.status}'." if refresh.completed?
|
191
|
+
completed_groups.push groups.delete(name)
|
192
|
+
elsif !refresh.percentage_complete.nil?
|
193
|
+
logger.info "Auto Scaling Group: **#{name}**, #{refresh.percentage_complete}% completed, status '#{refresh.status}'."
|
194
|
+
else
|
195
|
+
logger.info "Auto Scaling Group: **#{name}**, status '#{refresh.status}'."
|
196
|
+
end
|
192
197
|
end
|
198
|
+
next if groups.empty?
|
199
|
+
|
200
|
+
wait_for = config.instance_refresh_polling_interval
|
201
|
+
logger.info "Instance refresh(es) not completed, waiting #{wait_for} seconds..."
|
202
|
+
sleep wait_for
|
193
203
|
end
|
194
|
-
next if groups.empty?
|
195
204
|
|
196
|
-
|
197
|
-
|
198
|
-
sleep wait_for
|
205
|
+
failed = completed_groups.any? { |group| group.latest_instance_refresh.failed? }
|
206
|
+
raise Capistrano::ASG::Rolling::InstanceRefreshFailed if failed
|
199
207
|
end
|
200
208
|
end
|
201
209
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capistrano-asg-rolling
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kentaa
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-10-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,7 +75,7 @@ dependencies:
|
|
75
75
|
version: '1'
|
76
76
|
- - ">="
|
77
77
|
- !ruby/object:Gem::Version
|
78
|
-
version: 1.
|
78
|
+
version: 1.100.0
|
79
79
|
type: :runtime
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -85,7 +85,7 @@ dependencies:
|
|
85
85
|
version: '1'
|
86
86
|
- - ">="
|
87
87
|
- !ruby/object:Gem::Version
|
88
|
-
version: 1.
|
88
|
+
version: 1.100.0
|
89
89
|
- !ruby/object:Gem::Dependency
|
90
90
|
name: aws-sdk-ec2
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
@@ -165,6 +165,7 @@ files:
|
|
165
165
|
- lib/capistrano/asg/rolling/plugin.rb
|
166
166
|
- lib/capistrano/asg/rolling/snapshot.rb
|
167
167
|
- lib/capistrano/asg/rolling/ssh.rb
|
168
|
+
- lib/capistrano/asg/rolling/tags.rb
|
168
169
|
- lib/capistrano/asg/rolling/version.rb
|
169
170
|
- lib/capistrano/asg/tasks/rolling.rake
|
170
171
|
homepage: https://github.com/KentaaNL/capistrano-asg-rolling
|
@@ -187,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
188
|
- !ruby/object:Gem::Version
|
188
189
|
version: '0'
|
189
190
|
requirements: []
|
190
|
-
rubygems_version: 3.
|
191
|
+
rubygems_version: 3.1.6
|
191
192
|
signing_key:
|
192
193
|
specification_version: 4
|
193
194
|
summary: Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups
|