elbas 0.27.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +25 -35
  3. data/elbas.gemspec +15 -12
  4. data/lib/elbas.rb +15 -9
  5. data/lib/elbas/aws/ami.rb +71 -0
  6. data/lib/elbas/aws/autoscale_group.rb +43 -0
  7. data/lib/elbas/aws/base.rb +39 -0
  8. data/lib/elbas/aws/instance.rb +24 -0
  9. data/lib/elbas/aws/instance_collection.rb +36 -0
  10. data/lib/elbas/aws/launch_template.rb +32 -0
  11. data/lib/elbas/aws/snapshot.rb +21 -0
  12. data/lib/elbas/aws/taggable.rb +18 -0
  13. data/lib/elbas/capistrano.rb +14 -12
  14. data/lib/elbas/errors/no_launch_template.rb +6 -0
  15. data/lib/elbas/logger.rb +10 -2
  16. data/lib/elbas/retryable.rb +34 -9
  17. data/lib/elbas/tasks/elbas.rake +19 -10
  18. data/lib/elbas/version.rb +1 -1
  19. data/spec/aws/ami_spec.rb +140 -0
  20. data/spec/aws/autoscale_group_spec.rb +53 -0
  21. data/spec/aws/instance_collection_spec.rb +28 -0
  22. data/spec/aws/instance_spec.rb +30 -0
  23. data/spec/aws/launch_template_spec.rb +52 -0
  24. data/spec/aws/taggable_spec.rb +53 -0
  25. data/spec/spec_helper.rb +14 -24
  26. data/spec/support/stubs/CreateLaunchTemplateVersion.200.xml +16 -0
  27. data/spec/support/stubs/DescribeAutoScalingGroups.200.xml +60 -0
  28. data/spec/support/stubs/DescribeImages.200.xml +36 -1
  29. data/spec/support/stubs/DescribeInstances.200.xml +109 -2
  30. metadata +60 -21
  31. data/lib/elbas/ami.rb +0 -56
  32. data/lib/elbas/aws/autoscaling.rb +0 -21
  33. data/lib/elbas/aws/credentials.rb +0 -20
  34. data/lib/elbas/aws/ec2.rb +0 -13
  35. data/lib/elbas/aws_resource.rb +0 -36
  36. data/lib/elbas/launch_configuration.rb +0 -64
  37. data/lib/elbas/taggable.rb +0 -9
  38. data/spec/ami_spec.rb +0 -10
  39. data/spec/elbas_spec.rb +0 -69
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ba77ed5f65f5066df79abab27f088a338a0f1f46
4
- data.tar.gz: 3fcf047a084ec476facacedf96dc41882b2ba90c
2
+ SHA256:
3
+ metadata.gz: 001b685faad190688be4ec49c511781a58129cd94800172575d0a8d739755837
4
+ data.tar.gz: a7fa9684df82682256c2629bc294260d3aa6b6426663b17534613c26263bb782
5
5
  SHA512:
6
- metadata.gz: 8602effc5044cbeaae866c39805db27e737471b015157f6a44b07a47e70be987624486c63d0919b8a428fde6c1f79666b9417e683fd6bbfa8264884f5316a17d
7
- data.tar.gz: f3b212fc1e38e2015d7884e464d2679f9a271c4166b8c650743e75063a26789907e09d0e6df59156bf078107af49fabfc2a12358cd89a97f162532360ecabfd8
6
+ metadata.gz: 3a5b2534c8f036a8f3044beb836d2c33b0bf74f092b28fc3f71ce7e129b3c586f6cad1594e41f84ed68d93de4f0c4270b34ef530e11aa8b1583e1af8a9ed947d
7
+ data.tar.gz: d9a71090a1a53139e2cbf80c3b8fff0a27e91f28640f649793d3f48c35d5d581e5e0a828bb90999cb9cabbc3703b738b515ce1e4774dee286ca4b9a6c5b62e36
data/README.md CHANGED
@@ -1,60 +1,50 @@
1
- # ELBAS (Elastic Load Balancer & AutoScaling)
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
2
 
