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 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, AWS::ElasticBeanstalk::Errors::OperationInProgressFailure => e
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 two deployment strategies: 'blue-green' or 'inplace-update'.
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(resource_stack_name)
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
@@ -1,3 +1,3 @@
1
1
  module EbDeployer
2
- VERSION = "0.4.7.beta1"
2
+ VERSION = "0.4.8"
3
3
  end
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
- provisioner = CloudFormationProvisioner.new("#{app}-#{env_name}", cf)
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
- bs = ThrottlingHandling.new(bs, AWS::ElasticBeanstalk::Errors::Throttling)
189
- environment = Environment.new(application, opts[:environment], bs) do |env|
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 with bucket name saperated by colon, e.g. bucket_name:key_name") do |v|
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
@@ -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.7.beta1
5
- prerelease: 6
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-07-03 00:00:00.000000000 Z
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: 1.3.1
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