capistrano-asg-rolling 0.6.0 → 0.8.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 +1 -1
- data/.rubocop.yml +1 -1
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile.lock +65 -53
- data/LICENSE.txt +1 -1
- data/README.md +37 -1
- data/capistrano-asg-rolling.gemspec +5 -6
- data/lib/capistrano/asg/rolling/ami.rb +10 -3
- data/lib/capistrano/asg/rolling/autoscale_group.rb +5 -1
- data/lib/capistrano/asg/rolling/autoscale_groups.rb +2 -2
- data/lib/capistrano/asg/rolling/aws.rb +2 -0
- data/lib/capistrano/asg/rolling/configuration.rb +16 -0
- data/lib/capistrano/asg/rolling/exception.rb +7 -0
- data/lib/capistrano/asg/rolling/instance.rb +0 -12
- data/lib/capistrano/asg/rolling/instances.rb +2 -6
- data/lib/capistrano/asg/rolling/launch_templates.rb +2 -2
- data/lib/capistrano/asg/rolling/parallel.rb +19 -3
- data/lib/capistrano/asg/rolling/plugin.rb +11 -0
- data/lib/capistrano/asg/rolling/ssh.rb +26 -7
- data/lib/capistrano/asg/rolling/version.rb +1 -1
- data/lib/capistrano/asg/tasks/rolling.rake +23 -9
- metadata +20 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b336d65bcb11063bb59bd4934158b9cda2f3e03613442800d135d1bed513e208
|
|
4
|
+
data.tar.gz: 1bd33673762478806487a3d10f16662e9909bfbdf9edd21841b4db9de5ded2b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f013f9d109849878be032295ab1c518e7e0f7617d32fadc6c3c71bf3da03d8cf57d0fd28f4260e4047ed91c424573b32341e8d4ecc22c6544aa8147da05c3f89
|
|
7
|
+
data.tar.gz: d0f61692e322b744772d463825044746ac57224b6dbf2c61e1ca85ced9efa09fbae18dc9f26690be9d2f8d82a768d701b9e1bbc024c50a5b4afe5636ee8d51cd
|
data/.github/workflows/test.yml
CHANGED
data/.rubocop.yml
CHANGED
data/CODE_OF_CONDUCT.md
CHANGED
|
@@ -55,7 +55,7 @@ further defined and clarified by project maintainers.
|
|
|
55
55
|
## Enforcement
|
|
56
56
|
|
|
57
57
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
-
reported by contacting the project team at
|
|
58
|
+
reported by contacting the project team at tech-arnhem@iraiser.eu. All
|
|
59
59
|
complaints will be reviewed and investigated and will result in a response that
|
|
60
60
|
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
61
|
obligated to maintain confidentiality with regard to the reporter of an incident.
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
capistrano-asg-rolling (0.
|
|
4
|
+
capistrano-asg-rolling (0.8.0)
|
|
5
5
|
aws-sdk-autoscaling (~> 1, >= 1.100.0)
|
|
6
6
|
aws-sdk-ec2 (~> 1)
|
|
7
7
|
capistrano (~> 3)
|
|
@@ -10,111 +10,122 @@ PATH
|
|
|
10
10
|
GEM
|
|
11
11
|
remote: https://rubygems.org/
|
|
12
12
|
specs:
|
|
13
|
-
addressable (2.
|
|
14
|
-
public_suffix (>= 2.0.2, <
|
|
15
|
-
airbrussh (1.
|
|
13
|
+
addressable (2.9.0)
|
|
14
|
+
public_suffix (>= 2.0.2, < 8.0)
|
|
15
|
+
airbrussh (1.6.1)
|
|
16
16
|
sshkit (>= 1.6.1, != 1.7.0)
|
|
17
|
-
ast (2.4.
|
|
18
|
-
aws-eventstream (1.
|
|
19
|
-
aws-partitions (1.
|
|
20
|
-
aws-sdk-autoscaling (1.
|
|
21
|
-
aws-sdk-core (~> 3, >= 3.
|
|
17
|
+
ast (2.4.3)
|
|
18
|
+
aws-eventstream (1.4.0)
|
|
19
|
+
aws-partitions (1.1250.0)
|
|
20
|
+
aws-sdk-autoscaling (1.158.0)
|
|
21
|
+
aws-sdk-core (~> 3, >= 3.247.0)
|
|
22
22
|
aws-sigv4 (~> 1.5)
|
|
23
|
-
aws-sdk-core (3.
|
|
23
|
+
aws-sdk-core (3.247.0)
|
|
24
24
|
aws-eventstream (~> 1, >= 1.3.0)
|
|
25
25
|
aws-partitions (~> 1, >= 1.992.0)
|
|
26
26
|
aws-sigv4 (~> 1.9)
|
|
27
27
|
base64
|
|
28
|
+
bigdecimal
|
|
28
29
|
jmespath (~> 1, >= 1.6.1)
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
logger
|
|
31
|
+
aws-sdk-ec2 (1.617.0)
|
|
32
|
+
aws-sdk-core (~> 3, >= 3.247.0)
|
|
31
33
|
aws-sigv4 (~> 1.5)
|
|
32
|
-
aws-sigv4 (1.
|
|
34
|
+
aws-sigv4 (1.12.1)
|
|
33
35
|
aws-eventstream (~> 1, >= 1.0.2)
|
|
34
|
-
base64 (0.
|
|
35
|
-
bigdecimal (
|
|
36
|
-
capistrano (3.
|
|
36
|
+
base64 (0.3.0)
|
|
37
|
+
bigdecimal (4.1.2)
|
|
38
|
+
capistrano (3.20.1)
|
|
37
39
|
airbrussh (>= 1.0.0)
|
|
38
40
|
i18n
|
|
39
41
|
rake (>= 10.0.0)
|
|
40
42
|
sshkit (>= 1.9.0)
|
|
41
|
-
concurrent-ruby (1.3.
|
|
42
|
-
crack (1.0.
|
|
43
|
+
concurrent-ruby (1.3.6)
|
|
44
|
+
crack (1.0.1)
|
|
43
45
|
bigdecimal
|
|
44
46
|
rexml
|
|
45
|
-
diff-lcs (1.6.
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
diff-lcs (1.6.2)
|
|
48
|
+
docile (1.4.1)
|
|
49
|
+
hashdiff (1.2.1)
|
|
50
|
+
i18n (1.14.8)
|
|
48
51
|
concurrent-ruby (~> 1.0)
|
|
49
52
|
jmespath (1.6.2)
|
|
50
|
-
json (2.
|
|
51
|
-
language_server-protocol (3.17.0.
|
|
53
|
+
json (2.19.5)
|
|
54
|
+
language_server-protocol (3.17.0.5)
|
|
52
55
|
lint_roller (1.1.0)
|
|
53
|
-
logger (1.
|
|
56
|
+
logger (1.7.0)
|
|
54
57
|
net-scp (4.1.0)
|
|
55
58
|
net-ssh (>= 2.6.5, < 8.0.0)
|
|
56
59
|
net-sftp (4.0.0)
|
|
57
60
|
net-ssh (>= 5.0.0, < 8.0.0)
|
|
58
|
-
net-ssh (7.3.
|
|
59
|
-
ostruct (0.6.
|
|
60
|
-
parallel (1.
|
|
61
|
-
parser (3.3.
|
|
61
|
+
net-ssh (7.3.2)
|
|
62
|
+
ostruct (0.6.3)
|
|
63
|
+
parallel (1.28.0)
|
|
64
|
+
parser (3.3.11.1)
|
|
62
65
|
ast (~> 2.4.1)
|
|
63
66
|
racc
|
|
64
|
-
|
|
67
|
+
prism (1.9.0)
|
|
68
|
+
public_suffix (6.0.2)
|
|
65
69
|
racc (1.8.1)
|
|
66
70
|
rainbow (3.1.1)
|
|
67
|
-
rake (13.2
|
|
68
|
-
regexp_parser (2.
|
|
69
|
-
rexml (3.4.
|
|
70
|
-
rspec (3.13.
|
|
71
|
+
rake (13.4.2)
|
|
72
|
+
regexp_parser (2.12.0)
|
|
73
|
+
rexml (3.4.4)
|
|
74
|
+
rspec (3.13.2)
|
|
71
75
|
rspec-core (~> 3.13.0)
|
|
72
76
|
rspec-expectations (~> 3.13.0)
|
|
73
77
|
rspec-mocks (~> 3.13.0)
|
|
74
|
-
rspec-core (3.13.
|
|
78
|
+
rspec-core (3.13.6)
|
|
75
79
|
rspec-support (~> 3.13.0)
|
|
76
|
-
rspec-expectations (3.13.
|
|
80
|
+
rspec-expectations (3.13.5)
|
|
77
81
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
78
82
|
rspec-support (~> 3.13.0)
|
|
79
|
-
rspec-mocks (3.13.
|
|
83
|
+
rspec-mocks (3.13.8)
|
|
80
84
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
81
85
|
rspec-support (~> 3.13.0)
|
|
82
|
-
rspec-support (3.13.
|
|
83
|
-
rubocop (1.
|
|
86
|
+
rspec-support (3.13.7)
|
|
87
|
+
rubocop (1.86.2)
|
|
84
88
|
json (~> 2.3)
|
|
85
89
|
language_server-protocol (~> 3.17.0.2)
|
|
86
90
|
lint_roller (~> 1.1.0)
|
|
87
|
-
parallel (
|
|
91
|
+
parallel (>= 1.10)
|
|
88
92
|
parser (>= 3.3.0.2)
|
|
89
93
|
rainbow (>= 2.2.2, < 4.0)
|
|
90
94
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
91
|
-
rubocop-ast (>= 1.
|
|
95
|
+
rubocop-ast (>= 1.49.0, < 2.0)
|
|
92
96
|
ruby-progressbar (~> 1.7)
|
|
93
97
|
unicode-display_width (>= 2.4.0, < 4.0)
|
|
94
|
-
rubocop-ast (1.
|
|
95
|
-
parser (>= 3.3.
|
|
96
|
-
|
|
98
|
+
rubocop-ast (1.49.1)
|
|
99
|
+
parser (>= 3.3.7.2)
|
|
100
|
+
prism (~> 1.7)
|
|
101
|
+
rubocop-performance (1.26.1)
|
|
97
102
|
lint_roller (~> 1.1)
|
|
98
|
-
rubocop (>= 1.
|
|
99
|
-
rubocop-ast (>= 1.
|
|
103
|
+
rubocop (>= 1.75.0, < 2.0)
|
|
104
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
|
100
105
|
rubocop-rake (0.7.1)
|
|
101
106
|
lint_roller (~> 1.1)
|
|
102
107
|
rubocop (>= 1.72.1)
|
|
103
|
-
rubocop-rspec (3.
|
|
108
|
+
rubocop-rspec (3.9.0)
|
|
104
109
|
lint_roller (~> 1.1)
|
|
105
|
-
rubocop (~> 1.
|
|
110
|
+
rubocop (~> 1.81)
|
|
106
111
|
ruby-progressbar (1.13.0)
|
|
107
|
-
|
|
112
|
+
simplecov (0.22.0)
|
|
113
|
+
docile (~> 1.1)
|
|
114
|
+
simplecov-html (~> 0.11)
|
|
115
|
+
simplecov_json_formatter (~> 0.1)
|
|
116
|
+
simplecov-html (0.13.2)
|
|
117
|
+
simplecov_json_formatter (0.1.4)
|
|
118
|
+
sshkit (1.25.0)
|
|
108
119
|
base64
|
|
109
120
|
logger
|
|
110
121
|
net-scp (>= 1.1.2)
|
|
111
122
|
net-sftp (>= 2.1.2)
|
|
112
123
|
net-ssh (>= 2.8.0)
|
|
113
124
|
ostruct
|
|
114
|
-
unicode-display_width (3.
|
|
115
|
-
unicode-emoji (~> 4.
|
|
116
|
-
unicode-emoji (4.0
|
|
117
|
-
webmock (3.
|
|
125
|
+
unicode-display_width (3.2.0)
|
|
126
|
+
unicode-emoji (~> 4.1)
|
|
127
|
+
unicode-emoji (4.2.0)
|
|
128
|
+
webmock (3.26.2)
|
|
118
129
|
addressable (>= 2.8.0)
|
|
119
130
|
crack (>= 0.3.2)
|
|
120
131
|
hashdiff (>= 0.4.0, < 2.0.0)
|
|
@@ -131,7 +142,8 @@ DEPENDENCIES
|
|
|
131
142
|
rubocop-performance (~> 1.20)
|
|
132
143
|
rubocop-rake (~> 0.6)
|
|
133
144
|
rubocop-rspec (~> 3.0)
|
|
145
|
+
simplecov (~> 0.22)
|
|
134
146
|
webmock (~> 3.11)
|
|
135
147
|
|
|
136
148
|
BUNDLED WITH
|
|
137
|
-
2.
|
|
149
|
+
2.6.7
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/capistrano-asg-rolling)
|
|
4
4
|
[](https://github.com/KentaaNL/capistrano-asg-rolling/actions)
|
|
5
|
-
[](https://github.com/KentaaNL/capistrano-asg-rolling/actions/workflows/github-code-scanning/codeql)
|
|
6
6
|
|
|
7
7
|
Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups using the [instance refresh feature](https://docs.aws.amazon.com/autoscaling/ec2/userguide/asg-instance-refresh.html).
|
|
8
8
|
|
|
@@ -113,6 +113,22 @@ Enable or disable auto-rollback on instance refreshes (default: false):
|
|
|
113
113
|
set :asg_instance_refresh_auto_rollback, true
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
The AWS clients are configured with `adaptive` retry mode and a retry limit of 10 by default, so transient throttling (`Aws::AutoScaling::Errors::Throttling: Rate exceeded`) does not abort a deployment. You can tune both:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# config/deploy.rb
|
|
120
|
+
set :asg_aws_retry_mode, 'adaptive' # default; one of 'legacy', 'standard', 'adaptive'
|
|
121
|
+
set :asg_aws_retry_limit, 10 # default
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
After creating an AMI, the gem waits until it becomes available before triggering an instance refresh. The defaults match the AWS SDK defaults (~10 minutes), but for larger root volumes (e.g. resizing 24 GB → 32 GB) the AMI can take longer to become available. Override the waiter via:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# config/deploy.rb
|
|
128
|
+
set :asg_ami_wait_delay, 15 # seconds between polls (default: 15)
|
|
129
|
+
set :asg_ami_wait_max_attempts, 40 # max polls (default: 40, ≈ 10 minutes)
|
|
130
|
+
```
|
|
131
|
+
|
|
116
132
|
## Usage
|
|
117
133
|
|
|
118
134
|
Specify the Auto Scaling Groups with the keyword `autoscale` instead of using the `server` keyword in Capistrano's stage configuration. Provide the name of the Auto Scaling Group and any properties you want to pass to the server:
|
|
@@ -181,6 +197,26 @@ autoscale 'web-autoscale-group', rolling: true, user: 'deployer'
|
|
|
181
197
|
|
|
182
198
|
With these two stages, you can run any tasks with `cap production <task name>` and rolling deployments with `cap production_rolling deploy`.
|
|
183
199
|
|
|
200
|
+
### Tags
|
|
201
|
+
|
|
202
|
+
During deployment, the following tags will be added to the AMI and snapshot containing information about the current application, stage and deployment:
|
|
203
|
+
- `capistrano-asg-rolling:application`
|
|
204
|
+
- `capistrano-asg-rolling:stage`
|
|
205
|
+
- `capistrano-asg-rolling:deployment-branch`
|
|
206
|
+
- `capistrano-asg-rolling:deployment-release`
|
|
207
|
+
- `capistrano-asg-rolling:deployment-revision`
|
|
208
|
+
- `capistrano-asg-rolling:deployment-user`
|
|
209
|
+
|
|
210
|
+
In addition to that, the tag `capistrano-asg-rolling:gem-version` will be added with the value of the current gem version.
|
|
211
|
+
This tag is also used to determine if the AMI was created by this gem, and can be deleted automatically.
|
|
212
|
+
|
|
213
|
+
You can add custom tag(s) to the AMI and snapshot by setting the property `asg_rolling_ami_tags`, for example:
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
# config/deploy/<stage>.rb
|
|
217
|
+
set :asg_rolling_ami_tags, { 'Application' => 'My Application', 'Environment' => 'Production' }
|
|
218
|
+
```
|
|
219
|
+
|
|
184
220
|
## Useful commands
|
|
185
221
|
|
|
186
222
|
### Test deployment
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
-
require 'capistrano/asg/rolling/version'
|
|
3
|
+
require_relative 'lib/capistrano/asg/rolling/version'
|
|
6
4
|
|
|
7
5
|
Gem::Specification.new do |spec|
|
|
8
6
|
spec.name = 'capistrano-asg-rolling'
|
|
9
7
|
spec.version = Capistrano::ASG::Rolling::VERSION
|
|
10
|
-
spec.authors = [
|
|
11
|
-
spec.email = ['
|
|
8
|
+
spec.authors = %w[Kentaa iRaiser]
|
|
9
|
+
spec.email = ['tech-arnhem@iraiser.eu']
|
|
12
10
|
|
|
13
11
|
spec.summary = 'Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups using Instance Refresh'
|
|
14
12
|
spec.homepage = 'https://github.com/KentaaNL/capistrano-asg-rolling'
|
|
@@ -26,11 +24,12 @@ Gem::Specification.new do |spec|
|
|
|
26
24
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
27
25
|
spec.require_paths = ['lib']
|
|
28
26
|
|
|
29
|
-
spec.required_ruby_version = '>= 3.
|
|
27
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
30
28
|
|
|
31
29
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
32
30
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
33
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
32
|
+
spec.add_development_dependency 'simplecov', '~> 0.22'
|
|
34
33
|
spec.add_development_dependency 'webmock', '~> 3.11'
|
|
35
34
|
|
|
36
35
|
spec.add_dependency 'aws-sdk-autoscaling', '~> 1', '>= 1.100.0'
|
|
@@ -36,10 +36,17 @@ module Capistrano
|
|
|
36
36
|
response = aws_ec2_client.create_image(options)
|
|
37
37
|
|
|
38
38
|
begin
|
|
39
|
-
aws_ec2_client.wait_until(:image_available, image_ids: [response.image_id])
|
|
40
|
-
|
|
39
|
+
aws_ec2_client.wait_until(:image_available, image_ids: [response.image_id]) do |waiter|
|
|
40
|
+
waiter.delay = Configuration.ami_wait_delay
|
|
41
|
+
waiter.max_attempts = Configuration.ami_wait_max_attempts
|
|
42
|
+
end
|
|
43
|
+
rescue Aws::Waiters::Errors::TooManyAttemptsError => e
|
|
41
44
|
# When waiting for the AMI takes longer than the default (10 minutes),
|
|
42
|
-
# then assume it will eventually succeed and just continue.
|
|
45
|
+
# then assume it will eventually succeed and just continue. Surface a
|
|
46
|
+
# warning so operators can see that the wait did not complete in time
|
|
47
|
+
# (for example after increasing the root volume size of the source
|
|
48
|
+
# instance) before the subsequent Instance Refresh is started.
|
|
49
|
+
Kernel.warn("WARNING: timed out waiting for AMI #{response.image_id} to become available: #{e.message}")
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
new(response.image_id, instance)
|
|
@@ -81,7 +81,11 @@ module Capistrano
|
|
|
81
81
|
auto_rollback: auto_rollback
|
|
82
82
|
}.compact
|
|
83
83
|
).instance_refresh_id
|
|
84
|
-
rescue Aws::AutoScaling::Errors::
|
|
84
|
+
rescue Aws::AutoScaling::Errors::ServiceError => e
|
|
85
|
+
# Wrap all AWS Auto Scaling service errors (InstanceRefreshInProgress,
|
|
86
|
+
# ValidationError, throttling, etc.) so the rake task can rescue a
|
|
87
|
+
# single gem-defined error type and continue with the remaining
|
|
88
|
+
# Auto Scaling Group(s).
|
|
85
89
|
raise Capistrano::ASG::Rolling::StartInstanceRefreshError, e
|
|
86
90
|
end
|
|
87
91
|
|
|
@@ -22,6 +22,8 @@ module Capistrano
|
|
|
22
22
|
options = {}
|
|
23
23
|
options[:region] = aws_region if aws_region
|
|
24
24
|
options[:credentials] = aws_credentials if aws_credentials.set?
|
|
25
|
+
options[:retry_mode] = Configuration.aws_retry_mode
|
|
26
|
+
options[:retry_limit] = Configuration.aws_retry_limit
|
|
25
27
|
options[:http_wire_trace] = true if ENV['AWS_HTTP_WIRE_TRACE'] == '1'
|
|
26
28
|
options
|
|
27
29
|
end
|
|
@@ -79,6 +79,22 @@ module Capistrano
|
|
|
79
79
|
def instance_refresh_polling_interval
|
|
80
80
|
fetch(:asg_instance_refresh_polling_interval, 30)
|
|
81
81
|
end
|
|
82
|
+
|
|
83
|
+
def aws_retry_mode
|
|
84
|
+
fetch(:asg_aws_retry_mode, 'adaptive')
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def aws_retry_limit
|
|
88
|
+
fetch(:asg_aws_retry_limit, 10)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def ami_wait_delay
|
|
92
|
+
fetch(:asg_ami_wait_delay, 15)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ami_wait_max_attempts
|
|
96
|
+
fetch(:asg_ami_wait_max_attempts, 40)
|
|
97
|
+
end
|
|
82
98
|
end
|
|
83
99
|
end
|
|
84
100
|
end
|
|
@@ -39,6 +39,13 @@ module Capistrano
|
|
|
39
39
|
super('No instances have been launched. Are you using a configuration with rolling deployments?')
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
|
+
|
|
43
|
+
# Exception when waiting for SSH availability timed out.
|
|
44
|
+
class SSHAvailabilityTimeoutError < Capistrano::ASG::Rolling::Exception
|
|
45
|
+
def initialize(timeout)
|
|
46
|
+
super("Timed out waiting for SSH to become available after #{timeout} seconds")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
42
49
|
end
|
|
43
50
|
end
|
|
44
51
|
end
|
|
@@ -65,18 +65,6 @@ module Capistrano
|
|
|
65
65
|
new(instance.instance_id, instance.private_ip_address, instance.public_ip_address, instance.image_id, autoscaling_group)
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
-
def wait_for_ssh
|
|
69
|
-
started_at = Time.now
|
|
70
|
-
|
|
71
|
-
loop do
|
|
72
|
-
result = SSH.test?(ip_address, autoscale_group.properties[:user], Configuration.ssh_options)
|
|
73
|
-
|
|
74
|
-
break if result || Time.now - started_at > 300
|
|
75
|
-
|
|
76
|
-
sleep 1
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
68
|
def ip_address
|
|
81
69
|
Configuration.use_private_ip_address? ? private_ip_address : public_ip_address
|
|
82
70
|
end
|
|
@@ -15,8 +15,8 @@ module Capistrano
|
|
|
15
15
|
@instances << instance
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def each(&
|
|
19
|
-
instances.each(&
|
|
18
|
+
def each(&)
|
|
19
|
+
instances.each(&)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def empty?
|
|
@@ -31,10 +31,6 @@ module Capistrano
|
|
|
31
31
|
self.class.new(select { |instance| instance.image_id == image_id })
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
def wait_for_ssh
|
|
35
|
-
Parallel.run(instances, &:wait_for_ssh)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
34
|
def stop
|
|
39
35
|
Parallel.run(instances, &:stop)
|
|
40
36
|
end
|
|
@@ -9,18 +9,34 @@ module Capistrano
|
|
|
9
9
|
module Parallel
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
|
+
# Runs the given block once per element of `work`, in parallel.
|
|
13
|
+
#
|
|
14
|
+
# All threads are joined before this method returns, so a failure in one
|
|
15
|
+
# block does not abandon the other in-flight threads. If any block
|
|
16
|
+
# raises, the first error is re-raised after all threads have completed;
|
|
17
|
+
# additional errors are surfaced via `Kernel.warn`.
|
|
12
18
|
def run(work)
|
|
13
|
-
|
|
19
|
+
results = Concurrent::Array.new
|
|
20
|
+
errors = Concurrent::Array.new
|
|
14
21
|
|
|
15
22
|
threads = work.map do |w|
|
|
16
23
|
Thread.new do
|
|
17
|
-
|
|
24
|
+
results << yield(w)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
errors << e
|
|
18
27
|
end
|
|
19
28
|
end
|
|
20
29
|
|
|
21
30
|
threads.each(&:join)
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
if errors.any?
|
|
33
|
+
errors.drop(1).each do |e|
|
|
34
|
+
Kernel.warn("WARNING: parallel task failed: #{e.class}: #{e.message}")
|
|
35
|
+
end
|
|
36
|
+
raise errors.first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
results
|
|
24
40
|
end
|
|
25
41
|
end
|
|
26
42
|
end
|
|
@@ -29,6 +29,7 @@ module Capistrano
|
|
|
29
29
|
after 'rolling:update', 'rolling:cleanup'
|
|
30
30
|
after 'rolling:create_ami', 'rolling:cleanup'
|
|
31
31
|
after 'rolling:update', 'rolling:instance_refresh_status'
|
|
32
|
+
after 'rolling:trigger_instance_refresh', 'rolling:instance_refresh_status'
|
|
32
33
|
|
|
33
34
|
# Register an exit hook to do some cleanup when Capistrano
|
|
34
35
|
# terminates without calling our after cleanup hook.
|
|
@@ -47,6 +48,16 @@ module Capistrano
|
|
|
47
48
|
Capistrano::ASG::Rolling::Configuration
|
|
48
49
|
end
|
|
49
50
|
|
|
51
|
+
# Add an instance to the Capistrano server list, with the given properties.
|
|
52
|
+
def add_instance(instance, properties)
|
|
53
|
+
server_properties = properties.merge(instance_id: instance.id)
|
|
54
|
+
|
|
55
|
+
logger.verbose "Adding server: **#{instance.ip_address}**"
|
|
56
|
+
|
|
57
|
+
server(instance.ip_address, server_properties)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Make sure any instances that were launched for rolling updates are terminated.
|
|
50
61
|
def cleanup
|
|
51
62
|
instances = config.instances.auto_terminate
|
|
52
63
|
return if instances.empty?
|
|
@@ -1,22 +1,41 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
3
5
|
module Capistrano
|
|
4
6
|
module ASG
|
|
5
7
|
module Rolling
|
|
6
|
-
#
|
|
8
|
+
# Test the SSHKit backend for availability.
|
|
7
9
|
module SSH
|
|
8
10
|
module_function
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
WAIT_TIMEOUT = 300
|
|
13
|
+
|
|
14
|
+
def wait_for_availability(backend)
|
|
15
|
+
timeout = WAIT_TIMEOUT
|
|
16
|
+
expires_at = Time.now + timeout
|
|
17
|
+
|
|
18
|
+
loop do
|
|
19
|
+
break if available?(backend)
|
|
20
|
+
|
|
21
|
+
raise SSHAvailabilityTimeoutError, timeout if Time.now > expires_at
|
|
13
22
|
|
|
14
|
-
|
|
15
|
-
ssh.exec!('echo hello')
|
|
23
|
+
sleep 1
|
|
16
24
|
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def available?(backend)
|
|
28
|
+
backend.test('echo hello')
|
|
17
29
|
|
|
18
30
|
true
|
|
19
|
-
rescue ::Net::SSH::
|
|
31
|
+
rescue ::Net::SSH::AuthenticationFailed, ::Net::SSH::Authentication::DisallowedMethod
|
|
32
|
+
# SSH server is reachable and responding.
|
|
33
|
+
true
|
|
34
|
+
rescue ::Net::SSH::ConnectionTimeout, ::Net::SSH::Proxy::ConnectError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ECONNRESET
|
|
35
|
+
# SSH server not reachable or port closed.
|
|
36
|
+
false
|
|
37
|
+
rescue ::Net::SSH::Disconnect # rubocop:disable Lint/DuplicateBranch
|
|
38
|
+
# SSH server is reachable, but the connection dropped unexpectedly.
|
|
20
39
|
false
|
|
21
40
|
end
|
|
22
41
|
end
|
|
@@ -14,10 +14,7 @@ namespace :rolling do
|
|
|
14
14
|
logger.info "Launched Instance: **#{instance.id}**"
|
|
15
15
|
config.instances << instance
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# Add server to the Capistrano server list.
|
|
20
|
-
server(instance.ip_address, group.properties)
|
|
17
|
+
add_instance(instance, group.properties)
|
|
21
18
|
else
|
|
22
19
|
logger.info "Auto Scaling Group: **#{group.name}**, standard deployment strategy."
|
|
23
20
|
|
|
@@ -29,17 +26,17 @@ namespace :rolling do
|
|
|
29
26
|
server_properties = group.properties
|
|
30
27
|
end
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# Add server to the Capistrano server list.
|
|
35
|
-
server(instance.ip_address, server_properties)
|
|
29
|
+
add_instance(instance, server_properties)
|
|
36
30
|
end
|
|
37
31
|
end
|
|
38
32
|
end
|
|
39
33
|
|
|
40
34
|
unless config.instances.empty?
|
|
41
35
|
logger.info 'Waiting for SSH to be available...'
|
|
42
|
-
|
|
36
|
+
|
|
37
|
+
on roles(:all) do
|
|
38
|
+
Capistrano::ASG::Rolling::SSH.wait_for_availability(self)
|
|
39
|
+
end
|
|
43
40
|
end
|
|
44
41
|
end
|
|
45
42
|
|
|
@@ -71,6 +68,17 @@ namespace :rolling do
|
|
|
71
68
|
end
|
|
72
69
|
end
|
|
73
70
|
|
|
71
|
+
desc 'Trigger instance refresh of deployed autoscaling groups'
|
|
72
|
+
task :trigger_instance_refresh do
|
|
73
|
+
logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
|
|
74
|
+
config.autoscale_groups.each do |group|
|
|
75
|
+
group.start_instance_refresh(group.launch_template)
|
|
76
|
+
logger.info "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
|
|
77
|
+
rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
|
|
78
|
+
logger.info "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
74
82
|
desc 'Clean up old Launch Template versions and AMIs and terminate instances'
|
|
75
83
|
task :cleanup do
|
|
76
84
|
unless config.launch_templates.empty?
|
|
@@ -194,6 +202,12 @@ namespace :rolling do
|
|
|
194
202
|
else
|
|
195
203
|
logger.info "Auto Scaling Group: **#{name}**, status '#{refresh.status}'."
|
|
196
204
|
end
|
|
205
|
+
rescue Aws::AutoScaling::Errors::ServiceError => e
|
|
206
|
+
# The instance refresh is still running in AWS even though we hit a
|
|
207
|
+
# transient API error (typically throttling that exceeded the SDK's
|
|
208
|
+
# retry budget). Log and retry on the next polling interval instead
|
|
209
|
+
# of aborting the deployment.
|
|
210
|
+
logger.warning "Auto Scaling Group: **#{name}**, failed to fetch status: #{e.class}: #{e.message} - retrying on next poll."
|
|
197
211
|
end
|
|
198
212
|
next if groups.empty?
|
|
199
213
|
|
metadata
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kentaa
|
|
8
|
+
- iRaiser
|
|
8
9
|
autorequire:
|
|
9
10
|
bindir: exe
|
|
10
11
|
cert_chain: []
|
|
11
|
-
date:
|
|
12
|
+
date: 2026-05-22 00:00:00.000000000 Z
|
|
12
13
|
dependencies:
|
|
13
14
|
- !ruby/object:Gem::Dependency
|
|
14
15
|
name: bundler
|
|
@@ -52,6 +53,20 @@ dependencies:
|
|
|
52
53
|
- - "~>"
|
|
53
54
|
- !ruby/object:Gem::Version
|
|
54
55
|
version: '3.0'
|
|
56
|
+
- !ruby/object:Gem::Dependency
|
|
57
|
+
name: simplecov
|
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - "~>"
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0.22'
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0.22'
|
|
55
70
|
- !ruby/object:Gem::Dependency
|
|
56
71
|
name: webmock
|
|
57
72
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -130,7 +145,7 @@ dependencies:
|
|
|
130
145
|
version: '1'
|
|
131
146
|
description:
|
|
132
147
|
email:
|
|
133
|
-
-
|
|
148
|
+
- tech-arnhem@iraiser.eu
|
|
134
149
|
executables: []
|
|
135
150
|
extensions: []
|
|
136
151
|
extra_rdoc_files: []
|
|
@@ -181,14 +196,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
181
196
|
requirements:
|
|
182
197
|
- - ">="
|
|
183
198
|
- !ruby/object:Gem::Version
|
|
184
|
-
version: 3.
|
|
199
|
+
version: 3.1.0
|
|
185
200
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
201
|
requirements:
|
|
187
202
|
- - ">="
|
|
188
203
|
- !ruby/object:Gem::Version
|
|
189
204
|
version: '0'
|
|
190
205
|
requirements: []
|
|
191
|
-
rubygems_version: 3.
|
|
206
|
+
rubygems_version: 3.3.27
|
|
192
207
|
signing_key:
|
|
193
208
|
specification_version: 4
|
|
194
209
|
summary: Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups
|