eb_deployer 0.4.7.beta1 → 0.4.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +14 -0
- data/lib/eb_deployer/application.rb +1 -1
- data/lib/eb_deployer/default_config.yml +4 -1
- data/lib/eb_deployer/deployment_strategy/blue_only.rb +39 -0
- data/lib/eb_deployer/deployment_strategy.rb +3 -0
- data/lib/eb_deployer/eb_environment.rb +4 -0
- data/lib/eb_deployer/environment.rb +3 -6
- data/lib/eb_deployer/version.rb +1 -1
- data/lib/eb_deployer.rb +13 -4
- data/test/blue_only_deploy_test.rb +78 -0
- data/test/eb_environment_test.rb +11 -0
- data/test/resources_deploy_test.rb +14 -0
- metadata +8 -5
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
0.4.8
|
2
|
+
====
|
3
|
+
* Raise an error if the environment launched with problems (contributed by kmanning)
|
4
|
+
* Add --stack-name option that let's use choose the name of the cloud
|
5
|
+
formation stack to operate on (contributed by NET-A-PORTER)
|
6
|
+
* Document typo/grammar fix (contributed by stig)
|
7
|
+
* Retry on AWS API throttling error when operating on versions
|
8
|
+
|
9
|
+
0.4.7
|
10
|
+
====
|
11
|
+
* Added blue-only deployment strategy (a variant of blue-green) that
|
12
|
+
skips the cname swap so that the newly deployed code remains on the
|
13
|
+
inactive "blue" instance. (contributed by jlabrecque)
|
14
|
+
|
1
15
|
0.4.6
|
2
16
|
====
|
3
17
|
* Make elasticbeanstalk event polling robust against clock shifting problem.
|
@@ -67,7 +67,7 @@ module EbDeployer
|
|
67
67
|
begin
|
68
68
|
log("Removing #{version}")
|
69
69
|
@eb_driver.delete_application_version(@name, version, delete_from_s3)
|
70
|
-
rescue AWS::ElasticBeanstalk::Errors::SourceBundleDeletionFailure
|
70
|
+
rescue AWS::ElasticBeanstalk::Errors::SourceBundleDeletionFailure => e
|
71
71
|
log(e.message)
|
72
72
|
end
|
73
73
|
end
|
@@ -12,9 +12,12 @@ common:
|
|
12
12
|
# AWS region to deploy. Default to us-east-1
|
13
13
|
# region: us-west-1
|
14
14
|
|
15
|
-
# There are
|
15
|
+
# There are three deployment strategies: 'blue-green', 'blue-only', or 'inplace-update'.
|
16
16
|
# Blue green deployments keep two elastic beanstalk environments and always deploy to
|
17
17
|
# inactive one, to achieve zero downtime.
|
18
|
+
# Blue only deployments do everything that the blue green deployments do except for the final
|
19
|
+
# inactive to active CNAME swap leaving the newly deployed application on the inactive
|
20
|
+
# "blue" instance.
|
18
21
|
# Inplace-update strategy will only keep one environment, and update the version inplace on
|
19
22
|
# deploy. Inplace-update will save resources but will suffer from downtime.
|
20
23
|
# (All old environments need be destroyed when you switching between strategies.)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module EbDeployer
|
2
|
+
module DeploymentStrategy
|
3
|
+
class BlueOnly
|
4
|
+
def initialize(env)
|
5
|
+
@env = env
|
6
|
+
end
|
7
|
+
|
8
|
+
def deploy(version_label, env_settings, inactive_settings=[])
|
9
|
+
if !ebenvs.any?(&method(:active_ebenv?))
|
10
|
+
ebenv('a', @env.cname_prefix).
|
11
|
+
deploy(version_label, env_settings)
|
12
|
+
return
|
13
|
+
end
|
14
|
+
|
15
|
+
active_ebenv = ebenvs.detect(&method(:active_ebenv?))
|
16
|
+
inactive_ebenv = ebenvs.reject(&method(:active_ebenv?)).first
|
17
|
+
|
18
|
+
inactive_ebenv.deploy(version_label, env_settings)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def active_ebenv?(ebenv)
|
23
|
+
ebenv.cname_prefix == @env.cname_prefix
|
24
|
+
end
|
25
|
+
|
26
|
+
def ebenvs
|
27
|
+
[ebenv('a'), ebenv('b')]
|
28
|
+
end
|
29
|
+
|
30
|
+
def ebenv(suffix, cname_prefix=nil)
|
31
|
+
@env.new_eb_env(suffix, cname_prefix || inactive_cname_prefix)
|
32
|
+
end
|
33
|
+
|
34
|
+
def inactive_cname_prefix
|
35
|
+
"#{@env.cname_prefix}-inactive"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'eb_deployer/deployment_strategy/inplace_update'
|
2
2
|
require 'eb_deployer/deployment_strategy/blue_green'
|
3
|
+
require 'eb_deployer/deployment_strategy/blue_only'
|
3
4
|
|
4
5
|
module EbDeployer
|
5
6
|
module DeploymentStrategy
|
@@ -9,6 +10,8 @@ module EbDeployer
|
|
9
10
|
InplaceUpdate.new(env)
|
10
11
|
when 'blue_green', 'blue-green'
|
11
12
|
BlueGreen.new(env)
|
13
|
+
when 'blue_only', 'blue-only'
|
14
|
+
BlueOnly.new(env)
|
12
15
|
else
|
13
16
|
raise 'strategy_name: ' + strategy_name.to_s + ' not supported'
|
14
17
|
end
|
@@ -100,6 +100,10 @@ module EbDeployer
|
|
100
100
|
raise "Elasticbeanstalk instance provision failed (maybe a problem with your .ebextension files). The original message: #{event[:message]}"
|
101
101
|
end
|
102
102
|
|
103
|
+
if event[:message] =~ /However, there were issues during launch\. See event log for details\./
|
104
|
+
raise "Environment launched, but with errors. The original message: #{event[:message]}"
|
105
|
+
end
|
106
|
+
|
103
107
|
log_event(event)
|
104
108
|
break if event[:message] =~ terminate_pattern
|
105
109
|
end
|
@@ -7,9 +7,10 @@ module EbDeployer
|
|
7
7
|
|
8
8
|
attr_reader :name
|
9
9
|
|
10
|
-
def initialize(app, name, eb_driver, &block)
|
10
|
+
def initialize(app, name, stack_name, eb_driver, &block)
|
11
11
|
@app = app
|
12
12
|
@name = name
|
13
|
+
@stack_name = stack_name
|
13
14
|
@eb_driver = eb_driver
|
14
15
|
@creation_opts = {}
|
15
16
|
@settings = []
|
@@ -26,7 +27,7 @@ module EbDeployer
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def deploy(version_label)
|
29
|
-
resource_settings = @resource_stacks.provision(
|
30
|
+
resource_settings = @resource_stacks.provision(@stack_name)
|
30
31
|
components_to_deploy.each do |component|
|
31
32
|
component.deploy(version_label, @settings + resource_settings, @inactive_settings)
|
32
33
|
end
|
@@ -54,9 +55,5 @@ module EbDeployer
|
|
54
55
|
def component_named(name)
|
55
56
|
@components.detect { |c| c.name == name }
|
56
57
|
end
|
57
|
-
|
58
|
-
def resource_stack_name
|
59
|
-
"#{app_name}-#{@name}"
|
60
|
-
end
|
61
58
|
end
|
62
59
|
end
|
data/lib/eb_deployer/version.rb
CHANGED
data/lib/eb_deployer.rb
CHANGED
@@ -45,7 +45,8 @@ module EbDeployer
|
|
45
45
|
app = opts[:application]
|
46
46
|
env_name = opts[:environment]
|
47
47
|
cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
|
48
|
-
|
48
|
+
stack_name = opts[:stack_name] || "#{app}-#{env_name}"
|
49
|
+
provisioner = CloudFormationProvisioner.new(stack_name, cf)
|
49
50
|
provisioner.output(key)
|
50
51
|
end
|
51
52
|
|
@@ -174,10 +175,12 @@ module EbDeployer
|
|
174
175
|
end
|
175
176
|
|
176
177
|
bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
|
178
|
+
bs = ThrottlingHandling.new(bs, AWS::ElasticBeanstalk::Errors::Throttling)
|
177
179
|
s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
|
178
180
|
cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
|
179
181
|
|
180
182
|
app_name = opts[:application]
|
183
|
+
env_name = opts[:environment]
|
181
184
|
version_prefix = opts[:version_prefix].to_s.strip
|
182
185
|
version_label = "#{version_prefix}#{opts[:version_label].to_s.strip}"
|
183
186
|
|
@@ -185,8 +188,10 @@ module EbDeployer
|
|
185
188
|
resource_stacks = ResourceStacks.new(opts[:resources],
|
186
189
|
cf,
|
187
190
|
opts[:skip_resource_stack_update])
|
188
|
-
|
189
|
-
|
191
|
+
|
192
|
+
stack_name = opts[:stack_name] || "#{app_name}-#{env_name}"
|
193
|
+
|
194
|
+
environment = Environment.new(application, env_name, stack_name, bs) do |env|
|
190
195
|
env.resource_stacks = resource_stacks
|
191
196
|
env.settings = opts[:option_settings] || opts[:settings] || []
|
192
197
|
env.inactive_settings = opts[:inactive_settings] || []
|
@@ -255,7 +260,7 @@ module EbDeployer
|
|
255
260
|
def self.cli_parser(options)
|
256
261
|
OptionParser.new do |opts|
|
257
262
|
opts.banner = "Usage: eb_deployer [options]"
|
258
|
-
opts.on("-p", "--package [FILE/S3_OBJECT]", "Package to deploy, can be a war file for java application, or yaml specification for package location on S3, or a S3 object
|
263
|
+
opts.on("-p", "--package [FILE/S3_OBJECT]", "Package to deploy, can be a war file for java application, or yaml specification for package location on S3, or a S3 object & bucket name separated by colon, e.g. bucket_name:key_name") do |v|
|
259
264
|
options[:package] = v
|
260
265
|
end
|
261
266
|
|
@@ -271,6 +276,10 @@ module EbDeployer
|
|
271
276
|
options[:action] = :destroy
|
272
277
|
end
|
273
278
|
|
279
|
+
opts.on("-s", "--stack-name [STACK_NAME]", "CloudFormation stack name to use. If not specified, defaults to {app}-{env_name}") do |v|
|
280
|
+
options[:stack_name] = v
|
281
|
+
end
|
282
|
+
|
274
283
|
opts.on("--skip-resource-stack-update", "Skip cloud-formation stack update. (only for extreme situation like hitting a cloudformation bug)") do |v|
|
275
284
|
options[:skip_resource_stack_update] = true
|
276
285
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'deploy_test'
|
2
|
+
|
3
|
+
class BlueOnlyDeployTest < DeployTest
|
4
|
+
def test_blue_only_deployment_strategy_should_create_blue_env_on_first_deployment
|
5
|
+
do_deploy(42)
|
6
|
+
assert @eb.environment_exists?('simple', t('production-a', 'simple'))
|
7
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def test_blue_only_deployment_should_create_green_env_if_blue_exists
|
12
|
+
do_deploy(42)
|
13
|
+
do_deploy(43)
|
14
|
+
assert @eb.environment_exists?('simple', t('production-a', 'simple'))
|
15
|
+
assert @eb.environment_exists?('simple', t('production-b', 'simple'))
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def test_blue_only_deployment_should_not_swap_cname_to_make_active_most_recent_updated_env
|
20
|
+
do_deploy(42)
|
21
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
22
|
+
assert_nil(@eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
23
|
+
do_deploy(43)
|
24
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
25
|
+
assert_match(/simple-production-inactive/, @eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
26
|
+
do_deploy(44)
|
27
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
28
|
+
assert_match(/simple-production-inactive/, @eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def test_blue_only_deploy_should_run_smoke_test_before_cname_switch
|
33
|
+
smoked_host = []
|
34
|
+
smoke_test = lambda { |host| smoked_host << host }
|
35
|
+
[42, 43, 44].each do |version_label|
|
36
|
+
do_deploy(version_label, :smoke_test => smoke_test)
|
37
|
+
end
|
38
|
+
|
39
|
+
assert_equal ['simple-production.elasticbeanstalk.com',
|
40
|
+
'simple-production-inactive.elasticbeanstalk.com',
|
41
|
+
'simple-production-inactive.elasticbeanstalk.com'], smoked_host
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def test_blue_only_deployment_should_delete_and_recreate_inactive_env_if_phoenix_mode_is_enabled
|
46
|
+
do_deploy(42, :phoenix_mode => true)
|
47
|
+
do_deploy(43, :phoenix_mode => true)
|
48
|
+
assert_equal [], @eb.environments_been_deleted('simple')
|
49
|
+
|
50
|
+
inactive_env = t('production-b', 'simple')
|
51
|
+
assert_match(/inactive/, @eb.environment_cname_prefix('simple', inactive_env))
|
52
|
+
|
53
|
+
do_deploy(44, :phoenix_mode => true)
|
54
|
+
assert_equal [inactive_env], @eb.environments_been_deleted('simple')
|
55
|
+
|
56
|
+
assert_match(/inactive/, @eb.environment_cname_prefix('simple', inactive_env))
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_destroy_should_clean_up_env
|
60
|
+
[42, 44].each do |version|
|
61
|
+
do_deploy(version)
|
62
|
+
end
|
63
|
+
|
64
|
+
destroy(:application => 'simple', :environment => 'production')
|
65
|
+
assert !@eb.environment_exists?('simple', t('production-a', 'simple'))
|
66
|
+
assert !@eb.environment_exists?('simple', t('production-b', 'simple'))
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def do_deploy(version_label, options={})
|
72
|
+
deploy( {:application => 'simple',
|
73
|
+
:environment => "production",
|
74
|
+
:strategy => 'blue-only',
|
75
|
+
}.merge(options).merge(:version_label => version_label))
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/test/eb_environment_test.rb
CHANGED
@@ -61,6 +61,17 @@ class EbEnvironmentTest < MiniTest::Unit::TestCase
|
|
61
61
|
assert_raises(RuntimeError) { env.deploy("version 1") }
|
62
62
|
end
|
63
63
|
|
64
|
+
def test_should_raise_runtime_error_when_issues_during_launch
|
65
|
+
env = EbDeployer::EbEnvironment.new("myapp", "production", @eb_driver)
|
66
|
+
@eb_driver.set_events("myapp", t("production", 'myapp'),
|
67
|
+
[],
|
68
|
+
["start deploying",
|
69
|
+
"create environment",
|
70
|
+
"Launched environment: dev-a-1234567. However, there were issues during launch. See event log for details."])
|
71
|
+
|
72
|
+
assert_raises(RuntimeError) { env.deploy("version 1") }
|
73
|
+
end
|
74
|
+
|
64
75
|
def test_terminate_should_delete_environment
|
65
76
|
env = EbDeployer::EbEnvironment.new("myapp", "production", @eb_driver)
|
66
77
|
env.deploy("version1")
|
@@ -119,4 +119,18 @@ class ResourcesDeployTest < DeployTest
|
|
119
119
|
:environment => "production")
|
120
120
|
end
|
121
121
|
end
|
122
|
+
|
123
|
+
def test_custom_stack_name
|
124
|
+
cf_template = temp_file(JSON.dump({
|
125
|
+
'Resources' => {'R1' => {}},
|
126
|
+
'Outputs' => {'O1' => {}, 'O2' => {}}
|
127
|
+
}))
|
128
|
+
deploy(:application => 'simple',
|
129
|
+
:environment => "production",
|
130
|
+
:resources => { :template => cf_template },
|
131
|
+
:stack_name => 'my-lovely-stack')
|
132
|
+
|
133
|
+
assert !@cf_driver.stack_exists?('simple-production')
|
134
|
+
assert @cf_driver.stack_exists?('my-lovely-stack')
|
135
|
+
end
|
122
136
|
end
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: eb_deployer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.8
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- wpc
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-08-23 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: aws-sdk
|
@@ -63,6 +63,7 @@ files:
|
|
63
63
|
- lib/eb_deployer/default_config.yml
|
64
64
|
- lib/eb_deployer/deployment_strategy.rb
|
65
65
|
- lib/eb_deployer/deployment_strategy/blue_green.rb
|
66
|
+
- lib/eb_deployer/deployment_strategy/blue_only.rb
|
66
67
|
- lib/eb_deployer/deployment_strategy/inplace_update.rb
|
67
68
|
- lib/eb_deployer/eb_environment.rb
|
68
69
|
- lib/eb_deployer/environment.rb
|
@@ -81,6 +82,7 @@ files:
|
|
81
82
|
- lib/generators/eb_deployer/install/templates/postgres_rds.json
|
82
83
|
- test/aws_driver_stubs.rb
|
83
84
|
- test/blue_green_deploy_test.rb
|
85
|
+
- test/blue_only_deploy_test.rb
|
84
86
|
- test/cloud_formation_provisioner_test.rb
|
85
87
|
- test/config_loader_test.rb
|
86
88
|
- test/deploy_test.rb
|
@@ -110,9 +112,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
110
112
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
113
|
none: false
|
112
114
|
requirements:
|
113
|
-
- - ! '
|
115
|
+
- - ! '>='
|
114
116
|
- !ruby/object:Gem::Version
|
115
|
-
version:
|
117
|
+
version: '0'
|
116
118
|
requirements: []
|
117
119
|
rubyforge_project:
|
118
120
|
rubygems_version: 1.8.23
|
@@ -124,6 +126,7 @@ summary: Low friction deployments should be a breeze. Elastic Beanstalk provides
|
|
124
126
|
test_files:
|
125
127
|
- test/aws_driver_stubs.rb
|
126
128
|
- test/blue_green_deploy_test.rb
|
129
|
+
- test/blue_only_deploy_test.rb
|
127
130
|
- test/cloud_formation_provisioner_test.rb
|
128
131
|
- test/config_loader_test.rb
|
129
132
|
- test/deploy_test.rb
|