eb_deployer 0.1.0 → 0.1.1

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/Gemfile CHANGED
@@ -2,3 +2,7 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in eb_deployer.gemspec
4
4
  gemspec
5
+
6
+ gem 'redcarpet'
7
+ gem 'yard'
8
+
data/lib/eb_deployer.rb CHANGED
@@ -16,6 +16,16 @@ require 'timeout'
16
16
  require 'aws-sdk'
17
17
 
18
18
  module EbDeployer
19
+
20
+ ##
21
+ # Query ouput value of the cloud formation stack
22
+ # arguments:
23
+ # key: CloudFormation ouput key
24
+ # options: a hash
25
+ # :application application name
26
+ # :environment environment name (e.g. staging, production)
27
+ #
28
+
19
29
  def self.query_resource_output(key, opts)
20
30
  # AWS.config(:logger => Logger.new($stdout))
21
31
  if region = opts[:region]
@@ -88,6 +98,22 @@ module EbDeployer
88
98
  # :capabilities => An array. You need set it to ['CAPABILITY_IAM']
89
99
  # if you want to provision IAM Instance Profile.
90
100
  #
101
+ # :strategy (optional default :blue-green)
102
+ # There are two options: blue-green or inplace-update. Blue green
103
+ # keep two elastic beanstalk environments and always deploy to
104
+ # inactive one, to achive zero downtime. inplace-update strategy
105
+ # will only keep one environment, and update the version inplace on
106
+ # deploy. this will save resources but will have downtime.
107
+ #
108
+ # :phoenix_mode (optional default false)
109
+ # If phoenix mode is turn on, it will terminate the old elastic
110
+ # beanstalk environment and recreate on deploy. For blue-green
111
+ # deployment it terminate the inactive environment first then
112
+ # recreate it. This is useful to avoiding configuration drift and
113
+ # accumulating state on the ec2 instances. Also it has the benifit of
114
+ # keeping your ec2 instance system package upto date, because everytime ec2
115
+ # instance boot up from AMI it does a system update.
116
+ #
91
117
  # :smoke_test (optional)
92
118
  # Value should be a proc or a lambda which accept single argument that will
93
119
  # passed in as environment DNS name. Smoke test proc or lambda will be
@@ -128,6 +154,7 @@ module EbDeployer
128
154
  strategy_name = opts[:strategy] || :blue_green
129
155
  cname_prefix = opts[:cname_prefix] || [app, env_name].join('-')
130
156
  smoke_test = opts[:smoke_test] || Proc.new {}
157
+ phoenix_mode = opts[:phoenix_mode]
131
158
 
132
159
  application = Application.new(app, bs, s3)
133
160
 
@@ -136,7 +163,8 @@ module EbDeployer
136
163
  strategy = DeploymentStrategy.create(strategy_name, app, env_name, bs,
137
164
  :solution_stack => stack_name,
138
165
  :cname_prefix => cname_prefix,
139
- :smoke_test => smoke_test)
166
+ :smoke_test => smoke_test,
167
+ :phoenix_mode => phoenix_mode)
140
168
 
141
169
  if resources = opts[:resources]
142
170
  env_settings += cf.provision(resources)
@@ -19,9 +19,8 @@ module EbDeployer
19
19
  @app = app
20
20
  @env_name = env_name
21
21
  @eb_driver = eb_driver
22
+ @env_creation_opts = env_creation_opts
22
23
  @major_cname_prefix = env_creation_opts[:cname_prefix]
23
- @solution_stack = env_creation_opts[:solution_stack]
24
- @smoke_test = env_creation_opts[:smoke_test]
25
24
  end
26
25
 
27
26
  def deploy(version_label, env_settings)
@@ -48,10 +47,9 @@ module EbDeployer
48
47
  end
49
48
 
50
49
  def env(suffix, cname_prefix=nil)
51
- Environment.new(@app, @env_name + '-' + suffix, @eb_driver,
52
- :solution_stack => @solution_stack,
53
- :cname_prefix => cname_prefix || inactive_cname_prefix,
54
- :smoke_test => @smoke_test)
50
+ Environment.new(@app, @env_name + '-' + suffix,
51
+ @eb_driver,
52
+ @env_creation_opts.merge({:cname_prefix => cname_prefix || inactive_cname_prefix}))
55
53
  end
56
54
 
57
55
  def inactive_cname_prefix
@@ -13,12 +13,11 @@ module EbDeployer
13
13
  @name = self.class.unique_ebenv_name(app, env_name)
14
14
  @bs = eb_driver
15
15
  @creation_opts = creation_opts
