autocanary24 0.1.0.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 26fa649c88fc42c4c4e77d4a732695f298e9276d
4
+ data.tar.gz: 24d0de4d0b38ef6ce5f033db668da64cee47405e
5
+ SHA512:
6
+ metadata.gz: 50666494d784f0252d165b4476c6ba7f84fc1f8ed83f919322992f4e43385009f227e178b5f90b2a7a8f5a358e02a6778a3dae0b498c03919e48731b9a3e29ff
7
+ data.tar.gz: 127c59c643c6077142f821fb9c918916d2dc5a593fc82fe154ed760804ebaf563a382837964232e76636495d71f73818b5a08911f9136ed27d072e998ef01d71
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ vendor
11
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in autocanary24.gemspec
4
+ gemspec
5
+
6
+ gem 'aws-sdk', '~>2'
7
+ gem 'autostacker24'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 AutoScout24 GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # AutoCanary24
2
+
3
+ AutoCanary24 is a ruby utility to do [blue/green](http://martinfowler.com/bliki/BlueGreenDeployment.html) and [canary](http://martinfowler.com/bliki/CanaryRelease.html) deployments with AWS CloudFormation stacks.
4
+
5
+ This library use the [Swap AutoScaling Groups](http://www.slideshare.net/AmazonWebServices/dvo401-deep-dive-into-bluegreen-deployments-on-aws/32) approach and expects two stacks. A "base" stack which includes at least the `ELB` and another which includes the `AutoScaling Group`.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'autocanary24'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle install
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install autocanary24
23
+
24
+
25
+ ## Usage
26
+
27
+ Initialize the client, e.g. in your `Rakefile` and deploy your stack:
28
+
29
+ ```ruby
30
+ # Deploy the base stack before
31
+
32
+ ac = AutoCanary24::Client.new({
33
+ keep_inactive_stack: false,
34
+ scaling_instance_percent: 50,
35
+ keep_instances_balanced: false})
36
+
37
+ ac.deploy_stack(parent_stack_name, template, parameters, tags, deployment_check)
38
+ ```
39
+
40
+ - `parent_stack_name`: the name of the 'base' stack. In addition `AutoStacker24` will read the output parameters of an existing stack and merge them to the given parameters.
41
+ - `template`: is either the template json data itself or the name of a file containing the template body
42
+ - `parameters`: specify the input parameter as a simple ruby hash. It gets converted to the
43
+ cumbersome AWS format automatically.
44
+ The template body will be validated and optionally preprocessed.
45
+ - `tags`: Optional. Key-value pairs to associate with this stack.
46
+ - 'deployment_check': Optional. A `lambda` which is executed whenever new instances are added to the ELB. If `true` AutoCanary continues, if `false` it will rollback to the current stack.
47
+
48
+ > For more information about stacked CloudFormation stacks visit [AutoStacker24](https://github.com/autoscout24/autostacker24).
49
+
50
+ The available configuration:
51
+ - `keep_inactive_stack`: If `true` the inactive stack gets not deleted. Default is `false`
52
+ - `keep_instances_balanced`: If `true` a instance from current stack gets removed whenever a new instance from the new stack is added. If `false` first all new instances are created and afterwards the old instances gets removed.
53
+ - `scaling_instance_percent`: Percent of instances which are added at once (depends on the actual number of instances, read from desired)
54
+
55
+
56
+
57
+ ### Examples
58
+ Have a look into the [examples subfolder](https://github.com/autoscout24/autocanary24/blob/master/examples/)
59
+
60
+ ## Development
61
+
62
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
63
+
64
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
65
+
66
+
67
+ ## Contributing
68
+
69
+ Bug reports and pull requests are welcome on GitHub at https://github.com/autoscout24/autocanary24.
70
+
71
+
72
+ ## License
73
+
74
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'autocanary24/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "autocanary24"
8
+ spec.version = AutoCanary24::VERSION
9
+ spec.authors = ["Philipp Garbe"]
10
+ spec.email = ["pgarbe@autoscout24.com"]
11
+
12
+ spec.summary = %q{A very narrow interface to AWS ECR}
13
+ spec.description = %q{autocanary24 provides a small convenient module for blue/green and canary deployments with CloudFormation.}
14
+ spec.homepage = "https://github.com/autoscout24/autocanary24"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.11"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "autocanary24"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,3 @@
1
+ ---
2
+ BUNDLE_PATH: vendor/bundle
3
+ BUNDLE_DISABLE_SHARED_GEMS: '1'
data/examples/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'aws-sdk', '~>2'
4
+ gem 'autostacker24'
@@ -0,0 +1,26 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ autostacker24 (1.0.71)
5
+ aws-sdk-core (~> 2)
6
+ json (~> 1.8)
7
+ json_pure (~> 1.8)
8
+ aws-sdk (2.2.30)
9
+ aws-sdk-resources (= 2.2.30)
10
+ aws-sdk-core (2.2.30)
11
+ jmespath (~> 1.0)
12
+ aws-sdk-resources (2.2.30)
13
+ aws-sdk-core (= 2.2.30)
14
+ jmespath (1.1.3)
15
+ json (1.8.3)
16
+ json_pure (1.8.3)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ autostacker24
23
+ aws-sdk (~> 2)
24
+
25
+ BUNDLED WITH
26
+ 1.11.2
File without changes
data/examples/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ require "aws-sdk-core"
2
+ require "autostacker24"
3
+ require_relative "../lib/autocanary24/client"
4
+
5
+ task :default do
6
+
7
+ # 1) Create the base stack which includes at least the ELB
8
+ parameters = {}
9
+ Stacker.create_or_update_stack('autocanary24-example', 'base-stack.json', parameters)
10
+
11
+ # 2) Create the stack which includes the ASG
12
+ client = AutoCanary24::Client.new({
13
+ :keep_inactive_stack => true
14
+ })
15
+
16
+ parameters = {}
17
+ client.deploy_stack('autocanary24-example', 'asg-stack.json', parameters)
18
+
19
+ end
20
+
21
+ task :cleanup do
22
+ Stacker.delete_stack('autocanary24-example-B')
23
+ Stacker.delete_stack('autocanary24-example-G')
24
+ Stacker.delete_stack('autocanary24-example')
25
+ end
@@ -0,0 +1,326 @@
1
+ //AutoStacker24
2
+ {
3
+ "AWSTemplateFormatVersion" : "2010-09-09",
4
+
5
+ "Description" : "AWS CloudFormation Sample Template AutoScalingMultiAZWithNotifications: Create a multi-az, load balanced and Auto Scaled sample web site running on an Apache Web Serever. The application is configured to span all Availability Zones in the region and is Auto-Scaled based on the CPU utilization of the web servers. Notifications will be sent to the operator email address on scaling events. The instances are load balanced with a simple health check against the default web page. **WARNING** This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.",
6
+
7
+ "Parameters" : {
8
+ "InstanceSecurityGroup": {
9
+ "Type": "String"
10
+ },
11
+ "InstanceType" : {
12
+ "Description" : "WebServer EC2 instance type",
13
+ "Type" : "String",
14
+ "Default" : "t2.nano",
15
+ "AllowedValues" : [ "t1.micro", "t2.nano", "t2.micro", "t2.small", "t2.medium", "t2.large", "m1.small", "m1.medium", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "m3.medium", "m3.large", "m3.xlarge", "m3.2xlarge", "m4.large", "m4.xlarge", "m4.2xlarge", "m4.4xlarge", "m4.10xlarge", "c1.medium", "c1.xlarge", "c3.large", "c3.xlarge", "c3.2xlarge", "c3.4xlarge", "c3.8xlarge", "c4.large", "c4.xlarge", "c4.2xlarge", "c4.4xlarge", "c4.8xlarge", "g2.2xlarge", "g2.8xlarge", "r3.large", "r3.xlarge", "r3.2xlarge", "r3.4xlarge", "r3.8xlarge", "i2.xlarge", "i2.2xlarge", "i2.4xlarge", "i2.8xlarge", "d2.xlarge", "d2.2xlarge", "d2.4xlarge", "d2.8xlarge", "hi1.4xlarge", "hs1.8xlarge", "cr1.8xlarge", "cc2.8xlarge", "cg1.4xlarge"],
16
+ "ConstraintDescription" : "must be a valid EC2 instance type."
17
+ }
18
+ },
19
+
20
+ "Mappings" : {
21
+ "Region2Examples" : {
22
+ "us-east-1" : { "Examples" : "https://s3.amazonaws.com/cloudformation-examples-us-east-1" },
23
+ "us-west-2" : { "Examples" : "https://s3-us-west-2.amazonaws.com/cloudformation-examples-us-west-2" },
24
+ "us-west-1" : { "Examples" : "https://s3-us-west-1.amazonaws.com/cloudformation-examples-us-west-1" },
25
+ "eu-west-1" : { "Examples" : "https://s3-eu-west-1.amazonaws.com/cloudformation-examples-eu-west-1" },
26
+ "eu-central-1" : { "Examples" : "https://s3-eu-central-1.amazonaws.com/cloudformation-examples-eu-central-1" },
27
+ "ap-southeast-1" : { "Examples" : "https://s3-ap-southeast-1.amazonaws.com/cloudformation-examples-ap-southeast-1" },
28
+ "ap-northeast-1" : { "Examples" : "https://s3-ap-northeast-1.amazonaws.com/cloudformation-examples-ap-northeast-1" },
29
+ "ap-northeast-2" : { "Examples" : "https://s3-ap-northeast-2.amazonaws.com/cloudformation-examples-ap-northeast-2" },
30
+ "ap-southeast-2" : { "Examples" : "https://s3-ap-southeast-2.amazonaws.com/cloudformation-examples-ap-southeast-2" },
31
+ "sa-east-1" : { "Examples" : "https://s3-sa-east-1.amazonaws.com/cloudformation-examples-sa-east-1" },
32
+ "cn-north-1" : { "Examples" : "https://s3.cn-north-1.amazonaws.com.cn/cloudformation-examples-cn-north-1" }
33
+ },
34
+ "AWSInstanceType2Arch" : {
35
+ "t1.micro" : { "Arch" : "PV64" },
36
+ "t2.nano" : { "Arch" : "HVM64" },
37
+ "t2.micro" : { "Arch" : "HVM64" },
38
+ "t2.small" : { "Arch" : "HVM64" },
39
+ "t2.medium" : { "Arch" : "HVM64" },
40
+ "t2.large" : { "Arch" : "HVM64" },
41
+ "m1.small" : { "Arch" : "PV64" },
42
+ "m1.medium" : { "Arch" : "PV64" },
43
+ "m1.large" : { "Arch" : "PV64" },
44
+ "m1.xlarge" : { "Arch" : "PV64" },
45
+ "m2.xlarge" : { "Arch" : "PV64" },
46
+ "m2.2xlarge" : { "Arch" : "PV64" },
47
+ "m2.4xlarge" : { "Arch" : "PV64" },
48
+ "m3.medium" : { "Arch" : "HVM64" },
49
+ "m3.large" : { "Arch" : "HVM64" },
50
+ "m3.xlarge" : { "Arch" : "HVM64" },
51
+ "m3.2xlarge" : { "Arch" : "HVM64" },
52
+ "m4.large" : { "Arch" : "HVM64" },
53
+ "m4.xlarge" : { "Arch" : "HVM64" },
54
+ "m4.2xlarge" : { "Arch" : "HVM64" },
55
+ "m4.4xlarge" : { "Arch" : "HVM64" },
56
+ "m4.10xlarge" : { "Arch" : "HVM64" },
57
+ "c1.medium" : { "Arch" : "PV64" },
58
+ "c1.xlarge" : { "Arch" : "PV64" },
59
+ "c3.large" : { "Arch" : "HVM64" },
60
+ "c3.xlarge" : { "Arch" : "HVM64" },
61
+ "c3.2xlarge" : { "Arch" : "HVM64" },
62
+ "c3.4xlarge" : { "Arch" : "HVM64" },
63
+ "c3.8xlarge" : { "Arch" : "HVM64" },
64
+ "c4.large" : { "Arch" : "HVM64" },
65
+ "c4.xlarge" : { "Arch" : "HVM64" },
66
+ "c4.2xlarge" : { "Arch" : "HVM64" },
67
+ "c4.4xlarge" : { "Arch" : "HVM64" },
68
+ "c4.8xlarge" : { "Arch" : "HVM64" },
69
+ "g2.2xlarge" : { "Arch" : "HVMG2" },
70
+ "g2.8xlarge" : { "Arch" : "HVMG2" },
71
+ "r3.large" : { "Arch" : "HVM64" },
72
+ "r3.xlarge" : { "Arch" : "HVM64" },
73
+ "r3.2xlarge" : { "Arch" : "HVM64" },
74
+ "r3.4xlarge" : { "Arch" : "HVM64" },
75
+ "r3.8xlarge" : { "Arch" : "HVM64" },
76
+ "i2.xlarge" : { "Arch" : "HVM64" },
77
+ "i2.2xlarge" : { "Arch" : "HVM64" },
78
+ "i2.4xlarge" : { "Arch" : "HVM64" },
79
+ "i2.8xlarge" : { "Arch" : "HVM64" },
80
+ "d2.xlarge" : { "Arch" : "HVM64" },
81
+ "d2.2xlarge" : { "Arch" : "HVM64" },
82
+ "d2.4xlarge" : { "Arch" : "HVM64" },
83
+ "d2.8xlarge" : { "Arch" : "HVM64" },
84
+ "hi1.4xlarge" : { "Arch" : "HVM64" },
85
+ "hs1.8xlarge" : { "Arch" : "HVM64" },
86
+ "cr1.8xlarge" : { "Arch" : "HVM64" },
87
+ "cc2.8xlarge" : { "Arch" : "HVM64" }
88
+ },
89
+ "AWSInstanceType2NATArch" : {
90
+ "t1.micro" : { "Arch" : "NATPV64" },
91
+ "t2.nano" : { "Arch" : "NATHVM64" },
92
+ "t2.micro" : { "Arch" : "NATHVM64" },
93
+ "t2.small" : { "Arch" : "NATHVM64" },
94
+ "t2.medium" : { "Arch" : "NATHVM64" },
95
+ "t2.large" : { "Arch" : "NATHVM64" },
96
+ "m1.small" : { "Arch" : "NATPV64" },
97
+ "m1.medium" : { "Arch" : "NATPV64" },
98
+ "m1.large" : { "Arch" : "NATPV64" },
99
+ "m1.xlarge" : { "Arch" : "NATPV64" },
100
+ "m2.xlarge" : { "Arch" : "NATPV64" },
101
+ "m2.2xlarge" : { "Arch" : "NATPV64" },
102
+ "m2.4xlarge" : { "Arch" : "NATPV64" },
103
+ "m3.medium" : { "Arch" : "NATHVM64" },
104
+ "m3.large" : { "Arch" : "NATHVM64" },
105
+ "m3.xlarge" : { "Arch" : "NATHVM64" },
106
+ "m3.2xlarge" : { "Arch" : "NATHVM64" },
107
+ "m4.large" : { "Arch" : "NATHVM64" },
108
+ "m4.xlarge" : { "Arch" : "NATHVM64" },
109
+ "m4.2xlarge" : { "Arch" : "NATHVM64" },
110
+ "m4.4xlarge" : { "Arch" : "NATHVM64" },
111
+ "m4.10xlarge" : { "Arch" : "NATHVM64" },
112
+ "c1.medium" : { "Arch" : "NATPV64" },
113
+ "c1.xlarge" : { "Arch" : "NATPV64" },
114
+ "c3.large" : { "Arch" : "NATHVM64" },
115
+ "c3.xlarge" : { "Arch" : "NATHVM64" },
116
+ "c3.2xlarge" : { "Arch" : "NATHVM64" },
117
+ "c3.4xlarge" : { "Arch" : "NATHVM64" },
118
+ "c3.8xlarge" : { "Arch" : "NATHVM64" },
119
+ "c4.large" : { "Arch" : "NATHVM64" },
120
+ "c4.xlarge" : { "Arch" : "NATHVM64" },
121
+ "c4.2xlarge" : { "Arch" : "NATHVM64" },
122
+ "c4.4xlarge" : { "Arch" : "NATHVM64" },
123
+ "c4.8xlarge" : { "Arch" : "NATHVM64" },
124
+ "g2.2xlarge" : { "Arch" : "NATHVMG2" },
125
+ "g2.8xlarge" : { "Arch" : "NATHVMG2" },
126
+ "r3.large" : { "Arch" : "NATHVM64" },
127
+ "r3.xlarge" : { "Arch" : "NATHVM64" },
128
+ "r3.2xlarge" : { "Arch" : "NATHVM64" },
129
+ "r3.4xlarge" : { "Arch" : "NATHVM64" },
130
+ "r3.8xlarge" : { "Arch" : "NATHVM64" },
131
+ "i2.xlarge" : { "Arch" : "NATHVM64" },
132
+ "i2.2xlarge" : { "Arch" : "NATHVM64" },
133
+ "i2.4xlarge" : { "Arch" : "NATHVM64" },
134
+ "i2.8xlarge" : { "Arch" : "NATHVM64" },
135
+ "d2.xlarge" : { "Arch" : "NATHVM64" },
136
+ "d2.2xlarge" : { "Arch" : "NATHVM64" },
137
+ "d2.4xlarge" : { "Arch" : "NATHVM64" },
138
+ "d2.8xlarge" : { "Arch" : "NATHVM64" },
139
+ "hi1.4xlarge" : { "Arch" : "NATHVM64" },
140
+ "hs1.8xlarge" : { "Arch" : "NATHVM64" },
141
+ "cr1.8xlarge" : { "Arch" : "NATHVM64" },
142
+ "cc2.8xlarge" : { "Arch" : "NATHVM64" }
143
+ },
144
+ "AWSRegionArch2AMI" : {
145
+ "us-east-1" : {"PV64" : "ami-5fb8c835", "HVM64" : "ami-60b6c60a", "HVMG2" : "ami-e998ea83"},
146
+ "us-west-2" : {"PV64" : "ami-d93622b8", "HVM64" : "ami-f0091d91", "HVMG2" : "ami-315f4850"},
147
+ "us-west-1" : {"PV64" : "ami-56ea8636", "HVM64" : "ami-d5ea86b5", "HVMG2" : "ami-943956f4"},
148
+ "eu-west-1" : {"PV64" : "ami-95e33ce6", "HVM64" : "ami-bff32ccc", "HVMG2" : "ami-83fd23f0"},
149
+ "eu-central-1" : {"PV64" : "ami-794a5915", "HVM64" : "ami-bc5b48d0", "HVMG2" : "ami-ba1a09d6"},
150
+ "ap-northeast-1" : {"PV64" : "ami-393c1957", "HVM64" : "ami-383c1956", "HVMG2" : "ami-08e5c166"},
151
+ "ap-northeast-2" : {"PV64" : "NOT_SUPPORTED", "HVM64" : "ami-249b554a", "HVMG2" : "NOT_SUPPORTED"},
152
+ "ap-southeast-1" : {"PV64" : "ami-34bd7a57", "HVM64" : "ami-c9b572aa", "HVMG2" : "ami-5a15d239"},
153
+ "ap-southeast-2" : {"PV64" : "ami-ced887ad", "HVM64" : "ami-48d38c2b", "HVMG2" : "ami-0c1a446f"},
154
+ "sa-east-1" : {"PV64" : "ami-7d15ad11", "HVM64" : "ami-6817af04", "HVMG2" : "NOT_SUPPORTED"},
155
+ "cn-north-1" : {"PV64" : "ami-18ac6575", "HVM64" : "ami-43a36a2e", "HVMG2" : "NOT_SUPPORTED"}
156
+ }
157
+ },
158
+
159
+ "Resources" : {
160
+ "WebServerGroup" : {
161
+ "Type" : "AWS::AutoScaling::AutoScalingGroup",
162
+ "Properties" : {
163
+ "AvailabilityZones" : { "Fn::GetAZs" : ""},
164
+ "LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
165
+ "MinSize" : "1",
166
+ "MaxSize" : "3",
167
+ "HealthCheckGracePeriod" : "600",
168
+ "HealthCheckType" : "ELB"
169
+ },
170
+ "CreationPolicy" : {
171
+ "ResourceSignal" : {
172
+ "Timeout" : "PT15M",
173
+ "Count" : "1"
174
+ }
175
+ },
176
+ "UpdatePolicy": {
177
+ "AutoScalingRollingUpdate": {
178
+ "MinInstancesInService": "1",
179
+ "MaxBatchSize": "1",
180
+ "PauseTime" : "PT15M",
181
+ "WaitOnResourceSignals": "true"
182
+ }
183
+ }
184
+ },
185
+
186
+ "LaunchConfig" : {
187
+ "Type" : "AWS::AutoScaling::LaunchConfiguration",
188
+ "Metadata" : {
189
+ "Comment" : "Install a simple application",
190
+ "AWS::CloudFormation::Init" : {
191
+ "config" : {
192
+ "packages" : {
193
+ "yum" : {
194
+ "httpd" : []
195
+ }
196
+ },
197
+
198
+ "files" : {
199
+ "/var/www/html/index.html" : {
200
+ "content" : { "Fn::Join" : ["\n", [
201
+ "<img src=\"", {"Fn::FindInMap" : ["Region2Examples", {"Ref" : "AWS::Region"}, "Examples"]}, "/cloudformation_graphic.png\" alt=\"AWS CloudFormation Logo\"/>",
202
+ "<h1>Congratulations, you have successfully launched the AWS CloudFormation sample.</h1>"
203
+ ]]},
204
+ "mode" : "000644",
205
+ "owner" : "root",
206
+ "group" : "root"
207
+ },
208
+
209
+ "/etc/cfn/cfn-hup.conf" : {
210
+ "content" : { "Fn::Join" : ["", [
211
+ "[main]\n",
212
+ "stack=", { "Ref" : "AWS::StackId" }, "\n",
213
+ "region=", { "Ref" : "AWS::Region" }, "\n"
214
+ ]]},
215
+ "mode" : "000400",
216
+ "owner" : "root",
217
+ "group" : "root"
218
+ },
219
+
220
+ "/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
221
+ "content": { "Fn::Join" : ["", [
222
+ "[cfn-auto-reloader-hook]\n",
223
+ "triggers=post.update\n",
224
+ "path=Resources.LaunchConfig.Metadata.AWS::CloudFormation::Init\n",
225
+ "action=/opt/aws/bin/cfn-init -v ",
226
+ " --stack ", { "Ref" : "AWS::StackName" },
227
+ " --resource LaunchConfig ",
228
+ " --region ", { "Ref" : "AWS::Region" }, "\n",
229
+ "runas=root\n"
230
+ ]]}
231
+ }
232
+ },
233
+
234
+ "services" : {
235
+ "sysvinit" : {
236
+ "httpd" : { "enabled" : "true", "ensureRunning" : "true" },
237
+ "cfn-hup" : { "enabled" : "true", "ensureRunning" : "true",
238
+ "files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]}
239
+ }
240
+ }
241
+ }
242
+ }
243
+ },
244
+ "Properties" : {
245
+ "ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
246
+ { "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
247
+ "SecurityGroups" : [ { "Ref" : "InstanceSecurityGroup" } ],
248
+ "InstanceType" : { "Ref" : "InstanceType" },
249
+ "UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
250
+ "#!/bin/bash -xe\n",
251
+ "yum update -y aws-cfn-bootstrap\n",
252
+
253
+ "/opt/aws/bin/cfn-init -v ",
254
+ " --stack ", { "Ref" : "AWS::StackName" },
255
+ " --resource LaunchConfig ",
256
+ " --region ", { "Ref" : "AWS::Region" }, "\n",
257
+
258
+ "/opt/aws/bin/cfn-signal -e $? ",
259
+ " --stack ", { "Ref" : "AWS::StackName" },
260
+ " --resource WebServerGroup ",
261
+ " --region ", { "Ref" : "AWS::Region" }, "\n"
262
+ ]]}}
263
+ }
264
+ },
265
+
266
+ "CPUAlarmHigh": {
267
+ "Type": "AWS::CloudWatch::Alarm",
268
+ "Properties": {
269
+ "AlarmDescription": "Scale-up if CPU > 90% for 10 minutes",
270
+ "MetricName": "CPUUtilization",
271
+ "Namespace": "AWS/EC2",
272
+ "Statistic": "Average",
273
+ "Period": "300",
274
+ "EvaluationPeriods": "2",
275
+ "Threshold": "90",
276
+ "AlarmActions": [ { "Ref": "WebServerScaleUpPolicy" } ],
277
+ "Dimensions": [
278
+ {
279
+ "Name": "AutoScalingGroupName",
280
+ "Value": { "Ref": "WebServerGroup" }
281
+ }
282
+ ],
283
+ "ComparisonOperator": "GreaterThanThreshold"
284
+ }
285
+ },
286
+ "CPUAlarmLow": {
287
+ "Type": "AWS::CloudWatch::Alarm",
288
+ "Properties": {
289
+ "AlarmDescription": "Scale-down if CPU < 70% for 10 minutes",
290
+ "MetricName": "CPUUtilization",
291
+ "Namespace": "AWS/EC2",
292
+ "Statistic": "Average",
293
+ "Period": "300",
294
+ "EvaluationPeriods": "2",
295
+ "Threshold": "70",
296
+ "AlarmActions": [ { "Ref": "WebServerScaleDownPolicy" } ],
297
+ "Dimensions": [
298
+ {
299
+ "Name": "AutoScalingGroupName",
300
+ "Value": { "Ref": "WebServerGroup" }
301
+ }
302
+ ],
303
+ "ComparisonOperator": "LessThanThreshold"
304
+ }
305
+ },
306
+
307
+ "WebServerScaleUpPolicy" : {
308
+ "Type" : "AWS::AutoScaling::ScalingPolicy",
309
+ "Properties" : {
310
+ "AdjustmentType" : "ChangeInCapacity",
311
+ "AutoScalingGroupName" : { "Ref" : "WebServerGroup" },
312
+ "Cooldown" : "60",
313
+ "ScalingAdjustment" : "1"
314
+ }
315
+ },
316
+ "WebServerScaleDownPolicy" : {
317
+ "Type" : "AWS::AutoScaling::ScalingPolicy",
318
+ "Properties" : {
319
+ "AdjustmentType" : "ChangeInCapacity",
320
+ "AutoScalingGroupName" : { "Ref" : "WebServerGroup" },
321
+ "Cooldown" : "60",
322
+ "ScalingAdjustment" : "-1"
323
+ }
324
+ }
325
+ }
326
+ }
@@ -0,0 +1,59 @@
1
+ //AutoStacker24
2
+ {
3
+ "AWSTemplateFormatVersion" : "2010-09-09",
4
+
5
+ "Description" : "AWS CloudFormation Sample Template AutoScalingMultiAZWithNotifications: Create a multi-az, load balanced and Auto Scaled sample web site running on an Apache Web Serever. The application is configured to span all Availability Zones in the region and is Auto-Scaled based on the CPU utilization of the web servers. Notifications will be sent to the operator email address on scaling events. The instances are load balanced with a simple health check against the default web page. **WARNING** This template creates one or more Amazon EC2 instances and an Elastic Load Balancer. You will be billed for the AWS resources used if you create a stack from this template.",
6
+
7
+ "Parameters" : {
8
+ },
9
+
10
+ "Resources" : {
11
+
12
+ "ElasticLoadBalancer" : {
13
+ "Type" : "AWS::ElasticLoadBalancing::LoadBalancer",
14
+ "Properties" : {
15
+ "AvailabilityZones" : { "Fn::GetAZs" : "" },
16
+ "CrossZone" : "true",
17
+ "Listeners" : [ {
18
+ "LoadBalancerPort" : "80",
19
+ "InstancePort" : "80",
20
+ "Protocol" : "HTTP"
21
+ } ],
22
+ "HealthCheck" : {
23
+ "Target" : "HTTP:80/",
24
+ "HealthyThreshold" : "3",
25
+ "UnhealthyThreshold" : "5",
26
+ "Interval" : "30",
27
+ "Timeout" : "5"
28
+ }
29
+ }
30
+ },
31
+
32
+ "InstanceSecurityGroup" : {
33
+ "Type" : "AWS::EC2::SecurityGroup",
34
+ "Properties" : {
35
+ "GroupDescription" : "Enable SSH access and HTTP from the load balancer only",
36
+ "SecurityGroupIngress" : [
37
+ {
38
+ "IpProtocol" : "tcp",
39
+ "FromPort" : "80",
40
+ "ToPort" : "80",
41
+ "SourceSecurityGroupOwnerId" : {"Fn::GetAtt" : ["ElasticLoadBalancer", "SourceSecurityGroup.OwnerAlias"]},
42
+ "SourceSecurityGroupName" : {"Fn::GetAtt" : ["ElasticLoadBalancer", "SourceSecurityGroup.GroupName"]}
43
+ }
44
+ ]
45
+ }
46
+ }
47
+ },
48
+
49
+ "Outputs" : {
50
+ "InstanceSecurityGroup" : {
51
+ "Description" : "The security group id of the instance",
52
+ "Value" : { "Fn::GetAtt" : [ "InstanceSecurityGroup", "GroupId" ]}
53
+ },
54
+ "URL" : {
55
+ "Description" : "The URL of the website",
56
+ "Value" : { "Fn::Join" : [ "", [ "http://", { "Fn::GetAtt" : [ "ElasticLoadBalancer", "DNSName" ]}]]}
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,9 @@
1
+ require "autocanary24/version"
2
+ require "autocanary24/client"
3
+ require "autocanary24/configuration"
4
+ require "autocanary24/canarystack"
5
+ require 'aws-sdk-core'
6
+ require 'base64'
7
+
8
+ module AutoCanary24
9
+ end
@@ -0,0 +1,178 @@
1
+ require 'aws-sdk-core'
2
+
3
+ module AutoCanary24
4
+ class CanaryStack
5
+ attr_reader :stack_name
6
+
7
+ def initialize(stack_name, wait_timeout, sleep_during_wait = 5)
8
+ raise "ERR: stack_name is missing" if stack_name.nil?
9
+ raise "ERR: wait_timeout is missing" if wait_timeout.nil?
10
+ @stack_name = stack_name
11
+ @wait_timeout = wait_timeout
12
+ @sleep_during_wait = sleep_during_wait
13
+ end
14
+
15
+ def get_desired_capacity
16
+ asg = get_autoscaling_group
17
+ asg_client = Aws::AutoScaling::Client.new
18
+ describe_asg(asg).desired_capacity
19
+ end
20
+
21
+ def set_desired_capacity_and_wait(desired_capacity)
22
+ asg = get_autoscaling_group
23
+ asg_client = Aws::AutoScaling::Client.new
24
+ resp = asg_client.set_desired_capacity({
25
+ auto_scaling_group_name: asg,
26
+ desired_capacity: desired_capacity,
27
+ honor_cooldown: false,
28
+ })
29
+ wait_for_instances_in_asg(asg, desired_capacity)
30
+ end
31
+
32
+ def is_attached_to(elb)
33
+ asg = get_autoscaling_group
34
+ elbs = get_attached_loadbalancers(asg) unless asg.nil?
35
+ (!elbs.nil? && elbs.any? { |e| e.load_balancer_name == elb })
36
+ end
37
+
38
+ def attach_instances_to_elb_and_wait(elb, instances)
39
+ elb_client = Aws::ElasticLoadBalancing::Client.new
40
+ elb_client.register_instances_with_load_balancer({ load_balancer_name: elb, instances: instances })
41
+ wait_for_instances_attached_to_elb(instances, elb)
42
+ end
43
+
44
+ def detach_instances_from_elb(elb, instances)
45
+ elb_client = Aws::ElasticLoadBalancing::Client.new
46
+ elb_client.deregister_instances_from_load_balancer({ load_balancer_name: elb, instances: instances })
47
+ end
48
+
49
+ def detach_asg_from_elb_and_wait(elb)
50
+ asg = get_autoscaling_group
51
+ asg_client = Aws::AutoScaling::Client.new
52
+ asg_client.detach_load_balancers({auto_scaling_group_name: asg, load_balancer_names: [elb]})
53
+ wait_for_asg_detached_from_elb(asg, elb)
54
+ end
55
+
56
+ def attach_asg_to_elb_and_wait(elb)
57
+ asg = get_autoscaling_group
58
+ asg_client = Aws::AutoScaling::Client.new
59
+ asg_client.attach_load_balancers({auto_scaling_group_name: asg, load_balancer_names: [elb]})
60
+ wait_for_asg_on_elb(asg, elb)
61
+ end
62
+
63
+ def suspend_asg_processes
64
+ processes = ['Launch', 'Terminate', 'AddToLoadBalancer', 'AlarmNotification']
65
+ asg = get_autoscaling_group
66
+ asg_client = Aws::AutoScaling::Client.new
67
+ asg_client.suspend_processes({auto_scaling_group_name: asg, scaling_processes: processes})
68
+ end
69
+
70
+ def resume_asg_processes
71
+ processes = ['Launch', 'Terminate', 'AddToLoadBalancer', 'AlarmNotification']
72
+ asg = get_autoscaling_group
73
+ asg_client = Aws::AutoScaling::Client.new
74
+ asg_client.resume_processes({auto_scaling_group_name: asg, scaling_processes: processes})
75
+ end
76
+
77
+ def get_instance_ids
78
+ asg = get_autoscaling_group
79
+ asg_client = Aws::AutoScaling::Client.new
80
+ describe_asg(asg)[:instances].map{ |i| { instance_id: i[:instance_id] } }
81
+ end
82
+
83
+
84
+ private
85
+ def describe_asg(asg)
86
+ asg_client = Aws::AutoScaling::Client.new
87
+ asg_client.describe_auto_scaling_groups({auto_scaling_group_names: [asg], max_records: 1})[:auto_scaling_groups][0]
88
+ end
89
+
90
+ def wait_for_asg_detached_from_elb(asg, elb)
91
+ auto_scaling_group = describe_asg(asg)
92
+
93
+ if auto_scaling_group[:load_balancer_names].select{|l| l == elb}.length == 1
94
+ puts "WARNING: ASG still on the ELB!"
95
+ end
96
+
97
+ instances = auto_scaling_group[:instances].map{ |i| { instance_id: i[:instance_id] } }
98
+ wait_for_instances_detached_from_elb(instances, elb)
99
+ end
100
+
101
+ def wait_for_instances_detached_from_elb(instances, elb)
102
+ elb_client = Aws::ElasticLoadBalancing::Client.new
103
+ retries = (@wait_timeout / @sleep_during_wait).round
104
+ while retries > 0
105
+ begin
106
+ elb_instances = elb_client.describe_instance_health({load_balancer_name: elb, instances: instances})
107
+ break if elb_instances[:instance_states].select{ |s| s.state == 'InService' }.length == 0
108
+ rescue Aws::ElasticLoadBalancing::Errors::InvalidInstance
109
+ end
110
+ sleep @sleep_during_wait
111
+ retries -= 1
112
+ end
113
+
114
+ raise "Timeout. Couldn't wait for instances '#{instances}' to get detached from ELB '#{elb}'." if retries == 0
115
+ end
116
+
117
+ def wait_for_asg_on_elb(asg, elb)
118
+ auto_scaling_group = describe_asg(asg)
119
+
120
+ if auto_scaling_group[:load_balancer_names].select{|l| l == elb}.length == 0
121
+ puts "WARNING: ASG not on the ELB yet!"
122
+ end
123
+
124
+ instances = auto_scaling_group[:instances].map{ |i| { instance_id: i[:instance_id] } }
125
+ wait_for_instances_attached_to_elb(instances, elb)
126
+ end
127
+
128
+ def wait_for_instances_attached_to_elb(instances, elb)
129
+ elb_client = Aws::ElasticLoadBalancing::Client.new
130
+ retries = (@wait_timeout / @sleep_during_wait).round
131
+ while retries > 0
132
+ begin
133
+ elb_instances = elb_client.describe_instance_health({load_balancer_name: elb, instances: instances})
134
+ break if elb_instances[:instance_states].select{ |s| s.state != 'InService' }.length == 0
135
+ rescue Aws::ElasticLoadBalancing::Errors::InvalidInstance
136
+ end
137
+ sleep @sleep_during_wait
138
+ retries -= 1
139
+ end
140
+
141
+ raise "Timeout. Couldn't wait for instances '#{instances}' to get attached to ELB '#{elb}'." if retries == 0
142
+ end
143
+
144
+ def wait_for_instances_in_asg(asg, expected_number_of_instances)
145
+ asg_client = Aws::AutoScaling::Client.new
146
+ retries = (@wait_timeout / @sleep_during_wait).round
147
+ while retries > 0
148
+ instances = asg_client.describe_auto_scaling_groups({auto_scaling_group_names: [asg]})[:auto_scaling_groups][0].instances
149
+ healthy_instances = instances.select{ |i| i[:health_status] == "Healthy" && i[:lifecycle_state]=="InService"}.length
150
+ break if healthy_instances == expected_number_of_instances
151
+ sleep @sleep_during_wait
152
+ retries -= 1
153
+ end
154
+
155
+ raise "Timeout. Only #{healthy_instances} of #{expected_number_of_instances} instances got healthy in ASG '#{asg}'." if retries == 0
156
+ end
157
+
158
+ def get_attached_loadbalancers(asg)
159
+ asg_client = Aws::AutoScaling::Client.new
160
+ asg_client.describe_load_balancers({ auto_scaling_group_name: asg }).load_balancers
161
+ end
162
+
163
+ def get_autoscaling_group
164
+ get_first_resource_id('AWS::AutoScaling::AutoScalingGroup')
165
+ end
166
+
167
+ def get_first_resource_id(resource_type)
168
+ client = Aws::CloudFormation::Client.new
169
+ begin
170
+ response = client.list_stack_resources({ stack_name: @stack_name })
171
+ rescue Exception
172
+ return nil
173
+ end
174
+ resources = response.data.stack_resource_summaries.select{|x| x[:resource_type] == resource_type }
175
+ resources.map{ |e| e.physical_resource_id }[0]
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,183 @@
1
+ require 'aws-sdk-core'
2
+ require 'autostacker24'
3
+ require 'base64'
4
+
5
+ require_relative 'configuration'
6
+ require_relative 'canarystack'
7
+
8
+ module AutoCanary24
9
+
10
+ class Client
11
+ def initialize(**params)
12
+ @configuration = Configuration.new(params) #params.fetch(:configuration, Configuration::new(params))
13
+ end
14
+
15
+ def deploy_stack(parent_stack_name, template, parameters, tags = nil, deployment_check = lambda { |stacks, elb, instances_to_create| true })
16
+ begin
17
+ write_log(parent_stack_name, "Starting the deployment")
18
+ write_log(parent_stack_name, "Using the following configuration #{@configuration.inspect}")
19
+
20
+ elb = get_elb(parent_stack_name)
21
+ raise "No ELB found in stack #{parent_stack_name}" if elb.nil?
22
+
23
+ blue_cs = get_canary_stack("#{parent_stack_name}-B")
24
+ green_cs = get_canary_stack("#{parent_stack_name}-G")
25
+
26
+ stacks = get_stacks_to_create_and_to_delete_for(blue_cs, green_cs, elb)
27
+
28
+ before_switch(stacks, template, parameters, parent_stack_name, tags)
29
+
30
+ failed = switch(stacks, elb, deployment_check)
31
+
32
+ after_switch(stacks, failed || @configuration.keep_inactive_stack)
33
+
34
+ rescue Exception => e
35
+ write_log(parent_stack_name, "Unexpected exception #{e}")
36
+ end
37
+ write_log(parent_stack_name, "Deployment finished")
38
+ end
39
+
40
+ private
41
+ def get_stacks_to_create_and_to_delete_for(blue_cs, green_cs, elb)
42
+
43
+ green_is_attached = green_cs.is_attached_to(elb)
44
+ blue_is_attached = blue_cs.is_attached_to(elb)
45
+
46
+ if green_is_attached
47
+ stack_to_delete = green_cs
48
+ stack_to_create = blue_cs
49
+ elsif blue_is_attached
50
+ stack_to_delete = blue_cs
51
+ stack_to_create = green_cs
52
+ else
53
+ stack_to_delete = nil
54
+ stack_to_create = blue_cs
55
+ end
56
+
57
+ write_log(blue_cs.stack_name, blue_is_attached ? "Stack is attached to ELB #{elb}" : "Stack is not attached")
58
+ write_log(green_cs.stack_name, green_is_attached ? "Stack is attached to ELB #{elb}" : "Stack is not attached")
59
+
60
+ write_log(stack_to_create.stack_name, "will be created")
61
+ write_log(stack_to_delete.stack_name, "will be deleted")
62
+
63
+ {stack_to_create: stack_to_create, stack_to_delete: stack_to_delete}
64
+ end
65
+
66
+ def before_switch(stacks, template, parameters, parent_stack_name, tags)
67
+
68
+ create_stack(stacks[:stack_to_create].stack_name, template, parameters, parent_stack_name, tags)
69
+
70
+ unless stacks[:stack_to_delete].nil?
71
+ desired = stacks[:stack_to_delete].get_desired_capacity
72
+ write_log(stacks[:stack_to_delete].stack_name, "Found #{desired} instances")
73
+
74
+ stacks[:stack_to_create].set_desired_capacity_and_wait(desired)
75
+ stacks[:stack_to_delete].suspend_asg_processes
76
+ end
77
+
78
+ stacks[:stack_to_create].suspend_asg_processes
79
+
80
+ end
81
+
82
+ def switch(stacks, elb, deployment_check)
83
+
84
+ desired = stacks[:stack_to_create].get_desired_capacity
85
+
86
+ instances_to_toggle = (desired / 100.0 * @configuration.scaling_instance_percent).round
87
+ instances_to_toggle = 1 if (instances_to_toggle < 1)
88
+
89
+ instances_to_create = stacks[:stack_to_create].get_instance_ids
90
+ instances_to_delete = stacks[:stack_to_delete].nil? ? [] : stacks[:stack_to_delete].get_instance_ids
91
+
92
+ missing = desired
93
+ while missing > 0
94
+
95
+ write_log(stacks[:stack_to_create].stack_name, "Adding #{instances_to_toggle} instances (#{desired-missing+instances_to_toggle}/#{desired})")
96
+
97
+ begin
98
+ stacks[:stack_to_create].attach_instances_to_elb_and_wait(elb, instances_to_create[desired-missing, instances_to_toggle])
99
+ rescue
100
+ rollback(stacks, elb, instances_to_create, instances_to_delete)
101
+ return true
102
+ end
103
+
104
+ unless deployment_check.call(stacks, elb, instances_to_create)
105
+ rollback(stacks, elb, instances_to_create, instances_to_delete)
106
+ return true
107
+ end
108
+
109
+ if @configuration.keep_instances_balanced && !stacks[:stack_to_delete].nil?
110
+ begin
111
+ write_log(stacks[:stack_to_delete].stack_name, "Removing #{instances_to_toggle} instances (#{instances_to_delete[desired-missing, instances_to_toggle]})")
112
+ stacks[:stack_to_delete].detach_instances_from_elb(elb, instances_to_delete[desired-missing, instances_to_toggle])
113
+ rescue Exception => e
114
+ write_log(stacks[:stack_to_delete].stack_name, "WARNING: #{e}")
115
+ end
116
+ end
117
+
118
+ missing -= instances_to_toggle
119
+ if missing < instances_to_toggle
120
+ instances_to_toggle = missing
121
+ end
122
+ end
123
+
124
+ write_log(stacks[:stack_to_create].stack_name, "Attach to ELB #{elb}")
125
+ stacks[:stack_to_create].attach_asg_to_elb_and_wait(elb)
126
+
127
+ unless stacks[:stack_to_delete].nil?
128
+ write_log(stacks[:stack_to_delete].stack_name, "Detach from ELB #{elb}")
129
+ stacks[:stack_to_delete].detach_asg_from_elb_and_wait(elb)
130
+ end
131
+ end
132
+
133
+ def rollback(stacks, elb, instances_to_create, instances_to_delete)
134
+ begin
135
+ stacks[:stack_to_create].detach_instances_from_elb(elb, instances_to_create)
136
+ stacks[:stack_to_delete].attach_instances_to_elb_and_wait(elb, instances_to_delete)
137
+ rescue Exception => e
138
+ write_log("", "ROLLBACK FAILED: #{e}")
139
+ end
140
+ end
141
+
142
+ def after_switch(stacks, keep_inactive_stack)
143
+ stacks[:stack_to_create].resume_asg_processes
144
+ stacks[:stack_to_delete].resume_asg_processes unless stacks[:stack_to_delete].nil?
145
+
146
+ if keep_inactive_stack == false
147
+ delete_stack(stacks[:stack_to_delete])
148
+ end
149
+ end
150
+
151
+ def get_elb(stack_name)
152
+ get_first_resource_id(stack_name, 'AWS::ElasticLoadBalancing::LoadBalancer')
153
+ end
154
+
155
+ def get_first_resource_id(stack_name, resource_type)
156
+ return nil if stack_name.nil?
157
+
158
+ client = Aws::CloudFormation::Client.new
159
+ resp = client.list_stack_resources({ stack_name: stack_name }).data.stack_resource_summaries
160
+
161
+ resource_ids = resp.select{|x| x[:resource_type] == resource_type }.map { |e| e.physical_resource_id }
162
+ resource_ids[0]
163
+ end
164
+
165
+ def create_stack(stack_name, template, parameters, parent_stack_name, tags)
166
+ write_log(stack_name, "Create/Update stack")
167
+ Stacker.create_or_update_stack(stack_name, template, parameters, parent_stack_name, tags)
168
+ end
169
+
170
+ def delete_stack(stack_name)
171
+ Stacker.delete_stack(stack_name) unless stack_name.nil?
172
+ end
173
+
174
+ def get_canary_stack(stack_name)
175
+ CanaryStack.new(stack_name, @configuration.wait_timeout)
176
+ end
177
+
178
+ def write_log(stack_name, message)
179
+ puts "#{Time.now.utc}\t#{stack_name.ljust(20)}\t#{message.ljust(40)}"
180
+ end
181
+ end
182
+
183
+ end
@@ -0,0 +1,40 @@
1
+ module AutoCanary24
2
+ class Configuration
3
+ # Defines what should happen with the inactive stack
4
+ attr_accessor :keep_inactive_stack
5
+ # If true a instance from current stack gets removed whenever a new instance from the new stack is added.
6
+ attr_accessor :keep_instances_balanced
7
+ # Percent of instances which are added at once (depends on the actual number of instances, read from desired)
8
+ attr_accessor :scaling_instance_percent
9
+ # Timeout to wait for checking AWS operations are done before do a rollback
10
+ attr_accessor :wait_timeout
11
+
12
+ def initialize(**params)
13
+
14
+ @keep_inactive_stack = false
15
+ unless params[:keep_inactive_stack].nil?
16
+ raise "ERR: inactive_stack_state should be a boolean" unless [true, false].include? params[:keep_inactive_stack]
17
+ @keep_inactive_stack = params[:keep_inactive_stack]
18
+ end
19
+
20
+ @keep_instances_balanced = false
21
+ unless params[:keep_instances_balanced].nil?
22
+ raise 'ERR: keep_instances_balanced needs to a boolean' unless [true, false].include? params[:keep_instances_balanced]
23
+ @keep_instances_balanced = params[:keep_instances_balanced]
24
+ end
25
+
26
+ @scaling_instance_percent = 100
27
+ unless params[:scaling_instance_percent].nil?
28
+ raise 'ERR: scaling_instance_percent needs to be a number between 1 and 100' unless params[:scaling_instance_percent].is_a?(Integer) && (1..100).include?(params[:scaling_instance_percent])
29
+ @scaling_instance_percent = params[:scaling_instance_percent]
30
+ end
31
+
32
+ @wait_timeout = 300
33
+ unless params[:wait_timeout].nil?
34
+ raise 'ERR: wait_timeout needs to be a number' unless params[:wait_timeout].is_a?(Integer)
35
+ @wait_timeout = params[:wait_timeout]
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module AutoCanary24
2
+ VERSION = "0.1.0-alpha"
3
+ end
metadata ADDED
@@ -0,0 +1,109 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: autocanary24
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.alpha
5
+ platform: ruby
6
+ authors:
7
+ - Philipp Garbe
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description: autocanary24 provides a small convenient module for blue/green and canary
56
+ deployments with CloudFormation.
57
+ email:
58
+ - pgarbe@autoscout24.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - ".rspec"
65
+ - ".travis.yml"
66
+ - Gemfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - autocanary24.gemspec
71
+ - bin/console
72
+ - bin/setup
73
+ - examples/.bundle/config
74
+ - examples/Gemfile
75
+ - examples/Gemfile.lock
76
+ - examples/README.md
77
+ - examples/Rakefile
78
+ - examples/asg-stack.json
79
+ - examples/base-stack.json
80
+ - lib/autocanary24.rb
81
+ - lib/autocanary24/canarystack.rb
82
+ - lib/autocanary24/client.rb
83
+ - lib/autocanary24/configuration.rb
84
+ - lib/autocanary24/version.rb
85
+ homepage: https://github.com/autoscout24/autocanary24
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">"
101
+ - !ruby/object:Gem::Version
102
+ version: 1.3.1
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.4.5.1
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: A very narrow interface to AWS ECR
109
+ test_files: []