capistrano-elb-autoscaling 1.0.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/.gitignore +26 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +20 -0
- data/README.md +97 -0
- data/Rakefile +5 -0
- data/elbas.gemspec +31 -0
- data/lib/elbas/aws/ami.rb +71 -0
- data/lib/elbas/aws/autoscale_group.rb +52 -0
- data/lib/elbas/aws/base.rb +39 -0
- data/lib/elbas/aws/instance.rb +29 -0
- data/lib/elbas/aws/instance_collection.rb +35 -0
- data/lib/elbas/aws/launch_template.rb +39 -0
- data/lib/elbas/aws/snapshot.rb +21 -0
- data/lib/elbas/aws/taggable.rb +18 -0
- data/lib/elbas/capistrano.rb +34 -0
- data/lib/elbas/errors/no_launch_template.rb +6 -0
- data/lib/elbas/logger.rb +38 -0
- data/lib/elbas/retryable.rb +41 -0
- data/lib/elbas/tasks/elbas.rake +38 -0
- data/lib/elbas/version.rb +3 -0
- data/lib/elbas.rb +21 -0
- data/spec/aws/ami_spec.rb +140 -0
- data/spec/aws/autoscale_group_spec.rb +67 -0
- data/spec/aws/instance_collection_spec.rb +38 -0
- data/spec/aws/instance_spec.rb +30 -0
- data/spec/aws/launch_template_spec.rb +52 -0
- data/spec/aws/taggable_spec.rb +53 -0
- data/spec/capistrano_spec.rb +77 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/stubs/CreateImage.200.xml +4 -0
- data/spec/support/stubs/CreateLaunchConfiguration.200.xml +5 -0
- data/spec/support/stubs/CreateLaunchTemplateVersion.200.xml +16 -0
- data/spec/support/stubs/CreateTags.200.xml +5 -0
- data/spec/support/stubs/DeleteLaunchConfiguration.200.xml +5 -0
- data/spec/support/stubs/DeleteSnapshot.200.xml +4 -0
- data/spec/support/stubs/DeregisterImage.200.xml +4 -0
- data/spec/support/stubs/DescribeAutoScalingGroups.200.xml +60 -0
- data/spec/support/stubs/DescribeImages.200.xml +70 -0
- data/spec/support/stubs/DescribeInstances.200.xml +231 -0
- data/spec/support/stubs/DescribeInstances_Empty.200.xml +17 -0
- data/spec/support/stubs/DescribeInstances_MultipleReservations.200.xml +243 -0
- data/spec/support/stubs/DescribeInstances_MultipleRunning.200.xml +338 -0
- data/spec/support/stubs/DescribeLaunchConfigurations.200.xml +29 -0
- data/spec/support/stubs/DescribeSnapshots.200.xml +18 -0
- data/spec/support/stubs/DescribeTags.200.xml +17 -0
- data/spec/support/stubs/UpdateAutoScalingGroup.200.xml +5 -0
- data/spec/support/stubs/security-credentials.200.json +9 -0
- metadata +243 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9414a155b6afd51ac1d0a5357cab5599baf5c51bc4efeaf601d16acd54c310c0
|
4
|
+
data.tar.gz: c68a44e88ca7dedbf8072d932687a4385e966d5f0a58af39620a53254b3ad826
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ccc33d9417dcefd95575116cadac35ca400cc9ab849894d81286419f7fafa77986dc3bc86d47d9b3e5b9bff4e7f89e9e311b34ad88c0ed65f7322b936178ef0
|
7
|
+
data.tar.gz: f77880fe0f5974c425646278a031f00075d02826e5dcb7e521bdcd1522eb378288a222993e6e269e8347050fb532ff22b2b646badbdb7d181ed5c826868c8d46
|
data/.gitignore
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
bin/
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
/Gemfile.lock
|
5
|
+
.bundle
|
6
|
+
.config
|
7
|
+
.yardoc
|
8
|
+
InstalledFiles
|
9
|
+
_yardoc
|
10
|
+
coverage
|
11
|
+
doc/
|
12
|
+
lib/bundler/man
|
13
|
+
pkg
|
14
|
+
rdoc
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
|
24
|
+
.ruby-version
|
25
|
+
|
26
|
+
.idea
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
*Versions < 3 of ELBAS are no longer being maintained. I will only be maintaining the current feature-set which relies on Launch Templates and AWS SDK v3.*
|
2
|
+
|
3
|
+
# Capistrano ELBAS (Elastic Load Balancer & AutoScaling)
|
4
|
+
|
5
|
+
[](https://badge.fury.io/rb/elbas)
|
6
|
+
|
7
|
+
ELBAS was written to ease the deployment of Rails applications to AWS AutoScale
|
8
|
+
groups. During your Capistrano deployment, ELBAS will:
|
9
|
+
|
10
|
+
- Deploy your code to each running instance connected to a given AutoScale group
|
11
|
+
- After deployment, create an AMI from one of the running instances
|
12
|
+
- Update the AutoScale group's launch template with the AMI ID
|
13
|
+
- Delete any outdated AMIs created by previous ELBAS deployments
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add to Gemfile, then `bundle`:
|
18
|
+
|
19
|
+
`gem 'elbas'`
|
20
|
+
|
21
|
+
Add to Capfile:
|
22
|
+
|
23
|
+
`require 'elbas/capistrano'`
|
24
|
+
|
25
|
+
## Configuration
|
26
|
+
|
27
|
+
Setup AWS credentials:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
set :aws_access_key, ENV['AWS_ACCESS_KEY_ID']
|
31
|
+
set :aws_secret_key, ENV['AWS_SECRET_ACCESS_KEY']
|
32
|
+
set :aws_region, ENV['AWS_REGION']
|
33
|
+
```
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Instead of using Capistrano's `server` method, use `autoscale` instead in
|
38
|
+
`deploy/<environment>.rb` (replace <environment> with your environment). Provide
|
39
|
+
the name of your AutoScale group instead of a hostname:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
autoscale 'my-autoscale-group', user: 'apps', roles: [:app, :web, :db]
|
43
|
+
```
|
44
|
+
|
45
|
+
If you have multiple autoscaling groups to deploy to, specify each of them:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
autoscale 'app-autoscale-group', user: 'apps', roles: [:app, :web]
|
49
|
+
autoscale 'worker-autoscale-group', user: 'apps', roles: [:worker]
|
50
|
+
```
|
51
|
+
|
52
|
+
Run `cap production deploy`.
|
53
|
+
|
54
|
+
**As of version 3, your AWS setup must use launch templates as opposed to launch
|
55
|
+
configurations.** This allows ELBAS to simply create a new launch template version
|
56
|
+
with the new AMI ID after a deployment. It no longer needs to update your
|
57
|
+
AutoScale group or mess around with network settings, instance sizes, etc., as
|
58
|
+
that information is all contained within the launch template. Failure to use a
|
59
|
+
launch template will result in a `Elbas::Errors::NoLaunchTemplate` error.
|
60
|
+
|
61
|
+
### Customizing Server Properties
|
62
|
+
|
63
|
+
You can pass a block to `autoscale` and return properties for any specific server.
|
64
|
+
The block accepts the server and the server's index as arguments.
|
65
|
+
|
66
|
+
For example, if you want to apply the `:db` role to only the first server:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
autoscale 'my-autoscale-group', roles: [:app, :web] do |server, i|
|
70
|
+
{ roles: [:app, :web, :db] } if i == 0
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
Returning `nil` from this block will cause the server to use the properties
|
75
|
+
passed to `autoscale`.
|
76
|
+
|
77
|
+
Returning anything but `nil` will override the entire properties hash (as
|
78
|
+
opposed to merging the two hashes together).
|
79
|
+
|
80
|
+
### Listing Servers
|
81
|
+
|
82
|
+
You may need to SSH into your servers while debugging deployed code, and
|
83
|
+
not everyone has a jumpbox on a basic AutoScaling setup. ELBAS provides a command
|
84
|
+
that will list the `ssh` command necessary to connect to each server in any given
|
85
|
+
environment:
|
86
|
+
|
87
|
+
```
|
88
|
+
cap production elbas:ssh
|
89
|
+
```
|
90
|
+
|
91
|
+
Output will be something like:
|
92
|
+
|
93
|
+
```
|
94
|
+
[ELBAS] Adding server: ec2-12-34-567-890.compute-1.amazonaws.com
|
95
|
+
[ELBAS] SSH commands:
|
96
|
+
[ELBAS] 1) ssh deploy@ec2-12-34-567-890.compute-1.amazonaws.com
|
97
|
+
```
|
data/Rakefile
ADDED
data/elbas.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'elbas/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'capistrano-elb-autoscaling'
|
8
|
+
spec.version = Elbas::VERSION
|
9
|
+
spec.authors = ['Heng Gek']
|
10
|
+
spec.email = ['tanhengegk@gmail.com']
|
11
|
+
spec.summary = 'Capistrano plugin for deploying to AWS Auto Scaling Group.'
|
12
|
+
spec.description = spec.summary
|
13
|
+
spec.homepage = 'https://github.com/tanhenggek/capistrano-elb-autoscaling'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler'
|
22
|
+
spec.add_development_dependency 'byebug'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
spec.add_development_dependency 'webmock'
|
26
|
+
spec.add_development_dependency 'webmock-rspec-helper'
|
27
|
+
|
28
|
+
spec.add_dependency 'aws-sdk-autoscaling', '~> 1'
|
29
|
+
spec.add_dependency 'aws-sdk-ec2', '~> 1'
|
30
|
+
spec.add_dependency 'capistrano', '> 3'
|
31
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class AMI < Base
|
4
|
+
include Taggable
|
5
|
+
|
6
|
+
DEPLOY_ID_TAG = 'ELBAS-Deploy-id'.freeze
|
7
|
+
DEPLOY_GROUP_TAG = 'ELBAS-Deploy-group'.freeze
|
8
|
+
|
9
|
+
attr_reader :id, :snapshots
|
10
|
+
|
11
|
+
def initialize(id, snapshots = [])
|
12
|
+
@id = id
|
13
|
+
@aws_counterpart = ::Aws::EC2::Image.new id, client: aws_client
|
14
|
+
|
15
|
+
@snapshots = snapshots.map do |snapshot|
|
16
|
+
Elbas::AWS::Snapshot.new snapshot&.ebs&.snapshot_id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def deploy_id
|
21
|
+
tags[DEPLOY_ID_TAG]
|
22
|
+
end
|
23
|
+
|
24
|
+
def deploy_group
|
25
|
+
tags[DEPLOY_GROUP_TAG]
|
26
|
+
end
|
27
|
+
|
28
|
+
def ancestors
|
29
|
+
aws_amis_in_deploy_group.select { |aws_ami|
|
30
|
+
deploy_id_from_aws_tags(aws_ami.tags) != deploy_id
|
31
|
+
}.map { |aws_ami|
|
32
|
+
self.class.new aws_ami.image_id, aws_ami.block_device_mappings
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def delete
|
37
|
+
aws_client.deregister_image image_id: id
|
38
|
+
snapshots.each(&:delete)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.create(instance, no_reboot: true)
|
42
|
+
ami = instance.aws_counterpart.create_image({
|
43
|
+
name: "ELBAS-ami-#{Time.now.to_i}",
|
44
|
+
instance_id: instance.id,
|
45
|
+
no_reboot: no_reboot
|
46
|
+
})
|
47
|
+
|
48
|
+
new ami.id
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
def aws_namespace
|
53
|
+
::Aws::EC2
|
54
|
+
end
|
55
|
+
|
56
|
+
def aws_amis_in_deploy_group
|
57
|
+
aws_client.describe_images({
|
58
|
+
owners: ['self'],
|
59
|
+
filters: [{
|
60
|
+
name: "tag:#{DEPLOY_GROUP_TAG}",
|
61
|
+
values: [deploy_group],
|
62
|
+
}]
|
63
|
+
}).images
|
64
|
+
end
|
65
|
+
|
66
|
+
def deploy_id_from_aws_tags(tags)
|
67
|
+
tags.detect { |tag| tag.key == DEPLOY_ID_TAG }&.value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class AutoscaleGroup < Base
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@aws_counterpart = query_autoscale_group_by_name(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def instance_ids
|
12
|
+
aws_counterpart.instances.map(&:instance_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def instances
|
16
|
+
InstanceCollection.new instance_ids
|
17
|
+
end
|
18
|
+
|
19
|
+
def launch_template
|
20
|
+
lts = aws_launch_template || aws_launch_template_specification
|
21
|
+
raise Elbas::Errors::NoLaunchTemplate unless lts
|
22
|
+
|
23
|
+
LaunchTemplate.new(
|
24
|
+
lts.launch_template_id,
|
25
|
+
lts.launch_template_name,
|
26
|
+
lts.version
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def aws_namespace
|
32
|
+
::Aws::AutoScaling
|
33
|
+
end
|
34
|
+
|
35
|
+
def query_autoscale_group_by_name(name)
|
36
|
+
aws_client
|
37
|
+
.describe_auto_scaling_groups(auto_scaling_group_names: [name])
|
38
|
+
.auto_scaling_groups
|
39
|
+
.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def aws_launch_template
|
43
|
+
aws_counterpart.launch_template
|
44
|
+
end
|
45
|
+
|
46
|
+
def aws_launch_template_specification
|
47
|
+
aws_counterpart.mixed_instances_policy&.launch_template
|
48
|
+
&.launch_template_specification
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class Base
|
4
|
+
include Capistrano::DSL
|
5
|
+
|
6
|
+
attr_reader :aws_counterpart
|
7
|
+
|
8
|
+
def aws_client(namespace = aws_namespace)
|
9
|
+
@aws_client ||= begin
|
10
|
+
options = {}
|
11
|
+
options[:region] = aws_region if aws_region
|
12
|
+
options[:credentials] = aws_credentials if aws_credentials.set?
|
13
|
+
|
14
|
+
namespace::Client.new options
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def aws_credentials
|
19
|
+
fetch :aws_credentials, ::Aws::Credentials.new(aws_access_key, aws_secret_key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def aws_access_key
|
23
|
+
fetch :aws_access_key
|
24
|
+
end
|
25
|
+
|
26
|
+
def aws_secret_key
|
27
|
+
fetch :aws_secret_key
|
28
|
+
end
|
29
|
+
|
30
|
+
def aws_region
|
31
|
+
fetch :aws_region
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.aws_client(namespace = aws_namespace)
|
35
|
+
Base.new.aws_client namespace
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class Instance < Base
|
4
|
+
STATE_RUNNING = 16.freeze
|
5
|
+
|
6
|
+
attr_reader :aws_counterpart, :id, :state
|
7
|
+
|
8
|
+
def initialize(id, public_dns, state)
|
9
|
+
@id = id
|
10
|
+
@public_dns = public_dns
|
11
|
+
@state = state
|
12
|
+
@aws_counterpart = aws_namespace::Instance.new id, client: aws_client
|
13
|
+
end
|
14
|
+
|
15
|
+
def hostname
|
16
|
+
@public_dns
|
17
|
+
end
|
18
|
+
|
19
|
+
def running?
|
20
|
+
state == STATE_RUNNING
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def aws_namespace
|
25
|
+
::Aws::EC2
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class InstanceCollection < Base
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :instances
|
7
|
+
|
8
|
+
def initialize(ids)
|
9
|
+
@ids = ids
|
10
|
+
@instances = query_instances_by_ids(ids).map do |i|
|
11
|
+
Instance.new(i.instance_id, i.public_dns_name, i.state.code)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def running
|
16
|
+
select(&:running?)
|
17
|
+
end
|
18
|
+
|
19
|
+
def each(&block)
|
20
|
+
instances.each(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def aws_namespace
|
25
|
+
::Aws::EC2
|
26
|
+
end
|
27
|
+
|
28
|
+
def query_instances_by_ids(ids)
|
29
|
+
aws_client
|
30
|
+
.describe_instances(instance_ids: @ids)
|
31
|
+
.reservations.flat_map(&:instances)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class LaunchTemplate < Base
|
4
|
+
attr_reader :id, :name, :version
|
5
|
+
|
6
|
+
def initialize(id, name, version)
|
7
|
+
@id = id
|
8
|
+
@name = name
|
9
|
+
@version = version
|
10
|
+
end
|
11
|
+
|
12
|
+
def update(ami)
|
13
|
+
latest = aws_client.create_launch_template_version({
|
14
|
+
launch_template_data: { image_id: ami.id },
|
15
|
+
launch_template_id: self.id,
|
16
|
+
source_version: self.version
|
17
|
+
}).launch_template_version
|
18
|
+
|
19
|
+
response = aws_client.modify_launch_template({
|
20
|
+
default_version: latest.version_number.to_s,
|
21
|
+
launch_template_id: latest.launch_template_id
|
22
|
+
})
|
23
|
+
return unless response.successful?
|
24
|
+
|
25
|
+
self.class.new(
|
26
|
+
latest&.launch_template_id,
|
27
|
+
latest&.launch_template_name,
|
28
|
+
latest&.version_number
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def aws_namespace
|
35
|
+
::Aws::EC2
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
class Snapshot < Base
|
4
|
+
attr_reader :id
|
5
|
+
|
6
|
+
def initialize(id)
|
7
|
+
@id = id
|
8
|
+
end
|
9
|
+
|
10
|
+
def delete
|
11
|
+
return unless id
|
12
|
+
aws_client.delete_snapshot snapshot_id: id
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def aws_namespace
|
17
|
+
::Aws::EC2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Elbas
|
2
|
+
module AWS
|
3
|
+
module Taggable
|
4
|
+
def tag(key, value)
|
5
|
+
@tags ||= {}
|
6
|
+
|
7
|
+
Elbas::Retryable.times(3).delay(5) do
|
8
|
+
aws_counterpart.create_tags tags: [{ key: key, value: value }]
|
9
|
+
@tags[key] = value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def tags
|
14
|
+
@tags || {}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'capistrano/dsl'
|
2
|
+
|
3
|
+
load File.expand_path("../tasks/elbas.rake", __FILE__)
|
4
|
+
|
5
|
+
def autoscale(groupname, properties = {})
|
6
|
+
include Capistrano::DSL
|
7
|
+
include Elbas::Logger
|
8
|
+
|
9
|
+
set :aws_autoscale_group_names, Array(fetch(:aws_autoscale_group_names)).push(groupname)
|
10
|
+
|
11
|
+
asg = Elbas::AWS::AutoscaleGroup.new groupname
|
12
|
+
instances = asg.instances.running
|
13
|
+
|
14
|
+
info "Auto Scaling Group: #{groupname}"
|
15
|
+
instances.each.with_index do |instance, i|
|
16
|
+
info "Adding server: #{instance.hostname}"
|
17
|
+
|
18
|
+
props = nil
|
19
|
+
props = yield(instance, i) if block_given?
|
20
|
+
props ||= properties
|
21
|
+
|
22
|
+
server instance.hostname, props
|
23
|
+
end
|
24
|
+
|
25
|
+
if instances.any?
|
26
|
+
after 'deploy', 'elbas:deploy'
|
27
|
+
else
|
28
|
+
error <<~MESSAGE
|
29
|
+
Could not create AMI because no running instances were found in the specified
|
30
|
+
AutoScale group. Ensure that the AutoScale group name is correct and that
|
31
|
+
there is at least one running instance attached to it.
|
32
|
+
MESSAGE
|
33
|
+
end
|
34
|
+
end
|
data/lib/elbas/logger.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'capistrano/doctor/output_helpers'
|
2
|
+
|
3
|
+
module Elbas
|
4
|
+
module Logger
|
5
|
+
include Capistrano::Doctor::OutputHelpers
|
6
|
+
|
7
|
+
PREFIX_TEXT = '[ELBAS] '.freeze
|
8
|
+
|
9
|
+
def info(message)
|
10
|
+
$stdout.puts [prefix, message, "\n"].join
|
11
|
+
end
|
12
|
+
|
13
|
+
def error(message)
|
14
|
+
$stderr.puts [error_prefix, message, "\n"].join
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def prefix
|
19
|
+
@prefix ||= cyan(PREFIX_TEXT)
|
20
|
+
end
|
21
|
+
|
22
|
+
def error_prefix
|
23
|
+
@error_prefix ||= red(PREFIX_TEXT)
|
24
|
+
end
|
25
|
+
|
26
|
+
def cyan(text)
|
27
|
+
color_text text, :cyan
|
28
|
+
end
|
29
|
+
|
30
|
+
def red(text)
|
31
|
+
color_text text, :red
|
32
|
+
end
|
33
|
+
|
34
|
+
def color_text(text, coloring)
|
35
|
+
color.colorize text, coloring
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Elbas
|
2
|
+
class Retryable
|
3
|
+
include Elbas::Logger
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@max = 0
|
7
|
+
@delay = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def times(max, &block)
|
11
|
+
@max = max
|
12
|
+
run block if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
def delay(seconds, &block)
|
16
|
+
@delay = seconds
|
17
|
+
run block if block_given?
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(proc)
|
21
|
+
attempts ||= 0
|
22
|
+
attempts += 1
|
23
|
+
proc.call
|
24
|
+
rescue => e
|
25
|
+
info "Rescued error in retryable action: #{e.message}"
|
26
|
+
if attempts < @max
|
27
|
+
info "Retrying in #{@delay} seconds..."
|
28
|
+
sleep @delay
|
29
|
+
retry
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.times(max, &block)
|
34
|
+
new.tap { |r| r.times max }
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.delay(seconds, &block)
|
38
|
+
new.tap { |r| r.delay seconds }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'elbas'
|
2
|
+
include Elbas::Logger
|
3
|
+
|
4
|
+
namespace :elbas do
|
5
|
+
task :ssh do
|
6
|
+
include Capistrano::DSL
|
7
|
+
|
8
|
+
info "SSH commands:"
|
9
|
+
env.servers.to_a.each.with_index do |server, i|
|
10
|
+
info " #{i + 1}) ssh #{fetch(:user)}@#{server.hostname}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
task :deploy do
|
15
|
+
fetch(:aws_autoscale_group_names).each do |aws_autoscale_group_name|
|
16
|
+
info "Auto Scaling Group: #{aws_autoscale_group_name}"
|
17
|
+
asg = Elbas::AWS::AutoscaleGroup.new aws_autoscale_group_name
|
18
|
+
|
19
|
+
info "Creating AMI from a running instance..."
|
20
|
+
ami = Elbas::AWS::AMI.create asg.instances.running.sample
|
21
|
+
ami.tag 'ELBAS-Deploy-group', asg.name
|
22
|
+
ami.tag 'ELBAS-Deploy-id', env.timestamp.to_i.to_s
|
23
|
+
info "Created AMI: #{ami.id}"
|
24
|
+
|
25
|
+
info "Updating launch template with the new AMI..."
|
26
|
+
launch_template = asg.launch_template.update ami
|
27
|
+
info "Updated launch template, new default version = #{launch_template.version}"
|
28
|
+
|
29
|
+
info "Cleaning up old AMIs..."
|
30
|
+
ami.ancestors.each do |ancestor|
|
31
|
+
info "Deleting old AMI: #{ancestor.id}"
|
32
|
+
ancestor.delete
|
33
|
+
end
|
34
|
+
|
35
|
+
info "Deployment complete!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|