capistrano-asg-rolling 0.1.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 +7 -0
- data/.github/workflows/test.yml +21 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.rubocop.yml +28 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +113 -0
- data/LICENSE.txt +21 -0
- data/README.md +214 -0
- data/Rakefile +10 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/capistrano-asg-rolling.gemspec +38 -0
- data/lib/capistrano/asg/rolling/ami.rb +76 -0
- data/lib/capistrano/asg/rolling/autoscale_group.rb +102 -0
- data/lib/capistrano/asg/rolling/autoscale_groups.rb +70 -0
- data/lib/capistrano/asg/rolling/aws.rb +47 -0
- data/lib/capistrano/asg/rolling/configuration.rb +67 -0
- data/lib/capistrano/asg/rolling/dsl.rb +22 -0
- data/lib/capistrano/asg/rolling/exception.rb +17 -0
- data/lib/capistrano/asg/rolling/instance.rb +102 -0
- data/lib/capistrano/asg/rolling/instances.rb +60 -0
- data/lib/capistrano/asg/rolling/launch_template.rb +87 -0
- data/lib/capistrano/asg/rolling/logger.rb +44 -0
- data/lib/capistrano/asg/rolling/parallel.rb +29 -0
- data/lib/capistrano/asg/rolling/plugin.rb +62 -0
- data/lib/capistrano/asg/rolling/snapshot.rb +22 -0
- data/lib/capistrano/asg/rolling/ssh.rb +25 -0
- data/lib/capistrano/asg/rolling/version.rb +9 -0
- data/lib/capistrano/asg/rolling.rb +20 -0
- data/lib/capistrano/asg/tasks/rolling.rake +134 -0
- metadata +193 -0
@@ -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,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: []
|