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,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.exist?(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
|
data/lib/eb_deployer.rb
ADDED
@@ -0,0 +1,339 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'set'
|
3
|
+
require 'time'
|
4
|
+
require 'json'
|
5
|
+
require 'timeout'
|
6
|
+
require 'aws-sdk-s3'
|
7
|
+
require 'aws-sdk-elasticbeanstalk'
|
8
|
+
require 'aws-sdk-cloudformation'
|
9
|
+
require 'optparse'
|
10
|
+
require 'erb'
|
11
|
+
require 'fileutils'
|
12
|
+
|
13
|
+
require 'eb_deployer/version'
|
14
|
+
require 'eb_deployer/utils'
|
15
|
+
require 'eb_deployer/aws_driver'
|
16
|
+
require 'eb_deployer/deployment_strategy'
|
17
|
+
require 'eb_deployer/cloud_formation_provisioner'
|
18
|
+
require 'eb_deployer/application'
|
19
|
+
require 'eb_deployer/resource_stacks'
|
20
|
+
require 'eb_deployer/throttling_handling'
|
21
|
+
require 'eb_deployer/eb_environment'
|
22
|
+
require 'eb_deployer/environment'
|
23
|
+
require 'eb_deployer/default_component'
|
24
|
+
require 'eb_deployer/component'
|
25
|
+
require 'eb_deployer/eb_event_source'
|
26
|
+
require 'eb_deployer/cf_event_source'
|
27
|
+
require 'eb_deployer/event_poller'
|
28
|
+
require 'eb_deployer/package'
|
29
|
+
require 'eb_deployer/config_loader'
|
30
|
+
require 'eb_deployer/default_config'
|
31
|
+
require 'eb_deployer/smoke_test'
|
32
|
+
require 'eb_deployer/version_cleaner'
|
33
|
+
|
34
|
+
module EbDeployer
|
35
|
+
|
36
|
+
#
|
37
|
+
# Query ouput value of the cloud formation stack
|
38
|
+
#
|
39
|
+
# @param [String] key CloudFormation output key
|
40
|
+
# @param [Hash] opts
|
41
|
+
# @option opts [Symbol] :application application name
|
42
|
+
# @option opts [Symbol] :environment environment name (e.g. staging, production)
|
43
|
+
# @option opts [Symbol] :region AWS Region (e.g. "us-west-2", "us-east-1")
|
44
|
+
#
|
45
|
+
def self.query_resource_output(key, opts)
|
46
|
+
if region = opts[:region]
|
47
|
+
Aws.config.update({
|
48
|
+
region: region
|
49
|
+
})
|
50
|
+
end
|
51
|
+
app = opts[:application]
|
52
|
+
env_name = opts[:environment]
|
53
|
+
cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
|
54
|
+
stack_name = opts[:stack_name] || "#{app}-#{env_name}"
|
55
|
+
provisioner = CloudFormationProvisioner.new(stack_name, cf)
|
56
|
+
provisioner.output(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
#
|
61
|
+
# Deploy a package to specified environments on elastic beanstalk
|
62
|
+
#
|
63
|
+
# @param [Hash] opts
|
64
|
+
#
|
65
|
+
# @option opts [Symbol] :application *required* Application name, this
|
66
|
+
# used for isolate packages and contribute to your elastic beanstalk cname
|
67
|
+
# for environments
|
68
|
+
#
|
69
|
+
# @option opts [Symbol] :environment *required* Environment for same
|
70
|
+
# application, e.g. testing, staging, production. This will map to 2 elastic
|
71
|
+
# beanstalk environments (env-a-xxx, env-b-xxx) if blue-green deployment
|
72
|
+
# strategy specified
|
73
|
+
#
|
74
|
+
# @option opts [Symbol] :package *required* package for the application
|
75
|
+
# which should be suitable for elastic beanstalk deploying. For example, a
|
76
|
+
# war file should be provided for java solution stacks and a ZIP file
|
77
|
+
# should be provided for Rails or Sinatra stack.
|
78
|
+
#
|
79
|
+
# @option opts [Symbol] :option_settings (optional) Elastic Beanstalk
|
80
|
+
# settings that will apply to the environments you deploying. Value should be
|
81
|
+
# array of hash with format such as:
|
82
|
+
#
|
83
|
+
# [{
|
84
|
+
# :namespace => 'aws:autoscaling:launchconfiguration',
|
85
|
+
# :option_name => 'InstanceType',
|
86
|
+
# :value => 'm1.small' }]
|
87
|
+
#
|
88
|
+
# When there are many, using an external yaml file to hold those
|
89
|
+
# configuration is recommended. Such as:
|
90
|
+
#
|
91
|
+
# YAML.load(File.read("my_settings_file.yml"))
|
92
|
+
#
|
93
|
+
# For all available options take a look at
|
94
|
+
# http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html
|
95
|
+
#
|
96
|
+
# @option opts [Symbol] :accepted_healthy_states (['Green']) If :accepted_healthy_states
|
97
|
+
# is specified, EBDeployer will accept provided values when checking
|
98
|
+
# health of an environment instead of default value 'Green'. You can use it
|
99
|
+
# to specify additional healthy states, for example: ['Green', "Yellow"]
|
100
|
+
#
|
101
|
+
# @option opts [Symbol] :phoenix_mode (false) If phoenix mode is turn on, it
|
102
|
+
# will terminate the old elastic beanstalk environment and recreate on
|
103
|
+
# deploy. For blue-green deployment it terminate the inactive environment
|
104
|
+
# first then recreate it. This is useful to avoiding configuration drift and
|
105
|
+
# accumulating state on the EC2 instances. Also it has the benefit of keeping
|
106
|
+
# your EC2 instance system package upto date, because every time EC2 instance
|
107
|
+
# boot up from AMI it does a system update.
|
108
|
+
#
|
109
|
+
#
|
110
|
+
# @option opts [Symbol] :region set the region for application deployment
|
111
|
+
# (e.g. "us-west-2", "us-east-1"). See available zones at
|
112
|
+
# http://aws.amazon.com/elasticbeanstalk/faqs/#regions
|
113
|
+
#
|
114
|
+
# @option opts [Symbol] :resources If :resources specified, EBDeployer will
|
115
|
+
# use the CloudFormation template you provide to create a default
|
116
|
+
# CloudFormation stack with name <application_name>-<env-name> for the
|
117
|
+
# environment current deploying. Value of resources need to be hash with
|
118
|
+
# following keys:
|
119
|
+
#
|
120
|
+
# :template => CloudFormation template file with JSON format
|
121
|
+
# :policy => CloudFormation policy file with JSON format
|
122
|
+
# :override_policy => (false) If override_policy is true and a policy file is provided then the
|
123
|
+
# policy will temporarily override any existing policy on the resource stack during this update,
|
124
|
+
# otherwise the provided policy will replace any existing policy on the resource stack
|
125
|
+
# :parameters (or :inputs) => A Hash, input values for the CloudFormation template
|
126
|
+
# :transforms => A Hash with key map to your CloudFormation
|
127
|
+
# template outputs and value as lambda that return a single or array of
|
128
|
+
# elastic beanstalk settings.
|
129
|
+
#
|
130
|
+
# :capabilities => An array. You need set it to ['CAPABILITY_IAM']
|
131
|
+
#
|
132
|
+
# if you want to provision IAM Instance Profile.
|
133
|
+
#
|
134
|
+
# @option opts [Symbol] :settings See `option_settings`
|
135
|
+
#
|
136
|
+
# @option opts [Symbol] :package_bucket Name of s3 bucket where uploaded application
|
137
|
+
# packages will be stored. Note that the string ".packages" will be added as
|
138
|
+
# a suffix to your bucket. So, if "thoughtworks.simple" is passed as the bucket name,
|
139
|
+
# the actual s3 bucket name will be thoughtworks.simple.packages.
|
140
|
+
#
|
141
|
+
# @option opts [Symbol] :smoke_test Value should be a proc or a lambda which
|
142
|
+
# accept single argument that will passed in as environment DNS name. Smoke
|
143
|
+
# test proc or lambda will be called at the end of the deployment for
|
144
|
+
# inplace-update deployment strategy. For blue-green deployment it will run
|
145
|
+
# after inactive environment update finish and before switching. Defining a
|
146
|
+
# smoke test is high recommended for serious usage. The simplest one could
|
147
|
+
# just be checking the server is up using curl, e.g.
|
148
|
+
#
|
149
|
+
# :smoke_test => lambda { |host|
|
150
|
+
# curl_http_code = "curl -k -s -o /dev/null -w \"%{http_code}\" https://#{host}"
|
151
|
+
# Timeout.timeout(600) do
|
152
|
+
# while `#{curl_http_code}`.strip != '200'
|
153
|
+
# sleep 5
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
# }
|
157
|
+
#
|
158
|
+
# @option opts [Symbol] :strategy (:blue-green) There are two options:
|
159
|
+
# blue-green or inplace-update. Blue green keep two elastic beanstalk
|
160
|
+
# environments and always deploy to inactive one, to achieve zero downtime.
|
161
|
+
# inplace-update strategy will only keep one environment, and update the
|
162
|
+
# version inplace on deploy. this will save resources but will have downtime.
|
163
|
+
#
|
164
|
+
# @option opts [Symbol] :solution_stack_name ("64bit Amazon Linux 2013.09 running Tomcat 7 Java 7")
|
165
|
+
# The elastic beanstalk solution stack you want to deploy on top of.
|
166
|
+
#
|
167
|
+
# @option opts [Symbol] :tier ("WebServer")
|
168
|
+
# The environment tier. Either "WebServer" or "Worker"
|
169
|
+
#
|
170
|
+
# @option opts [Symbol] :version_label *required*. Version label give the
|
171
|
+
# package uploaded a unique identifier. Should use something related to
|
172
|
+
# pipeline counter if you have build pipeline setup to build the installer.
|
173
|
+
# For the convenience of dev we recommend use md5 digest of the installer so
|
174
|
+
# that every time you upload new installer it forms a new version. e.g.
|
175
|
+
#
|
176
|
+
# :version_label => ENV['MY_PIPELINE_COUNTER']
|
177
|
+
# || "dev-" + Digest::MD5.file(my_package).hexdigest
|
178
|
+
#
|
179
|
+
# @options opts [Symbol] :version_prefix. Specifies a prefix to prepend to the
|
180
|
+
# version label. This can be useful if you want to use different binaries for
|
181
|
+
# different environments.
|
182
|
+
#
|
183
|
+
# @option opts [Symbol] :keep_latest. Specifies the maximum number of versions to
|
184
|
+
# keep. Older versions are removed and deleted from the S3 source bucket as well.
|
185
|
+
# If specified as zero or not specified, all versions will be kept. If a
|
186
|
+
# version_prefix is given, only removes version starting with the prefix.
|
187
|
+
#
|
188
|
+
# @option opts [Symbol] :template_name. Specifies the environement template you wish
|
189
|
+
# to use to build your environment.
|
190
|
+
def self.deploy(opts)
|
191
|
+
if region = opts[:region]
|
192
|
+
Aws.config.update(:region => region)
|
193
|
+
end
|
194
|
+
|
195
|
+
bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
|
196
|
+
bs = ThrottlingHandling.new(bs, Aws::ElasticBeanstalk::Errors::Throttling)
|
197
|
+
s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
|
198
|
+
cf = opts[:cf_driver] || AWSDriver::CloudFormationDriver.new
|
199
|
+
|
200
|
+
app_name = opts[:application]
|
201
|
+
env_name = opts[:environment]
|
202
|
+
version_prefix = opts[:version_prefix].to_s.strip
|
203
|
+
version_label = "#{version_prefix}#{opts[:version_label].to_s.strip}"
|
204
|
+
|
205
|
+
application = Application.new(app_name, bs, s3, opts[:package_bucket])
|
206
|
+
resource_stacks = ResourceStacks.new(opts[:resources],
|
207
|
+
cf,
|
208
|
+
!!opts[:skip_resource_stack_update],
|
209
|
+
opts[:tags])
|
210
|
+
|
211
|
+
stack_name = opts[:stack_name] || "#{app_name}-#{env_name}"
|
212
|
+
|
213
|
+
environment = Environment.new(application, env_name, stack_name, bs) do |env|
|
214
|
+
env.resource_stacks = resource_stacks
|
215
|
+
env.settings = opts[:option_settings] || opts[:settings] || []
|
216
|
+
env.inactive_settings = opts[:inactive_settings] || []
|
217
|
+
env.creation_opts = {
|
218
|
+
:template_name => opts[:template_name],
|
219
|
+
:solution_stack => opts[:solution_stack_name],
|
220
|
+
:cname_prefix => opts[:cname_prefix],
|
221
|
+
:smoke_test => opts[:smoke_test],
|
222
|
+
:phoenix_mode => opts[:phoenix_mode],
|
223
|
+
:accepted_healthy_states => opts[:accepted_healthy_states],
|
224
|
+
:blue_green_terminate_inactive => opts[:blue_green_terminate_inactive] || false,
|
225
|
+
:blue_green_terminate_inactive_wait => opts[:blue_green_terminate_inactive_wait] || 600,
|
226
|
+
:blue_green_terminate_inactive_sleep => opts[:blue_green_terminate_inactive_sleep] || 15,
|
227
|
+
:tags => opts[:tags],
|
228
|
+
:tier => opts[:tier]
|
229
|
+
}
|
230
|
+
env.strategy_name = opts[:strategy] || :blue_green
|
231
|
+
env.components = opts[:components]
|
232
|
+
env.component_under_deploy = opts[:component]
|
233
|
+
end
|
234
|
+
|
235
|
+
application.create_version(version_label, opts[:package])
|
236
|
+
environment.deploy(version_label)
|
237
|
+
application.clean_versions(version_prefix, opts[:keep_latest].to_i || 0)
|
238
|
+
end
|
239
|
+
|
240
|
+
def self.destroy(opts)
|
241
|
+
if region = opts[:region]
|
242
|
+
Aws.config.update(:region => region)
|
243
|
+
end
|
244
|
+
|
245
|
+
app = opts[:application]
|
246
|
+
bs = opts[:bs_driver] || AWSDriver::Beanstalk.new
|
247
|
+
s3 = opts[:s3_driver] || AWSDriver::S3Driver.new
|
248
|
+
|
249
|
+
Application.new(app, bs, s3).delete(opts[:environment])
|
250
|
+
end
|
251
|
+
|
252
|
+
def self.cli
|
253
|
+
options = {
|
254
|
+
:action => :deploy,
|
255
|
+
:environment => 'dev',
|
256
|
+
:config_file => 'config/eb_deployer.yml'
|
257
|
+
}
|
258
|
+
|
259
|
+
parser = cli_parser(options)
|
260
|
+
parser.parse!
|
261
|
+
action = options.delete(:action)
|
262
|
+
|
263
|
+
if File.exist?(options[:config_file])
|
264
|
+
puts "Found configuration at #{options[:config_file]}."
|
265
|
+
else
|
266
|
+
puts "Generated default configuration at #{options[:config_file]}."
|
267
|
+
DefaultConfig.new(File.basename(Dir.pwd)).write_to(options[:config_file])
|
268
|
+
exit(2)
|
269
|
+
end
|
270
|
+
|
271
|
+
if !options[:package] && action == :deploy
|
272
|
+
puts "Missing options: -p (--package)"
|
273
|
+
puts "'eb_deploy --help' for details"
|
274
|
+
puts parser
|
275
|
+
exit(-1)
|
276
|
+
end
|
277
|
+
|
278
|
+
self.send(action, ConfigLoader.new.load(options))
|
279
|
+
end
|
280
|
+
|
281
|
+
private
|
282
|
+
|
283
|
+
def self.cli_parser(options)
|
284
|
+
OptionParser.new do |opts|
|
285
|
+
opts.banner = "Usage: eb_deployer [options]"
|
286
|
+
opts.on("-p", "--package [FILE/S3_OBJECT]", "Package to deploy, can be a war file for java application, or yaml specification for package location on S3, or a S3 object & bucket name separated by colon, e.g. bucket_name:key_name") do |v|
|
287
|
+
options[:package] = v
|
288
|
+
end
|
289
|
+
|
290
|
+
opts.on("-e", "--environment [ENV_NAME]", "(Defaults to 'dev') Environment on which to operate (e.g. dev, staging, production). This must be defined in 'environments' section of the config file") do |v|
|
291
|
+
options[:environment] = v
|
292
|
+
end
|
293
|
+
|
294
|
+
opts.on("-c", "--config-file [FILE]", "eb_deployer config file. Default location is config/eb_deployer.yml") do |v|
|
295
|
+
options[:config_file] = v
|
296
|
+
end
|
297
|
+
|
298
|
+
opts.on("-d", "--destroy", "Destroy all Elasticbeanstalk environments under the application which have specified environment as name prefix") do |v|
|
299
|
+
options[:action] = :destroy
|
300
|
+
end
|
301
|
+
|
302
|
+
opts.on("-s", "--stack-name [STACK_NAME]", "CloudFormation stack name to use. If not specified, defaults to {app}-{env_name}") do |v|
|
303
|
+
options[:stack_name] = v
|
304
|
+
end
|
305
|
+
|
306
|
+
opts.on("--skip-resource-stack-update", "Skip cloud-formation stack update. (only for extreme situation like hitting a cloudformation bug)") do |v|
|
307
|
+
options[:skip_resource_stack_update] = true
|
308
|
+
end
|
309
|
+
|
310
|
+
opts.on("--component [COMPONENT]", "Specify which component to deploy") do |v|
|
311
|
+
options[:component] = v
|
312
|
+
end
|
313
|
+
|
314
|
+
opts.on("-v", "--version", "Print current version") do |v|
|
315
|
+
puts "eb_deployer v#{VERSION}"
|
316
|
+
exit(0)
|
317
|
+
end
|
318
|
+
|
319
|
+
opts.on("--debug", "Output AWS debug log") do |d|
|
320
|
+
require 'logger'
|
321
|
+
logger = Logger.new($stdout)
|
322
|
+
logger.level = Logger::DEBUG
|
323
|
+
Aws.config.update(:logger => logger)
|
324
|
+
end
|
325
|
+
|
326
|
+
opts.on("-h", "--help", "help") do
|
327
|
+
puts opts
|
328
|
+
puts ""
|
329
|
+
puts "S3 object package format: s3_bucket_name:s3_object_key"
|
330
|
+
puts "YAML package file format:"
|
331
|
+
puts "s3_bucket: <bucket_name>"
|
332
|
+
puts "s3_key: <object_path>"
|
333
|
+
exit(0)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'eb_deployer/default_config'
|
3
|
+
require 'aws-sdk-elasticbeanstalk'
|
4
|
+
require 'securerandom'
|
5
|
+
|
6
|
+
module EbDeployer
|
7
|
+
module Generators
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
9
|
+
DEFAULT_STACK_NAME = '64bit Amazon Linux 2014.09 v1.1.0 running Ruby 2.1 (Passenger Standalone)'
|
10
|
+
source_root File.expand_path("../templates", __FILE__)
|
11
|
+
|
12
|
+
def do_install
|
13
|
+
in_root do
|
14
|
+
copy_file 'eb_deployer.rake', 'lib/tasks/eb_deployer.rake'
|
15
|
+
template 'eb_deployer.yml.erb', 'config/eb_deployer.yml'
|
16
|
+
setup_database
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def setup_database
|
22
|
+
gem 'pg'
|
23
|
+
setup_database_yml
|
24
|
+
copy_file 'postgres_rds.json', 'config/rds.json'
|
25
|
+
directory 'ebextensions', '.ebextensions'
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup_database_yml
|
29
|
+
gsub_file('config/database.yml', /^production:.+/m) do |match|
|
30
|
+
prod_start = false
|
31
|
+
match.split("\n").map do |l|
|
32
|
+
case l
|
33
|
+
when /^production/
|
34
|
+
prod_start = true
|
35
|
+
"# #{l}"
|
36
|
+
when /^\s+/
|
37
|
+
prod_start ? "# #{l}" : l
|
38
|
+
else
|
39
|
+
prod_start = false
|
40
|
+
l
|
41
|
+
end
|
42
|
+
end.join("\n")
|
43
|
+
end
|
44
|
+
append_to_file('config/database.yml', <<-YAML)
|
45
|
+
|
46
|
+
|
47
|
+
production:
|
48
|
+
adapter: postgresql
|
49
|
+
database: <%= ENV['DATABASE_NAME'] || '#{app_name}_production' %>
|
50
|
+
host: <%= ENV['DATABASE_HOST'] || 'localhost' %>
|
51
|
+
port: <%= ENV['DATABASE_PORT'] || 5432 %>
|
52
|
+
username: <%= ENV['DATABASE_USERNAME'] || #{ENV['USER'].inspect} %>
|
53
|
+
password: <%= ENV['DATABASE_PASSWORD'] %>
|
54
|
+
min_messages: ERROR
|
55
|
+
YAML
|
56
|
+
end
|
57
|
+
def db_password
|
58
|
+
"PleaseChangeMe"
|
59
|
+
end
|
60
|
+
|
61
|
+
def solution_stack_name
|
62
|
+
Aws::ElasticBeanstalk.Client.new.list_available_solution_stacks[:solution_stacks].find do |s|
|
63
|
+
s =~ /Amazon Linux/ && s =~ /running Ruby 2.1 \(Passenger Standalone\)/
|
64
|
+
end
|
65
|
+
rescue
|
66
|
+
DEFAULT_STACK_NAME
|
67
|
+
end
|
68
|
+
|
69
|
+
def alphanumeric_name
|
70
|
+
app_name.gsub(/-/, '')
|
71
|
+
end
|
72
|
+
|
73
|
+
def secure_random(length)
|
74
|
+
SecureRandom.hex(length)
|
75
|
+
end
|
76
|
+
|
77
|
+
def app_name
|
78
|
+
File.basename(Dir.pwd).downcase.gsub(/[^0-9a-z]/, '-').gsub(/--/, '-')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'eb_deployer/tasks'
|