eb_deployer 0.4.5 → 0.4.6

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