capistrano-elb-autoscaling 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +26 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +20 -0
  6. data/README.md +97 -0
  7. data/Rakefile +5 -0
  8. data/elbas.gemspec +31 -0
  9. data/lib/elbas/aws/ami.rb +71 -0
  10. data/lib/elbas/aws/autoscale_group.rb +52 -0
  11. data/lib/elbas/aws/base.rb +39 -0
  12. data/lib/elbas/aws/instance.rb +29 -0
  13. data/lib/elbas/aws/instance_collection.rb +35 -0
  14. data/lib/elbas/aws/launch_template.rb +39 -0
  15. data/lib/elbas/aws/snapshot.rb +21 -0
  16. data/lib/elbas/aws/taggable.rb +18 -0
  17. data/lib/elbas/capistrano.rb +34 -0
  18. data/lib/elbas/errors/no_launch_template.rb +6 -0
  19. data/lib/elbas/logger.rb +38 -0
  20. data/lib/elbas/retryable.rb +41 -0
  21. data/lib/elbas/tasks/elbas.rake +38 -0
  22. data/lib/elbas/version.rb +3 -0
  23. data/lib/elbas.rb +21 -0
  24. data/spec/aws/ami_spec.rb +140 -0
  25. data/spec/aws/autoscale_group_spec.rb +67 -0
  26. data/spec/aws/instance_collection_spec.rb +38 -0
  27. data/spec/aws/instance_spec.rb +30 -0
  28. data/spec/aws/launch_template_spec.rb +52 -0
  29. data/spec/aws/taggable_spec.rb +53 -0
  30. data/spec/capistrano_spec.rb +77 -0
  31. data/spec/spec_helper.rb +27 -0
  32. data/spec/support/stubs/CreateImage.200.xml +4 -0
  33. data/spec/support/stubs/CreateLaunchConfiguration.200.xml +5 -0
  34. data/spec/support/stubs/CreateLaunchTemplateVersion.200.xml +16 -0
  35. data/spec/support/stubs/CreateTags.200.xml +5 -0
  36. data/spec/support/stubs/DeleteLaunchConfiguration.200.xml +5 -0
  37. data/spec/support/stubs/DeleteSnapshot.200.xml +4 -0
  38. data/spec/support/stubs/DeregisterImage.200.xml +4 -0
  39. data/spec/support/stubs/DescribeAutoScalingGroups.200.xml +60 -0
  40. data/spec/support/stubs/DescribeImages.200.xml +70 -0
  41. data/spec/support/stubs/DescribeInstances.200.xml +231 -0
  42. data/spec/support/stubs/DescribeInstances_Empty.200.xml +17 -0
  43. data/spec/support/stubs/DescribeInstances_MultipleReservations.200.xml +243 -0
  44. data/spec/support/stubs/DescribeInstances_MultipleRunning.200.xml +338 -0
  45. data/spec/support/stubs/DescribeLaunchConfigurations.200.xml +29 -0
  46. data/spec/support/stubs/DescribeSnapshots.200.xml +18 -0
  47. data/spec/support/stubs/DescribeTags.200.xml +17 -0
  48. data/spec/support/stubs/UpdateAutoScalingGroup.200.xml +5 -0
  49. data/spec/support/stubs/security-credentials.200.json +9 -0
  50. 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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in elbas.gemspec
4
+ gemspec
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
+ [![Gem Version](https://badge.fury.io/rb/elbas.svg)](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
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ task :default => :spec
5
+ RSpec::Core::RakeTask.new
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
@@ -0,0 +1,6 @@
1
+ module Elbas
2
+ module Errors
3
+ class NoLaunchTemplate < StandardError
4
+ end
5
+ end
6
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Elbas
2
+ VERSION = '1.0.0'.freeze
3
+ end