capistrano-asg-rolling 0.4.0 → 0.5.0

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