eb_deployer 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: