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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef34189670d90583e896e138bb9d8de0647adcfbea6b9e9680de7de8d676bb77
4
- data.tar.gz: f33d46261711274cfec41257f2d627d50ffb4e8bbe6c01b8dbbb899a7655f246
3
+ metadata.gz: b336d65bcb11063bb59bd4934158b9cda2f3e03613442800d135d1bed513e208
4
+ data.tar.gz: 1bd33673762478806487a3d10f16662e9909bfbdf9edd21841b4db9de5ded2b5
5
5
  SHA512:
6
- metadata.gz: 51334eff0a1cee5fe93b86da3e1494be9422ac9936bffbeaf3fa714a73618913593b688f602ee7edf29be6575a1ef4639901d707ad05448c5844cbdbd1d88deb
7
- data.tar.gz: e25f26ef2ca81085af106b2708c7b13038e247e43c5c036e5c5a750f91f16989a771ce726f2efce0c80e2a45eecc99e86f20aac388cd344ede587faf47c1cd94
6
+ metadata.gz: f013f9d109849878be032295ab1c518e7e0f7617d32fadc6c3c71bf3da03d8cf57d0fd28f4260e4047ed91c424573b32341e8d4ecc22c6544aa8147da05c3f89
7
+ data.tar.gz: d0f61692e322b744772d463825044746ac57224b6dbf2c61e1ca85ced9efa09fbae18dc9f26690be9d2f8d82a768d701b9e1bbc024c50a5b4afe5636ee8d51cd
@@ -9,7 +9,7 @@ jobs:
9
9
  runs-on: ubuntu-latest
10
10
  strategy:
11
11
  matrix:
12
- ruby-version: ['3.0', '3.1', '3.2', '3.3', '3.4']
12
+ ruby-version: ['3.1', '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.0
8
+ TargetRubyVersion: 3.1
9
9
  DisplayCopNames: true
10
10
  DisplayStyleGuide: true
11
11
 
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 peter.postma@kentaa.nl. All
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.6.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.8.7)
14
- public_suffix (>= 2.0.2, < 7.0)
15
- airbrussh (1.5.3)
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.2)
18
- aws-eventstream (1.3.2)
19
- aws-partitions (1.1066.0)
20
- aws-sdk-autoscaling (1.132.0)
21
- aws-sdk-core (~> 3, >= 3.216.0)
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.220.1)
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
- aws-sdk-ec2 (1.512.0)
30
- aws-sdk-core (~> 3, >= 3.216.0)
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.11.0)
34
+ aws-sigv4 (1.12.1)
33
35
  aws-eventstream (~> 1, >= 1.0.2)
34
- base64 (0.2.0)
35
- bigdecimal (3.1.9)
36
- capistrano (3.19.2)
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.5)
42
- crack (1.0.0)
43
+ concurrent-ruby (1.3.6)
44
+ crack (1.0.1)
43
45
  bigdecimal
44
46
  rexml
45
- diff-lcs (1.6.0)
46
- hashdiff (1.1.2)
47
- i18n (1.14.7)
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.10.2)
51
- language_server-protocol (3.17.0.4)
53
+ json (2.19.5)
54
+ language_server-protocol (3.17.0.5)
52
55
  lint_roller (1.1.0)
53
- logger (1.6.6)
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.0)
59
- ostruct (0.6.1)
60
- parallel (1.26.3)
61
- parser (3.3.7.1)
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
- public_suffix (6.0.1)
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.1)
68
- regexp_parser (2.10.0)
69
- rexml (3.4.1)
70
- rspec (3.13.0)
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.3)
78
+ rspec-core (3.13.6)
75
79
  rspec-support (~> 3.13.0)
76
- rspec-expectations (3.13.3)
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.2)
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.2)
83
- rubocop (1.74.0)
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 (~> 1.10)
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.38.0, < 2.0)
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.38.1)
95
- parser (>= 3.3.1.0)
96
- rubocop-performance (1.24.0)
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.72.1, < 2.0)
99
- rubocop-ast (>= 1.38.0, < 2.0)
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.5.0)
108
+ rubocop-rspec (3.9.0)
104
109
  lint_roller (~> 1.1)
105
- rubocop (~> 1.72, >= 1.72.1)
110
+ rubocop (~> 1.81)
106
111
  ruby-progressbar (1.13.0)
107
- sshkit (1.24.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)
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.1.4)
115
- unicode-emoji (~> 4.0, >= 4.0.4)
116
- unicode-emoji (4.0.4)
117
- webmock (3.25.1)
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.3.7
149
+ 2.6.7
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 Kentaa BV
3
+ Copyright (c) 2025 Kentaa BV
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/capistrano-asg-rolling.svg)](https://badge.fury.io/rb/capistrano-asg-rolling)
4
4
  [![Build Status](https://github.com/KentaaNL/capistrano-asg-rolling/actions/workflows/test.yml/badge.svg)](https://github.com/KentaaNL/capistrano-asg-rolling/actions)
5
- [![Code Climate](https://codeclimate.com/github/KentaaNL/capistrano-asg-rolling/badges/gpa.svg)](https://codeclimate.com/github/KentaaNL/capistrano-asg-rolling)
5
+ [![CodeQL](https://github.com/KentaaNL/capistrano-asg-rolling/actions/workflows/github-code-scanning/codeql/badge.svg)](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
- lib = File.expand_path('lib', __dir__)
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 = ['Kentaa']
11
- spec.email = ['developers@kentaa.nl']
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.0.0'
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
- rescue Aws::Waiters::Errors::TooManyAttemptsError
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::InstanceRefreshInProgress => e
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
 
@@ -15,8 +15,8 @@ module Capistrano
15
15
  @groups << group
16
16
  end
17
17
 
18
- def each(&block)
19
- @groups.reject { |group| filtered?(group) }.each(&block)
18
+ def each(&)
19
+ @groups.reject { |group| filtered?(group) }.each(&)
20
20
  end
21
21
 
22
22
  def launch_templates
@@ -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(&block)
19
- instances.each(&block)
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
@@ -21,8 +21,8 @@ module Capistrano
21
21
  @templates.merge(templates)
22
22
  end
23
23
 
24
- def each(&block)
25
- @templates.each(&block)
24
+ def each(&)
25
+ @templates.each(&)
26
26
  end
27
27
 
28
28
  def empty?
@@ -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
- result = Concurrent::Array.new
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
- result << yield(w)
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
- result
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
- # SSH availability test.
8
+ # Test the SSHKit backend for availability.
7
9
  module SSH
8
10
  module_function
9
11
 
10
- def test?(ip_address, user, ssh_options)
11
- options = ssh_options || {}
12
- options[:timeout] = 10
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
- ::Net::SSH.start(ip_address, user, options) do |ssh|
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::ConnectionTimeout, ::Net::SSH::Proxy::ConnectError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
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
@@ -3,7 +3,7 @@
3
3
  module Capistrano
4
4
  module ASG
5
5
  module Rolling
6
- VERSION = '0.6.0'
6
+ VERSION = '0.8.0'
7
7
  end
8
8
  end
9
9
  end
@@ -14,10 +14,7 @@ namespace :rolling do
14
14
  logger.info "Launched Instance: **#{instance.id}**"
15
15
  config.instances << instance
16
16
 
17
- logger.verbose "Adding server: **#{instance.ip_address}**"
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
- logger.verbose "Adding server: **#{instance.ip_address}**"
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
- config.instances.wait_for_ssh
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.6.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: 2025-03-14 00:00:00.000000000 Z
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
- - developers@kentaa.nl
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.0.0
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.2.33
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