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,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
|