capistrano-asg-rolling 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capistrano/plugin'
4
+
5
+ module Capistrano
6
+ module ASG
7
+ module Rolling
8
+ # Load this Capistrano Plugin in your Capfile:
9
+ #
10
+ # require 'capistrano/asg/rolling'
11
+ # install_plugin Capistrano::ASG::Rolling::Plugin
12
+ #
13
+ class Plugin < Capistrano::Plugin
14
+ def set_defaults
15
+ set_if_empty :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID']
16
+ set_if_empty :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY']
17
+ set_if_empty :aws_region, ENV['AWS_REGION']
18
+
19
+ set_if_empty :asg_rolling_group_name, ENV['asg_name']
20
+ set_if_empty :asg_rolling_use_private_ip_address, true
21
+ set_if_empty :asg_rolling_verbose, true
22
+ end
23
+
24
+ def register_hooks
25
+ Capistrano::DSL.stages.each do |stage|
26
+ after stage, 'rolling:setup'
27
+ end
28
+
29
+ after 'deploy', 'rolling:update'
30
+ after 'deploy:failed', 'rolling:cleanup'
31
+
32
+ after 'rolling:update', 'rolling:cleanup'
33
+ after 'rolling:create_ami', 'rolling:cleanup'
34
+
35
+ # Register an exit hook to do some cleanup when Capistrano
36
+ # terminates without calling our after cleanup hook.
37
+ at_exit { cleanup }
38
+ end
39
+
40
+ def define_tasks
41
+ eval_rakefile File.expand_path('../tasks/rolling.rake', __dir__)
42
+ end
43
+
44
+ def logger
45
+ @logger ||= Logger.new(verbose: config.verbose?)
46
+ end
47
+
48
+ def config
49
+ Capistrano::ASG::Rolling::Configuration
50
+ end
51
+
52
+ def cleanup
53
+ instances = config.instances.auto_terminate
54
+ return if instances.empty?
55
+
56
+ logger.info 'Terminating instance(s)...'
57
+ instances.terminate
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capistrano
4
+ module ASG
5
+ module Rolling
6
+ # AWS EC2 EBS snapshot.
7
+ class Snapshot
8
+ include AWS
9
+
10
+ attr_reader :id
11
+
12
+ def initialize(id)
13
+ @id = id
14
+ end
15
+
16
+ def delete
17
+ aws_ec2_client.delete_snapshot(snapshot_id: id)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capistrano
4
+ module ASG
5
+ module Rolling
6
+ # SSH availability test.
7
+ module SSH
8
+ module_function
9
+
10
+ def test?(ip_address, user, ssh_options)
11
+ options = ssh_options || {}
12
+ options[:timeout] = 10
13
+
14
+ ::Net::SSH.start(ip_address, user, options) do |ssh|
15
+ ssh.exec!('echo hello')
16
+ end
17
+
18
+ true
19
+ rescue ::Net::SSH::ConnectionTimeout, ::Net::SSH::Proxy::ConnectError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
20
+ false
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capistrano
4
+ module ASG
5
+ module Rolling
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'capistrano/all'
4
+
5
+ require 'capistrano/asg/rolling/aws'
6
+ require 'capistrano/asg/rolling/ami'
7
+ require 'capistrano/asg/rolling/autoscale_group'
8
+ require 'capistrano/asg/rolling/autoscale_groups'
9
+ require 'capistrano/asg/rolling/configuration'
10
+ require 'capistrano/asg/rolling/dsl'
11
+ require 'capistrano/asg/rolling/exception'
12
+ require 'capistrano/asg/rolling/instance'
13
+ require 'capistrano/asg/rolling/instances'
14
+ require 'capistrano/asg/rolling/launch_template'
15
+ require 'capistrano/asg/rolling/logger'
16
+ require 'capistrano/asg/rolling/parallel'
17
+ require 'capistrano/asg/rolling/plugin'
18
+ require 'capistrano/asg/rolling/snapshot'
19
+ require 'capistrano/asg/rolling/ssh'
20
+ require 'capistrano/asg/rolling/version'
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :rolling do
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?
8
+ logger.info "Auto Scaling Group: **#{group.name}**, rolling deployment strategy."
9
+
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?
12
+
13
+ instance = Capistrano::ASG::Rolling::Instance.run(autoscaling_group: group, overrides: config.instance_overrides)
14
+ logger.info "Launched Instance: **#{instance.id}**"
15
+ config.instances << instance
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)
21
+ else
22
+ logger.info "Auto Scaling Group: **#{group.name}**, standard deployment strategy."
23
+
24
+ group.instances.each_with_index do |instance, index| # rubocop:disable Lint/ShadowingOuterLocalVariable
25
+ if index.zero? && group.properties.key?(:primary_roles)
26
+ server_properties = group.properties.dup
27
+ server_properties[:roles] = server_properties.delete(:primary_roles)
28
+ else
29
+ server_properties = group.properties
30
+ end
31
+
32
+ logger.verbose "Adding server: **#{instance.ip_address}**"
33
+
34
+ # Add server to the Capistrano server list.
35
+ server(instance.ip_address, server_properties)
36
+ end
37
+ end
38
+ end
39
+
40
+ unless config.instances.empty?
41
+ logger.info 'Waiting for SSH to be available...'
42
+ config.instances.wait_for_ssh
43
+ end
44
+ end
45
+
46
+ desc 'Update Auto Scaling Groups: create AMIs, update Launch Templates and start Instance Refresh'
47
+ task :update do
48
+ unless config.instances.empty?
49
+ logger.info 'Stopping instance(s)...'
50
+ config.instances.stop
51
+
52
+ logger.info 'Creating AMI(s)...'
53
+ amis = config.instances.create_ami(description: revision_log_message)
54
+
55
+ logger.info 'Updating Launch Template(s) with the new AMI(s)...'
56
+ launch_templates = config.autoscale_groups.update_launch_templates(amis: amis, description: revision_log_message)
57
+
58
+ logger.info 'Triggering Instance Refresh on Auto Scaling Group(s)...'
59
+ launch_templates.each do |launch_template|
60
+ config.autoscale_groups.with_launch_template(launch_template).start_instance_refresh
61
+ end
62
+
63
+ config.launch_templates.merge(launch_templates)
64
+ end
65
+ end
66
+
67
+ desc 'Clean up old Launch Template versions and AMIs and terminate instances'
68
+ task :cleanup do
69
+ unless config.launch_templates.empty?
70
+ logger.info 'Cleaning up old Launch Template version(s) and AMI(s)...'
71
+ config.launch_templates.each do |launch_template|
72
+ launch_template.previous_versions.drop(config.keep_versions).each do |version|
73
+ next if version.default_version?
74
+
75
+ # Need to retrieve AMI before deleting the Launch Template version.
76
+ ami = version.ami
77
+
78
+ logger.verbose "Deleting Launch Template version **#{version.version}**..."
79
+ version.delete
80
+
81
+ if ami.exists?
82
+ logger.verbose "Deleting AMI **#{version.ami.id}** and snapshots..."
83
+ ami.delete
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ instances = config.instances.auto_terminate
90
+ if instances.any?
91
+ logger.info 'Terminating instance(s)...'
92
+ instances.terminate
93
+ end
94
+ end
95
+
96
+ desc 'Launch Instances by marking instances to not automatically terminate'
97
+ task :launch_instances do
98
+ if config.instances.any?
99
+ config.instances.each do |instance|
100
+ instance.auto_terminate = false
101
+ end
102
+ else
103
+ logger.error 'No instances have been launched. Are you using a configuration with rolling deployments?'
104
+ end
105
+ end
106
+
107
+ desc 'Create an AMI from an Instance in the Auto Scaling Groups'
108
+ task :create_ami do
109
+ config.autoscale_groups.each do |group|
110
+ logger.info 'Selecting instance to create AMI from...'
111
+
112
+ # Pick a random instance, put it in standby and create an AMI.
113
+ instance = group.instances.sample
114
+ if instance
115
+ logger.info "Instance **#{instance.id}** entering standby state..."
116
+ group.enter_standby(instance)
117
+
118
+ logger.info 'Creating AMI...'
119
+ ami = instance.create_ami(description: revision_log_message)
120
+
121
+ logger.info "Instance **#{instance.id}** exiting standby state..."
122
+ group.exit_standby(instance)
123
+
124
+ logger.info 'Updating Launch Template with the new AMI...'
125
+ launch_template = group.launch_template
126
+ launch_template.create_version(image_id: ami.id, description: revision_log_message)
127
+
128
+ config.launch_templates << launch_template
129
+ else
130
+ logger.error 'Unable to create AMI. No instance with a valid state was found in the Auto Scaling Group.'
131
+ end
132
+ end
133
+ end
134
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-asg-rolling
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kentaa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-10-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.11'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.11'
69
+ - !ruby/object:Gem::Dependency
70
+ name: aws-sdk-autoscaling
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1'
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '1.39'
79
+ type: :runtime
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - "~>"
84
+ - !ruby/object:Gem::Version
85
+ version: '1'
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '1.39'
89
+ - !ruby/object:Gem::Dependency
90
+ name: aws-sdk-ec2
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '1'
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1'
103
+ - !ruby/object:Gem::Dependency
104
+ name: capistrano
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '3'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3'
117
+ - !ruby/object:Gem::Dependency
118
+ name: concurrent-ruby
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1'
124
+ type: :runtime
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '1'
131
+ description:
132
+ email:
133
+ - developers@kentaa.nl
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - ".github/workflows/test.yml"
139
+ - ".gitignore"
140
+ - ".rspec"
141
+ - ".rubocop.yml"
142
+ - CODE_OF_CONDUCT.md
143
+ - Gemfile
144
+ - Gemfile.lock
145
+ - LICENSE.txt
146
+ - README.md
147
+ - Rakefile
148
+ - bin/console
149
+ - bin/setup
150
+ - capistrano-asg-rolling.gemspec
151
+ - lib/capistrano/asg/rolling.rb
152
+ - lib/capistrano/asg/rolling/ami.rb
153
+ - lib/capistrano/asg/rolling/autoscale_group.rb
154
+ - lib/capistrano/asg/rolling/autoscale_groups.rb
155
+ - lib/capistrano/asg/rolling/aws.rb
156
+ - lib/capistrano/asg/rolling/configuration.rb
157
+ - lib/capistrano/asg/rolling/dsl.rb
158
+ - lib/capistrano/asg/rolling/exception.rb
159
+ - lib/capistrano/asg/rolling/instance.rb
160
+ - lib/capistrano/asg/rolling/instances.rb
161
+ - lib/capistrano/asg/rolling/launch_template.rb
162
+ - lib/capistrano/asg/rolling/logger.rb
163
+ - lib/capistrano/asg/rolling/parallel.rb
164
+ - lib/capistrano/asg/rolling/plugin.rb
165
+ - lib/capistrano/asg/rolling/snapshot.rb
166
+ - lib/capistrano/asg/rolling/ssh.rb
167
+ - lib/capistrano/asg/rolling/version.rb
168
+ - lib/capistrano/asg/tasks/rolling.rake
169
+ homepage: https://github.com/KentaaNL/capistrano-asg-rolling
170
+ licenses:
171
+ - MIT
172
+ metadata: {}
173
+ post_install_message:
174
+ rdoc_options: []
175
+ require_paths:
176
+ - lib
177
+ required_ruby_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: 2.5.0
182
+ required_rubygems_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ requirements: []
188
+ rubygems_version: 3.0.9
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Capistrano plugin for performing rolling updates to AWS Auto Scaling Groups
192
+ using Instance Refresh
193
+ test_files: []