capistrano-asg-rolling 0.8.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b336d65bcb11063bb59bd4934158b9cda2f3e03613442800d135d1bed513e208
4
- data.tar.gz: 1bd33673762478806487a3d10f16662e9909bfbdf9edd21841b4db9de5ded2b5
3
+ metadata.gz: f2365c9ab16d5af20fe6502ec5ddb678b71d50516a6e9e514b87a94a6a34f60b
4
+ data.tar.gz: ab344dc87be037b41e0e56e18cdc3532339a0e368983cac5ce8c3707d8b82729
5
5
  SHA512:
6
- metadata.gz: f013f9d109849878be032295ab1c518e7e0f7617d32fadc6c3c71bf3da03d8cf57d0fd28f4260e4047ed91c424573b32341e8d4ecc22c6544aa8147da05c3f89
7
- data.tar.gz: d0f61692e322b744772d463825044746ac57224b6dbf2c61e1ca85ced9efa09fbae18dc9f26690be9d2f8d82a768d701b9e1bbc024c50a5b4afe5636ee8d51cd
6
+ metadata.gz: fac5e7d23d81518e9e71924b2596be4e630ab1b38d5f57a481c49bfe71abbb5ea704465e22364957998aa00373420d5f7b8ce3805fd0ba05c5ca97b1f373bb3a
7
+ data.tar.gz: 007da3693ba149357ffe55e600d772c52984295e432eb79b65ecb5696e0861572e0776aa36b40ff2adaaee758209337c4406683bc88cb313e064aea1b82d0c93
@@ -9,7 +9,7 @@ jobs:
9
9
  runs-on: ubuntu-latest
10
10
  strategy:
11
11
  matrix:
12
- ruby-version: ['3.1', '3.2', '3.3', '3.4', '4.0']
12
+ ruby-version: ['3.2', '3.3', '3.4', '4.0']
13
13
 
14
14
  steps:
15
15
  - uses: actions/checkout@v2
data/.rubocop.yml CHANGED
@@ -5,7 +5,7 @@ plugins:
5
5
 
6
6
  AllCops:
7
7
  NewCops: enable
8
- TargetRubyVersion: 3.1
8
+ TargetRubyVersion: 3.2
9
9
  DisplayCopNames: true
10
10
  DisplayStyleGuide: true
11
11
 
@@ -39,5 +39,8 @@ RSpec/IndexedLet:
39
39
  RSpec/MultipleExpectations:
40
40
  Max: 5
41
41
 
42
+ RSpec/MultipleMemoizedHelpers:
43
+ Max: 11
44
+
42
45
  Style/GuardClause:
43
46
  Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- capistrano-asg-rolling (0.8.0)
4
+ capistrano-asg-rolling (0.9.0)
5
5
  aws-sdk-autoscaling (~> 1, >= 1.100.0)
6
6
  aws-sdk-ec2 (~> 1)
7
7
  capistrano (~> 3)
@@ -12,15 +12,15 @@ GEM
12
12
  specs:
13
13
  addressable (2.9.0)
14
14
  public_suffix (>= 2.0.2, < 8.0)
15
- airbrussh (1.6.1)
15
+ airbrussh (1.6.2)
16
16
  sshkit (>= 1.6.1, != 1.7.0)
17
17
  ast (2.4.3)
18
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)
19
+ aws-partitions (1.1282.0)
20
+ aws-sdk-autoscaling (1.166.0)
21
+ aws-sdk-core (~> 3, >= 3.254.0)
22
22
  aws-sigv4 (~> 1.5)
23
- aws-sdk-core (3.247.0)
23
+ aws-sdk-core (3.254.1)
24
24
  aws-eventstream (~> 1, >= 1.3.0)
25
25
  aws-partitions (~> 1, >= 1.992.0)
26
26
  aws-sigv4 (~> 1.9)
@@ -28,8 +28,8 @@ GEM
28
28
  bigdecimal
29
29
  jmespath (~> 1, >= 1.6.1)
30
30
  logger
31
- aws-sdk-ec2 (1.617.0)
32
- aws-sdk-core (~> 3, >= 3.247.0)
31
+ aws-sdk-ec2 (1.641.0)
32
+ aws-sdk-core (~> 3, >= 3.254.0)
33
33
  aws-sigv4 (~> 1.5)
34
34
  aws-sigv4 (1.12.1)
35
35
  aws-eventstream (~> 1, >= 1.0.2)
@@ -40,32 +40,31 @@ GEM
40
40
  i18n
41
41
  rake (>= 10.0.0)
42
42
  sshkit (>= 1.9.0)
43
- concurrent-ruby (1.3.6)
43
+ concurrent-ruby (1.3.8)
44
44
  crack (1.0.1)
45
45
  bigdecimal
46
46
  rexml
47
47
  diff-lcs (1.6.2)
48
- docile (1.4.1)
49
48
  hashdiff (1.2.1)
50
- i18n (1.14.8)
49
+ i18n (1.15.2)
51
50
  concurrent-ruby (~> 1.0)
52
51
  jmespath (1.6.2)
53
- json (2.19.5)
54
- language_server-protocol (3.17.0.5)
52
+ json (2.21.2)
53
+ language_server-protocol (3.17.0.6)
55
54
  lint_roller (1.1.0)
56
55
  logger (1.7.0)
57
56
  net-scp (4.1.0)
58
57
  net-ssh (>= 2.6.5, < 8.0.0)
59
58
  net-sftp (4.0.0)
