man_eb_deployer 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/release.yml +31 -0
- data/.github/workflows/test.yml +16 -0
- data/.gitignore +12 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +143 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +138 -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 +176 -0
@@ -0,0 +1,159 @@
|
|
1
|
+
# application name
|
2
|
+
application: <%= app_name %>
|
3
|
+
|
4
|
+
# common settings for all environments
|
5
|
+
common:
|
6
|
+
# Solution stack for elastic beanstalk, default is 64bit tomcat 7 for JAVA app
|
7
|
+
# solution_stack_name: 64bit Amazon Linux 2014.03 v1.0.3 running Tomcat 7 Java 7
|
8
|
+
|
9
|
+
# Tier name for environments. Current supported values are WebServer and Worker
|
10
|
+
# tier: WebServer
|
11
|
+
|
12
|
+
# AWS region to deploy. Default to us-east-1
|
13
|
+
# region: us-west-1
|
14
|
+
|
15
|
+
# There are three deployment strategies: 'blue-green', 'blue-only', or 'inplace-update'.
|
16
|
+
# Blue green deployments keep two elastic beanstalk environments and always deploy to
|
17
|
+
# inactive one, to achieve zero downtime.
|
18
|
+
# Blue only deployments do everything that the blue green deployments do except for the final
|
19
|
+
# inactive to active CNAME swap leaving the newly deployed application on the inactive
|
20
|
+
# "blue" instance.
|
21
|
+
# Inplace-update strategy will only keep one environment, and update the version inplace on
|
22
|
+
# deploy. Inplace-update will save resources but will suffer from downtime.
|
23
|
+
# (All old environments need be destroyed when you switching between strategies.)
|
24
|
+
# Default strategy is 'blue-green'.
|
25
|
+
# strategy: blue-green
|
26
|
+
|
27
|
+
# Name of s3 bucket where uploaded application packages will be stored.
|
28
|
+
# Note that the string ".packages" will be added as a suffix to your bucket.
|
29
|
+
# So, if "thoughtworks.simple" is passed as the bucket name, the actual s3 bucket
|
30
|
+
# name will be thoughtworks.simple.packages. Default to application name.
|
31
|
+
# package_bucket: my-s3-bucket
|
32
|
+
|
33
|
+
# If phoenix mode is turned on, it will terminate the old elastic
|
34
|
+
# beanstalk environment and recreate on deploy. For blue-green
|
35
|
+
# deployment it terminates the inactive environment first then
|
36
|
+
# recreate it. This is useful to avoid configuration drift and
|
37
|
+
# accumulating state on the ec2 instances. Default is off but we recommend
|
38
|
+
# it to be turned on for production environment.
|
39
|
+
# phoenix_mode: off
|
40
|
+
|
41
|
+
# The tags you would like to be associated with your resources.
|
42
|
+
# These tags will only be used when you first launch an environment. If you are using
|
43
|
+
# phoenix_mode set as true each time you deploy you will get a new environment and therefore
|
44
|
+
# any changes to your tags. If phoenix_mode is false then it will only use your tags on the
|
45
|
+
# initial deploy.
|
46
|
+
# tags:
|
47
|
+
# my_tag_key: my_tag_value
|
48
|
+
|
49
|
+
# Specifies the maximum number of versions to keep. Older versions are removed
|
50
|
+
# and deleted from the S3 source bucket as well. If specified as zero or not
|
51
|
+
# specified, all versions will be kept. If a version_prefix is given, only removes
|
52
|
+
# version starting with the prefix.
|
53
|
+
# keep_latest: 200
|
54
|
+
|
55
|
+
# Specifies a prefix to prepend to the version label.
|
56
|
+
# This can be useful if you want to use different binaries for different
|
57
|
+
# environments.
|
58
|
+
# version_prefix:
|
59
|
+
|
60
|
+
# Generating version label for package to be deployed. A readable version label will
|
61
|
+
# provide better traceablity of your deployment process.
|
62
|
+
# By default setting is:
|
63
|
+
# version_label: <%%= package_digest %>
|
64
|
+
# which means using MD5 digest of the package file. If you deploy using build
|
65
|
+
# pipeline tool such as GO, switching to pipline counter is highly suggested to
|
66
|
+
# increase the readability. Following example will read pipeline counter from environment
|
67
|
+
# variable with a fall back to digest for local deployment:
|
68
|
+
# version_label: <%%= ENV['GO_PIPELINE_COUNTER'] || package_digest %>
|
69
|
+
|
70
|
+
|
71
|
+
# Smoke test value should be a piece of ruby code with access to single variable
|
72
|
+
# "host_name" -- environment DNS name. Smoke test snippet will be evaluated at
|
73
|
+
# the end of the deployment for inplace-update deployment. For blue-green
|
74
|
+
# deployment it will run after inactive environment update is completed and before
|
75
|
+
# switching over.
|
76
|
+
# Defining a smoke test is highly recommended for serious usage. By default we use
|
77
|
+
# The simplest one that just be checking server landing page using curl, e.g.
|
78
|
+
smoke_test: |
|
79
|
+
curl_http_code = "curl -s -o /dev/null -w \"%{http_code}\" http://#{host_name}"
|
80
|
+
Timeout.timeout(600) do
|
81
|
+
until ['200', '301', '302'].include?(`#{curl_http_code}`.strip)
|
82
|
+
sleep 5
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# List of Elastic Beanstalk health states that will be considered healthy when deploying.
|
87
|
+
# You may want to override default 'Green' with that list, if for example you are using
|
88
|
+
# Elastic Beanstalk worker tier and you don't want eb_deployer to fail if you have messages
|
89
|
+
# in your SQS queue.
|
90
|
+
# By default eb_deployer only considers 'Green' as the healthy state.
|
91
|
+
# For a list of all Elastic Beanstalk health states refer to:
|
92
|
+
# http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.healthstatus.html#using-features.healthstatus.colors
|
93
|
+
# accepted_healthy_states:
|
94
|
+
# - Green
|
95
|
+
# - Yellow
|
96
|
+
|
97
|
+
# Elastic Beanstalk settings that will apply to the environments you are
|
98
|
+
# deploying.
|
99
|
+
# For all available options take a look at
|
100
|
+
# http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html
|
101
|
+
option_settings:
|
102
|
+
# Following is an example of set EC2 ssh key name. This allow you ssh into the ec2
|
103
|
+
# instance. See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html
|
104
|
+
# - namespace: aws:autoscaling:launchconfiguration
|
105
|
+
# option_name: EC2KeyName
|
106
|
+
# value: <my-ec2-key-name>
|
107
|
+
|
108
|
+
# Following is an example which changes EC2 instance type
|
109
|
+
# from t1.micro (default) to m1.small. Instances with t1.micro type sometime
|
110
|
+
# are not very responsible, so m1.small is suggested for saving time.
|
111
|
+
# But if you care about the marginal cost difference you can comment this out to
|
112
|
+
# go back to t1.micro.
|
113
|
+
- namespace: aws:autoscaling:launchconfiguration
|
114
|
+
option_name: InstanceType
|
115
|
+
value: m1.small
|
116
|
+
|
117
|
+
|
118
|
+
# If resources specified, eb_deployer will use the CloudFormation
|
119
|
+
# template you provide to create a default CloudFormation stack with
|
120
|
+
# name <application_name>-<env-name> for the environment current
|
121
|
+
# deploying. And Outputs of the CloudFormation can be mapped to Elastic Beanstalk
|
122
|
+
# options settings.
|
123
|
+
# keys:
|
124
|
+
# template => CloudFormation template file with JSON format
|
125
|
+
# policy => CloudFormation policy file with JSON format
|
126
|
+
# override_policy => (false) If override_policy is true and a policy file is provided then the policy will temporarily override any existing policy on the resource stack during this update,
|
127
|
+
# otherwise the provided policy will replace any existing policy on the resource stack
|
128
|
+
# inputs => A Hash, input values for the CloudFormation template
|
129
|
+
# outputs => A Hash with key map to your CloudFormation template outputs and value as elastic beanstalk settings namespace and option_name.
|
130
|
+
# capabilities => An array. You need set it to ['CAPABILITY_IAM'] if the
|
131
|
+
# template include IAM Instance Profile.
|
132
|
+
resources:
|
133
|
+
|
134
|
+
# For example creating a RDS instance for blue green deployment(check jruby-rails4
|
135
|
+
# sample project which has a working example):
|
136
|
+
# template: config/my_rds.json
|
137
|
+
# inputs:
|
138
|
+
# DBPassword: <%%= ENV["MYDBPASSWORD"] %>
|
139
|
+
# outputs:
|
140
|
+
# RDSPassSecurityGroup:
|
141
|
+
# namespace: aws:autoscaling:launchconfiguration
|
142
|
+
# option_name: SecurityGroups
|
143
|
+
# RDSDatabaseConfig:
|
144
|
+
# namespace: aws:elasticbeanstalk:application:environment
|
145
|
+
# option_name: databaseConfig
|
146
|
+
|
147
|
+
|
148
|
+
# You can define environment here. Each environment can overriden any common settings
|
149
|
+
environments:
|
150
|
+
dev:
|
151
|
+
# example for overriding common settings
|
152
|
+
# strategy: inplace-update
|
153
|
+
production:
|
154
|
+
option_settings:
|
155
|
+
# example for overriding common option_settings: providing least redundancy
|
156
|
+
# in production environment.
|
157
|
+
# - namespace: aws:autoscaling:asg
|
158
|
+
# option_name: MinSize
|
159
|
+
# value: "2"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module EbDeployer
|
2
|
+
module DeploymentStrategy
|
3
|
+
class BlueGreen
|
4
|
+
def initialize(component)
|
5
|
+
@component = component
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_compatibility(env_create_options)
|
9
|
+
@create_opts = env_create_options
|
10
|
+
tier = env_create_options[:tier]
|
11
|
+
|
12
|
+
if tier && tier.downcase == 'worker'
|
13
|
+
raise "Blue green deployment is not supported for Worker tier"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def deploy(version_label, env_settings, inactive_settings=[])
|
18
|
+
|
19
|
+
if !ebenvs.any?(&method(:active_ebenv?))
|
20
|
+
ebenv('a', @component.cname_prefix).
|
21
|
+
deploy(version_label, env_settings)
|
22
|
+
return
|
23
|
+
end
|
24
|
+
|
25
|
+
active_ebenv = ebenvs.detect(&method(:active_ebenv?))
|
26
|
+
inactive_ebenv = ebenvs.reject(&method(:active_ebenv?)).first
|
27
|
+
|
28
|
+
inactive_ebenv.deploy(version_label, env_settings)
|
29
|
+
active_ebenv.swap_cname_with(inactive_ebenv)
|
30
|
+
|
31
|
+
blue_green_terminate_inactive = @create_opts[:blue_green_terminate_inactive]
|
32
|
+
blue_green_terminate_inactive_wait = @create_opts[:blue_green_terminate_inactive_wait]
|
33
|
+
blue_green_terminate_inactive_sleep = @create_opts[:blue_green_terminate_inactive_sleep]
|
34
|
+
|
35
|
+
if blue_green_terminate_inactive
|
36
|
+
active_ebenv.log("Waiting #{blue_green_terminate_inactive_wait}s before terminating environment...")
|
37
|
+
|
38
|
+
# Loop until timeout reached or environment becomes Red
|
39
|
+
count = 0
|
40
|
+
loop do
|
41
|
+
break if count >= blue_green_terminate_inactive_wait or inactive_ebenv.health_state != 'Green'
|
42
|
+
sleep blue_green_terminate_inactive_sleep
|
43
|
+
count += blue_green_terminate_inactive_sleep
|
44
|
+
end
|
45
|
+
|
46
|
+
if inactive_ebenv.health_state == 'Green'
|
47
|
+
active_ebenv.log("Active environment healthy, terminating inactive (black) environment")
|
48
|
+
active_ebenv.terminate
|
49
|
+
else
|
50
|
+
active_ebenv.log("Active environment changed state to unhealthy. Existing (black) environment will not be terminated")
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
unless inactive_settings.empty? || blue_green_terminate_inactive
|
56
|
+
active_ebenv.log("applying inactive settings...")
|
57
|
+
active_ebenv.apply_settings(inactive_settings)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def active_ebenv?(ebenv)
|
63
|
+
ebenv.cname_prefix == @component.cname_prefix
|
64
|
+
end
|
65
|
+
|
66
|
+
def ebenvs
|
67
|
+
[ebenv('a'), ebenv('b')]
|
68
|
+
end
|
69
|
+
|
70
|
+
def ebenv(suffix, cname_prefix=nil)
|
71
|
+
@component.new_eb_env(suffix, cname_prefix || inactive_cname_prefix)
|
72
|
+
end
|
73
|
+
|
74
|
+
def inactive_cname_prefix
|
75
|
+
"#{@component.cname_prefix}-inactive"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -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
|