eb_deployer 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +31 -51
- data/bin/eb_deploy +11 -0
- data/lib/eb_deployer/cloud_formation_driver.rb +2 -1
- data/lib/eb_deployer/cloud_formation_provisioner.rb +19 -3
- data/lib/eb_deployer/config_loader.rb +62 -0
- data/lib/eb_deployer/default_config.rb +20 -0
- data/lib/eb_deployer/default_config.yml +109 -0
- data/lib/eb_deployer/environment.rb +7 -10
- data/lib/eb_deployer/smoke_test.rb +23 -0
- data/lib/eb_deployer/version.rb +1 -1
- data/lib/eb_deployer.rb +67 -4
- data/test/cloud_formation_provisioner_test.rb +28 -0
- data/test/config_loader_test.rb +124 -0
- data/test/deploy_test.rb +13 -10
- data/test/smoke_test_test.rb +23 -0
- data/test/test_helper.rb +16 -0
- metadata +18 -5
data/README.md
CHANGED
@@ -6,16 +6,6 @@ ElasticBeanstalk Deployer thus allows you to do continuous delivery on AWS.
|
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
|
-
Add this line to your application's Gemfile:
|
10
|
-
|
11
|
-
gem 'eb_deployer'
|
12
|
-
|
13
|
-
And then execute:
|
14
|
-
|
15
|
-
$ bundle
|
16
|
-
|
17
|
-
Or install it yourself as:
|
18
|
-
|
19
9
|
$ gem install eb_deployer
|
20
10
|
|
21
11
|
## Usage
|
@@ -26,57 +16,50 @@ Create an AWS IAM user for deploy and give it privilege to operate Elastic Beans
|
|
26
16
|
|
27
17
|
### Step Two: Packaging
|
28
18
|
|
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.
|
19
|
+
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. (Please remember do rake assets:precompile first for rails app)
|
20
|
+
|
30
21
|
|
22
|
+
### Step Three: Generate configuration and Configure deployment process
|
31
23
|
|
32
|
-
|
33
|
-
Add a deploy task for deployment in your Rakefile
|
24
|
+
$ eb_deploy
|
34
25
|
|
35
|
-
|
26
|
+
This will generate a default configuration at location 'config/eb_deployer.yml'. It is almost empty but working one. And it will generate settings for two environment 'dev' and 'production'. If you had time please try to read through it to see the options you can tweak.
|
36
27
|
|
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
28
|
|
46
29
|
### Step Four: Fasten your seat belt
|
47
|
-
run deploy
|
30
|
+
run deploy
|
31
|
+
|
32
|
+
$ eb_deploy -p <package built> -e <environment>
|
48
33
|
|
49
|
-
rake deploy[<package built>] AWS_ACCESS_KEY_ID=<deployers_aws_key> AWS_SECRET_ACCESS_KEY=<secret>
|
50
34
|
Then open aws console for Elastic Beanstalk to see what happened.
|
51
35
|
|
52
36
|
|
53
37
|
### Smoke Testing your stack
|
54
38
|
|
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.
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
You can add more smoke tests by calling arbitrary tasks from this rake task.
|
39
|
+
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.
|
40
|
+
Adding a smoke test suite is also simple. Check "smoke_test" section in your eb_deployer.yml. The simplest thing you can do is using curl make sure landing page get loaded, e.g.:
|
41
|
+
|
42
|
+
smoke_test: >
|
43
|
+
curl_http_code = "curl -s -o /dev/null -w \"%{http_code}\" http://#{host_name}"
|
44
|
+
Timeout.timeout(600) do
|
45
|
+
while `#{curl_http_code}`.strip != '200'
|
46
|
+
sleep 5
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
You can add more smoke tests by calling arbitrary rake tasks (Please make sure check return status):
|
52
|
+
|
53
|
+
smoke_test: >
|
54
|
+
`rake test:smoke HOST_NAME=#{host_name}`
|
55
|
+
raise("Smoke failed!") unless $?.success?
|
56
|
+
|
74
57
|
Smoke testing gets you one step closer to continuous delivery.
|
75
|
-
|
58
|
+
|
76
59
|
### 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.
|
60
|
+
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
61
|
|
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.
|
62
|
+
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
63
|
|
81
64
|
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
65
|
|
@@ -84,12 +67,9 @@ Once this new stack is stable or has run for a while you can choose to delete th
|
|
84
67
|
|
85
68
|
### Destroying a stack
|
86
69
|
So you are done with this application or environment, you can destroy it easily as well.
|
70
|
+
|
71
|
+
$ eb_deployer -d -e <environment>
|
87
72
|
|
88
|
-
desc "clean up everything"
|
89
|
-
task :teardown do |t, args|
|
90
|
-
EbDeployer.destroy(:application => "ebtest-simple")
|
91
|
-
end
|
92
|
-
|
93
73
|
and you are done!
|
94
74
|
|
95
75
|
Later tutorials coming soon will cover
|
data/bin/eb_deploy
ADDED
@@ -12,14 +12,17 @@ module EbDeployer
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def provision(resources)
|
15
|
+
resources = symbolize_keys(resources)
|
15
16
|
template = File.read(resources[:template])
|
16
|
-
|
17
|
+
outputs = resources[:outputs] || {}
|
18
|
+
transforms = resources[:transforms] || {}
|
17
19
|
capabilities = resources[:capabilities] || []
|
18
|
-
params = resources[:parameters] || {}
|
20
|
+
params = resources[:inputs] || resources[:parameters] || {}
|
19
21
|
|
20
22
|
stack_exists? ? update_stack(template, params, capabilities) : create_stack(template, params, capabilities)
|
21
23
|
wait_for_stack_op_terminate
|
22
|
-
|
24
|
+
|
25
|
+
transform_output_to_settings(convert_to_transforms(outputs).merge(transforms))
|
23
26
|
end
|
24
27
|
|
25
28
|
def output(key)
|
@@ -30,6 +33,19 @@ module EbDeployer
|
|
30
33
|
|
31
34
|
private
|
32
35
|
|
36
|
+
#todo: remove duplication
|
37
|
+
def symbolize_keys(hash)
|
38
|
+
hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def convert_to_transforms(outputs)
|
43
|
+
outputs.inject({}) do |memo, (key, value)|
|
44
|
+
memo[key] = lambda { |output_value| value.merge('value' => output_value) }
|
45
|
+
memo
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
33
49
|
def update_stack(template, params, capabilities)
|
34
50
|
@cf_driver.update_stack(@stack_name, template,
|
35
51
|
:capabilities => capabilities,
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'digest'
|
3
|
+
|
4
|
+
module EbDeployer
|
5
|
+
class ConfigLoader
|
6
|
+
|
7
|
+
class EvalBinding
|
8
|
+
def initialize(package_digest)
|
9
|
+
@package_digest = package_digest
|
10
|
+
end
|
11
|
+
|
12
|
+
def random_hash
|
13
|
+
SecureRandom.hex[0..9]
|
14
|
+
end
|
15
|
+
|
16
|
+
def package_digest
|
17
|
+
@package_digest
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def load(options)
|
22
|
+
options = options.dup
|
23
|
+
package_digest = package_digest(options[:package])
|
24
|
+
config_file = options.delete(:config_file)
|
25
|
+
config_settings = load_config_settings(config_file, package_digest)
|
26
|
+
|
27
|
+
common_settings = symbolize_keys(config_settings.delete(:common))
|
28
|
+
common_settings[:version_label] ||= package_digest
|
29
|
+
|
30
|
+
env = options[:environment]
|
31
|
+
envs = config_settings.delete(:environments)
|
32
|
+
raise 'Environment #{evn} is not defined in #{config_file}' unless envs.has_key?(env)
|
33
|
+
env_settings = symbolize_keys(envs[env] || {})
|
34
|
+
env_option_settings = env_settings.delete(:option_settings) || []
|
35
|
+
|
36
|
+
ret = options.merge(config_settings).merge(common_settings).merge(env_settings)
|
37
|
+
|
38
|
+
ret[:option_settings] ||= []
|
39
|
+
ret[:option_settings] += env_option_settings
|
40
|
+
ret
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def symbolize_keys(hash)
|
46
|
+
hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
47
|
+
end
|
48
|
+
|
49
|
+
def load_config_settings(config_file, package_digest)
|
50
|
+
yaml = ERB.new(File.read(config_file)).result(eval_binding(package_digest))
|
51
|
+
symbolize_keys(YAML.load(yaml))
|
52
|
+
end
|
53
|
+
|
54
|
+
def eval_binding(package_digest)
|
55
|
+
EvalBinding.new(package_digest).instance_eval { binding }
|
56
|
+
end
|
57
|
+
|
58
|
+
def package_digest(package)
|
59
|
+
Digest::MD5.file(package).hexdigest
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module EbDeployer
|
2
|
+
class DefaultConfig
|
3
|
+
attr_reader :app_name
|
4
|
+
|
5
|
+
def initialize(app_name)
|
6
|
+
@app_name = app_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def write_to(path)
|
10
|
+
FileUtils.mkdir_p(File.dirname(path))
|
11
|
+
File.open(path, 'w') { |f| f << ERB.new(File.read(config_template)).result(binding) }
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def config_template
|
17
|
+
File.expand_path("../default_config.yml", __FILE__)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# applicaiton name
|
2
|
+
application: <%= app_name %>
|
3
|
+
|
4
|
+
# common settings for all environments
|
5
|
+
common:
|
6
|
+
# Solution stack for elastic beanstalk, default is 64bit tomcat 7 for JAVA app
|
7
|
+
# solution_stack_name: 64bit Amazon Linux running Tomcat 7
|
8
|
+
|
9
|
+
# AWS region to deploy. Default to us-east-1
|
10
|
+
# region: us-west-1
|
11
|
+
|
12
|
+
# There are two deployment strategies: blue-green or inplace-update.
|
13
|
+
# Blue green keep two elastic beanstalk environments and always deploy to
|
14
|
+
# inactive one, to achive zero downtime. inplace-update strategy
|
15
|
+
# will only keep one environment, and update the version inplace on
|
16
|
+
# deploy. inplace-update will save resources but will have downtime. Default
|
17
|
+
# strategy is blue-green. PS. All old environments need be destroyed when you
|
18
|
+
# switching between strategies.
|
19
|
+
# strategy: blue-green
|
20
|
+
|
21
|
+
# If phoenix mode is turn on, it will terminate the old elastic
|
22
|
+
# beanstalk environment and recreate on deploy. For blue-green
|
23
|
+
# deployment it terminate the inactive environment first then
|
24
|
+
# recreate it. This is useful to avoiding configuration drift and
|
25
|
+
# accumulating state on the ec2 instances. Also it has the benifit of
|
26
|
+
# keeping your ec2 instance system package upto date, because everytime ec2
|
27
|
+
# instance boot up from AMI it does a system update. Default is off but we suggest
|
28
|
+
# overriden it to on for production environment.
|
29
|
+
# phoenix_mode: false
|
30
|
+
|
31
|
+
# Generating version label for package be deployed. A readable version label will
|
32
|
+
# provide better traceablity of your deployment process.
|
33
|
+
# By default setting is:
|
34
|
+
# version_label: <%%= package_digest %>
|
35
|
+
# which means using MD5 digest of the package file. If you deploy using build
|
36
|
+
# pipeline tool such as GO, switching to pipline counter is highly suggested to
|
37
|
+
# increase the readability. Following example read pipeline counter from environment
|
38
|
+
# variable with a fall back to digest for local deployment:
|
39
|
+
# version_label: <%%= ENV['GO_PIPELINE_COUNTER'] || package_digest %>
|
40
|
+
|
41
|
+
|
42
|
+
# Smoke test value should be a piece of ruby code with access to single variable
|
43
|
+
# "host_name" -- environment DNS name. Smoke test snippet will be evaluated at
|
44
|
+
# the end of the deployment for inplace-update deployment. For blue-green
|
45
|
+
# deployment it will run after inactive environment update finish and before
|
46
|
+
# switching.
|
47
|
+
# Defining a smoke test is high recommended for serious usage. The
|
48
|
+
# simplest one could just be checking server landing page using curl, e.g.
|
49
|
+
# smoke_test: >
|
50
|
+
# curl_http_code = "curl -s -o /dev/null -w \"%{http_code}\" http://#{host_name}"
|
51
|
+
# Timeout.timeout(600) do
|
52
|
+
# while `#{curl_http_code}`.strip != '200'
|
53
|
+
# sleep 5
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
|
57
|
+
|
58
|
+
# Elastic Beanstalk settings that will apply to the environments you
|
59
|
+
# deploying.
|
60
|
+
# For all available options take a look at
|
61
|
+
# http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html
|
62
|
+
option_settings:
|
63
|
+
# Following is an option_settings example which changes EC2 instance type
|
64
|
+
# from t1.micro (default) to m1.small. Intances with t1.micro type sometime
|
65
|
+
# are not very responsible, so m1.small is suggested for saving time.
|
66
|
+
# But if you care about the marginal cost difference you can comment this out to
|
67
|
+
# go back to t1.micro.
|
68
|
+
- namespace: aws:autoscaling:launchconfiguration
|
69
|
+
option_name: InstanceType
|
70
|
+
value: m1.small
|
71
|
+
|
72
|
+
# If resources specified, eb_deployer will use the CloudFormation
|
73
|
+
# template you provide to create a default CloudFormation stack with
|
74
|
+
# name <application_name>-<env-name> for the environment current
|
75
|
+
# deploying. And Outputs of the CloudFormation can be mapped to Elastic Beanstalk
|
76
|
+
# options settings.
|
77
|
+
# keys:
|
78
|
+
# template => CloudFormation template file with JSON format
|
79
|
+
# inputs => A Hash, input values for the CloudFormation template
|
80
|
+
# outputs => A Hash with key map to your CloudFormation
|
81
|
+
# template outputs and value as elastic beanstalk settings namespace and option_name.
|
82
|
+
# :capabilities => An array. You need set it to ['CAPABILITY_IAM'] if the
|
83
|
+
# template include IAM Instance Profile.
|
84
|
+
resources:
|
85
|
+
# For example creating a RDS instance for blue green deployment:
|
86
|
+
# template: config/my_rds.json
|
87
|
+
# inputs:
|
88
|
+
# DBPassword: <%%= random_hash %>
|
89
|
+
# outputs:
|
90
|
+
# RDSPassSecurityGroup:
|
91
|
+
# namespace: aws:autoscaling:launchconfiguration
|
92
|
+
# option_name: SecurityGroups
|
93
|
+
# RDSDatabaseConfig:
|
94
|
+
# namespace: aws:elasticbeanstalk:application:environment
|
95
|
+
# option_name: databaseConfig
|
96
|
+
|
97
|
+
|
98
|
+
# You can define environment here. Each environment can overriden any common settings
|
99
|
+
environments:
|
100
|
+
dev:
|
101
|
+
# example for overriding common settings
|
102
|
+
# strategy: inplace-update
|
103
|
+
production:
|
104
|
+
option_settings:
|
105
|
+
# example for overriding common option_settings: providing least redanduncy
|
106
|
+
# in production environment.
|
107
|
+
# - namespace: aws:autoscaling:asg
|
108
|
+
# option_name: MinSize
|
109
|
+
# value: "2"
|
@@ -34,6 +34,10 @@ module EbDeployer
|
|
34
34
|
@bs.environment_swap_cname(self.app, self.name, another.name)
|
35
35
|
end
|
36
36
|
|
37
|
+
def log(msg)
|
38
|
+
puts "[#{Time.now.utc}][beanstalk:#{@name}] #{msg}"
|
39
|
+
end
|
40
|
+
|
37
41
|
private
|
38
42
|
|
39
43
|
def shorten(str, max_length, digest_length=5)
|
@@ -64,12 +68,8 @@ module EbDeployer
|
|
64
68
|
end
|
65
69
|
|
66
70
|
def smoke_test
|
67
|
-
|
68
|
-
|
69
|
-
log("running smoke test for #{host}...")
|
70
|
-
smoke.call(host)
|
71
|
-
log("smoke test succeeded.")
|
72
|
-
end
|
71
|
+
host_name = @bs.environment_cname(@app, @name)
|
72
|
+
SmokeTest.new(@creation_opts[:smoke_test]).run(host_name, self)
|
73
73
|
end
|
74
74
|
|
75
75
|
def with_polling_events(terminate_pattern, &block)
|
@@ -97,12 +97,9 @@ module EbDeployer
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
-
def log(msg)
|
101
|
-
puts "[#{Time.now.utc}][beanstalk-#{@name}] #{msg}"
|
102
|
-
end
|
103
100
|
|
104
101
|
def log_event(event)
|
105
|
-
puts "[#{event[:event_date]}][beanstalk
|
102
|
+
puts "[#{event[:event_date]}][beanstalk:#{@name}] #{event[:message]}"
|
106
103
|
end
|
107
104
|
end
|
108
105
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EbDeployer
|
2
|
+
class SmokeTest
|
3
|
+
def initialize(test_body)
|
4
|
+
@test_body = test_body
|
5
|
+
end
|
6
|
+
|
7
|
+
def run(host_name, logger=nil)
|
8
|
+
return unless @test_body
|
9
|
+
logger.log("running smoke test for #{host_name}...") if logger
|
10
|
+
|
11
|
+
case @test_body
|
12
|
+
when Proc
|
13
|
+
@test_body.call(host_name)
|
14
|
+
when String
|
15
|
+
eval(@test_body, binding)
|
16
|
+
else
|
17
|
+
raise "smoke test can only be a string to evaluate or a proc object such as lambda"
|
18
|
+
end
|
19
|
+
|
20
|
+
logger.log("smoke test succeeded.") if logger
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/eb_deployer/version.rb
CHANGED
data/lib/eb_deployer.rb
CHANGED
@@ -8,12 +8,17 @@ require "eb_deployer/event_poller"
|
|
8
8
|
require "eb_deployer/package"
|
9
9
|
require 'eb_deployer/s3_driver'
|
10
10
|
require 'eb_deployer/cloud_formation_driver'
|
11
|
+
require 'eb_deployer/config_loader'
|
12
|
+
require 'eb_deployer/default_config'
|
13
|
+
require 'eb_deployer/smoke_test'
|
11
14
|
require 'digest'
|
12
15
|
require 'set'
|
13
16
|
require 'time'
|
14
17
|
require 'json'
|
15
18
|
require 'timeout'
|
16
19
|
require 'aws-sdk'
|
20
|
+
require 'optparse'
|
21
|
+
require 'erb'
|
17
22
|
|
18
23
|
module EbDeployer
|
19
24
|
|
@@ -39,6 +44,8 @@ module EbDeployer
|
|
39
44
|
end
|
40
45
|
|
41
46
|
|
47
|
+
# #
|
48
|
+
# Deploy a package to specfied environments on elastic beanstalk
|
42
49
|
#
|
43
50
|
# Options available:
|
44
51
|
#
|
@@ -70,7 +77,7 @@ module EbDeployer
|
|
70
77
|
# The elastic beanstalk solution stack you want to deploy on top of.
|
71
78
|
# Current possible values include:
|
72
79
|
#
|
73
|
-
# :settings (optional)
|
80
|
+
# :option_settings (or :settings) (optional)
|
74
81
|
# Elastic Beanstalk settings that will apply to the environments you
|
75
82
|
# deploying. Value should be array of hash with format such as:
|
76
83
|
# [{
|
@@ -90,7 +97,7 @@ module EbDeployer
|
|
90
97
|
# deploying. Value of resources need to be hash with following
|
91
98
|
# keys:
|
92
99
|
# :template => CloudFormation template file with JSON format
|
93
|
-
# :parameters => A Hash, input values for the CloudFormation
|
100
|
+
# :parameters (or :inputs) => A Hash, input values for the CloudFormation
|
94
101
|
# template
|
95
102
|
# :transforms => A Hash with key map to your CloudFormation
|
96
103
|
# template outputs and value as lambda that return a single or array of
|
@@ -133,7 +140,6 @@ module EbDeployer
|
|
133
140
|
# }
|
134
141
|
#
|
135
142
|
#
|
136
|
-
# deploy a package to specfied environments on elastic beanstalk
|
137
143
|
#
|
138
144
|
|
139
145
|
def self.deploy(opts)
|
@@ -150,7 +156,7 @@ module EbDeployer
|
|
150
156
|
env_name = opts[:environment]
|
151
157
|
version_label = opts[:version_label].to_s.strip
|
152
158
|
cname = opts[:cname]
|
153
|
-
env_settings = opts[:settings] || []
|
159
|
+
env_settings = opts[:option_settings] || opts[:settings] || []
|
154
160
|
strategy_name = opts[:strategy] || :blue_green
|
155
161
|
cname_prefix = opts[:cname_prefix] || [app, env_name].join('-')
|
156
162
|
smoke_test = opts[:smoke_test] || Proc.new {}
|
@@ -186,4 +192,61 @@ module EbDeployer
|
|
186
192
|
Application.new(app, bs, s3).delete
|
187
193
|
end
|
188
194
|
|
195
|
+
def self.cli
|
196
|
+
options = {
|
197
|
+
:action => :deploy,
|
198
|
+
:environment => 'dev',
|
199
|
+
:config_file => 'config/eb_deployer.yml'
|
200
|
+
}
|
201
|
+
|
202
|
+
parser = cli_parser(options)
|
203
|
+
parser.parse!
|
204
|
+
action = options.delete(:action)
|
205
|
+
|
206
|
+
unless File.exists?(options[:config_file])
|
207
|
+
puts "Generat default configuration one at location #{options[:config_file]}."
|
208
|
+
DefaultConfig.new(File.basename(Dir.pwd)).write_to(options[:config_file])
|
209
|
+
exit(2)
|
210
|
+
end
|
211
|
+
|
212
|
+
if !options[:package] && action == :deploy
|
213
|
+
puts "Missing options: -p (--package)"
|
214
|
+
puts parser
|
215
|
+
exit(-1)
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
self.send(action, ConfigLoader.new.load(options))
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def self.cli_parser(options)
|
225
|
+
OptionParser.new do |opts|
|
226
|
+
opts.banner = "Usage: eb_deployer [options]"
|
227
|
+
opts.on("-p", "--package [FILE]", "Package to deploy, for example a war file for java application") do |v|
|
228
|
+
options[:package] = v
|
229
|
+
end
|
230
|
+
|
231
|
+
opts.on("-e", "--environment [ENV_NAME]", "(Default to 'dev') Environment to operating on, for example dev, staging or production. This must be defined in 'environments' section of the config file") do |v|
|
232
|
+
options[:environment] = v
|
233
|
+
end
|
234
|
+
|
235
|
+
opts.on("-c", "--config-file [FILE]", "eb_deployer config file. Default location is config/eb_deployer.yml") do |v|
|
236
|
+
options[:config_file] = v
|
237
|
+
end
|
238
|
+
|
239
|
+
opts.on("-d", "--destroy", "Destroy specified environment") do |v|
|
240
|
+
action = :destroy
|
241
|
+
end
|
242
|
+
|
243
|
+
opts.on("-v", "--version", "Print current version") do |v|
|
244
|
+
puts "eb_deployer v#{VERSION}"
|
245
|
+
exit(0)
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
|
189
252
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CloudFormationProvisionerTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@cf = CFStub.new
|
6
|
+
@provisioner = EbDeployer::CloudFormationProvisioner.new("myresources", @cf)
|
7
|
+
@template = sample_file("sample_template.json")
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def test_convert_inputs_as_params_to_cf
|
12
|
+
@provisioner.provision(:template => @template,
|
13
|
+
:inputs => { 'Foo' => 'Bar' })
|
14
|
+
|
15
|
+
assert_equal({ 'Foo' => 'Bar' }, @cf.stack_config("myresources")[:parameters])
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_transform_to_eb_settings
|
19
|
+
settings = @provisioner.provision(:template => @template,
|
20
|
+
:outputs => {
|
21
|
+
'S' => {
|
22
|
+
'namespace' => "foo",
|
23
|
+
"option_name" => "bar"
|
24
|
+
}
|
25
|
+
})
|
26
|
+
assert_equal [{'namespace' => 'foo', 'option_name' => 'bar', 'value' => 'value of S'}], settings
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConfigLoaderTest < Minitest::Test
|
4
|
+
def setup
|
5
|
+
@loader = EbDeployer::ConfigLoader.new
|
6
|
+
@sample_package = sample_file('app-package.war')
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_all_default_cases
|
10
|
+
config = @loader.load(generate_input(<<-YAML))
|
11
|
+
application: myapp
|
12
|
+
common:
|
13
|
+
option_settings:
|
14
|
+
resources:
|
15
|
+
environments:
|
16
|
+
dev:
|
17
|
+
production:
|
18
|
+
YAML
|
19
|
+
assert_equal('myapp', config[:application])
|
20
|
+
assert_equal(@sample_package, config[:package])
|
21
|
+
assert_equal('dev', config[:environment])
|
22
|
+
assert_equal(md5_digest(@sample_package), config[:version_label])
|
23
|
+
assert_equal([], config[:option_settings])
|
24
|
+
assert_equal(nil, config[:resources])
|
25
|
+
assert_equal(nil, config[:common])
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_common_settings_get_merge_into_the_config
|
29
|
+
config = @loader.load(generate_input(<<-YAML))
|
30
|
+
application: myapp
|
31
|
+
common:
|
32
|
+
strategy: inplace-update
|
33
|
+
phoenix_mode: true
|
34
|
+
option_settings:
|
35
|
+
- namespace: aws:autoscaling:launchconfiguration
|
36
|
+
option_name: InstanceType
|
37
|
+
value: m1.small
|
38
|
+
resources:
|
39
|
+
environments:
|
40
|
+
dev:
|
41
|
+
production:
|
42
|
+
YAML
|
43
|
+
assert_equal('inplace-update', config[:strategy])
|
44
|
+
assert_equal([{'namespace' => 'aws:autoscaling:launchconfiguration',
|
45
|
+
'option_name' => 'InstanceType',
|
46
|
+
'value' => 'm1.small'}], config[:option_settings])
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_eval_random_hash
|
50
|
+
yaml = <<-YAML
|
51
|
+
application: myapp
|
52
|
+
common:
|
53
|
+
resources:
|
54
|
+
template: config/my_rds.json
|
55
|
+
inputs:
|
56
|
+
DBPassword: <%= random_hash %>
|
57
|
+
environments:
|
58
|
+
dev:
|
59
|
+
production:
|
60
|
+
YAML
|
61
|
+
first_time = @loader.load(generate_input(yaml))[:resources]['inputs']['DBPassword']
|
62
|
+
second_time = @loader.load(generate_input(yaml))[:resources]['inputs']['DBPassword']
|
63
|
+
assert first_time && second_time
|
64
|
+
assert first_time != second_time
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_environment_specific_setting_will_override_common_settings
|
68
|
+
yaml = <<-YAML
|
69
|
+
application: myapp
|
70
|
+
common:
|
71
|
+
phoenix_mode: true
|
72
|
+
environments:
|
73
|
+
dev:
|
74
|
+
phoenix_mode: false
|
75
|
+
production:
|
76
|
+
YAML
|
77
|
+
|
78
|
+
assert !@loader.load(generate_input(yaml, :environment => 'dev'))[:phoenix_mode]
|
79
|
+
assert @loader.load(generate_input(yaml, :environment => 'production'))[:phoenix_mode]
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_env_specific_option_settings_will_merge_with_commons
|
83
|
+
config = @loader.load(generate_input(<<-YAML, :environment => 'production'))
|
84
|
+
application: myapp
|
85
|
+
common:
|
86
|
+
strategy: inplace-update
|
87
|
+
phoenix_mode: true
|
88
|
+
option_settings:
|
89
|
+
- namespace: aws:autoscaling:launchconfiguration
|
90
|
+
option_name: InstanceType
|
91
|
+
value: m1.small
|
92
|
+
resources:
|
93
|
+
environments:
|
94
|
+
dev:
|
95
|
+
production:
|
96
|
+
option_settings:
|
97
|
+
- namespace: aws:autoscaling:asg
|
98
|
+
option_name: MinSize
|
99
|
+
value: "2"
|
100
|
+
YAML
|
101
|
+
assert_equal([{'namespace' => 'aws:autoscaling:launchconfiguration',
|
102
|
+
'option_name' => 'InstanceType',
|
103
|
+
'value' => 'm1.small'},
|
104
|
+
{'namespace' => 'aws:autoscaling:asg',
|
105
|
+
'option_name' => 'MinSize',
|
106
|
+
'value' => "2"}], config[:option_settings])
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def md5_digest(file)
|
112
|
+
Digest::MD5.file(file).hexdigest
|
113
|
+
end
|
114
|
+
|
115
|
+
def generate_input(config_file_content, overriding={})
|
116
|
+
{ :environment => 'dev',
|
117
|
+
:package => @sample_package,
|
118
|
+
:config_file => generate_config(config_file_content)}.merge(overriding)
|
119
|
+
end
|
120
|
+
|
121
|
+
def generate_config(content)
|
122
|
+
sample_file('eb_deployer.yml', content)
|
123
|
+
end
|
124
|
+
end
|
data/test/deploy_test.rb
CHANGED
@@ -1,16 +1,11 @@
|
|
1
|
-
|
2
|
-
require 'tempfile'
|
3
|
-
require 'eb_deployer'
|
4
|
-
require 'aws_driver_stubs'
|
5
|
-
require 'minitest/autorun'
|
1
|
+
require 'test_helper'
|
6
2
|
|
7
3
|
class DeployTest < Minitest::Test
|
8
4
|
def setup
|
9
5
|
@eb_driver = EBStub.new
|
10
6
|
@s3_driver = S3Stub.new
|
11
7
|
@cf_driver = CFStub.new
|
12
|
-
@sample_package = '
|
13
|
-
File.open(@sample_package, 'w') { |f| f << 's' * 100 }
|
8
|
+
@sample_package = sample_file('app-package.war')
|
14
9
|
end
|
15
10
|
|
16
11
|
def test_first_deplyment_create_eb_application
|
@@ -19,6 +14,17 @@ class DeployTest < Minitest::Test
|
|
19
14
|
assert @eb_driver.application_exists?('simple')
|
20
15
|
end
|
21
16
|
|
17
|
+
def test_set_option_settings_on_deployment
|
18
|
+
redudant = [{:namespace => 'aws:autoscaling:launchconfiguration',
|
19
|
+
:option_name => 'MinSize',
|
20
|
+
:value => '2' }]
|
21
|
+
deploy(:application => 'simple', :environment => "production",
|
22
|
+
:option_settings => [redudant])
|
23
|
+
|
24
|
+
assert_equal [redudant], @eb_driver.environment_settings('simple', eb_envname('simple', 'production'))
|
25
|
+
|
26
|
+
end
|
27
|
+
|
22
28
|
def test_destroy_should_clean_up_eb_application_and_env
|
23
29
|
deploy(:application => 'simple', :environment => "production")
|
24
30
|
destroy(:application => 'simple')
|
@@ -304,7 +310,4 @@ class DeployTest < Minitest::Test
|
|
304
310
|
:cf_driver => @cf_driver
|
305
311
|
}
|
306
312
|
end
|
307
|
-
|
308
|
-
|
309
|
-
|
310
313
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SmokeTestTest < MiniTest::Test
|
4
|
+
def test_call_proc_type_smoke_tests
|
5
|
+
host_name_in_proc = nil
|
6
|
+
EbDeployer::SmokeTest.new(lambda {|v| host_name_in_proc = v }).run("foo")
|
7
|
+
|
8
|
+
assert_equal 'foo', host_name_in_proc
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_eval_string_type_smoke_test
|
12
|
+
$host_name_in_proc = nil
|
13
|
+
EbDeployer::SmokeTest.new("$host_name_in_proc=host_name").run("foo")
|
14
|
+
assert_equal 'foo', $host_name_in_proc
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_should_raise_if_test_body_raise
|
18
|
+
assert_raises(RuntimeError) do
|
19
|
+
EbDeployer::SmokeTest.new("raise host_name").run("foo")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'eb_deployer'
|
5
|
+
require 'aws_driver_stubs'
|
6
|
+
require 'minitest/autorun'
|
7
|
+
require 'minitest/pride'
|
8
|
+
|
9
|
+
|
10
|
+
class Minitest::Test
|
11
|
+
def sample_file(file_name, content='s' * 100)
|
12
|
+
path = File.join('/tmp', file_name)
|
13
|
+
File.open(path, 'w') { |f| f << content }
|
14
|
+
path
|
15
|
+
end
|
16
|
+
end
|
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.
|
4
|
+
version: 0.2.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-09-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: aws-sdk
|
@@ -48,7 +48,8 @@ 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
|
51
|
-
executables:
|
51
|
+
executables:
|
52
|
+
- eb_deploy
|
52
53
|
extensions: []
|
53
54
|
extra_rdoc_files: []
|
54
55
|
files:
|
@@ -57,20 +58,29 @@ files:
|
|
57
58
|
- LICENSE
|
58
59
|
- README.md
|
59
60
|
- Rakefile
|
61
|
+
- bin/eb_deploy
|
60
62
|
- eb_deployer.gemspec
|
61
63
|
- lib/eb_deployer.rb
|
62
64
|
- lib/eb_deployer/application.rb
|
63
65
|
- lib/eb_deployer/beanstalk.rb
|
64
66
|
- lib/eb_deployer/cloud_formation_driver.rb
|
65
67
|
- lib/eb_deployer/cloud_formation_provisioner.rb
|
68
|
+
- lib/eb_deployer/config_loader.rb
|
69
|
+
- lib/eb_deployer/default_config.rb
|
70
|
+
- lib/eb_deployer/default_config.yml
|
66
71
|
- lib/eb_deployer/deployment_strategy.rb
|
67
72
|
- lib/eb_deployer/environment.rb
|
68
73
|
- lib/eb_deployer/event_poller.rb
|
69
74
|
- lib/eb_deployer/package.rb
|
70
75
|
- lib/eb_deployer/s3_driver.rb
|
76
|
+
- lib/eb_deployer/smoke_test.rb
|
71
77
|
- lib/eb_deployer/version.rb
|
72
78
|
- test/aws_driver_stubs.rb
|
79
|
+
- test/cloud_formation_provisioner_test.rb
|
80
|
+
- test/config_loader_test.rb
|
73
81
|
- test/deploy_test.rb
|
82
|
+
- test/smoke_test_test.rb
|
83
|
+
- test/test_helper.rb
|
74
84
|
homepage: https://github.com/ThoughtWorksStudios/eb_deployer
|
75
85
|
licenses: []
|
76
86
|
post_install_message:
|
@@ -91,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
101
|
version: '0'
|
92
102
|
requirements: []
|
93
103
|
rubyforge_project:
|
94
|
-
rubygems_version: 1.8.
|
104
|
+
rubygems_version: 1.8.25
|
95
105
|
signing_key:
|
96
106
|
specification_version: 3
|
97
107
|
summary: Low friction deployments should be a breeze. Elastic Beanstalk provides a
|
@@ -99,5 +109,8 @@ summary: Low friction deployments should be a breeze. Elastic Beanstalk provides
|
|
99
109
|
top to automate the whole flow out of box.
|
100
110
|
test_files:
|
101
111
|
- test/aws_driver_stubs.rb
|
112
|
+
- test/cloud_formation_provisioner_test.rb
|
113
|
+
- test/config_loader_test.rb
|
102
114
|
- test/deploy_test.rb
|
103
|
-
|
115
|
+
- test/smoke_test_test.rb
|
116
|
+
- test/test_helper.rb
|