60
59
  net-ssh (>= 5.0.0, < 8.0.0)
61
- net-ssh (7.3.2)
60
+ net-ssh (7.3.3)
62
61
  ostruct (0.6.3)
63
62
  parallel (1.28.0)
64
- parser (3.3.11.1)
63
+ parser (3.3.12.0)
65
64
  ast (~> 2.4.1)
66
65
  racc
67
66
  prism (1.9.0)
68
- public_suffix (6.0.2)
67
+ public_suffix (7.0.5)
69
68
  racc (1.8.1)
70
69
  rainbow (3.1.1)
71
70
  rake (13.4.2)
@@ -84,8 +83,8 @@ GEM
84
83
  diff-lcs (>= 1.2.0, < 2.0)
85
84
  rspec-support (~> 3.13.0)
86
85
  rspec-support (3.13.7)
87
- rubocop (1.86.2)
88
- json (~> 2.3)
86
+ rubocop (1.90.0)
87
+ json (>= 2.3)
89
88
  language_server-protocol (~> 3.17.0.2)
90
89
  lint_roller (~> 1.1.0)
91
90
  parallel (>= 1.10)
@@ -95,27 +94,23 @@ GEM
95
94
  rubocop-ast (>= 1.49.0, < 2.0)
96
95
  ruby-progressbar (~> 1.7)
97
96
  unicode-display_width (>= 2.4.0, < 4.0)
98
- rubocop-ast (1.49.1)
97
+ rubocop-ast (1.50.0)
99
98
  parser (>= 3.3.7.2)
100
99
  prism (~> 1.7)
101
- rubocop-performance (1.26.1)
100
+ rubocop-performance (1.27.0)
102
101
  lint_roller (~> 1.1)
103
- rubocop (>= 1.75.0, < 2.0)
102
+ rubocop (>= 1.89.0, < 2.0)
104
103
  rubocop-ast (>= 1.47.1, < 2.0)
105
104
  rubocop-rake (0.7.1)
106
105
  lint_roller (~> 1.1)
107
106
  rubocop (>= 1.72.1)
108
- rubocop-rspec (3.9.0)
107
+ rubocop-rspec (3.10.2)
109
108
  lint_roller (~> 1.1)
110
- rubocop (~> 1.81)
109
+ regexp_parser (>= 2.0)
110
+ rubocop (~> 1.86, >= 1.86.2)
111
111
  ruby-progressbar (1.13.0)
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)
112
+ simplecov (1.1.1)
113
+ sshkit (1.25.1)
119
114
  base64
120
115
  logger
121
116
  net-scp (>= 1.1.2)
@@ -125,7 +120,7 @@ GEM
125
120
  unicode-display_width (3.2.0)
126
121
  unicode-emoji (~> 4.1)
127
122
  unicode-emoji (4.2.0)
128
- webmock (3.26.2)
123
+ webmock (3.26.3)
129
124
  addressable (>= 2.8.0)
130
125
  crack (>= 0.3.2)
131
126
  hashdiff (>= 0.4.0, < 2.0.0)
@@ -142,7 +137,7 @@ DEPENDENCIES
142
137
  rubocop-performance (~> 1.20)
143
138
  rubocop-rake (~> 0.6)
144
139
  rubocop-rspec (~> 3.0)
145
- simplecov (~> 0.22)
140
+ simplecov (~> 1.0)
146
141
  webmock (~> 3.11)
147
142
 
148
143
  BUNDLED WITH
data/README.md CHANGED
@@ -6,15 +6,15 @@
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
 
9
- Instead of deploying to live servers, capistrano-asg-rolling will create a temporary instance for deployment and then trigger an instance refresh to perform a rolling update of the Auto Scaling Group(s). In more detail, during deployment it will:
9
+ Instead of deploying to live servers, capistrano-asg-rolling will create temporary instances for deployment and then trigger an instance refresh to perform a rolling update of the Auto Scaling Group(s). In more detail, during deployment it will:
10
10
 
11
- - Launch an instance from the AMI defined in the Launch Template of the Auto Scaling Group(s).
11
+ - Launch an instance for each Auto Scaling Group, from the AMI defined in its Launch Template.
12
12
  - Deploy your application to the launched instances.
13
13
  - After deployment, stop the instances and create an AMI for each instance.
14
14
  - Create new Launch Template versions for the new AMIs.
15
15
  - Trigger Instance Refresh on the Auto Scaling Group(s) to perform a rolling update.
16
16
  - Delete any outdated Launch Template versions, AMIs and snapshots created by previous deployments.
17
- - Terminate the no longer needed instances.
17
+ - Terminate the temporary instances.
18
18
 
19
19
  ## Important
20
20
 
@@ -25,13 +25,13 @@ https://docs.aws.amazon.com/autoscaling/ec2/userguide/instance-refresh-overview.
25
25
 
26
26
  ### Launch Templates
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
+ 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 a `Capistrano::ASG::Rolling::NoLaunchTemplate` error.
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
+ Instance refresh uses the desired configuration to update the Launch Template version of the Auto Scaling Group after a successful deployment. Setting the Launch Template version to `Latest` on the Auto Scaling Group is not needed.
31
31
 
32
32
  ### Experimental
33
33
 
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.
34
+ Please note that this gem works well for our configuration / use case, but it might not fit yours. Any feedback via GitHub issues / pull requests is much appreciated.
35
35
 
36
36
  ## Installation
37
37
 
@@ -59,74 +59,94 @@ install_plugin Capistrano::ASG::Rolling::Plugin
59
59
 