3
- ELBAS was written to ease the deployment of Rails applications to AWS AutoScale groups. ELBAS will:
3
+ # Capistrano ELBAS (Elastic Load Balancer & AutoScaling)
4
+
5
+ ELBAS was written to ease the deployment of Rails applications to AWS AutoScale
6
+ groups. During your Capistrano deployment, ELBAS will:
4
7
 
5
8
  - Deploy your code to each running instance connected to a given AutoScale group
6
9
  - After deployment, create an AMI from one of the running instances
7
- - Attach the AMI with the new code to a new AWS Launch Configuration
8
- - Update your AutoScale group to use the new launch configuration
9
- - Delete any old AMIs created by ELBAS
10
- - Delete any old launch configurations created by ELBAS
11
-
12
- This ensures that your current and future servers will be running the newly deployed code.
10
+ - Update the AutoScale group's launch template with the AMI ID
11
+ - Delete any outdated AMIs created by previous ELBAS deployments
13
12
 
14
13
  ## Installation
15
14
 
15
+ Add to Gemfile, then `bundle`:
16
+
16
17
  `gem 'elbas'`
17
18
 
18
- Add this statement to your Capfile:
19
+ Add to Capfile:
19
20
 
20
21
  `require 'elbas/capistrano'`
21
22
 
22
23
  ## Configuration
23
24
 
24
- Below are the Capistrano configuration options with their defaults:
25
+ Setup AWS credentials:
25
26
 
26
27
  ```ruby
27
- set :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID']
28
- set :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY']
29
- set :aws_region, ENV['AWS_REGION']
30
-
31
- set :aws_no_reboot_on_create_ami, true
32
- set :aws_autoscale_instance_size, 'm1.small'
33
-
34
- set :aws_launch_configuration_detailed_instance_monitoring, true
35
- set :aws_launch_configuration_associate_public_ip, true
28
+ set :aws_access_key, ENV['AWS_ACCESS_KEY_ID']
29
+ set :aws_secret_key, ENV['AWS_SECRET_ACCESS_KEY']
30
+ set :aws_region, ENV['AWS_REGION']
36
31
  ```
37
32
 
38
33
  ## Usage
39
34
 
40
- Instead of using Capistrano's `server` method, use `autoscale` instead in `deploy/production.rb` (or
41
- whichever environment you're deploying to). Provide the name of your AutoScale group instead of a
42
- hostname:
35
+ Instead of using Capistrano's `server` method, use `autoscale` instead in
36
+ `deploy/<environment>.rb` (replace <environment> with your environment). Provide
37
+ the name of your AutoScale group instead of a hostname:
43
38
 
44
39
  ```ruby
45
- autoscale 'production', user: 'apps', roles: [:app, :web, :db]
40
+ autoscale 'my-autoscale-group', user: 'apps', roles: [:app, :web, :db]
46
41
  ```
47
42
 
48
- That's it! Run `cap production deploy`. ELBAS will print the following log statements during your
49
- deployment:
43
+ Run `cap production deploy`.
50
44
 
51
- ```
52
- "ELBAS: Adding server: ec2-XX-XX-XX-XXX.compute-1.amazonaws.com"
53
- "ELBAS: Creating EC2 AMI from i-123abcd"
54
- "ELBAS: Created AMI: ami-123456"
55
- "ELBAS: Creating an EC2 Launch Configuration for AMI: ami-123456"
56
- "ELBAS: Created Launch Configuration: elbas-lc-ENVIRONMENT-UNIX_TIMESTAMP"
57
- "ELBAS: Attaching Launch Configuration to AutoScale Group"
58
- "ELBAS: Deleting old launch configuration: elbas-lc-production-123456"
59
- "ELBAS: Deleting old image: ami-999999"
60
- ```
45
+ **As of version 3, your AWS setup must use launch templates as opposed to launch
46
+ configurations.** This allows ELBAS to simply create a new launch template version
47
+ with the new AMI ID after a deployment. It no longer needs to update your
48
+ AutoScale group or mess around with network settings, instance sizes, etc., as
49
+ that information is all contained within the launch template. Failure to use a
50
+ launch template will result in a `Elbas::Errors::NoLaunchTemplate` error.
@@ -4,26 +4,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'elbas/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "elbas"
7
+ spec.name = 'elbas'
8
8
  spec.version = Elbas::VERSION
