capistrano-asg-rolling 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-autoscaling'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module ASG
|
7
|
+
module Rolling
|
8
|
+
# AWS EC2 Auto Scaling Group.
|
9
|
+
class AutoscaleGroup
|
10
|
+
include AWS
|
11
|
+
|
12
|
+
LIFECYCLE_STATE_IN_SERVICE = 'InService'
|
13
|
+
LIFECYCLE_STATE_STANDBY = 'Standby'
|
14
|
+
|
15
|
+
attr_reader :name, :properties
|
16
|
+
|
17
|
+
def initialize(name, properties = {})
|
18
|
+
@name = name
|
19
|
+
@properties = properties
|
20
|
+
end
|
21
|
+
|
22
|
+
def exists?
|
23
|
+
aws_autoscaling_group.exists?
|
24
|
+
end
|
25
|
+
|
26
|
+
def launch_template
|
27
|
+
@launch_template ||= begin
|
28
|
+
template = aws_autoscaling_group.launch_template
|
29
|
+
raise Capistrano::ASG::Rolling::NoLaunchTemplate if template.nil?
|
30
|
+
|
31
|
+
LaunchTemplate.new(template.launch_template_id, template.version)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def subnet_ids
|
36
|
+
aws_autoscaling_group.vpc_zone_identifier.split(',')
|
37
|
+
end
|
38
|
+
|
39
|
+
def instance_warmup_time
|
40
|
+
aws_autoscaling_group.health_check_grace_period
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_instance_refresh
|
44
|
+
aws_autoscaling_client.start_instance_refresh(
|
45
|
+
auto_scaling_group_name: name,
|
46
|
+
strategy: 'Rolling',
|
47
|
+
preferences: {
|
48
|
+
instance_warmup: instance_warmup_time,
|
49
|
+
min_healthy_percentage: 100
|
50
|
+
}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns instances with lifecycle state "InService" for this Auto Scaling Group.
|
55
|
+
def instances
|
56
|
+
instance_ids = aws_autoscaling_group.instances.select { |i| i.lifecycle_state == LIFECYCLE_STATE_IN_SERVICE }.map(&:instance_id)
|
57
|
+
return [] if instance_ids.empty?
|
58
|
+
|
59
|
+
response = aws_ec2_client.describe_instances(instance_ids: instance_ids)
|
60
|
+
response.reservations.flat_map(&:instances).map do |instance|
|
61
|
+
Instance.new(instance.instance_id, instance.private_ip_address, instance.public_ip_address, instance.image_id, self)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def enter_standby(instance)
|
66
|
+
instance = aws_autoscaling_group.instances.find { |i| i.id == instance.id }
|
67
|
+
return if instance.nil?
|
68
|
+
|
69
|
+
instance.enter_standby(should_decrement_desired_capacity: true)
|
70
|
+
|
71
|
+
loop do
|
72
|
+
instance.load
|
73
|
+
break if instance.lifecycle_state == LIFECYCLE_STATE_STANDBY
|
74
|
+
|
75
|
+
sleep 1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def exit_standby(instance)
|
80
|
+
instance = aws_autoscaling_group.instances.find { |i| i.id == instance.id }
|
81
|
+
return if instance.nil?
|
82
|
+
|
83
|
+
instance.exit_standby
|
84
|
+
end
|
85
|
+
|
86
|
+
def rolling?
|
87
|
+
properties.fetch(:rolling, true)
|
88
|
+
end
|
89
|
+
|
90
|
+
def name_tag
|
91
|
+
"Deployment for #{name}"
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def aws_autoscaling_group
|
97
|
+
@aws_autoscaling_group ||= ::Aws::AutoScaling::AutoScalingGroup.new(name: name, client: aws_autoscaling_client)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module ASG
|
7
|
+
module Rolling
|
8
|
+
# Collection of Auto Scaling Groups.
|
9
|
+
class AutoscaleGroups
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize(groups = [])
|
13
|
+
@groups = groups
|
14
|
+
end
|
15
|
+
|
16
|
+
def <<(group)
|
17
|
+
@groups << group
|
18
|
+
end
|
19
|
+
|
20
|
+
def each(&block)
|
21
|
+
@groups.reject { |group| filtered?(group) }.each(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_launch_template(launch_template)
|
25
|
+
self.class.new(select { |group| group.launch_template == launch_template })
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_launch_templates(amis:, description: nil)
|
29
|
+
launch_templates = Set.new
|
30
|
+
|
31
|
+
amis.each do |ami|
|
32
|
+
old_image_id = ami.instance.image_id
|
33
|
+
new_image_id = ami.id
|
34
|
+
|
35
|
+
find_launch_templates_for(image_id: old_image_id).each do |launch_template|
|
36
|
+
launch_templates << launch_template.create_version(image_id: new_image_id, description: description)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
launch_templates
|
41
|
+
end
|
42
|
+
|
43
|
+
def start_instance_refresh
|
44
|
+
each(&:start_instance_refresh)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def filtered?(group)
|
50
|
+
return false if Configuration.auto_scaling_group_name.nil?
|
51
|
+
|
52
|
+
Configuration.auto_scaling_group_name != group.name
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_launch_templates_for(image_id:)
|
56
|
+
launch_templates = Set.new
|
57
|
+
|
58
|
+
each do |group|
|
59
|
+
launch_template = group.launch_template
|
60
|
+
next if launch_template.image_id != image_id
|
61
|
+
|
62
|
+
launch_templates << launch_template
|
63
|
+
end
|
64
|
+
|
65
|
+
launch_templates
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aws-sdk-autoscaling'
|
4
|
+
require 'aws-sdk-ec2'
|
5
|
+
|
6
|
+
module Capistrano
|
7
|
+
module ASG
|
8
|
+
module Rolling
|
9
|
+
# AWS SDK.
|
10
|
+
module AWS
|
11
|
+
def aws_autoscaling_client
|
12
|
+
@aws_autoscaling_client ||= ::Aws::AutoScaling::Client.new(aws_options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def aws_ec2_client
|
16
|
+
@aws_ec2_client ||= ::Aws::EC2::Client.new(aws_options)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def aws_options
|
22
|
+
options = {}
|
23
|
+
options[:region] = aws_region if aws_region
|
24
|
+
options[:credentials] = aws_credentials if aws_credentials.set?
|
25
|
+
options[:http_wire_trace] = true if ENV['AWS_HTTP_WIRE_TRACE'] == '1'
|
26
|
+
options
|
27
|
+
end
|
28
|
+
|
29
|
+
def aws_credentials
|
30
|
+
::Aws::Credentials.new(aws_access_key_id, aws_secret_access_key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def aws_access_key_id
|
34
|
+
Configuration.aws_access_key_id
|
35
|
+
end
|
36
|
+
|
37
|
+
def aws_secret_access_key
|
38
|
+
Configuration.aws_secret_access_key
|
39
|
+
end
|
40
|
+
|
41
|
+
def aws_region
|
42
|
+
Configuration.aws_region
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module ASG
|
7
|
+
module Rolling
|
8
|
+
# Singleton that holds the configuration.
|
9
|
+
module Configuration
|
10
|
+
extend Capistrano::DSL
|
11
|
+
|
12
|
+
module_function
|
13
|
+
|
14
|
+
# Registered Auto Scaling Groups.
|
15
|
+
def autoscale_groups
|
16
|
+
@autoscale_groups ||= AutoscaleGroups.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Launched Instances.
|
20
|
+
def instances
|
21
|
+
@instances ||= Instances.new
|
22
|
+
end
|
23
|
+
|
24
|
+
# Updated Launch Templates.
|
25
|
+
def launch_templates
|
26
|
+
@launch_templates ||= Set.new
|
27
|
+
end
|
28
|
+
|
29
|
+
def aws_access_key_id
|
30
|
+
fetch(:aws_access_key_id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def aws_secret_access_key
|
34
|
+
fetch(:aws_secret_access_key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def aws_region
|
38
|
+
fetch(:aws_region)
|
39
|
+
end
|
40
|
+
|
41
|
+
def auto_scaling_group_name
|
42
|
+
fetch(:asg_rolling_group_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ssh_options
|
46
|
+
fetch(:asg_rolling_ssh_options, fetch(:ssh_options))
|
47
|
+
end
|
48
|
+
|
49
|
+
def instance_overrides
|
50
|
+
fetch(:asg_rolling_instance_overrides)
|
51
|
+
end
|
52
|
+
|
53
|
+
def use_private_ip_address?
|
54
|
+
fetch(:asg_rolling_use_private_ip_address)
|
55
|
+
end
|
56
|
+
|
57
|
+
def keep_versions
|
58
|
+
fetch(:asg_rolling_keep_versions, fetch(:keep_releases))
|
59
|
+
end
|
60
|
+
|
61
|
+
def verbose?
|
62
|
+
fetch(:asg_rolling_verbose)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module ASG
|
5
|
+
module Rolling
|
6
|
+
# Adds the autoscale DSL to the Capistrano configuration:
|
7
|
+
#
|
8
|
+
# autoscale 'my-asg', user: 'deployer', roles: %w(app assets)
|
9
|
+
#
|
10
|
+
module DSL
|
11
|
+
def autoscale(name, properties = {})
|
12
|
+
group = Capistrano::ASG::Rolling::AutoscaleGroup.new(name, properties)
|
13
|
+
raise Capistrano::ASG::Rolling::NoAutoScalingGroup, "Auto Scaling Group #{name} could not be found." unless group.exists?
|
14
|
+
|
15
|
+
Capistrano::ASG::Rolling::Configuration.autoscale_groups << group
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
extend Capistrano::ASG::Rolling::DSL # rubocop:disable Style/MixinUsage
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module ASG
|
5
|
+
module Rolling
|
6
|
+
# Base class for exceptions.
|
7
|
+
class Exception < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
class NoAutoScalingGroup < Capistrano::ASG::Rolling::Exception
|
11
|
+
end
|
12
|
+
|
13
|
+
class NoLaunchTemplate < Capistrano::ASG::Rolling::Exception
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module ASG
|
7
|
+
module Rolling
|
8
|
+
# AWS EC2 instance.
|
9
|
+
class Instance
|
10
|
+
include AWS
|
11
|
+
|
12
|
+
attr_reader :id, :private_ip_address, :public_ip_address, :image_id, :autoscale_group
|
13
|
+
attr_accessor :auto_terminate, :terminated
|
14
|
+
alias auto_terminate? auto_terminate
|
15
|
+
alias terminated? terminated
|
16
|
+
|
17
|
+
def initialize(id, private_ip_address, public_ip_address, image_id, autoscale_group)
|
18
|
+
@id = id
|
19
|
+
@private_ip_address = private_ip_address
|
20
|
+
@public_ip_address = public_ip_address
|
21
|
+
@image_id = image_id
|
22
|
+
@autoscale_group = autoscale_group
|
23
|
+
@auto_terminate = true
|
24
|
+
@terminated = false
|
25
|
+
end
|
26
|
+
|
27
|
+
# Launch a new instance based on settings from Auto Scaling Group and associated Launch Template.
|
28
|
+
def self.run(autoscaling_group:, overrides: nil)
|
29
|
+
launch_template = autoscaling_group.launch_template
|
30
|
+
|
31
|
+
aws_ec2_client = autoscaling_group.aws_ec2_client
|
32
|
+
options = {
|
33
|
+
min_count: 1,
|
34
|
+
max_count: 1,
|
35
|
+
launch_template: {
|
36
|
+
launch_template_id: launch_template.id,
|
37
|
+
version: launch_template.version
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
# Add a subnet defined in the Auto Scaling Group, but only when the Launch Template
|
42
|
+
# does not define a network interface. Otherwise it will trigger the following error:
|
43
|
+
# => Network interfaces and an instance-level subnet ID may not be specified on the same request
|
44
|
+
options[:subnet_id] = autoscaling_group.subnet_ids.sample if launch_template.network_interfaces.empty?
|
45
|
+
|
46
|
+
# Optionally override settings in the Launch Template.
|
47
|
+
options.merge!(overrides) if overrides
|
48
|
+
|
49
|
+
options[:tag_specifications] = [
|
50
|
+
{ resource_type: 'instance', tags: [{ key: 'Name', value: autoscaling_group.name_tag }] },
|
51
|
+
{ resource_type: 'volume', tags: [{ key: 'Name', value: autoscaling_group.name_tag }] }
|
52
|
+
]
|
53
|
+
|
54
|
+
response = aws_ec2_client.run_instances(options)
|
55
|
+
|
56
|
+
instance = response.instances.first
|
57
|
+
|
58
|
+
# Wait until the instance is running and has a public IP address.
|
59
|
+
aws_instance = ::Aws::EC2::Instance.new(instance.instance_id, client: aws_ec2_client)
|
60
|
+
instance = aws_instance.wait_until_running
|
61
|
+
|
62
|
+
new(instance.instance_id, instance.private_ip_address, instance.public_ip_address, instance.image_id, autoscaling_group)
|
63
|
+
end
|
64
|
+
|
65
|
+
def wait_for_ssh
|
66
|
+
started_at = Time.now
|
67
|
+
|
68
|
+
loop do
|
69
|
+
result = SSH.test?(ip_address, autoscale_group.properties[:user], Configuration.ssh_options)
|
70
|
+
|
71
|
+
break if result || Time.now - started_at > 300
|
72
|
+
|
73
|
+
sleep 1
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def ip_address
|
78
|
+
Configuration.use_private_ip_address? ? private_ip_address : public_ip_address
|
79
|
+
end
|
80
|
+
|
81
|
+
def stop
|
82
|
+
aws_ec2_client.stop_instances(instance_ids: [id])
|
83
|
+
aws_ec2_client.wait_until(:instance_stopped, instance_ids: [id])
|
84
|
+
end
|
85
|
+
|
86
|
+
def terminate(wait: false)
|
87
|
+
aws_ec2_client.terminate_instances(instance_ids: [id])
|
88
|
+
aws_ec2_client.wait_until(:instance_terminated, instance_ids: [id]) if wait
|
89
|
+
|
90
|
+
@terminated = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_ami(name: nil, description: nil)
|
94
|
+
now = Time.now
|
95
|
+
name ||= "#{autoscale_group.name_tag} on #{now.strftime('%Y-%m-%d')} at #{now.strftime('%H.%M.%S')}"
|
96
|
+
|
97
|
+
AMI.create(instance: self, name: name, description: description, tags: { 'Name' => autoscale_group.name_tag })
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module ASG
|
5
|
+
module Rolling
|
6
|
+
# Collection of Instances. Runs commands on instances in parallel.
|
7
|
+
class Instances
|
8
|
+
include Enumerable
|
9
|
+
|
10
|
+
def initialize(instances = [])
|
11
|
+
@instances = instances
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(instance)
|
15
|
+
@instances << instance
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
instances.each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def empty?
|
23
|
+
instances.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def auto_terminate
|
27
|
+
self.class.new(select(&:auto_terminate?))
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_image(image_id)
|
31
|
+
self.class.new(select { |instance| instance.image_id == image_id })
|
32
|
+
end
|
33
|
+
|
34
|
+
def wait_for_ssh
|
35
|
+
Parallel.run(instances, &:wait_for_ssh)
|
36
|
+
end
|
37
|
+
|
38
|
+
def stop
|
39
|
+
Parallel.run(instances, &:stop)
|
40
|
+
end
|
41
|
+
|
42
|
+
def terminate
|
43
|
+
Parallel.run(instances, &:terminate)
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_ami(name: nil, description: nil)
|
47
|
+
Parallel.run(instances) do |instance|
|
48
|
+
instance.create_ami(name: name, description: description)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def instances
|
55
|
+
@instances.reject(&:terminated?)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module ASG
|
5
|
+
module Rolling
|
6
|
+
# AWS EC2 Launch Template.
|
7
|
+
class LaunchTemplate
|
8
|
+
include AWS
|
9
|
+
|
10
|
+
attr_reader :id, :version, :default_version
|
11
|
+
alias default_version? default_version
|
12
|
+
|
13
|
+
def initialize(id, version, default_version: false)
|
14
|
+
@id = id
|
15
|
+
@version = version.to_s
|
16
|
+
@default_version = default_version
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_version(image_id:, description: nil)
|
20
|
+
response = aws_ec2_client.create_launch_template_version(
|
21
|
+
launch_template_data: { image_id: image_id },
|
22
|
+
launch_template_id: id,
|
23
|
+
source_version: version,
|
24
|
+
version_description: description
|
25
|
+
)
|
26
|
+
version = response.launch_template_version
|
27
|
+
|
28
|
+
self.class.new(version.launch_template_id, version.version_number, default_version: version.default_version)
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete
|
32
|
+
aws_ec2_client.delete_launch_template_versions(
|
33
|
+
launch_template_id: id,
|
34
|
+
versions: [version]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def ami
|
39
|
+
@ami ||= AMI.new(image_id)
|
40
|
+
end
|
41
|
+
|
42
|
+
def previous_versions
|
43
|
+
aws_ec2_client.describe_launch_template_versions(launch_template_id: id)
|
44
|
+
.launch_template_versions
|
45
|
+
.sort_by { |v| -v.version_number }
|
46
|
+
.select { |v| v.version_number < version_number }
|
47
|
+
.map { |v| self.class.new(v.launch_template_id, v.version_number, default_version: v.default_version) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def version_number
|
51
|
+
aws_describe_launch_template_version.version_number
|
52
|
+
end
|
53
|
+
|
54
|
+
def image_id
|
55
|
+
aws_describe_launch_template_version.launch_template_data.image_id
|
56
|
+
end
|
57
|
+
|
58
|
+
def network_interfaces
|
59
|
+
aws_describe_launch_template_version.launch_template_data.network_interfaces
|
60
|
+
end
|
61
|
+
|
62
|
+
def security_group_ids
|
63
|
+
aws_describe_launch_template_version.launch_template_data.security_group_ids
|
64
|
+
end
|
65
|
+
|
66
|
+
# Object equality for Launch Templates is only by ID. Version number is deliberately not taken in account.
|
67
|
+
def ==(other)
|
68
|
+
id == other.id
|
69
|
+
end
|
70
|
+
|
71
|
+
alias eql? ==
|
72
|
+
|
73
|
+
def hash
|
74
|
+
id.hash
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def aws_describe_launch_template_version
|
80
|
+
@aws_describe_launch_template_version ||=
|
81
|
+
aws_ec2_client.describe_launch_template_versions(launch_template_id: id, versions: [version])
|
82
|
+
.launch_template_versions.first
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module ASG
|
5
|
+
module Rolling
|
6
|
+
# Logging support.
|
7
|
+
class Logger
|
8
|
+
def initialize(verbose: false)
|
9
|
+
@verbose = verbose
|
10
|
+
end
|
11
|
+
|
12
|
+
def info(text)
|
13
|
+
$stdout.puts format_text(text)
|
14
|
+
end
|
15
|
+
|
16
|
+
def error(text)
|
17
|
+
$stderr.puts color(format_text(text), :red) # rubocop:disable Style/StderrPuts
|
18
|
+
end
|
19
|
+
|
20
|
+
def verbose(text)
|
21
|
+
info(text) if @verbose
|
22
|
+
end
|
23
|
+
|
24
|
+
def bold(text, color = :light_white)
|
25
|
+
color(text, color, :bold)
|
26
|
+
end
|
27
|
+
|
28
|
+
def color(text, color, mode = nil)
|
29
|
+
_color.colorize(text, color, mode)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def format_text(text)
|
35
|
+
text.gsub(/\*\*(.+?)\*\*/, bold('\\1'))
|
36
|
+
end
|
37
|
+
|
38
|
+
def _color
|
39
|
+
@_color ||= SSHKit::Color.new($stdout)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent/array'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module ASG
|
7
|
+
module Rolling
|
8
|
+
# Simple helper for running code in parallel.
|
9
|
+
module Parallel
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def run(work)
|
13
|
+
result = Concurrent::Array.new
|
14
|
+
threads = []
|
15
|
+
|
16
|
+
work.each do |w|
|
17
|
+
threads << Thread.new do
|
18
|
+
result << yield(w)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
threads.each(&:join)
|
23
|
+
|
24
|
+
result
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|