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