autocanary24 0.1.0.pre.alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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: []