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 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