eb_deployer 0.0.12 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +2 -2
- data/README.md +81 -2
- data/eb_deployer.gemspec +3 -3
- data/lib/eb_deployer.rb +95 -1
- data/lib/eb_deployer/application.rb +10 -0
- data/lib/eb_deployer/beanstalk.rb +12 -0
- data/lib/eb_deployer/version.rb +1 -1
- data/test/aws_driver_stubs.rb +20 -0
- data/test/deploy_test.rb +21 -4
- metadata +7 -5
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2013
|
1
|
+
Copyright (c) 2013 Thoughtworks Inc.
|
2
2
|
|
3
3
|
MIT License
|
4
4
|
|
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
19
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
20
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
21
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# EbDeployer
|
2
2
|
|
3
|
-
|
3
|
+
Low friction deployments should be a breeze. Elastic Beanstalk provides a great foundation for performing Blue-Green deployments, and EbDeployer add a missing top to automate the whole flow out of box.
|
4
|
+
|
5
|
+
ElasticBeanstalk Deployer thus allows you to do continuous delivery on AWS.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -18,7 +20,84 @@ Or install it yourself as:
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
|
23
|
+
### Step One: AWS Account Setup
|
24
|
+
|
25
|
+
Create an AWS IAM user for deploy and give it privilege to operate Elastic Beanstalk. Download the access keys for executing the deployment tasks later.
|
26
|
+
|
27
|
+
### Step Two: Packaging
|
28
|
+
|
29
|
+
You need package your application for Elastic Beanstalk stack first. For Java app an warball is appropriate. For Ruby on Rails app a tar.gz file is good. You can also package a Rails/Sinatra app as war ball using warbler and deploy to Java stack.
|
30
|
+
|
31
|
+
|
32
|
+
### Step Three: Define the task
|
33
|
+
Add a deploy task for deployment in your Rakefile
|
34
|
+
|
35
|
+
require 'digest'
|
36
|
+
|
37
|
+
desc "deploy our beloved app to elastic beanstalk"
|
38
|
+
task :deploy, [:package] do |t, args|
|
39
|
+
EbDeployer.deploy(:application => "MYAPP",
|
40
|
+
:environment => "production",
|
41
|
+
:solution_stack_name => <SOLUTION_STACK_NAME>
|
42
|
+
:package => args[:package],
|
43
|
+
:version_label => "dev-" + Digest::MD5.file(args[:package]).hexdigest)
|
44
|
+
end
|
45
|
+
|
46
|
+
### Step Four: Fasten your seat belt
|
47
|
+
run deploy task:
|
48
|
+
|
49
|
+
rake deploy[<package built>] AWS_ACCESS_KEY_ID=<deployers_aws_key> AWS_SECRET_ACCESS_KEY=<secret>
|
50
|
+
Then open aws console for Elastic Beanstalk to see what happened.
|
51
|
+
|
52
|
+
|
53
|
+
### Smoke Testing your stack
|
54
|
+
|
55
|
+
EB_Deployer allows you to automate your deployment and then some. You can also add smoke tests to your deployment - thus ensuring that the app you deployed is also working correctly.
|
56
|
+
Adding a smoke test suite is also simple. All that you need to do is edit your rake task as follows:
|
57
|
+
|
58
|
+
desc "deploy our simple java app with one page"
|
59
|
+
task :deploy, [:package] do |t, args|
|
60
|
+
EbDeployer.deploy(:application => "MYAPP",
|
61
|
+
:environment => "production",
|
62
|
+
:solution_stack_name => <SOLUTION_STACK_NAME>
|
63
|
+
:package => args[:package],
|
64
|
+
:version_label => "dev-" + Digest::MD5.file(args[:package]).hexdigest)
|
65
|
+
:smoke_test => lambda { |host|
|
66
|
+
Timeout.timeout(600) do
|
67
|
+
until `curl http://#{host}`.include?('Hello, World')
|
68
|
+
sleep 5
|
69
|
+
end
|
70
|
+
end
|
71
|
+
})
|
72
|
+
end
|
73
|
+
You can add more smoke tests by calling arbitrary tasks from this rake task.
|
74
|
+
Smoke testing gets you one step closer to continuous delivery.
|
75
|
+
|
76
|
+
### Blue-Green deployment
|
77
|
+
Since every deployment now runs smoke test, you now have a better safety net around your deployments. This allows us to trigger automatic blue-green deployments.
|
78
|
+
|
79
|
+
To do this you need not do anything special. So far we have deployed the application only once. Let's call this the 'green' stack. Any subsequent calls to deployment will deployment a copy of this application to a new stack - the 'blue' stack. Smoke tests will be run on it and once everything passes the 'blue'(new) stack will be switched to the 'green' stack. Thus your new code will now be on the active stack and the user will experience no downtime.
|
80
|
+
|
81
|
+
Once this new stack is stable or has run for a while you can choose to delete the old stack. Or if you are doing continuous delivery you may be ready to another 'blue' deployment. You could just trigger another deployment and repeat this every hour/day/week... you get the idea.
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
### Destroying a stack
|
86
|
+
So you are done with this application or environment, you can destroy it easily as well.
|
87
|
+
|
88
|
+
desc "clean up everything"
|
89
|
+
task :teardown do |t, args|
|
90
|
+
EbDeployer.destroy(:application => "ebtest-simple")
|
91
|
+
end
|
92
|
+
|
93
|
+
and you are done!
|
94
|
+
|
95
|
+
Later tutorials coming soon will cover
|
96
|
+
* how to setup multiple environment suites: production, staging, and how to manage configurations for them
|
97
|
+
* how to setup RDS or other AWS resource and share them between blue green environments
|
98
|
+
|
99
|
+
Take a look at code if you can not wait for the documentation.
|
100
|
+
|
22
101
|
|
23
102
|
## Contributing
|
24
103
|
|
data/eb_deployer.gemspec
CHANGED
@@ -4,14 +4,14 @@ require File.expand_path('../lib/eb_deployer/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["wpc", "betarelease"]
|
6
6
|
gem.email = ["alex.hal9000@gmail.com", "sudhindra.r.rao@gmail.com"]
|
7
|
-
gem.description = %q{
|
8
|
-
gem.summary = %q{
|
7
|
+
gem.description = %q{For automate blue green deployment flow on Elasti Beanstalk.}
|
8
|
+
gem.summary = %q{Low friction deployments should be a breeze. Elastic Beanstalk provides a great foundation for performing Blue-Green deployments, and EbDeployer add a missing top to automate the whole flow out of box.}
|
9
9
|
gem.homepage = "https://github.com/ThoughtWorksStudios/eb_deployer"
|
10
10
|
|
11
11
|
gem.add_runtime_dependency 'aws-sdk'
|
12
12
|
gem.add_development_dependency 'minitest'
|
13
13
|
|
14
|
-
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.files = `git ls-files`.split($\).reject {|f| f =~ /^samples\// }
|
15
15
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
17
|
gem.name = "eb_deployer"
|
data/lib/eb_deployer.rb
CHANGED
@@ -28,6 +28,88 @@ module EbDeployer
|
|
28
28
|
provisioner.output(key)
|
29
29
|
end
|
30
30
|
|
31
|
+
|
32
|
+
#
|
33
|
+
# Options available:
|
34
|
+
#
|
35
|
+
# :application (required)
|
36
|
+
# Application name, this used for isolate packages and contribute
|
37
|
+
# to your elastic beanstalk cname for environments
|
38
|
+
#
|
39
|
+
# :environment (required)
|
40
|
+
# Environment for same application, e.g. testing, staging,
|
41
|
+
# production. This will map to 2 elastic beanstalk environments
|
42
|
+
# (env-a-xxx, env-b-xxx) if blue-green deployment strategy specified
|
43
|
+
#
|
44
|
+
# :package (required) package for the application which should be
|
45
|
+
# suitable for elastic beanstalk deploying. For example, a war file
|
46
|
+
# should be provided for java solution stacks and a tar gz file
|
47
|
+
# should be provided for rails stack.
|
48
|
+
#
|
49
|
+
# :version_label (required)
|
50
|
+
# Version label give the package uploaded a unique identifier.
|
51
|
+
# Should use something related to pipeline counter if you have build
|
52
|
+
# pipeline setup to build the installer. For the convient of dev we
|
53
|
+
# recommend use md5 digest of the installer so that everytime you
|
54
|
+
# upload new installer it forms a new version. e.g.
|
55
|
+
#
|
56
|
+
# :version_label => ENV['MY_PIPELINE_COUNTER']
|
57
|
+
# || "dev-" + Digest::MD5.file(my_package).hexdigest
|
58
|
+
#
|
59
|
+
# :solution_stack_name (optional default "64bit Amazon Linux running Tomcat 7")
|
60
|
+
# The elastic beanstalk solution stack you want to deploy on top of.
|
61
|
+
# Current possible values include:
|
62
|
+
#
|
63
|
+
# :settings (optional)
|
64
|
+
# Elastic Beanstalk settings that will apply to the environments you
|
65
|
+
# deploying. Value should be array of hash with format such as:
|
66
|
+
# [{
|
67
|
+
# :namespace => 'aws:autoscaling:launchconfiguration',
|
68
|
+
# :option_name => 'InstanceType',
|
69
|
+
# :value => 'm1.small' }]
|
70
|
+
# When there are many, Using an external yaml file to hold those
|
71
|
+
# configuration is recommended. Such as:
|
72
|
+
# YAML.load(File.read("my_settings_file.yml"))
|
73
|
+
# For all available options take a look at
|
74
|
+
# http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html
|
75
|
+
#
|
76
|
+
# :resources (optional)
|
77
|
+
# If :resources specified, EBDeployer will use the CloudFormation
|
78
|
+
# template you provide to create a default CloudFormation stack with
|
79
|
+
# name <application_name>-<env-name> for the environment current
|
80
|
+
# deploying. Value of resources need to be hash with following
|
81
|
+
# keys:
|
82
|
+
# :template => CloudFormation template file with JSON format
|
83
|
+
# :parameters => A Hash, input values for the CloudFormation
|
84
|
+
# template
|
85
|
+
# :transforms => A Hash with key map to your CloudFormation
|
86
|
+
# template outputs and value as lambda that return a single or array of
|
87
|
+
# elastic beanstalk settings.
|
88
|
+
# :capabilities => An array. You need set it to ['CAPABILITY_IAM']
|
89
|
+
# if you want to provision IAM Instance Profile.
|
90
|
+
#
|
91
|
+
# :smoke_test (optional)
|
92
|
+
# Value should be a proc or a lambda which accept single argument that will
|
93
|
+
# passed in as environment DNS name. Smoke test proc or lambda will be
|
94
|
+
# called at the end of the deployment for inplace-update deployment
|
95
|
+
# strategy. For blue-green deployment it will run after inactive
|
96
|
+
# environment update finish and before switching.
|
97
|
+
# Defining a smoke test is high recommended for serious usage. The
|
98
|
+
# simplest one could just be checking the server is up using curl, e.g.
|
99
|
+
#
|
100
|
+
# :smoke_test => lambda { |host|
|
101
|
+
# curl_http_code = "curl -k -s -o /dev/null -w \"%{http_code}\" https://#{host}"
|
102
|
+
# Timeout.timeout(600) do
|
103
|
+
# while `#{curl_http_code}`.strip != '200'
|
104
|
+
# sleep 5
|
105
|
+
# end
|
106
|
+
# end
|
107
|
+
# }
|
108
|
+
#
|
109
|
+
#
|
110
|
+
# deploy a package to specfied environments on elastic beanstalk
|
111
|
+
#
|
112
|
+
|
31
113
|
def self.deploy(opts)
|
32
114
|
# AWS.config(:logger => Logger.new($stdout))
|
33
115
|
if region = opts[:region]
|
@@ -43,7 +125,7 @@ module EbDeployer
|
|
43
125
|
version_label = opts[:version_label].to_s.strip
|
44
126
|
cname = opts[:cname]
|
45
127
|
env_settings = opts[:settings] || []
|
46
|
-
strategy_name = opts[:strategy] || :
|
128
|
+
strategy_name = opts[:strategy] || :blue_green
|
47
129
|
cname_prefix = opts[:cname_prefix] || [app, env_name].join('-')
|
48
130
|
smoke_test = opts[:smoke_test] || Proc.new {}
|
49
131
|
|
@@ -64,4 +146,16 @@ module EbDeployer
|
|
64
146
|
strategy.deploy(version_label, env_settings)
|
65
147
|
end
|
66
148
|
|
149
|
+
def self.destroy(opts)
|
150
|
+
if region = opts[:region]
|
151
|
+
AWS.config(:region => region)
|
152
|
+
end
|
153
|
+
|
154
|
+
app = opts[:application]
|
155
|
+
bs = opts[:bs_driver] || Beanstalk.new
|
156
|
+
s3 = opts[:s3_driver] || S3Driver.new
|
157
|
+
cf = opts[:cf_driver] || CloudFormationDriver.new
|
158
|
+
Application.new(app, bs, s3).delete
|
159
|
+
end
|
160
|
+
|
67
161
|
end
|
@@ -17,6 +17,16 @@ module EbDeployer
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
def delete
|
21
|
+
if @eb_driver.application_exists?(@name)
|
22
|
+
@eb_driver.environment_names_for_application(@name).each do |env|
|
23
|
+
@eb_driver.delete_environment(@name, env)
|
24
|
+
end
|
25
|
+
|
26
|
+
@eb_driver.delete_application(@name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
20
30
|
private
|
21
31
|
|
22
32
|
def create_application_if_not_exists
|
@@ -9,6 +9,10 @@ module EbDeployer
|
|
9
9
|
@client.create_application(:application_name => app)
|
10
10
|
end
|
11
11
|
|
12
|
+
def delete_application(app)
|
13
|
+
@client.delete_application(:application_name => app)
|
14
|
+
end
|
15
|
+
|
12
16
|
def application_exists?(app)
|
13
17
|
@client.describe_applications(:application_names => [app])[:applications].any?
|
14
18
|
end
|
@@ -24,6 +28,10 @@ module EbDeployer
|
|
24
28
|
alive_envs(app_name, [env_name]).any?
|
25
29
|
end
|
26
30
|
|
31
|
+
def environment_names_for_application(app_name)
|
32
|
+
alive_envs(app_name).collect { |env| env[:environment_name] }
|
33
|
+
end
|
34
|
+
|
27
35
|
def create_environment(app_name, env_name, stack_name, cname_prefix, version, settings)
|
28
36
|
request = {:application_name => app_name,
|
29
37
|
:environment_name => env_name,
|
@@ -34,6 +42,10 @@ module EbDeployer
|
|
34
42
|
@client.create_environment(request)
|
35
43
|
end
|
36
44
|
|
45
|
+
def delete_environment(app_name, env_name)
|
46
|
+
@client.terminate_environment(:environment_name => env_name)
|
47
|
+
end
|
48
|
+
|
37
49
|
def create_application_version(app_name, version_label, source_bundle)
|
38
50
|
@client.create_application_version(:application_name => app_name,
|
39
51
|
:source_bundle => source_bundle,
|
data/lib/eb_deployer/version.rb
CHANGED
data/test/aws_driver_stubs.rb
CHANGED
@@ -10,6 +10,11 @@ class EBStub
|
|
10
10
|
@apps << app
|
11
11
|
end
|
12
12
|
|
13
|
+
def delete_application(app)
|
14
|
+
raise "there are environments running for this app" unless environment_names_for_application(app).empty?
|
15
|
+
@apps.delete(app)
|
16
|
+
end
|
17
|
+
|
13
18
|
def application_exists?(app)
|
14
19
|
@apps.include?(app)
|
15
20
|
end
|
@@ -17,13 +22,19 @@ class EBStub
|
|
17
22
|
def create_environment(app, env, solution_stack, cname_prefix, version, settings)
|
18
23
|
raise 'cname prefix is not avaible' if @envs.values.detect { |env| env[:cname_prefix] == cname_prefix }
|
19
24
|
raise "env name #{env} is longer than 23 chars" if env.size > 23
|
25
|
+
raise "app not exists" unless application_exists?(app)
|
20
26
|
@envs[env_key(app, env)] = {
|
27
|
+
:application => app,
|
21
28
|
:solution_stack => solution_stack,
|
22
29
|
:version => version,
|
23
30
|
:cname_prefix => cname_prefix,
|
24
31
|
:settings => settings}
|
25
32
|
end
|
26
33
|
|
34
|
+
def delete_environment(app, env)
|
35
|
+
@envs.delete(env)
|
36
|
+
end
|
37
|
+
|
27
38
|
def update_environment(app, env, version, settings)
|
28
39
|
@envs[env_key(app, env)].merge!(:version => version, :settings => settings)
|
29
40
|
end
|
@@ -78,7 +89,16 @@ class EBStub
|
|
78
89
|
@envs[env_key(app_name, env_name)][:settings]
|
79
90
|
end
|
80
91
|
|
92
|
+
def environment_names_for_application(app)
|
93
|
+
@envs.inject([]) do |memo, pair|
|
94
|
+
env_name, env = pair
|
95
|
+
memo << env_name if env[:application] == app
|
96
|
+
memo
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
81
100
|
private
|
101
|
+
|
82
102
|
def env_key(app, name)
|
83
103
|
[app, name].join("-")
|
84
104
|
end
|
data/test/deploy_test.rb
CHANGED
@@ -19,6 +19,13 @@ class DeployTest < Minitest::Test
|
|
19
19
|
assert @eb_driver.application_exists?('simple')
|
20
20
|
end
|
21
21
|
|
22
|
+
def test_destroy_should_clean_up_eb_application_and_env
|
23
|
+
deploy(:application => 'simple', :environment => "production")
|
24
|
+
destroy(:application => 'simple')
|
25
|
+
assert !@eb_driver.application_exists?('simple')
|
26
|
+
assert !@eb_driver.environment_exists?('simple', eb_envname('simple', 'production'))
|
27
|
+
end
|
28
|
+
|
22
29
|
def test_first_deployment_create_environment
|
23
30
|
assert !@eb_driver.environment_exists?('simple', eb_envname('simple', 'production'))
|
24
31
|
deploy(:application => 'simple', :environment => "production")
|
@@ -246,10 +253,20 @@ class DeployTest < Minitest::Test
|
|
246
253
|
|
247
254
|
def deploy(opts)
|
248
255
|
EbDeployer.deploy({:package => @sample_package,
|
249
|
-
:
|
250
|
-
:
|
251
|
-
|
252
|
-
|
256
|
+
:strategy => :inplace_update,
|
257
|
+
:version_label => 1}.merge(opts).merge(stubs))
|
258
|
+
end
|
259
|
+
|
260
|
+
def destroy(opts)
|
261
|
+
EbDeployer.destroy(opts.merge(stubs))
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
def stubs
|
266
|
+
{ :bs_driver => @eb_driver,
|
267
|
+
:s3_driver => @s3_driver,
|
268
|
+
:cf_driver => @cf_driver
|
269
|
+
}
|
253
270
|
end
|
254
271
|
|
255
272
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eb_deployer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-08-01 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: aws-sdk
|
@@ -44,7 +44,7 @@ dependencies:
|
|
44
44
|
- - ! '>='
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '0'
|
47
|
-
description:
|
47
|
+
description: For automate blue green deployment flow on Elasti Beanstalk.
|
48
48
|
email:
|
49
49
|
- alex.hal9000@gmail.com
|
50
50
|
- sudhindra.r.rao@gmail.com
|
@@ -91,10 +91,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
91
|
version: '0'
|
92
92
|
requirements: []
|
93
93
|
rubyforge_project:
|
94
|
-
rubygems_version: 1.8.
|
94
|
+
rubygems_version: 1.8.25
|
95
95
|
signing_key:
|
96
96
|
specification_version: 3
|
97
|
-
summary:
|
97
|
+
summary: Low friction deployments should be a breeze. Elastic Beanstalk provides a
|
98
|
+
great foundation for performing Blue-Green deployments, and EbDeployer add a missing
|
99
|
+
top to automate the whole flow out of box.
|
98
100
|
test_files:
|
99
101
|
- test/aws_driver_stubs.rb
|
100
102
|
- test/deploy_test.rb
|