60
60
  ## Configuration
61
61
 
62
- Below are the Capistrano configuration options:
62
+ Below you can find all configurable settings for this gem.
63
63
 
64
- Setup AWS credentials:
64
+ ### AWS
65
+
66
+ Set up the AWS credentials and region:
65
67
 
66
68
  ```ruby
67
69
  # config/deploy.rb
68
70
  set :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID']
69
71
  set :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY']
72
+ set :aws_session_token, ENV['AWS_SESSION_TOKEN']
70
73
  set :aws_region, ENV['AWS_REGION']
71
74
  ```
72
75
 
73
- After deployment, any outdated Launch Template versions, AMIs and snapshots will be deleted. By default, the number of `keep_releases` will be kept. Change this with:
76
+ 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:
74
77
 
75
78
  ```ruby
76
79
  # config/deploy.rb
77
- set :asg_rolling_keep_versions, 10
80
+ set :asg_aws_retry_mode, 'adaptive' # default; one of 'legacy', 'standard', 'adaptive'
81
+ set :asg_aws_retry_limit, 10 # default
78
82
  ```
79
83
 
80
- Servers are added using their private IP address by default. Set to false to use the public IP address instead:
84
+ ### Launched instances
85
+
86
+ Launched instances are added to Capistrano as servers using their private IP address by default. Set this option to false to use the public IP address instead:
81
87
 
82
88
  ```ruby
83
89
  # config/deploy.rb
84
90
  set :asg_rolling_use_private_ip_address, false
85
91
  ```
86
92
 
87
- Verbose logging is enabled by default, set to false for less verbose logging:
93
+ When launching an instance, you can override any settings defined in the Launch Template with:
88
94
 
89
95
  ```ruby
90
96
  # config/deploy.rb
91
- set :asg_rolling_verbose, false
97
+ set :asg_rolling_instance_overrides, { instance_type: 'c5.large' }
92
98
  ```
93
99
 
94
- When launching an Instance, you can override any settings defined in the Launch Template with:
100
+ ### AMI
101
+
102
+ 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:
95
103
 
96
104
  ```ruby
97
105
  # config/deploy.rb
98
- set :asg_rolling_instance_overrides, { instance_type: 'c5.large' }
106
+ set :asg_ami_wait_delay, 15 # seconds between polls (default: 15)
107
+ set :asg_ami_wait_max_attempts, 40 # max polls (default: 40, ≈ 10 minutes)
99
108
  ```
100
109
 
101
- You can make Capistrano wait until the instances in the autoscaling group have completed refreshing with:
110
+ ### Instance refresh
111
+
112
+ You can make Capistrano wait until the instance refresh on the Auto Scaling Group(s) has completed:
102
113
 
103
114
  ```ruby
104
115
  # config/deploy.rb
105
116
  set :asg_wait_for_instance_refresh, true
106
- set :asg_instance_refresh_polling_interval, 30 # default
117
+ set :asg_instance_refresh_polling_interval, 30 # seconds (default: 30)
107
118
  ```
108
119
 
109
- Enable or disable auto-rollback on instance refreshes (default: false):
120
+ Enable auto-rollback to have the Auto Scaling Group roll back to its previous configuration when the instance refresh fails (default: false):
110
121
 
111
122
  ```ruby
112
123
  # config/deploy.rb
113
124
  set :asg_instance_refresh_auto_rollback, true
114
125
  ```
115
126
 
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:
127
+ ### Cleanup
128
+
129
+ After deployment, any outdated Launch Template versions, AMIs and snapshots will be deleted. By default, the same number of versions as Capistrano's `keep_releases` will be kept. Change this with:
117
130
 
118
131
  ```ruby
119
132
  # config/deploy.rb
120
- set :asg_aws_retry_mode, 'adaptive' # default; one of 'legacy', 'standard', 'adaptive'
121
- set :asg_aws_retry_limit, 10 # default
133
+ set :asg_rolling_keep_versions, 10
122
134
  ```
123
135
 
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:
136
+ ### Logging
137
+
138
+ Verbose logging is enabled by default; set this option to false for less verbose logging:
125
139
 
126
140
  ```ruby
127
141
  # 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)
142
+ set :asg_rolling_log_verbose, false
143
+ ```
144
+
145
+ By default, the log output does not contain any timestamps; set the option below to true to enable timestamps:
146
+
147
+ ```ruby
148
+ # config/deploy.rb
149
+ set :asg_rolling_log_timestamp, true
130
150
  ```
131
151
 
132
152
  ## Usage
@@ -145,7 +165,7 @@ Now start a deployment with `cap <stage> deploy` and enjoy.
145
165
 
146
166
  There might be cases where you just want to deploy your code to the servers in the Auto Scaling Group(s) without a rolling update.
147
167
 
148
- You can configure rolling updates per autoscaling group by using the `rolling` option:
168
+ You can configure rolling updates per Auto Scaling Group by using the `rolling` option:
149
169
 
150
170
  ```ruby
151
171
  # config/deploy/<stage>.rb
@@ -153,28 +173,28 @@ autoscale 'app-autoscale-group', rolling: true # default: use rolling deploym
153
173
  autoscale 'web-autoscale-group', rolling: false # override: use normal deployment
154
174
  ```
155
175
 
156
- ### Deploy with custom percentage of minimum/maximum healthy instances during the instance refresh
176
+ ### Deploy with custom minimum/maximum healthy percentages
157
177
 