16
- @poller = EventPoller.new(@app, @name, @bs)
17
16
  end
18
17
 
19
18
  def deploy(version_label, settings)
19
+ terminate if @creation_opts[:phoenix_mode]
20
20
  create_or_update_env(version_label, settings)
21
- poll_events
22
21
  smoke_test
23
22
  wait_for_env_become_healthy
24
23
  end
@@ -37,7 +36,6 @@ module EbDeployer
37
36
 
38
37
  private
39
38
 
40
-
41
39
  def shorten(str, max_length, digest_length=5)
42
40
  raise "max length (#{max_length}) should be larger than digest_length (#{digest_length})" if max_length < digest_length
43
41
  return self if str.size <= max_length
@@ -45,11 +43,23 @@ module EbDeployer
45
43
  sha1[0..(digest_length - 1)] + str[(max_length - digest_length - 1)..-1]
46
44
  end
47
45
 
46
+ def terminate
47
+ if @bs.environment_exists?(@app, @name)
48
+ with_polling_events(/terminateEnvironment completed successfully/i) do
49
+ @bs.delete_environment(@app, @name)
50
+ end
51
+ end
52
+ end
53
+
48
54
  def create_or_update_env(version_label, settings)
49
55
  if @bs.environment_exists?(@app, @name)
50
- @bs.update_environment(@app, @name, version_label, settings)
56
+ with_polling_events(/Environment update completed successfully/i) do
57
+ @bs.update_environment(@app, @name, version_label, settings)
58
+ end
51
59
  else
52
- @bs.create_environment(@app, @name, @creation_opts[:solution_stack], @creation_opts[:cname_prefix], version_label, settings)
60
+ with_polling_events(/Successfully launched environment/i) do
61
+ @bs.create_environment(@app, @name, @creation_opts[:solution_stack], @creation_opts[:cname_prefix], version_label, settings)
62
+ end
53
63
  end
54
64
  end
55
65
 
@@ -62,13 +72,14 @@ module EbDeployer
62
72
  end
63
73
  end
64
74
 
65
- def poll_events
66
- @poller.poll do |event|
75
+ def with_polling_events(terminate_pattern, &block)
76
+ event_start_time = Time.now
77
+ yield
78
+ EventPoller.new(@app, @name, @bs).poll(event_start_time) do |event|
67
79
  raise event[:message] if event[:message] =~ /Failed to deploy application/
68
80
 
69
81
  log_event(event)
70
- break if event[:message] =~ /Environment update completed successfully/ ||
71
- event[:message] =~ /Successfully launched environment/
82
+ break if event[:message] =~ terminate_pattern
72
83
  end
73
84
  end
74
85
 
@@ -1,13 +1,13 @@
1
1
  module EbDeployer
2
2
  class EventPoller
3
3
  def initialize(app, env, beanstalk)
4
- @app, @env, @beanstalk, @start_time = app, env, beanstalk, Time.now
4
+ @app, @env, @beanstalk = app, env, beanstalk
5
5
  end
6
6
 
7
- def poll(&block)
7
+ def poll(start_time = Time.now, &block)
8
8
  handled = Set.new
9
9
  loop do
10
- fetch_events do |events|
10
+ fetch_events(start_time) do |events|
11
11
  new_events = events.reject { |e| handled.include?(digest(e)) }
12
12
  handle(new_events, &block)
13
13
  handled += new_events.map { |e| digest(e) }
@@ -16,6 +16,7 @@ module EbDeployer
16
16
  end
17
17
  end
18
18
 
19
+
19
20
  private
20
21
 
21
22
  def digest(event)
@@ -26,8 +27,8 @@ module EbDeployer
26
27
  events.reverse.each(&block)
27
28
  end
28
29
 
29
- def fetch_events(&block)
30
- events, next_token = @beanstalk.fetch_events(@app, @env, :start_time => @start_time.iso8601)
30
+ def fetch_events(start_time, &block)
31
+ events, next_token = @beanstalk.fetch_events(@app, @env, :start_time => start_time.iso8601)
31
32
  yield(events)
32
33
  fetch_next(next_token, &block) if next_token
33
34
  end
@@ -1,3 +1,3 @@
1
1
  module EbDeployer
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -3,6 +3,7 @@ class EBStub
3
3
  @apps = []
4
4
  @envs = {}
5
5
  @versions = {}
6
+ @envs_been_deleted = {}
6
7
  end
7
8
 
8
9
  def create_application(app)
@@ -33,6 +34,8 @@ class EBStub
33
34
 
34
35
  def delete_environment(app, env)
