eb_deployer 0.4.7.beta1 → 0.4.8
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.
- 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
|