158
178
  The instance refresh is triggered without specifying a value for the minimum / maximum healthy percentages. This means that either the default
159
179
  values will be used (minimum: 90%, maximum: 100%) or the percentages set in the instance maintenance policy for the Auto Scaling Group.
160
- 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.
180
+ You can tune both the minimum and maximum values to have more control over the desired capacity that must be healthy to proceed with replacing instances.
161
181
 
162
- 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.
182
+ For example, reducing the minimum healthy percentage allows more instances to be terminated and replaced at once during the instance refresh; a value of 0 would terminate and replace all instances in the Auto Scaling Group at once.
163
183
 
164
- 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`.
184
+ You can configure the minimum healthy percentage per Auto Scaling 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`.
165
185
 
166
186
  ```ruby
167
187
  # config/deploy/<stage>.rb
168
- autoscale 'app-autoscale-group', # use default percentages or percentages set in the instance maintenance policy
188
+ autoscale 'app-autoscale-group' # use default percentages or percentages set in the instance maintenance policy
169
189
  autoscale 'web-autoscale-group', min_healthy_percentage: 75 # allow 25% of instances to be terminated and replaced at once
170
190
  autoscale 'web-autoscale-group', min_healthy_percentage: 100, max_healthy_percentage: 110 # allow for 10% above desired capacity during instance refresh
171
191
  ```
172
192
 
173
193
  ### Custom stage
174
194
 
175
- The rolling configuration of the stage has a side-effect: any Capistrano tasks you run, will also launch instances per Auto Scaling Group.
195
+ The rolling configuration of the stage has a side-effect: any Capistrano task you run will also launch instances per Auto Scaling Group.
176
196
 
177
- For example the command: `cap production rails:console`, will launch a new instance and run `rails:console` on that instance. While that can be useful, you often just want to run the task on the primary server. A solution is to create two stages with different rolling configurations, for example:
197
+ For example, the command `cap production rails:console` will launch a new instance and run `rails:console` on that instance. While that can be useful, you often just want to run the task on the primary server. A solution is to create two stages with different rolling configurations, for example:
178
198
 
179
199
  ```ruby
180
200
  # config/deploy/production.rb
@@ -199,7 +219,7 @@ With these two stages, you can run any tasks with `cap production <task name>` a
199
219
 
200
220
  ### Tags
201
221
 
202
- During deployment, the following tags will be added to the AMI and snapshot containing information about the current application, stage and deployment:
222
+ During deployment, the following tags will be added to the AMI and snapshot, containing information about the current application, stage and deployment:
203
223
  - `capistrano-asg-rolling:application`
204
224
  - `capistrano-asg-rolling:stage`
205
225
  - `capistrano-asg-rolling:deployment-branch`
@@ -208,7 +228,7 @@ During deployment, the following tags will be added to the AMI and snapshot cont
208
228
  - `capistrano-asg-rolling:deployment-user`
209
229
 
210
230
  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.
231
+ This tag is also used to determine whether the AMI was created by this gem and can therefore be deleted automatically.
212
232
 
213
233
  You can add custom tag(s) to the AMI and snapshot by setting the property `asg_rolling_ami_tags`, for example:
214
234
 
@@ -221,7 +241,7 @@ set :asg_rolling_ami_tags, { 'Application' => 'My Application', 'Environment' =>
221
241
 
222
242
  ### Test deployment
223
243
 
224
- Do a test deployment: run the deploy task, but do not trigger the update ASG task and do not automatically terminate instances.
244
+ Do a test deployment: run the deploy task, but do not trigger an instance refresh and do not automatically terminate the launched instances.
225
245
 
226
246
  ```bash
227
247
  cap <stage> rolling:deploy_test
@@ -229,8 +249,8 @@ cap <stage> rolling:deploy_test
229
249
 
230
250
  ### Launch instances
231
251
 
232
- Just launch instance(s) defined in the Auto Scale Group(s) launch template.
233
- This instance is not attached to the Auto Scale Group and needs to be terminated manually.
252
+ Launch instance(s) from the Launch Template of the Auto Scaling Group(s).
253
+ These instances are not attached to the Auto Scaling Group and need to be terminated manually.
234
254
 
235
255
  ```bash
236
256
  cap <stage> rolling:launch_instances
@@ -238,8 +258,8 @@ cap <stage> rolling:launch_instances
238
258
 
239
259
  ### Create AMI of instance in ASG
240
260
 
241
- Pick an instance in the Auto Scale Group(s), put it into standby and stop it.
242
- Then create an AMI and a new launch template. Then start the instance and put it into service again.
261
+ Pick an instance in the Auto Scaling Group(s), put it into standby and stop it.
262
+ Then create an AMI, start the instance and put it back into service, and create a new Launch Template version with the new AMI.
243
263
 
244
264
  ```bash
245
265
  cap <stage> rolling:create_ami
@@ -247,7 +267,7 @@ cap <stage> rolling:create_ami
247
267
 
248
268
  ## Filtering
249
269
 
250
- You can filter any command to run on a specific Auto Scale Group by using the parameter `asg_name`.
270
+ You can filter any command to run on a specific Auto Scaling Group by using the parameter `asg_name`.
251
271
 
252
272
  For example, the command:
253
273
 
@@ -255,7 +275,7 @@ For example, the command:
255
275
  cap <stage> deploy asg_name=app-autoscale-group