35
36
  @envs.delete(env)
37
+ @envs_been_deleted[app] ||= []
38
+ @envs_been_deleted[app] << env
36
39
  end
37
40
 
38
41
  def update_environment(app, env, version, settings)
@@ -54,7 +57,12 @@ class EBStub
54
57
 
55
58
  def fetch_events(app_name, env_name, options={})
56
59
  [[{:event_date => Time.now.utc,
57
- :message => 'Environment update completed successfully'}],
60
+ :message => 'Environment update completed successfully'},
61
+ {:event_date => Time.now.utc,
62
+ :message => 'terminateEnvironment completed successfully'},
63
+ {:event_date => Time.now.utc,
64
+ :message => 'Successfully launched environment'}
65
+ ],
58
66
  nil]
59
67
  end
60
68
 
@@ -80,6 +88,7 @@ class EBStub
80
88
  'Green'
81
89
  end
82
90
 
91
+
83
92
  #test only
84
93
  def environment_verion_label(app_name, env_name)
85
94
  @envs[env_key(app_name, env_name)][:version]
@@ -97,6 +106,10 @@ class EBStub
97
106
  end
98
107
  end
99
108
 
109
+ def environments_been_deleted(app)
110
+ @envs_been_deleted[app] || []
111
+ end
112
+
100
113
  private
101
114
 
102
115
  def env_key(app, name)
data/test/deploy_test.rb CHANGED
@@ -88,8 +88,6 @@ class DeployTest < Minitest::Test
88
88
  assert_equal 'foobar.elasticbeanstalk.com', host_for_smoke_test
89
89
  end
90
90
 
91
-
92
-
93
91
  def test_blue_green_deployment_strategy_should_create_blue_env_on_first_deployment
94
92
  deploy(:application => 'simple',
95
93
  :environment => "production",
@@ -233,6 +231,44 @@ class DeployTest < Minitest::Test
233
231
  end
234
232
  end
235
233
 
234
+ def test_should_terminate_old_environment_if_phoenix_mode_is_enabled
235
+ deploy(:application => 'simple', :environment => "production", :phoenix_mode => true)
236
+ assert @eb_driver.environment_exists?('simple', eb_envname('simple', 'production'))
237
+ deploy(:application => 'simple', :environment => "production", :phoenix_mode => true)
238
+ assert @eb_driver.environments_been_deleted('simple').include?(eb_envname('simple', 'production'))
239
+ assert @eb_driver.environment_exists?('simple', eb_envname('simple', 'production'))
240
+ end
241
+
242
+ def test_blue_green_deployment_should_delete_and_recreate_inactive_env_if_phoenix_mode_is_enabled
243
+ deploy(:application => 'simple',
244
+ :environment => "production",
245
+ :strategy => 'blue_green',
246
+ :version_label => 42,
247
+ :phoenix_mode => true)
248
+
249
+ deploy(:application => 'simple',
250
+ :environment => "production",
251
+ :strategy => 'blue_green',
252
+ :version_label => 43,
253
+ :phoenix_mode => true)
254
+
255
+ assert_equal [], @eb_driver.environments_been_deleted('simple')
256
+
257
+ inactive_env = eb_envname('simple', 'production-a')
258
+ assert_match(/inactive/, @eb_driver.environment_cname_prefix('simple', inactive_env))
259
+
260
+
261
+ deploy(:application => 'simple',
262
+ :environment => "production",
263
+ :strategy => 'blue_green',
264
+ :version_label => 44,
265
+ :phoenix_mode => true)
266
+
267
+ assert_equal [inactive_env], @eb_driver.environments_been_deleted('simple')
268
+
269
+ assert_equal 'simple-production', @eb_driver.environment_cname_prefix('simple', inactive_env)
270
+ end
271
+
236
272
  private
237
273
 
238
274
  def temp_file(content)
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.1.0
4
+ version: 0.1.1
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-08-01 00:00:00.000000000 Z
13
+ date: 2013-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws-sdk
@@ -91,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
91
  version: '0'
92
92
  requirements: []
93
93
  rubyforge_project:
94
- rubygems_version: 1.8.25
94
+ rubygems_version: 1.8.24
95
95
  signing_key:
96
96
  specification_version: 3
97
97
  summary: Low friction deployments should be a breeze. Elastic Beanstalk provides a
@@ -100,3 +100,4 @@ summary: Low friction deployments should be a breeze. Elastic Beanstalk provides
100
100
  test_files:
101
101
  - test/aws_driver_stubs.rb
102
102
  - test/deploy_test.rb
103
+ has_rdoc: