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.
@@ -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: []