9
- spec.authors = ["Logan Serman"]
10
- spec.email = ["logan.serman@metova.com"]
9
+ spec.authors = ['Logan Serman']
10
+ spec.email = ['loganserman@gmail.com']
11
11
  spec.summary = 'Capistrano plugin for deploying to AWS AutoScale Groups.'
12
- spec.description = "#{spec.summary} Deploys to all instances in a group, creates a fresh AMI post-deploy, and attaches the AMI to your AutoScale Group."
13
- spec.homepage = "http://github.com/metova/elbas"
14
- spec.license = "MIT"
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/lserman/capistrano-elbas'
14
+ spec.license = 'MIT'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
19
+ spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.6"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "rspec"
24
- spec.add_development_dependency "webmock"
21
+ spec.add_development_dependency 'bundler', '~> 1.6'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec'
24
+ spec.add_development_dependency 'webmock'
25
+ spec.add_development_dependency 'webmock-rspec-helper'
26
+
27
+ spec.add_dependency 'aws-sdk-autoscaling', '~> 1'
28
+ spec.add_dependency 'aws-sdk-ec2', '~> 1'
25
29
 
26
- spec.add_dependency 'aws-sdk-v1', '~> 1'
27
30
  spec.add_dependency 'capistrano', '> 3.0.0'
28
31
  spec.add_dependency 'activesupport', '>= 4.0.0'
29
32
  end
@@ -1,17 +1,23 @@
1
- require 'aws-sdk-v1'
2
1
  require 'capistrano/all'
3
2
  require 'active_support/concern'
4
3
 
4
+ require 'aws-sdk-autoscaling'
5
+ require 'aws-sdk-ec2'
6
+
5
7
  require 'elbas/version'
6
- require 'elbas/retryable'
7
- require 'elbas/taggable'
8
8
  require 'elbas/logger'
9
- require 'elbas/aws/credentials'
10
- require 'elbas/aws/autoscaling'
11
- require 'elbas/aws/ec2'
12
- require 'elbas/aws_resource'
13
- require 'elbas/ami'
14
- require 'elbas/launch_configuration'
9
+ require 'elbas/retryable'
10
+
11
+ require 'elbas/errors/no_launch_template'
12
+
13
+ require 'elbas/aws/base'
14
+ require 'elbas/aws/taggable'
15
+ require 'elbas/aws/instance_collection'
16
+ require 'elbas/aws/instance'
17
+ require 'elbas/aws/autoscale_group'
18
+ require 'elbas/aws/launch_template'
19
+ require 'elbas/aws/ami'
20
+ require 'elbas/aws/snapshot'
15
21
 
16
22
  module Elbas
17
23
  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
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,43 @@
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
+ raise Elbas::Errors::NoLaunchTemplate unless aws_counterpart.launch_template
21
+
22
+ LaunchTemplate.new(
23
+ aws_counterpart.launch_template.launch_template_id,
24
+ aws_counterpart.launch_template.launch_template_name,
25
+ aws_counterpart.launch_template.version,
26
+ )
27
+ end
28
+
29
+ private
30
+ def aws_namespace
31
+ ::Aws::AutoScaling
32
+ end
33
+
34
+ def query_autoscale_group_by_name(name)
35
+ aws_client
36
+ .describe_auto_scaling_groups(auto_scaling_group_names: [name])
37
+ .auto_scaling_groups
38
+ .first
39
+ end
40
+
41
+ end
42
+ end
43
+ 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,24 @@
1
+ module Elbas
2
+ module AWS
3
+ class Instance
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::EC2::Instance.new id
13
+ end
14
+
15
+ def hostname
16
+ @public_dns
17
+ end
18
+
19
+ def running?
20
+ state == STATE_RUNNING
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,36 @@
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[0]
32
+ .instances
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
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
+ self.class.new(
20
+ latest&.launch_template_id,
21
+ latest&.launch_template_name,
22
+ latest&.version_number
23
+ )
24
+ end
25
+
26
+ private
27
+ def aws_namespace
28
+ ::Aws::EC2
29
+ end
30
+ end
31
+ end
32
+ 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