eb_deployer_updated 0.8.0
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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +127 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +136 -0
- data/Rakefile +12 -0
- data/TODOS.md +11 -0
- data/bin/eb_deploy +13 -0
- data/eb_deployer.gemspec +22 -0
- data/lib/eb_deployer/application.rb +96 -0
- data/lib/eb_deployer/aws_driver/beanstalk.rb +158 -0
- data/lib/eb_deployer/aws_driver/cloud_formation_driver.rb +53 -0
- data/lib/eb_deployer/aws_driver/s3_driver.rb +35 -0
- data/lib/eb_deployer/aws_driver.rb +8 -0
- data/lib/eb_deployer/cf_event_source.rb +26 -0
- data/lib/eb_deployer/cloud_formation_provisioner.rb +120 -0
- data/lib/eb_deployer/component.rb +45 -0
- data/lib/eb_deployer/config_loader.rb +64 -0
- data/lib/eb_deployer/default_component.rb +32 -0
- data/lib/eb_deployer/default_config.rb +20 -0
- data/lib/eb_deployer/default_config.yml +159 -0
- data/lib/eb_deployer/deployment_strategy/blue_green.rb +79 -0
- data/lib/eb_deployer/deployment_strategy/blue_only.rb +45 -0
- data/lib/eb_deployer/deployment_strategy/inplace_update.rb +16 -0
- data/lib/eb_deployer/deployment_strategy.rb +20 -0
- data/lib/eb_deployer/eb_environment.rb +204 -0
- data/lib/eb_deployer/eb_event_source.rb +35 -0
- data/lib/eb_deployer/environment.rb +60 -0
- data/lib/eb_deployer/event_poller.rb +51 -0
- data/lib/eb_deployer/package.rb +39 -0
- data/lib/eb_deployer/resource_stacks.rb +20 -0
- data/lib/eb_deployer/smoke_test.rb +23 -0
- data/lib/eb_deployer/tasks.rb +45 -0
- data/lib/eb_deployer/throttling_handling.rb +17 -0
- data/lib/eb_deployer/utils.rb +33 -0
- data/lib/eb_deployer/version.rb +3 -0
- data/lib/eb_deployer/version_cleaner.rb +30 -0
- data/lib/eb_deployer.rb +339 -0
- data/lib/generators/eb_deployer/install/install_generator.rb +82 -0
- data/lib/generators/eb_deployer/install/templates/eb_deployer.rake +1 -0
- data/lib/generators/eb_deployer/install/templates/eb_deployer.yml.erb +181 -0
- data/lib/generators/eb_deployer/install/templates/ebextensions/01_postgres_packages.config +5 -0
- data/lib/generators/eb_deployer/install/templates/postgres_rds.json +88 -0
- data/test/aws_driver_stubs.rb +350 -0
- data/test/beanstalk_test.rb +23 -0
- data/test/blue_green_deploy_test.rb +114 -0
- data/test/blue_only_deploy_test.rb +78 -0
- data/test/cf_event_poller_test.rb +32 -0
- data/test/cloud_formation_provisioner_test.rb +47 -0
- data/test/config_loader_test.rb +205 -0
- data/test/deploy_test.rb +42 -0
- data/test/eb_environment_test.rb +120 -0
- data/test/eb_event_poller_test.rb +32 -0
- data/test/inplace_update_deploy_test.rb +110 -0
- data/test/multi_components_deploy_test.rb +164 -0
- data/test/rails_generators_test.rb +67 -0
- data/test/resources_deploy_test.rb +191 -0
- data/test/smoke_test_test.rb +23 -0
- data/test/template_deploy_test.rb +13 -0
- data/test/test_helper.rb +68 -0
- data/test/tier_setting_deploy_test.rb +24 -0
- data/test/versions_deploy_test.rb +120 -0
- metadata +194 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
module DeploymentStrategy
|
|
3
|
+
class BlueOnly
|
|
4
|
+
def initialize(component)
|
|
5
|
+
@component = component
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_compatibility(env_create_options)
|
|
9
|
+
tier = env_create_options[:tier]
|
|
10
|
+
if tier && tier.downcase == 'worker'
|
|
11
|
+
raise "Blue only deployment is not supported for Worker tier"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def deploy(version_label, env_settings, inactive_settings=[])
|
|
16
|
+
if !ebenvs.any?(&method(:active_ebenv?))
|
|
17
|
+
ebenv('a', @component.cname_prefix).
|
|
18
|
+
deploy(version_label, env_settings)
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
inactive_ebenv = ebenvs.reject(&method(:active_ebenv?)).first
|
|
23
|
+
|
|
24
|
+
inactive_ebenv.deploy(version_label, env_settings)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def active_ebenv?(ebenv)
|
|
29
|
+
ebenv.cname_prefix == @component.cname_prefix
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def ebenvs
|
|
33
|
+
[ebenv('a'), ebenv('b')]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def ebenv(suffix, cname_prefix=nil)
|
|
37
|
+
@component.new_eb_env(suffix, cname_prefix || inactive_cname_prefix)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inactive_cname_prefix
|
|
41
|
+
"#{@component.cname_prefix}-inactive"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
module DeploymentStrategy
|
|
3
|
+
class InplaceUpdate
|
|
4
|
+
def initialize(component)
|
|
5
|
+
@component = component
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def test_compatibility(env_create_opts)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def deploy(version_label, env_settings, inactive_settings)
|
|
12
|
+
@component.new_eb_env.deploy(version_label, env_settings)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require 'eb_deployer/deployment_strategy/inplace_update'
|
|
2
|
+
require 'eb_deployer/deployment_strategy/blue_green'
|
|
3
|
+
require 'eb_deployer/deployment_strategy/blue_only'
|
|
4
|
+
|
|
5
|
+
module EbDeployer
|
|
6
|
+
module DeploymentStrategy
|
|
7
|
+
def self.create(component, strategy_name)
|
|
8
|
+
case strategy_name.to_s
|
|
9
|
+
when 'inplace_update', 'inplace-update'
|
|
10
|
+
InplaceUpdate.new(component)
|
|
11
|
+
when 'blue_green', 'blue-green'
|
|
12
|
+
BlueGreen.new(component)
|
|
13
|
+
when 'blue_only', 'blue-only'
|
|
14
|
+
BlueOnly.new(component)
|
|
15
|
+
else
|
|
16
|
+
raise 'strategy_name: ' + strategy_name.to_s + ' not supported'
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class EbEnvironment
|
|
3
|
+
include Utils
|
|
4
|
+
|
|
5
|
+
attr_reader :app, :name
|
|
6
|
+
attr_writer :event_poller
|
|
7
|
+
|
|
8
|
+
def self.unique_ebenv_name(env_name, app_name)
|
|
9
|
+
raise "Environment name #{env_name} is too long, it must be under 32 chars" if env_name.size > 32
|
|
10
|
+
digest = Digest::SHA1.hexdigest(app_name + '-' + env_name)[0..6]
|
|
11
|
+
"#{env_name}-#{digest}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(app, name, eb_driver, creation_opts={})
|
|
15
|
+
@app = app
|
|
16
|
+
@name = self.class.unique_ebenv_name(name, app)
|
|
17
|
+
@bs = eb_driver
|
|
18
|
+
@creation_opts = default_create_options.merge(reject_nil(creation_opts))
|
|
19
|
+
@accepted_healthy_states = @creation_opts[:accepted_healthy_states]
|
|
20
|
+
@event_poller = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def deploy(version_label, settings={})
|
|
24
|
+
terminate if @creation_opts[:phoenix_mode]
|
|
25
|
+
|
|
26
|
+
if @bs.environment_exists?(@app, @name)
|
|
27
|
+
update_eb_env(settings, version_label)
|
|
28
|
+
else
|
|
29
|
+
create_eb_env(settings, version_label)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
smoke_test
|
|
33
|
+
wait_for_env_become_healthy
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def apply_settings(settings)
|
|
37
|
+
raise "Env #{self.name} not exists for applying settings" unless @bs.environment_exists?(@app, @name)
|
|
38
|
+
wait_for_env_status_to_be_ready
|
|
39
|
+
with_polling_events(/Successfully deployed new configuration to environment/i) do
|
|
40
|
+
@bs.update_environment_settings(@app, @name, settings)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def cname_prefix
|
|
45
|
+
@bs.environment_cname_prefix(@app, @name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def swap_cname_with(another)
|
|
49
|
+
log("Swap CNAME with env #{another.name}")
|
|
50
|
+
with_polling_events(/Completed swapping CNAMEs for environments/i) do
|
|
51
|
+
@bs.environment_swap_cname(self.app, self.name, another.name)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def log(msg)
|
|
56
|
+
puts "[#{Time.now.utc}][environment:#{@name}] #{msg}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def terminate
|
|
60
|
+
if @bs.environment_exists?(@app, @name)
|
|
61
|
+
with_polling_events(/terminateEnvironment completed successfully/i) do
|
|
62
|
+
@bs.delete_environment(@app, @name)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def health_state
|
|
68
|
+
@bs.environment_health_state(@app, @name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def configured_tier
|
|
74
|
+
@creation_opts[:tier]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def template_name
|
|
78
|
+
@creation_opts[:template_name]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def has_cname?
|
|
82
|
+
!configured_tier || configured_tier.downcase == 'webserver'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def configured_cname_prefix
|
|
86
|
+
@creation_opts[:cname_prefix]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def create_eb_env(settings, version_label)
|
|
90
|
+
solution_stack = @creation_opts[:solution_stack]
|
|
91
|
+
tags = convert_tags_hash_to_array(@creation_opts.delete(:tags))
|
|
92
|
+
validate_solutions_stack(solution_stack)
|
|
93
|
+
with_polling_events(/Successfully launched environment/i) do
|
|
94
|
+
@bs.create_environment(@app,
|
|
95
|
+
@name,
|
|
96
|
+
solution_stack,
|
|
97
|
+
has_cname? ? configured_cname_prefix : nil,
|
|
98
|
+
version_label,
|
|
99
|
+
configured_tier,
|
|
100
|
+
tags,
|
|
101
|
+
settings,
|
|
102
|
+
template_name)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def update_eb_env(settings, version_label)
|
|
107
|
+
with_polling_events(/Successfully deployed new configuration to environment/i) do
|
|
108
|
+
@bs.update_environment(@app,
|
|
109
|
+
@name,
|
|
110
|
+
version_label,
|
|
111
|
+
configured_tier,
|
|
112
|
+
settings,
|
|
113
|
+
template_name)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def validate_solutions_stack(stack_name)
|
|
118
|
+
names = @bs.list_solution_stack_names
|
|
119
|
+
raise "'#{stack_name}' is not a valid solution stack name, available solution stack names are: #{names.join(', ')}" unless names.include?(stack_name)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def smoke_test
|
|
123
|
+
host_name = @bs.environment_cname(@app, @name)
|
|
124
|
+
SmokeTest.new(@creation_opts[:smoke_test]).run(host_name, self)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def with_polling_events(terminate_pattern, &block)
|
|
128
|
+
anchor = event_poller.get_anchor
|
|
129
|
+
yield
|
|
130
|
+
event_poller.poll(anchor) do |event|
|
|
131
|
+
if event[:message] =~ /Failed to deploy application/
|
|
132
|
+
raise event[:message]
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
if event[:message] =~ /Command failed on instance/
|
|
136
|
+
raise "Elasticbeanstalk instance provision failed (maybe a problem with your .ebextension files). The original message: #{event[:message]}"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if event[:message] =~ /complete, but with errors/
|
|
140
|
+
raise event[:message]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if event[:message] =~ /However, there were issues during launch\. See event log for details\./
|
|
144
|
+
raise "Environment launched, but with errors. The original message: #{event[:message]}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
log_event(event)
|
|
148
|
+
break if event[:message] =~ terminate_pattern
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def convert_tags_hash_to_array tags
|
|
153
|
+
tags ||= {}
|
|
154
|
+
tags.inject([]) do |arr, (k, v)|
|
|
155
|
+
arr << {:key => k, :value => v}
|
|
156
|
+
arr
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def wait_for_env_status_to_be_ready
|
|
161
|
+
Timeout.timeout(600) do
|
|
162
|
+
current_status = @bs.environment_status(@app, @name)
|
|
163
|
+
|
|
164
|
+
while current_status.downcase != 'ready'
|
|
165
|
+
log("Environment status: #{current_status}")
|
|
166
|
+
sleep 15
|
|
167
|
+
current_status = @bs.environment_status(@app, @name)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
log("Environment status: #{current_status}")
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def wait_for_env_become_healthy
|
|
175
|
+
Timeout.timeout(600) do
|
|
176
|
+
current_health_status = @bs.environment_health_state(@app, @name)
|
|
177
|
+
while !@accepted_healthy_states.include?(current_health_status)
|
|
178
|
+
log("health status: #{current_health_status}")
|
|
179
|
+
sleep 15
|
|
180
|
+
current_health_status = @bs.environment_health_state(@app, @name)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
log("health status: #{current_health_status}")
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def event_poller
|
|
188
|
+
@event_poller || EventPoller.new(EbEventSource.new(@app, @name, @bs))
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def default_create_options
|
|
192
|
+
{
|
|
193
|
+
:solution_stack => "64bit Amazon Linux 2014.09 v1.1.0 running Tomcat 7 Java 7",
|
|
194
|
+
:smoke_test => Proc.new {},
|
|
195
|
+
:tier => 'WebServer',
|
|
196
|
+
:accepted_healthy_states => ['Green']
|
|
197
|
+
}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def log_event(event)
|
|
201
|
+
puts "[#{event[:event_date]}][environment:#{@name}] #{event[:message]}"
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class EbEventSource
|
|
3
|
+
def initialize(app, env, eb_driver)
|
|
4
|
+
@app, @env, @eb_driver = app, env, eb_driver
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def get_anchor
|
|
8
|
+
events, _ = fetch_events_from_eb(:max_records => 1)
|
|
9
|
+
events.first
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def fetch_events(from_anchor, &block)
|
|
13
|
+
options = {}
|
|
14
|
+
if from_anchor && from_anchor[:event_date]
|
|
15
|
+
options[:start_time] = from_anchor[:event_date].iso8601
|
|
16
|
+
end
|
|
17
|
+
events, next_token = fetch_events_from_eb(options)
|
|
18
|
+
should_continue = yield(events)
|
|
19
|
+
fetch_next(next_token, &block) if next_token && should_continue
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def fetch_next(next_token, &block)
|
|
25
|
+
events, next_token = fetch_events_from_eb(:next_token => next_token)
|
|
26
|
+
should_continue = yield(events)
|
|
27
|
+
fetch_next(next_token, &block) if next_token && should_continue
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fetch_events_from_eb(options)
|
|
31
|
+
@eb_driver.fetch_events(@app, @env, options)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class Environment
|
|
3
|
+
include Utils
|
|
4
|
+
attr_accessor :creation_opts, :strategy_name
|
|
5
|
+
|
|
6
|
+
attr_writer :resource_stacks, :settings, :inactive_settings, :component_under_deploy
|
|
7
|
+
|
|
8
|
+
attr_reader :name
|
|
9
|
+
|
|
10
|
+
def initialize(app, name, stack_name, eb_driver, &block)
|
|
11
|
+
@app = app
|
|
12
|
+
@name = name
|
|
13
|
+
@stack_name = stack_name
|
|
14
|
+
@eb_driver = eb_driver
|
|
15
|
+
@creation_opts = {}
|
|
16
|
+
@settings = []
|
|
17
|
+
@inactive_settings = []
|
|
18
|
+
@strategy_name = :blue_green
|
|
19
|
+
@components = nil
|
|
20
|
+
yield(self) if block_given?
|
|
21
|
+
unless @components
|
|
22
|
+
@components = [DefaultComponent.new(self, @creation_opts, @strategy_name, @eb_driver)]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def app_name
|
|
27
|
+
@app.name
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def deploy(version_label)
|
|
31
|
+
resource_settings = @resource_stacks.provision(@stack_name)
|
|
32
|
+
components_to_deploy.each do |component|
|
|
33
|
+
component.deploy(version_label, @settings + resource_settings, @inactive_settings)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def components=(components_attrs)
|
|
38
|
+
return unless components_attrs
|
|
39
|
+
@components = components_attrs.map do |attrs|
|
|
40
|
+
attrs = symbolize_keys(attrs)
|
|
41
|
+
Component.new(attrs.delete(:name), self, attrs, @eb_driver)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def components_to_deploy
|
|
47
|
+
if @component_under_deploy
|
|
48
|
+
component = component_named(@component_under_deploy)
|
|
49
|
+
raise "'#{@component_under_deploy}' is not in the configuration. Available components are #{@components.map(&:name) }" unless component
|
|
50
|
+
[component]
|
|
51
|
+
else
|
|
52
|
+
@components
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def component_named(name)
|
|
57
|
+
@components.detect { |c| c.name == name }
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class EventPoller
|
|
3
|
+
include Utils
|
|
4
|
+
POLL_INTERVAL = 15
|
|
5
|
+
|
|
6
|
+
def initialize(event_source)
|
|
7
|
+
@event_source = event_source
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def get_anchor
|
|
11
|
+
@event_source.get_anchor
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def poll(from_anchor, &block)
|
|
15
|
+
handled = Set.new
|
|
16
|
+
loop do
|
|
17
|
+
@event_source.fetch_events(from_anchor) do |events|
|
|
18
|
+
# events from event source 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
|
|
38
|
+
end
|
|
39
|
+
sleep POLL_INTERVAL
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def digest(event)
|
|
46
|
+
return nil unless event
|
|
47
|
+
event = event.to_h if event.respond_to?(:to_h)
|
|
48
|
+
event.to_json
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class Package
|
|
3
|
+
def initialize(file, bucket_name, s3_driver)
|
|
4
|
+
@file, @bucket_name = file, bucket_name
|
|
5
|
+
@s3 = s3_driver
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def upload
|
|
9
|
+
ensure_bucket(@bucket_name)
|
|
10
|
+
upload_if_not_exists(@file, @bucket_name)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def source_bundle
|
|
14
|
+
{ :s3_bucket => @bucket_name, :s3_key => s3_path }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def s3_path
|
|
20
|
+
@_s3_path ||= Digest::MD5.file(@file).hexdigest + "-" + File.basename(@file)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ensure_bucket(bucket_name)
|
|
24
|
+
@s3.create_bucket(@bucket_name) unless @s3.bucket_exists?(@bucket_name)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def upload_if_not_exists(file, bucket_name)
|
|
28
|
+
if @s3.object_length(@bucket_name, s3_path) != File.size(file)
|
|
29
|
+
log("start uploading to s3 bucket #{@bucket_name}...")
|
|
30
|
+
@s3.upload_file(@bucket_name, s3_path, file)
|
|
31
|
+
log("uploading finished")
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def log(message)
|
|
36
|
+
puts "[#{Time.now.utc}][package:#{File.basename(@file)}] #{message}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class ResourceStacks
|
|
3
|
+
def initialize(resources, cf_driver, skip_provision, tags)
|
|
4
|
+
@resources = resources
|
|
5
|
+
@cf_driver = cf_driver
|
|
6
|
+
@skip_provision = skip_provision
|
|
7
|
+
@tags = (tags || {}).map { |k, v| { key: k, value: v } }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def provision(stack_name)
|
|
11
|
+
provisioner = CloudFormationProvisioner.new(stack_name, @cf_driver)
|
|
12
|
+
if @resources
|
|
13
|
+
provisioner.provision(@resources, @tags) unless @skip_provision
|
|
14
|
+
provisioner.transform_outputs(@resources)
|
|
15
|
+
else
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class SmokeTest
|
|
3
|
+
def initialize(test_body)
|
|
4
|
+
@test_body = test_body
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def run(host_name, logger=nil)
|
|
8
|
+
return unless @test_body
|
|
9
|
+
logger.log("running smoke test for #{host_name}...") if logger
|
|
10
|
+
|
|
11
|
+
case @test_body
|
|
12
|
+
when Proc
|
|
13
|
+
@test_body.call(host_name)
|
|
14
|
+
when String
|
|
15
|
+
eval(@test_body, binding)
|
|
16
|
+
else
|
|
17
|
+
raise "smoke test can only be a string to evaluate or a proc object such as lambda"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
logger.log("smoke test succeeded.") if logger
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
require 'open3'
|
|
3
|
+
|
|
4
|
+
namespace :eb do
|
|
5
|
+
def eb_deployer_env
|
|
6
|
+
ENV['EB_DEPLOYER_ENV'] || 'dev'
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def eb_deployer_package
|
|
10
|
+
name = File.basename(Dir.pwd).downcase.gsub(/[^0-9a-z]/, '-').gsub(/--/, '-')
|
|
11
|
+
"tmp/#{name}.zip"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def eb_package_files
|
|
15
|
+
ignore_file = File.join(Dir.pwd, ".ebdeployerignore")
|
|
16
|
+
ignore_patterns = File.exists?(ignore_file) ? File.readlines(ignore_file).map(&:strip) : []
|
|
17
|
+
`git ls-files`.lines.reject { |f| ignore_patterns.any? { |p| File.fnmatch(p, f.strip) } }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Remove the package file we generated."
|
|
21
|
+
task :clean do
|
|
22
|
+
sh "rm -rf #{eb_deployer_package}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "Build package for eb_deployer to deploy to a Ruby environment in tmp directory. It zips all file list by 'git ls-files'"
|
|
26
|
+
task :package => [:clean] do
|
|
27
|
+
package = eb_deployer_package
|
|
28
|
+
FileUtils.mkdir_p(File.dirname(package))
|
|
29
|
+
Open3.popen2("zip #{package} -@") do |i, o, t|
|
|
30
|
+
i.write(eb_package_files.join)
|
|
31
|
+
i.close
|
|
32
|
+
puts o.read
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Deploy package we built in tmp directory. default to dev environment, specify environment variable EB_DEPLOYER_ENV to override, for example: EB_DEPLOYER_ENV=production rake eb:deploy."
|
|
37
|
+
task :deploy => [:package] do
|
|
38
|
+
sh "eb_deploy -p #{eb_deployer_package} -e #{eb_deployer_env}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc "Destroy Elastic Beanstalk environments. It won't destroy resources defined in eb_deployer.yml. Default to dev environment, specify EB_DEPLOYER_ENV to override."
|
|
42
|
+
task :destroy do
|
|
43
|
+
sh "eb_deploy -d -e #{eb_deployer_env}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class ThrottlingHandling
|
|
3
|
+
include Utils
|
|
4
|
+
|
|
5
|
+
def initialize(delegatee, throttling_error)
|
|
6
|
+
@delegatee = delegatee
|
|
7
|
+
@throttling_error = throttling_error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def method_missing(method, *args, &block)
|
|
11
|
+
super unless @delegatee.respond_to?(method)
|
|
12
|
+
backoff(@throttling_error) do
|
|
13
|
+
@delegatee.send(method, *args, &block)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
module Utils
|
|
3
|
+
BACKOFF_INITIAL_SLEEP = 1
|
|
4
|
+
|
|
5
|
+
# A util deal with throttling exceptions
|
|
6
|
+
# example:
|
|
7
|
+
# backoff(Aws::EC2::Errors::RequestLimitExceeded) do
|
|
8
|
+
# ...
|
|
9
|
+
# end
|
|
10
|
+
def backoff(error_class, retry_limit=9, &block)
|
|
11
|
+
next_sleep = BACKOFF_INITIAL_SLEEP
|
|
12
|
+
begin
|
|
13
|
+
yield
|
|
14
|
+
rescue error_class
|
|
15
|
+
raise if retry_limit == 0
|
|
16
|
+
sleep(next_sleep)
|
|
17
|
+
next_sleep *= 2
|
|
18
|
+
retry_limit -= 1
|
|
19
|
+
retry
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# convert top level key in a hash to symbol
|
|
24
|
+
def symbolize_keys(hash)
|
|
25
|
+
hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def reject_nil(hash)
|
|
30
|
+
hash.reject{| k, v| v.nil?}
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module EbDeployer
|
|
2
|
+
class VersionCleaner
|
|
3
|
+
def initialize(app, number_to_keep)
|
|
4
|
+
@app = app
|
|
5
|
+
@number_to_keep = number_to_keep
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def clean(version_prefix = "")
|
|
9
|
+
if @number_to_keep > 0
|
|
10
|
+
versions_to_remove = versions_to_clean(version_prefix)
|
|
11
|
+
@app.remove(versions_to_remove, true)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
def versions_to_clean(version_prefix = "")
|
|
17
|
+
all_versions = @app.versions.select do |apv|
|
|
18
|
+
apv[:version].start_with?(version_prefix)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
all_versions.sort! { |x, y| y[:date_updated] <=> x[:date_updated] }
|
|
22
|
+
all_versions.slice!(range_to_keep)
|
|
23
|
+
all_versions.map { |apv| apv[:version] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def range_to_keep
|
|
27
|
+
(0..(@number_to_keep-1))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|