256
276
  ```
257
277
 
258
- Will do a deployment only on the Auto Scale Group with the name `app-autoscale-group`.
278
+ This will do a deployment only on the Auto Scaling Group named `app-autoscale-group`.
259
279
 
260
280
  ## IAM policy
261
281
 
@@ -24,12 +24,12 @@ Gem::Specification.new do |spec|
24
24
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
25
25
  spec.require_paths = ['lib']
26
26
 
27
- spec.required_ruby_version = '>= 3.1.0'
27
+ spec.required_ruby_version = '>= 3.2.0'
28
28
 
29
29
  spec.add_development_dependency 'bundler', '~> 2.0'
30
30
  spec.add_development_dependency 'rake', '~> 13.0'
31
31
  spec.add_development_dependency 'rspec', '~> 3.0'
32
- spec.add_development_dependency 'simplecov', '~> 0.22'
32
+ spec.add_development_dependency 'simplecov', '~> 1.0'
33
33
  spec.add_development_dependency 'webmock', '~> 3.11'
34
34
 
35
35
  spec.add_dependency 'aws-sdk-autoscaling', '~> 1', '>= 1.100.0'
@@ -21,12 +21,6 @@ module Capistrano
21
21
  @name = name
22
22
  @properties = properties
23
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
24
  validate_properties!
31
25
  end
32
26
 
@@ -106,6 +100,10 @@ module Capistrano
106
100
  return nil if status.nil?
107
101
 
108
102
  InstanceRefreshStatus.new(status, percentage_complete)
103
+ rescue Aws::AutoScaling::Errors::ServiceError => e
104
+ # Wrap AWS service errors (typically throttling that exceeded the SDK's
105
+ # retry budget) so callers can rescue a gem-defined error type.
106
+ raise Capistrano::ASG::Rolling::InstanceRefreshStatusError, e
109
107
  end
110
108
 
111
109
  # Returns instances with lifecycle state "InService" for this Auto Scaling Group.
@@ -19,8 +19,24 @@ module Capistrano
19
19
  @groups.reject { |group| filtered?(group) }.each(&)
20
20
  end
21
21
 
22
+ def empty?
23
+ none?
24
+ end
25
+
26
+ def rolling
27
+ self.class.new(select(&:rolling?))
28
+ end
29
+
30
+ def standard
31
+ self.class.new(reject(&:rolling?))
32
+ end
33
+
34
+ def with_unique_images
35
+ self.class.new(uniq { |group| group.launch_template.image_id })
36
+ end
37
+
22
38
  def launch_templates
23
- templates = @groups.map(&:launch_template)
39
+ templates = map(&:launch_template)
24
40
  LaunchTemplates.new(templates)
25
41
  end
26
42
 
@@ -6,14 +6,16 @@ require 'aws-sdk-ec2'
6
6
  module Capistrano
7
7
  module ASG
8
8
  module Rolling
9
- # AWS SDK.
9
+ # AWS SDK helpers. Clients are memoized per object and shared across objects on the same thread.
10
10
  module AWS
11
11
  def aws_autoscaling_client
12
- @aws_autoscaling_client ||= ::Aws::AutoScaling::Client.new(aws_options)
12
+ @aws_autoscaling_client ||=
13
+ Thread.current[:aws_autoscaling_client] ||= ::Aws::AutoScaling::Client.new(aws_options)
13
14
  end
14
15
 
15
16
  def aws_ec2_client
16
- @aws_ec2_client ||= ::Aws::EC2::Client.new(aws_options)
17
+ @aws_ec2_client ||=
18
+ Thread.current[:aws_ec2_client] ||= ::Aws::EC2::Client.new(aws_options)
17
19
  end
18
20
 
19
21
  private
@@ -3,25 +3,39 @@
3
3
  module Capistrano
4
4
  module ASG
5
5
  module Rolling
6
- # Singleton that holds the configuration.
6
+ # Singleton that holds the configuration and global state of the plugin.
7
+ # State is eagerly initialized so accessors are lock-free.
7
8
  module Configuration
8
9
  extend Capistrano::DSL
9
10
 
11
+ # Registered Auto Scaling Groups.
12
+ @autoscale_groups = AutoscaleGroups.new
13
+
14
+ # Launched Instances.
15
+ @instances = Instances.new
16
+
17
+ # Updated Launch Templates.
18
+ @launch_templates = LaunchTemplates.new
19
+
10
20
  module_function
11
21
 
12
- # Registered Auto Scaling Groups.
13
22
  def autoscale_groups
14
- @autoscale_groups ||= AutoscaleGroups.new
23
+ @autoscale_groups
15
24
  end
16
25
 
17
- # Launched Instances.
18
26
  def instances
19
- @instances ||= Instances.new
27
+ @instances
20
28
  end
21
29
 
22
- # Updated Launch Templates.
23
30
  def launch_templates
24
- @launch_templates ||= LaunchTemplates.new
31
+ @launch_templates
32
+ end
33
+
34
+ # Resets all mutable state.
35
+ def reset!
36
+ @autoscale_groups = AutoscaleGroups.new
37
+ @instances = Instances.new
38
+ @launch_templates = LaunchTemplates.new
25
39
  end
26
40
 
27
41
  def aws_access_key_id
@@ -44,10 +58,6 @@ module Capistrano
44
58
  fetch(:asg_rolling_group_name)
45
59
  end
46
60
 
47
- def ssh_options
48
- fetch(:asg_rolling_ssh_options, fetch(:ssh_options))
49
- end
50
-
51
61
  def instance_overrides
52
62
  fetch(:asg_rolling_instance_overrides)
53
63
  end
@@ -60,8 +70,12 @@ module Capistrano
60
70
  fetch(:asg_rolling_keep_versions, fetch(:keep_releases))
61
71
  end
62
72
 
63
- def verbose?
64
- fetch(:asg_rolling_verbose)
73
+ def log_verbose?
74
+ fetch(:asg_rolling_log_verbose)
75
+ end
76
+
77
+ def log_timestamp?
78
+ fetch(:asg_rolling_log_timestamp)
65
79
  end
66
80
 
67
81
  def rolling_update=(value)
@@ -13,9 +13,14 @@ module Capistrano
13
13
  class NoLaunchTemplate < Capistrano::ASG::Rolling::Exception
14
14
  end
15
15
 
16
+ # Exception when starting an instance refresh failed.
16
17
  class StartInstanceRefreshError < Capistrano::ASG::Rolling::Exception
17
18
  end
18
19
 
20
+ # Exception when fetching the instance refresh status failed.
21
+ class InstanceRefreshStatusError < Capistrano::ASG::Rolling::Exception
22
+ end
23
+
19
24
  # Exception when the instance refresh failed on one of the ASGs.
20
25
  class InstanceRefreshFailed < Capistrano::ASG::Rolling::Exception
21
26
  def initialize
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'concurrent/array'
4
+
3
5
  module Capistrano
4
6
  module ASG
5
7
  module Rolling
@@ -8,7 +10,7 @@ module Capistrano
8
10
  include Enumerable
9
11
 
10
12
  def initialize(instances = [])
11
- @instances = instances
13
+ @instances = Concurrent::Array.new(instances)
12
14
  end
13
15
 
14
16
  def <<(instance)
@@ -23,7 +25,7 @@ module Capistrano
23
25
  instances.empty?
24
26
  end
25
27
 
26
- def auto_terminate
28
+ def auto_terminatable
27
29
  self.class.new(select(&:auto_terminate?))
28
30
  end
29
31
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'set'
4
-
5
3
  module Capistrano
6
4
  module ASG
7
5
  module Rolling
@@ -29,16 +27,20 @@ module Capistrano
29
27
  @templates.empty?
30
28
  end
31
29
 
30
+ # For each AMI, find templates still pointing at the old image it was built
31
+ # from, then create new template versions referencing the new AMI in parallel.
32
+ #
33
+ # Returns `LaunchTemplates` containing only the newly created versions.
32
34
  def update(amis:, description: nil)
33
- updated_templates = []
34
-
35
- amis.each do |ami|
35
+ template_image_pairs = amis.flat_map do |ami|
36
36
  old_image_id = ami.instance.image_id
37
37
  new_image_id = ami.id
38
38
 
39
- with_image(old_image_id).each do |template|
40
- updated_templates << template.create_version(image_id: new_image_id, description: description)
41
- end
39
+ with_image(old_image_id).map { |template| [template, new_image_id] }
40
+ end
41
+
42
+ updated_templates = Parallel.run(template_image_pairs) do |template, new_image_id|
43
+ template.create_version(image_id: new_image_id, description: description)
42
44
  end
43
45
 
44
46
  self.class.new(updated_templates)
@@ -3,9 +3,10 @@
3
3
  module Capistrano
4
4
  module ASG
5
5
  module Rolling
6
- # Logging support.
6
+ # Colorized stdout/stderr output with optional timestamps and **bold** formatting.
7
7
  class Logger
8
- def initialize(verbose: false)
8
+ def initialize(timestamp: false, verbose: false)
9
+ @timestamp = timestamp
9
10
  @verbose = verbose
10
11
  end
11
12
 
@@ -28,6 +29,7 @@ module Capistrano
28
29
  private
29
30
 
30
31
  def format_text(text, color: nil)
32
+ text = "[#{current_time}] #{text}" if @timestamp
31
33
  text = colorize_text(text, color) if color
32
34
  text.gsub(/\*\*(.+?)\*\*/, bold_text('\\1'))
33
35
  end
@@ -43,6 +45,10 @@ module Capistrano
43
45
  def _color
44
46
  @_color ||= SSHKit::Color.new($stdout)
45
47
  end
48
+
49
+ def current_time
50
+ Time.now.strftime('%H:%M:%S')
51
+ end
46
52
  end
47
53
  end
48
54
  end
@@ -14,7 +14,8 @@ module Capistrano
14
14
  def set_defaults
15
15
  set_if_empty :asg_rolling_group_name, ENV.fetch('asg_name', nil)
16
16
  set_if_empty :asg_rolling_use_private_ip_address, true
17
- set_if_empty :asg_rolling_verbose, true
17
+ set_if_empty :asg_rolling_log_verbose, true
18
+ set_if_empty :asg_rolling_log_timestamp, false
18
19
  set_if_empty :asg_rolling_update, true
19
20
  end
20
21
 
@@ -28,6 +29,7 @@ module Capistrano
28
29
 
29
30
  after 'rolling:update', 'rolling:cleanup'
30
31
  after 'rolling:create_ami', 'rolling:cleanup'
32
+
31
33
  after 'rolling:update', 'rolling:instance_refresh_status'
32
34
  after 'rolling:trigger_instance_refresh', 'rolling:instance_refresh_status'
33
35
 
@@ -41,7 +43,7 @@ module Capistrano
41
43
  end
42
44
 
43
45
  def logger
44
- @logger ||= Logger.new(verbose: config.verbose?)
46
+ @logger ||= Logger.new(timestamp: config.log_timestamp?, verbose: config.log_verbose?)
45
47
  end
46
48
 
47
49
  def config
@@ -59,7 +61,7 @@ module Capistrano
59
61
 
60
62
  # Make sure any instances that were launched for rolling updates are terminated.
61
63
  def cleanup
62
- instances = config.instances.auto_terminate
64
+ instances = config.instances.auto_terminatable
63
65
  return if instances.empty?
64
66
 
65
67
  logger.info 'Terminating instance(s)...'
@@ -5,7 +5,7 @@ require 'time'
5
5
  module Capistrano
6
6
  module ASG
7
7
  module Rolling
8
- # Test the SSHKit backend for availability.
8
+ # Polls an SSHKit backend until its SSH port is open and accepting connections.
9
9
  module SSH
10
10
  module_function
11
11
 
@@ -3,7 +3,7 @@
3
3
  module Capistrano
4
4
  module ASG
5
5
  module Rolling
6
- VERSION = '0.8.0'
6
+ VERSION = '0.9.0'
7
7
  end
8
8
  end
9
9
  end
@@ -6,7 +6,6 @@ require 'capistrano/asg/rolling/aws'
6
6
  require 'capistrano/asg/rolling/ami'
7
7
  require 'capistrano/asg/rolling/autoscale_group'
8
8
  require 'capistrano/asg/rolling/autoscale_groups'
9
- require 'capistrano/asg/rolling/configuration'
10
9
  require 'capistrano/asg/rolling/dsl'
11
10
  require 'capistrano/asg/rolling/exception'
12
11
  require 'capistrano/asg/rolling/instance'
@@ -15,8 +14,10 @@ require 'capistrano/asg/rolling/launch_template'
15
14
  require 'capistrano/asg/rolling/launch_templates'
16
15
  require 'capistrano/asg/rolling/logger'
17
16
  require 'capistrano/asg/rolling/parallel'
18
- require 'capistrano/asg/rolling/plugin'
19
17
  require 'capistrano/asg/rolling/snapshot'
20
18
  require 'capistrano/asg/rolling/ssh'
21
19
  require 'capistrano/asg/rolling/tags'
22
20
  require 'capistrano/asg/rolling/version'
21
+
22
+ require 'capistrano/asg/rolling/configuration'
23
+ require 'capistrano/asg/rolling/plugin'
@@ -2,35 +2,50 @@
2
2
 
3
3
  namespace :rolling do
4
4
  desc 'Setup servers to be used for (rolling) deployment'
5
- task :setup do
6
- config.autoscale_groups.each do |group|
7
- if group.rolling?
5
+ task setup: %w[rolling:prepare rolling:wait_for_ssh]
6
+
7
+ desc 'Resolve Auto Scaling Groups, launch rolling instances, and register all servers'
8
+ task :prepare do
9
+ rolling_groups = config.autoscale_groups.rolling
10
+ if rolling_groups.any?
11
+ rolling_groups.each do |group|
8
12
  logger.info "Auto Scaling Group: **#{group.name}**, rolling deployment strategy."
13
+ end
9
14
 
10
- # If we've already launched an instance with this image, then skip it.
11
- next unless config.instances.with_image(group.launch_template.image_id).empty?
15
+ launched_instances = Capistrano::ASG::Rolling::Parallel.run(rolling_groups.with_unique_images) do |group|
16
+ Capistrano::ASG::Rolling::Instance.run(
17
+ autoscaling_group: group,
18
+ overrides: config.instance_overrides
19
+ ).tap { |instance| config.instances << instance }
20
+ end
12
21
 
13
- instance = Capistrano::ASG::Rolling::Instance.run(autoscaling_group: group, overrides: config.instance_overrides)
22
+ launched_instances.each do |instance|
14
23
  logger.info "Launched Instance: **#{instance.id}**"
15
- config.instances << instance
16
24
 
25
+ group = instance.autoscale_group
17
26
  add_instance(instance, group.properties)
18
- else
19
- logger.info "Auto Scaling Group: **#{group.name}**, standard deployment strategy."
27
+ end
28
+ end
20
29
 
21
- group.instances.each_with_index do |instance, index|
30
+ standard_groups = config.autoscale_groups.standard
31
+ standard_groups.each do |group|
32
+ logger.info "Auto Scaling Group: **#{group.name}**, standard deployment strategy."
33
+
34
+ group.instances.each_with_index do |instance, index|
35
+ server_properties =
22
36
  if index.zero? && group.properties.key?(:primary_roles)
23
- server_properties = group.properties.dup
24
- server_properties[:roles] = server_properties.delete(:primary_roles)
37
+ group.properties.merge(roles: group.properties[:primary_roles]).except(:primary_roles)
25
38
  else
26
- server_properties = group.properties
39
+ group.properties
27
40
  end
28
41
 
29
- add_instance(instance, server_properties)
30
- end
42
+ add_instance(instance, server_properties)
31
43
  end
32
44
  end
45
+ end
33
46
 
47
+ desc 'Wait for SSH to become available on launched instances'
48
+ task :wait_for_ssh do
34
49
  unless config.instances.empty?
35
50
  logger.info 'Waiting for SSH to be available...'
36
51
 
@@ -54,31 +69,22 @@ namespace :rolling do
54
69
  updated_templates = launch_templates.update(amis: amis, description: revision_log_message)
55
70
 
56
71
  logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
57
- updated_templates.each do |launch_template|
58
- config.autoscale_groups.with_launch_template(launch_template).each do |group|
59
- group.start_instance_refresh(launch_template)
72
+ groups_with_templates = updated_templates.flat_map do |launch_template|
73
+ config.autoscale_groups.with_launch_template(launch_template).map { |group| [group, launch_template] }
74
+ end
60
75
 
61
- logger.verbose "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
62
- rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
63
- logger.info "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
64
- end
76
+ Capistrano::ASG::Rolling::Parallel.run(groups_with_templates) do |group, launch_template|
77
+ group.start_instance_refresh(launch_template)
78
+
79
+ logger.verbose "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
80
+ rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
81
+ logger.warning "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
65
82
  end
66
83
 
67
84
  config.launch_templates.merge(updated_templates)
68
85
  end
69
86
  end
70
87
 
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
-
82
88
  desc 'Clean up old Launch Template versions and AMIs and terminate instances'
83
89
  task :cleanup do
84
90
  unless config.launch_templates.empty?
@@ -114,7 +120,7 @@ namespace :rolling do
114
120
  end
115
121
  end
116
122
 
117
- instances = config.instances.auto_terminate
123
+ instances = config.instances.auto_terminatable
118
124
  if instances.any?
119
125
  logger.info 'Terminating instance(s)...'
120
126
  begin
@@ -176,39 +182,50 @@ namespace :rolling do
176
182
 
177
183
  logger.info 'Updating Launch Template with the new AMI...'
178
184
  launch_template = group.launch_template
179
- launch_template.create_version(image_id: ami.id, description: revision_log_message)
185
+ new_template = launch_template.create_version(image_id: ami.id, description: revision_log_message)
180
186
 
181
- config.launch_templates << launch_template
187
+ config.launch_templates << new_template
182
188
  else
183
189
  logger.error 'Unable to create AMI. No instance with a valid state was found in the Auto Scaling Group.'
184
190
  end
185
191
  end
186
192
  end
187
193
 
194
+ desc 'Trigger instance refresh on all Auto Scaling Groups'
195
+ task :trigger_instance_refresh do
196
+ logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
197
+ Capistrano::ASG::Rolling::Parallel.run(config.autoscale_groups) do |group|
198
+ group.start_instance_refresh(group.launch_template)
199
+
200
+ logger.info "Successfully started Instance Refresh on Auto Scaling Group **#{group.name}**."
201
+ rescue Capistrano::ASG::Rolling::StartInstanceRefreshError => e
202
+ logger.warning "Failed to start Instance Refresh on Auto Scaling Group **#{group.name}**: #{e.message}"
203
+ end
204
+ end
205
+
188
206
  desc 'Get status of instance refresh'
189
207
  task :instance_refresh_status do
190
208
  if config.wait_for_instance_refresh?
191
- groups = config.autoscale_groups.to_h { |group| [group.name, group] }
192
- completed_groups = []
209
+ groups = config.autoscale_groups.select(&:refresh_id).to_h { |group| [group.name, group] }
210
+ completed_refreshes = []
193
211
 
194
212
  while groups.any?
195
213
  groups.each do |name, group|
196
214
  refresh = group.latest_instance_refresh
197
215
  if refresh.nil? || refresh.completed?
198
- logger.info "Auto Scaling Group: **#{name}**, completed with status '#{refresh.status}'." if refresh.completed?
199
- completed_groups.push groups.delete(name)
200
- elsif !refresh.percentage_complete.nil?
216
+ logger.info "Auto Scaling Group: **#{name}**, completed with status '#{refresh.status}'." if refresh&.completed?
217
+
218
+ groups.delete(name)
219
+ completed_refreshes.push(refresh) if refresh
220
+ elsif refresh.percentage_complete
201
221
  logger.info "Auto Scaling Group: **#{name}**, #{refresh.percentage_complete}% completed, status '#{refresh.status}'."
202
222
  else
203
223
  logger.info "Auto Scaling Group: **#{name}**, status '#{refresh.status}'."
204
224
  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."
225
+ rescue Capistrano::ASG::Rolling::InstanceRefreshStatusError => e
226
+ logger.warning "Auto Scaling Group: **#{name}**, failed to fetch status: #{e.message} - retrying on next poll."
211
227
  end
228
+
212
229
  next if groups.empty?
213
230
 
214
231
  wait_for = config.instance_refresh_polling_interval
@@ -216,7 +233,7 @@ namespace :rolling do
216
233
  sleep wait_for
217
234
  end
218
235
 
219
- failed = completed_groups.any? { |group| group.latest_instance_refresh.failed? }
236
+ failed = completed_refreshes.any?(&:failed?)
220
237
  raise Capistrano::ASG::Rolling::InstanceRefreshFailed if failed
221
238
  end
222
239
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capistrano-asg-rolling
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kentaa
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2026-05-22 00:00:00.000000000 Z
12
+ date: 2026-08-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -59,14 +59,14 @@ dependencies:
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '0.22'
62
+ version: '1.0'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '0.22'
69
+ version: '1.0'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: webmock
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -196,14 +196,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
196
196
  requirements:
197
197
  - - ">="
198
198
  - !ruby/object:Gem::Version
199
- version: 3.1.0
199
+ version: 3.2.0
200
200
  required_rubygems_version: !ruby/object:Gem::Requirement
201
201
  requirements:
202
202
  - - ">="
203
203
  - !ruby/object:Gem::Version
204
204
  version: '0'
205
205
  requirements: []
206
- rubygems_version: 3.3.27
206
+ rubygems_version: 3.4.19
207
207
  signing_key:
208
208
  specification_version: 4
209
209
  summary: Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups