man_eb_deployer 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/.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
|