eb_deployer 0.4.5 → 0.4.6

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.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- eb_deployer (0.4.4)
4
+ eb_deployer (0.4.6)
5
5
  aws-sdk (>= 1.33.0)
6
6
 
7
7
  GEM
@@ -23,7 +23,7 @@ GEM
23
23
  activesupport (3.2.18)
24
24
  i18n (~> 0.6, >= 0.6.4)
25
25
  multi_json (~> 1.0)
26
- aws-sdk (1.42.0)
26
+ aws-sdk (1.43.1)
27
27
  json (~> 1.4)
28
28
  nokogiri (>= 1.4.4)
29
29
  builder (3.0.4)
data/README.md CHANGED
@@ -5,6 +5,8 @@ Low friction deployments should be a breeze. Elastic Beanstalk provides a great
5
5
 
6
6
  EbDeployer thus allows you to do continuous delivery on AWS.
7
7
 
8
+ [Introduction to EbDeployer](http://getmingle.io/scaling/2014/06/13/introduction-to-eb-deployer.html)
9
+
8
10
  ## Installation
9
11
 
10
12
  $ gem install eb_deployer
@@ -88,71 +90,11 @@ Later tutorials coming soon will cover
88
90
 
89
91
  Take a look at code if you can not wait for the documentation.
90
92
 
91
- ## Rails 3+ support
92
-
93
- EbDeployer ships with a Rails 3+ generator since version 0.4.5.
94
-
95
- ### Install
96
-
97
- Add eb_deployer to your Gemfile
98
-
99
- gem 'eb_deployer'
100
-
101
- Setup AWS credentials:
102
-
103
- $ export AWS_ACCESS_KEY_ID=xxx
104
- $ export AWS_SECRET_ACCESS_KEY=xxx
105
-
106
- ### Initial configurations and rake tasks
107
-
108
- Run Rails generator to generate configurations and rake file:
109
-
110
- rails generate eb_deployer:install
111
-
112
- It will setup AWS Elastic Beanstalk blue-green deployment configuration with a Postgres RDS instance as your Rails' backend database.
113
- The followings are details:
114
-
115
- * Add file "lib/tasks/eb_deployer.rake", please run "rake -T eb" for tasks description. These tasks are simple and designed for you to customize.
116
- * Add file "config/eb_deployer.yml", it includes basic blue-green configurations with a Postgres RDS instance resource.
117
- * Add file "config/rds.json", it is a CloudFormation template file which provisions the Postgres RDS instance. A separated CloudFormation stack maintains all resources that are shared between different Elastic Beanstalk environments in blue-green deployment. Notice: each eb_deployer environment will create one.
118
- * Add "gem 'pg'" to your Gemfile, as this initial configuration is hooking up with a Postgres RDS instance, we need postgres driver.
119
- * Add file ".ebextenstions/01_postgres_packages.config", which installs Postgres dev packages on EC2 instances, so that we can build gem "pg" on your EC2 machine after deployed.
120
- * Add a new production database configuration into "config/database.yml" file. Your original production configuration will be commented out.
121
-
122
- ### Deploy
123
-
124
- Add all files that need to be deployed into your Git repository, because we will simply use "git ls-files" to find all files need to be packaged.
125
-
126
- Deploy a dev environment for testing your application deployment:
127
-
128
- rake eb:deploy
129
-
130
- Then, when you're ready to deploy a production environment:
131
-
132
- EB_DEPLOYER_ENV=production rake eb:deploy
133
-
134
- ## EbDeployer environment
135
-
136
- There are so many things called environment:
137
-
138
- * Rails environment: development, test, production
139
- * Elastic Beanstalk environment
140
- * Development environment
141
- * Staging environment
142
- * Production environment
143
-
144
- An EbDeployer environment is your application running environment (= your running application + infrastructure), e.g. staging environment, production environment.
145
- All EbDeployer environments including dev environment are deployed as Rails production environment.
93
+ ## More
146
94
 
147
- What is different between EbDeployer environment and Elastic Beanstalk environment?
95
+ [Rails 3 Support](https://github.com/ThoughtWorksStudios/eb_deployer/wiki/Rails-3-Support)
148
96
 
149
- * There are 2 level concepts in Elastic Beanstalk: Application and Environment
150
- * EbDeployer environment sits between Elastic Beanstalk Application and Elastic Beanstalk Environment:
151
- * One Elastic Beanstalk Application has many EbDeployer environments: dev, staging, production, or whatever names you like.
152
- * Depending on deployment strategy, one EbDeployer environment has one or more Elastic Beanstalk environments
153
- * For 'inplace-update' deployment strategy, it's one Elastic Beanstalk environment.
154
- * For 'blue-green' deployment strategy, it's two Elastic Beanstalk environments.
155
- * You should consider an Elastic Beanstalk environment is designed to be replacable (by another Elastic Beanstalk environment).
97
+ [EbDeployer environment](https://github.com/ThoughtWorksStudios/eb_deployer/wiki/EbDeployer-environment)
156
98
 
157
99
  ## Contributing
158
100
 
@@ -89,9 +89,9 @@ module EbDeployer
89
89
  end
90
90
 
91
91
  def with_polling_events(terminate_pattern, &block)
92
- event_start_time = Time.now
92
+ anchor = event_poller.get_anchor
93
93
  yield
94
- event_poller.poll(event_start_time) do |event|
94
+ event_poller.poll(anchor) do |event|
95
95
  if event[:message] =~ /Failed to deploy application/
96
96
  raise event[:message]
97
97
  end
@@ -6,13 +6,35 @@ module EbDeployer
6
6
  @app, @env, @eb_driver = app, env, eb_driver
7
7
  end
8
8
 
9
- def poll(start_time = Time.now, &block)
9
+ def get_anchor
10
+ events, _ = fetch_events_from_eb(:max_records => 1)
11
+ events.first
12
+ end
13
+
14
+ def poll(from_anchor, &block)
10
15
  handled = Set.new
11
16
  loop do
12
- fetch_events(start_time) do |events|
13
- new_events = events.reject { |e| handled.include?(digest(e)) }
14
- handle(new_events, &block)
15
- handled += new_events.map { |e| digest(e) }
17
+ fetch_events(from_anchor) do |events|
18
+ # events from api is latest first order
19
+ to_be_handled = []
20
+ reached_anchor = false
21
+
22
+ events.each do |event|
23
+ if digest(event) == digest(from_anchor)
24
+ reached_anchor = true
25
+ end
26
+
27
+ if !handled.include?(digest(event)) && !reached_anchor
28
+ to_be_handled << event
29
+ end
30
+ end
31
+
32
+ to_be_handled.reverse.each do |event|
33
+ yield(event)
34
+ handled << digest(event)
35
+ end
36
+
37
+ !reached_anchor
16
38
  end
17
39
  sleep 15
18
40
  end
@@ -21,23 +43,24 @@ module EbDeployer
21
43
  private
22
44
 
23
45
  def digest(event)
46
+ return nil unless event
24
47
  JSON.dump(event)
25
48
  end
26
49
 
27
- def handle(events, &block)
28
- events.reverse.each(&block)
29
- end
30
-
31
- def fetch_events(start_time, &block)
32
- events, next_token = fetch_events_from_eb(:start_time => start_time.iso8601)
33
- yield(events)
34
- fetch_next(next_token, &block) if next_token
50
+ def fetch_events(from_anchor, &block)
51
+ options = {}
52
+ if from_anchor && from_anchor[:event_date]
53
+ options[:start_time] = from_anchor[:event_date].iso8601
54
+ end
55
+ events, next_token = fetch_events_from_eb(options)
56
+ should_continue = yield(events)
57
+ fetch_next(next_token, &block) if next_token && should_continue
35
58
  end
36
59
 
37
60
  def fetch_next(next_token, &block)
38
61
  events, next_token = fetch_events_from_eb(:next_token => next_token)
39
- yield(events)
40
- fetch_next(next_token, &block) if next_token
62
+ should_continue = yield(events)
63
+ fetch_next(next_token, &block) if next_token && should_continue
41
64
  end
42
65
 
43
66
  def fetch_events_from_eb(options)
@@ -1,3 +1,3 @@
1
1
  module EbDeployer
2
- VERSION = "0.4.5"
2
+ VERSION = "0.4.6"
3
3
  end
@@ -1,11 +1,12 @@
1
1
  class EBStub
2
- attr_reader :envs
2
+ attr_reader :envs, :events
3
3
  def initialize
4
4
  @apps = []
5
5
  @envs = {}
6
6
  @versions = {}
7
7
  @envs_been_deleted = {}
8
8
  @versions_deleted = {}
9
+ @event_fetched_times = 0
9
10
  end
10
11
 
11
12
  def create_application(app)
@@ -88,21 +89,53 @@ class EBStub
88
89
  end
89
90
  end
90
91
 
92
+ # simulating elasticbeanstalk events api behavior
93
+ # use set_events and append_events to set fake events
91
94
  def fetch_events(app_name, env_name, options={})
92
- set_env_ready(app_name, env_name, true)
95
+ @event_fetched_times += 1
96
+ set_env_ready(app_name, env_name, true) # assume env become ready after it spit out all the events
93
97
 
94
98
  unless @events # unrestricted mode for testing if no explicit events set
95
- return generate_event_from_messages(['Environment update completed successfully',
99
+ return [generate_event_from_messages(['Environment update completed successfully',
96
100
  'terminateEnvironment completed successfully',
97
101
  'Successfully launched environment',
98
102
  'Completed swapping CNAMEs for environments'
99
- ])
103
+ ], Time.now + @event_fetched_times), nil]
100
104
  end
101
105
 
102
- @events[env_key(app_name, env_name)]
103
- # assume env become ready after it spit out all the events
104
- end
106
+ events = @events[env_key(app_name, env_name)][@event_fetched_times - 1]
107
+
108
+ if options.has_key?(:start_time)
109
+ start_time = Time.parse(options[:start_time])
110
+ events = events.select { |e| e[:event_date] >= start_time }
111
+ end
112
+
113
+ if limit = options[:max_records]
114
+ events = events[0..limit]
115
+ end
105
116
 
117
+ [events, nil]
118
+ end
119
+
120
+ # add fake events for each times of fetch events call
121
+ # message passed in should be old to new order
122
+ # e.g. given set_events("myapp", "test", ['a'], ['b', 'c'])
123
+ # then
124
+ # fetch_events('myapp', 'test') # => [{message: 'a'}] for first time
125
+ # fetch_events('myapp', 'test') # => [{message: 'c'}, {message: 'b'}, {message: 'a'}] for the second time call
126
+ def set_events(app_name, env_name, *messages)
127
+ events_seq = []
128
+ messages.each do |messages_for_call_seq|
129
+ if old_events = events_seq.last
130
+ last_event_date = old_events.first && old_events.first[:event_date]
131
+ events_seq << (generate_event_from_messages(messages_for_call_seq, last_event_date) + old_events)
132
+ else
133
+ events_seq << generate_event_from_messages(messages_for_call_seq)
134
+ end
135
+ end
136
+ @events ||= {}
137
+ @events[env_key(app_name, env_name)] = events_seq
138
+ end
106
139
 
107
140
  def environment_cname_prefix(app_name, env_name)
108
141
  return unless @envs[env_key(app_name, env_name)]
@@ -135,7 +168,7 @@ class EBStub
135
168
  end
136
169
 
137
170
  def list_solution_stack_names
138
- @solution_stacks || ["64bit Amazon Linux 2014.02 v1.0.1 running Tomcat 7 Java 7"]
171
+ @solution_stacks || ["64bit Amazon Linux 2014.03 v1.0.3 running Tomcat 7 Java 7"]
139
172
  end
140
173
 
141
174
  #test only
@@ -175,11 +208,6 @@ class EBStub
175
208
  @versions_deleted[app_name]
176
209
  end
177
210
 
178
- def set_events(app_name, env_name, messages)
179
- @events ||= {}
180
- @events[env_key(app_name, env_name)] = generate_event_from_messages(messages)
181
- end
182
-
183
211
  private
184
212
 
185
213
  def set_env_ready(app, env, ready)
@@ -192,11 +220,17 @@ class EBStub
192
220
  @envs[env_key(app, env)][:ready]
193
221
  end
194
222
 
195
- def generate_event_from_messages(messages)
196
- [messages.reverse.map do |m|
197
- {:event_date => Time.now.utc,
198
- :message => m}
199
- end, nil]
223
+ def generate_event_from_messages(messages, start_time=Time.now)
224
+ start_time ||= Time.now
225
+ events = messages.map do |m|
226
+ { :message => m }
227
+ end
228
+
229
+ events.each_with_index do |e, i|
230
+ e[:event_date] = start_time + (i + 1)
231
+ end
232
+
233
+ events.reverse
200
234
  end
201
235
 
202
236
  def env_key(app, name)
@@ -43,16 +43,20 @@ class EbEnvironmentTest < MiniTest::Unit::TestCase
43
43
 
44
44
  def test_should_raise_runtime_error_when_deploy_failed
45
45
  env = EbDeployer::EbEnvironment.new("myapp", "production", @eb_driver)
46
- @eb_driver.set_events("myapp", t("production", 'myapp'), ["start deploying", "Failed to deploy application"])
46
+ @eb_driver.set_events("myapp", t("production", 'myapp'),
47
+ [],
48
+ ["start deploying", "Failed to deploy application"])
47
49
  assert_raises(RuntimeError) { env.deploy("version 1") }
48
50
  end
49
51
 
50
52
  def test_should_raise_runtime_error_when_eb_extension_execution_failed
51
53
  env = EbDeployer::EbEnvironment.new("myapp", "production", @eb_driver)
52
- @eb_driver.set_events("myapp", t("production", 'myapp'), ["start deploying",
53
- "create environment",
54
- "Command failed on instance. Return code: 1 Output: Error occurred during build: Command hooks failed",
55
- "Successfully launched environment"])
54
+ @eb_driver.set_events("myapp", t("production", 'myapp'),
55
+ [],
56
+ ["start deploying",
57
+ "create environment",
58
+ "Command failed on instance. Return code: 1 Output: Error occurred during build: Command hooks failed",
59
+ "Successfully launched environment"])
56
60
 
57
61
  assert_raises(RuntimeError) { env.deploy("version 1") }
58
62
  end
@@ -0,0 +1,32 @@
1
+ require 'test_helper'
2
+
3
+ class EventPollerTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @eb = EBStub.new
6
+ @poller = EbDeployer::EventPoller.new("myapp", "test", @eb)
7
+ end
8
+
9
+ def test_run_handle_block_through_all_events_when_there_is_no_from_anchor
10
+ messages_handled = []
11
+ @eb.set_events('myapp', 'test', ['a', 'b', nil])
12
+ @poller.poll(nil) do |event|
13
+ break if event[:message].nil?
14
+ messages_handled << event[:message]
15
+ end
16
+
17
+ assert_equal ['a', 'b'], messages_handled
18
+ end
19
+
20
+
21
+ def test_can_poll_all_events_after_an_anchor
22
+ @eb.set_events('myapp', 'test', ['a', 'b'], ['c', 'd', nil])
23
+ anchor = @poller.get_anchor
24
+ messages_handled = []
25
+ @poller.poll(anchor) do |event|
26
+ break if event[:message].nil?
27
+ messages_handled << event[:message]
28
+ end
29
+
30
+ assert_equal ['c', 'd'], messages_handled
31
+ end
32
+ end
@@ -22,7 +22,7 @@ class RailsGenratorsTest < Rails::Generators::TestCase
22
22
 
23
23
  assert_file 'config/rds.json'
24
24
  assert_file '.ebextensions/01_postgres_packages.config'
25
- assert_file 'config/database.yml', /database: <%= ENV\['RDS_DB_NAME'\]/m, /host: <%= ENV\['RDS_HOSTNAME'\]/m
25
+ assert_file 'config/database.yml', /database: <%= ENV\['DATABASE_NAME'\]/m, /host: <%= ENV\['DATABASE_HOST'\]/m
26
26
  assert_file 'Gemfile', /gem "pg"/
27
27
  end
28
28
 
@@ -52,11 +52,11 @@ test:
52
52
 
53
53
  production:
54
54
  adapter: postgresql
55
- database: <%= ENV['RDS_DB_NAME'] || 'tmp_production' %>
56
- host: <%= ENV['RDS_HOSTNAME'] || 'localhost' %>
57
- port: <%= ENV['RDS_PORT'] || 5432 %>
58
- username: <%= ENV['RDS_USERNAME'] || \"xli\" %>
59
- password: <%= ENV['RDS_PASSWORD'] %>
55
+ database: <%= ENV['DATABASE_NAME'] || 'tmp_production' %>
56
+ host: <%= ENV['DATABASE_HOST'] || 'localhost' %>
57
+ port: <%= ENV['DATABASE_PORT'] || 5432 %>
58
+ username: <%= ENV['DATABASE_USERNAME'] || #{ENV['USER'].inspect} %>
59
+ password: <%= ENV['DATABASE_PASSWORD'] %>
60
60
  min_messages: ERROR
61
61
  YAML
62
62
  end
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.4.5
4
+ version: 0.4.6
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: 2014-06-10 00:00:00.000000000 Z
13
+ date: 2014-06-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws-sdk
@@ -86,6 +86,7 @@ files:
86
86
  - test/config_loader_test.rb
87
87
  - test/deploy_test.rb
88
88
  - test/eb_environment_test.rb
89
+ - test/event_poller_test.rb
89
90
  - test/inplace_update_deploy_test.rb
90
91
  - test/multi_components_deploy_test.rb
91
92
  - test/rails_generators_test.rb
@@ -128,6 +129,7 @@ test_files:
128
129
  - test/config_loader_test.rb
129
130
  - test/deploy_test.rb
130
131
  - test/eb_environment_test.rb
132
+ - test/event_poller_test.rb
131
133
  - test/inplace_update_deploy_test.rb
132
134
  - test/multi_components_deploy_test.rb
133
135
  - test/rails_generators_test.rb