eb_deployer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in eb_deployer.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 tworker
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # EbDeployer
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'eb_deployer'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install eb_deployer
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/eb_deployer/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["wpc", "betarelease"]
6
+ gem.email = ["alex.hal9000@gmail.com", "sudhindra.r.rao@gmail.com"]
7
+ gem.description = %q{Elastic Beanstalk Deployer with different deployment strategies.}
8
+ gem.summary = %q{Pick strategies like InplaceUpdate, Blue/Green.}
9
+ gem.homepage = "https://github.com/ThoughtWorksStudios/eb_deployer"
10
+
11
+ gem.add_runtime_dependency 'aws-sdk'
12
+ gem.add_development_dependency 'minitest'
13
+
14
+ gem.files = `git ls-files`.split($\)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.name = "eb_deployer"
18
+ gem.require_paths = ["lib"]
19
+ gem.version = EbDeployer::VERSION
20
+ end
@@ -0,0 +1,82 @@
1
+ module EbDeployer
2
+ class Beanstalk
3
+ attr_reader :client
4
+ def initialize(client=AWS::ElasticBeanstalk.new.client)
5
+ @client = client
6
+ end
7
+
8
+ def update_environment(app_name, env_name, version, settings)
9
+ env_id = convert_env_name_to_id(app_name, [env_name]).first
10
+ @client.update_environment(:environment_id => env_id,
11
+ :version_label => version,
12
+ :option_settings => settings)
13
+ end
14
+
15
+ def environment_exists?(app_name, env_name)
16
+ alive_envs(app_name, [env_name]).any?
17
+ end
18
+
19
+ def create_environment(app_name, env_name, stack_name, cname_prefix, version, settings)
20
+ request = {:application_name => app_name,
21
+ :environment_name => env_name,
22
+ :solution_stack_name => stack_name,
23
+ :version_label => version,
24
+ :option_settings => settings }
25
+ request[:cname_prefix] = cname_prefix if cname_prefix
26
+ @client.create_environment(request)
27
+ end
28
+
29
+ def create_application_version(app_name, version_label, source_bundle)
30
+ @client.create_application_version(:application_name => app_name,
31
+ :source_bundle => source_bundle,
32
+ :version_label => version_label)
33
+ end
34
+
35
+ def application_version_labels
36
+ @client.describe_application_versions[:application_versions].map { |apv| apv[:version_label] }
37
+ end
38
+
39
+ def fetch_events(app_name, env_name, params, &block)
40
+ response = @client.describe_events(params.merge(:application_name => app_name,
41
+ :environment_name => env_name))
42
+ return [response[:events], response[:next_token]]
43
+ end
44
+
45
+ def environment_cname_prefix(app_name, env_name)
46
+ cname = environment_cname(app_name, env_name)
47
+ if cname =~ /^(.+)\.elasticbeanstalk\.com/
48
+ $1
49
+ end
50
+ end
51
+
52
+ def environment_cname(app_name, env_name)
53
+ env = alive_envs(app_name, [env_name]).first
54
+ env && env[:cname]
55
+ end
56
+
57
+ def environment_health_state(app_name, env_name)
58
+ env = alive_envs(app_name, [env_name]).first
59
+ env && env[:health]
60
+ end
61
+
62
+ def environment_swap_cname(app_name, env1, env2)
63
+ env1_id, env2_id = convert_env_name_to_id(app_name, [env1, env2])
64
+ @client.swap_environment_cnam_es(:source_environment_id => env1_id,
65
+ :destination_environment_id => env2_id)
66
+ end
67
+
68
+ private
69
+
70
+ def convert_env_name_to_id(app_name, env_names)
71
+ envs = alive_envs(app_name, env_names)
72
+ envs.map { |env| env[:environment_id] }
73
+ end
74
+
75
+ def alive_envs(app_name, env_names=[])
76
+ envs = @client.describe_environments(:application_name => app_name, :environment_names => env_names)[:environments]
77
+
78
+ envs.select {|e| e[:status] != 'Terminated' }
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,82 @@
1
+ module EbDeployer
2
+ class CloudFormationProvisioner
3
+ SUCCESS_STATS = [:create_complete, :update_complete, :update_rollback_complete]
4
+ FAILED_STATS = [:create_failed, :update_failed]
5
+
6
+ def initialize(stack_name)
7
+ @stack_name = stack_name
8
+ end
9
+
10
+ def provision(resources)
11
+ params = extract_params
12
+ template = File.read(resources[:template])
13
+ transforms = resources[:transforms]
14
+
15
+ stack.exists? ? update_stack(template, params) : create_stack(template, params)
16
+ wait_for_stack_op_terminate
17
+ transform_output_to_settings(transforms)
18
+ end
19
+
20
+ private
21
+
22
+ def update_stack(template, params)
23
+ begin
24
+ stack.update(:template => template, :parameters => params)
25
+ rescue AWS::CloudFormation::Errors::ValidationError => e
26
+ if e.message =~ /No updates are to be performed/
27
+ log(e.message)
28
+ else
29
+ raise
30
+ end
31
+ end
32
+ end
33
+
34
+ def create_stack(template, params)
35
+ cloud_formation.stacks.create(@stack_name, template, {
36
+ :disable_rollback => true,
37
+ :parameters => params
38
+ })
39
+ end
40
+
41
+ def transform_output_to_settings(transforms)
42
+ (transforms || []).inject([]) do |settings, pair|
43
+ key, transform = pair
44
+ settings << transform.call(output(key))
45
+ settings
46
+ end.flatten
47
+ end
48
+
49
+ def wait_for_stack_op_terminate
50
+ begin
51
+ sleep 15
52
+ stats = stack_status
53
+ raise "Resource stack update failed!" if FAILED_STATS.include?(stats)
54
+ log "current status: #{stack_status}"
55
+ end while !SUCCESS_STATS.include?(stats)
56
+ end
57
+
58
+ def output(key)
59
+ stack.outputs.find { |o| o.key == key }.try(:value)
60
+ end
61
+
62
+ def extract_params
63
+ Hash[ENV.map {|k, v| k =~ /^AWSRESOURCES_(.*)/ ? [$1, v] : nil }.compact]
64
+ end
65
+
66
+ def log(msg)
67
+ puts "[#{Time.now.utc}][resources-stack] #{msg}"
68
+ end
69
+
70
+ def stack_status
71
+ stack.status.downcase.to_sym
72
+ end
73
+
74
+ def stack
75
+ cloud_formation.stacks[@stack_name]
76
+ end
77
+
78
+ def cloud_formation
79
+ AWS::CloudFormation.new
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,74 @@
1
+ module EbDeployer
2
+ module DeploymentStrategy
3
+ class InplaceUpdate
4
+ def initialize(app, env_name, eb_driver, env_creation_opts)
5
+ @app = app
6
+ @env_name = env_name
7
+ @eb_driver = eb_driver
8
+ @env_creation_opts = env_creation_opts
9
+ end
10
+
11
+ def deploy(version_label, env_settings)
12
+ Environment.new(@app, @env_name, @eb_driver, @env_creation_opts).
13
+ deploy(version_label, env_settings)
14
+ end
15
+ end
16
+
17
+ class BlueGreen
18
+ def initialize(app, env_name, eb_driver, env_creation_opts)
19
+ @app = app
20
+ @env_name = env_name
21
+ @eb_driver = eb_driver
22
+ @major_cname_prefix = env_creation_opts[:cname_prefix]
23
+ @solution_stack = env_creation_opts[:solution_stack]
24
+ @smoke_test = env_creation_opts[:smoke_test]
25
+ end
26
+
27
+ def deploy(version_label, env_settings)
28
+ if !envs.any?(&method(:active_env?))
29
+ env('blue', @major_cname_prefix).
30
+ deploy(version_label, env_settings)
31
+ return
32
+ end
33
+
34
+ active_env = envs.detect(&method(:active_env?))
35
+ inactive_env = envs.reject(&method(:active_env?)).first
36
+
37
+ inactive_env.deploy(version_label, env_settings)
38
+ active_env.swap_cname_with(inactive_env)
39
+ end
40
+
41
+ private
42
+ def active_env?(env)
43
+ env.cname_prefix == @major_cname_prefix
44
+ end
45
+
46
+ def envs
47
+ [env('blue'), env('green')]
48
+ end
49
+
50
+ def env(color, cname_prefix=nil)
51
+ Environment.new(@app, @env_name + '-' + color, @eb_driver,
52
+ :solution_stack => @solution_stack,
53
+ :cname_prefix => cname_prefix || inactive_cname_prefix,
54
+ :smoke_test => @smoke_test)
55
+ end
56
+
57
+ def inactive_cname_prefix
58
+ "#{@app}-#{@env_name}-inactive"
59
+ end
60
+ end
61
+
62
+ def self.create(strategy_name, app, env_name, eb_driver, env_creation_opts={})
63
+ case strategy_name.to_sym
64
+ when :inplace_update
65
+ InplaceUpdate.new(app, env_name, eb_driver, env_creation_opts)
66
+ when :blue_green
67
+ BlueGreen.new(app, env_name, eb_driver, env_creation_opts)
68
+ else
69
+ raise 'strategy_name:' + strategy_name + ' not supported'
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,82 @@
1
+ module EbDeployer
2
+ class Environment
3
+ attr_reader :app, :name
4
+ def initialize(app, env_name, eb_driver, creation_opts={})
5
+ @app = app
6
+ @name = env_name
7
+ @bs = eb_driver
8
+ @creation_opts = creation_opts
9
+ @poller = EventPoller.new(@app, @name, @bs)
10
+ end
11
+
12
+ def deploy(version_label, settings)
13
+ create_or_update_env(version_label, settings)
14
+ poll_events
15
+ smoke_test
16
+ wait_for_env_become_healthy
17
+ end
18
+
19
+ def cname_prefix
20
+ @bs.environment_cname_prefix(@app, @name)
21
+ end
22
+
23
+ def ==(another)
24
+ self.app == another.app && self.name == another.name
25
+ end
26
+
27
+ def swap_cname_with(another)
28
+ @bs.environment_swap_cname(self.app, self.name, another.name)
29
+ end
30
+
31
+ private
32
+
33
+ def create_or_update_env(version_label, settings)
34
+ if @bs.environment_exists?(@app, @name)
35
+ @bs.update_environment(@app, @name, version_label, settings)
36
+ else
37
+ @bs.create_environment(@app, @name, @creation_opts[:solution_stack], @creation_opts[:cname_prefix], version_label, settings)
38
+ end
39
+ end
40
+
41
+ def smoke_test
42
+ if smoke = @creation_opts[:smoke_test]
43
+ host = @bs.environment_cname(@app, @name)
44
+ log("running smoke test for #{host}...")
45
+ smoke.call(host)
46
+ log("smoke test succeeded.")
47
+ end
48
+ end
49
+
50
+ def poll_events
51
+ @poller.poll do |event|
52
+ raise event[:message] if event[:message] =~ /Failed to deploy application/
53
+
54
+ log_event(event)
55
+ break if event[:message] =~ /Environment update completed successfully/ ||
56
+ event[:message] =~ /Successfully launched environment/
57
+ end
58
+ end
59
+
60
+ def wait_for_env_become_healthy
61
+ Timeout.timeout(600) do
62
+ current_health_status = @bs.environment_health_state(@app, @name)
63
+
64
+ while current_health_status != 'Green'
65
+ log("health status: #{current_health_status}")
66
+ sleep 15
67
+ current_health_status = @bs.environment_health_state(@app, @name)
68
+ end
69
+
70
+ log("health status: #{current_health_status}")
71
+ end
72
+ end
73
+
74
+ def log(msg)
75
+ puts "[#{Time.now.utc}][beanstalk-#{@name}] #{msg}"
76
+ end
77
+
78
+ def log_event(event)
79
+ puts "[#{event[:event_date]}][beanstalk-#{@name}] #{event[:message]}"
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,41 @@
1
+ module EbDeployer
2
+ class EventPoller
3
+ def initialize(app, env, beanstalk)
4
+ @app, @env, @beanstalk, @start_time = app, env, beanstalk, Time.now
5
+ end
6
+
7
+ def poll(&block)
8
+ handled = Set.new
9
+ loop do
10
+ fetch_events do |events|
11
+ new_events = events.reject { |e| handled.include?(digest(e)) }
12
+ handle(new_events, &block)
13
+ handled += new_events.map { |e| digest(e) }
14
+ end
15
+ sleep 15
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def digest(event)
22
+ JSON.dump(event)
23
+ end
24
+
25
+ def handle(events, &block)
26
+ events.reverse.each(&block)
27
+ end
28
+
29
+ def fetch_events(&block)
30
+ events, next_token = @beanstalk.fetch_events(@app, @env, :start_time => @start_time.iso8601)
31
+ yield(events)
32
+ fetch_next(next_token, &block) if next_token
33
+ end
34
+
35
+ def fetch_next(next_token, &block)
36
+ events, next_token = @beanstalk.fetch_events(@app, @env, :next_token => next_token)
37
+ yield(events)
38
+ fetch_next(next_token, &block) if next_token
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
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
+ @s3.upload_file(@bucket_name, s3_path, file)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module EbDeployer
2
+ class S3Driver
3
+ def create_bucket(bucket_name)
4
+ buckets.create(bucket_name)
5
+ end
6
+
7
+ def bucket_exists?(bucket_name)
8
+ buckets[bucket_name].exists?
9
+ end
10
+
11
+ def object_length(bucket_name, obj_name)
12
+ obj(bucket_name, obj_name).content_length rescue nil
13
+ end
14
+
15
+ def upload_file(bucket_name, obj_name, file)
16
+ o = obj(bucket_name, obj_name)
17
+ File.open(file) { |f| o.write(f) }
18
+ end
19
+
20
+ private
21
+ def s3
22
+ AWS::S3.new
23
+ end
24
+
25
+ def obj(bucket_name, obj_name)
26
+ buckets[bucket_name].objects[obj_name]
27
+ end
28
+
29
+ def buckets
30
+ s3.buckets
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ module EbDeployer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,54 @@
1
+ require "eb_deployer/version"
2
+ require "eb_deployer/deployment_strategy"
3
+ require "eb_deployer/beanstalk"
4
+ require "eb_deployer/cloud_formation_provisioner"
5
+ require "eb_deployer/environment"
6
+ require "eb_deployer/event_poller"
7
+ require "eb_deployer/package"
8
+ require 'eb_deployer/s3_driver'
9
+ require 'digest'
10
+ require 'set'
11
+ require 'time'
12
+ require 'json'
13
+ require 'timeout'
14
+
15
+ module EbDeployer
16
+ def self.deploy(opts)
17
+ # AWS.config(:logger => Logger.new($stdout))
18
+ if region = opts[:region]
19
+ AWS.config(:region => region)
20
+ end
21
+
22
+ bs = opts[:bs_driver] || Beanstalk.new
23
+ s3 = opts[:s3_driver] || S3Driver.new
24
+ stack_name = opts[:solution_stack_name] || "64bit Amazon Linux running Tomcat 7"
25
+ app = opts[:application]
26
+ env_name = opts[:environment]
27
+ version_label = opts[:version_label].to_s.strip
28
+ cname = opts[:cname]
29
+ env_settings = opts[:settings] || []
30
+ strategy_name = opts[:strategy] || :inplace_update
31
+ cname_prefix = opts[:cname_prefix] || [app, env_name].join('-')
32
+ smoke_test = opts[:smoke_test] || Proc.new {}
33
+
34
+ package = Package.new(opts[:package], app + "-packages", s3)
35
+ cf = CloudFormationProvisioner.new("#{app}-#{env_name}")
36
+ strategy = DeploymentStrategy.create(strategy_name, app, env_name, bs,
37
+ :solution_stack => stack_name,
38
+ :cname_prefix => cname_prefix,
39
+ :smoke_test => smoke_test)
40
+
41
+ if resources = opts[:resources]
42
+ env_settings += cf.provision(resources)
43
+ end
44
+
45
+ package.upload
46
+
47
+ unless bs.application_version_labels.include?(version_label)
48
+ bs.create_application_version(app, version_label, package.source_bundle)
49
+ end
50
+
51
+ strategy.deploy(version_label, env_settings)
52
+ end
53
+
54
+ end
@@ -0,0 +1,93 @@
1
+ class EBStub
2
+ def initialize
3
+ @envs = {}
4
+ @versions = {}
5
+ end
6
+
7
+ def create_environment(app, env, solution_stack, cname_prefix, version, settings)
8
+ raise 'cname prefix is not avaible' if @envs.values.detect { |env| env[:cname_prefix] == cname_prefix }
9
+ @envs[env_key(app, env)] = {
10
+ :solution_stack => solution_stack,
11
+ :version => version,
12
+ :cname_prefix => cname_prefix,
13
+ :settings => settings}
14
+ end
15
+
16
+ def update_environment(app, env, version, settings)
17
+ @envs[env_key(app, env)].merge!(:version => version, :settings => settings)
18
+ end
19
+
20
+ def environment_exists?(app_name, env_name)
21
+ @envs.has_key?(env_key(app_name, env_name))
22
+ end
23
+
24
+ def create_application_version(app_name, version_label, source_bundle)
25
+ @versions[app_name] ||= []
26
+ @versions[app_name] = { version_label => source_bundle }
27
+ end
28
+
29
+ def application_version_labels
30
+ @versions.values.map(&:keys).flatten
31
+ end
32
+
33
+ def fetch_events(app_name, env_name, options={})
34
+ [[{:event_date => Time.now.utc,
35
+ :message => 'Environment update completed successfully'}],
36
+ nil]
37
+ end
38
+
39
+ def environment_cname_prefix(app_name, env_name)
40
+ return unless @envs[env_key(app_name, env_name)]
41
+ @envs[env_key(app_name, env_name)][:cname_prefix] || app_name + "-" + SecureRandom.hex
42
+ end
43
+
44
+ def environment_cname(app_name, env_name)
45
+ return unless @envs[env_key(app_name, env_name)]
46
+ environment_cname_prefix(app_name, env_name) + ".elasticbeanstalk.com"
47
+ end
48
+
49
+
50
+ def environment_swap_cname(app_name, env1_name, env2_name)
51
+ env1, env2 = @envs[env_key(app_name, env1_name)], @envs[env_key(app_name, env2_name)]
52
+ temp = env1[:cname_prefix]
53
+ env1[:cname_prefix] = env2[:cname_prefix]
54
+ env2[:cname_prefix] = temp
55
+ end
56
+
57
+ def environment_health_state(app_name, env_name)
58
+ 'Green'
59
+ end
60
+
61
+ #test only
62
+ def environment_verion_label(app_name, env_name)
63
+ @envs[env_key(app_name, env_name)][:version]
64
+ end
65
+
66
+ private
67
+ def env_key(app, name)
68
+ [app, name].join("-")
69
+ end
70
+
71
+ end
72
+
73
+ class S3Stub
74
+ def initialize
75
+ @buckets = {}
76
+ end
77
+
78
+ def create_bucket(bucket_name)
79
+ @buckets[bucket_name] = {}
80
+ end
81
+
82
+ def bucket_exists?(bucket_name)
83
+ @buckets.has_key?(bucket_name)
84
+ end
85
+
86
+ def object_length(bucket_name, obj_name)
87
+ @buckets[bucket_name][obj_name] && File.size(@buckets[bucket_name][obj_name])
88
+ end
89
+
90
+ def upload_file(bucket_name, obj_name, file)
91
+ @buckets[bucket_name][obj_name] = file
92
+ end
93
+ end
@@ -0,0 +1,147 @@
1
+ require 'eb_deployer'
2
+ require 'aws_driver_stubs'
3
+ require 'minitest/autorun'
4
+
5
+ class DeployTest < Minitest::Test
6
+ def setup
7
+ @eb_driver = EBStub.new
8
+ @s3_driver = S3Stub.new
9
+ @sample_package = '/tmp/app-package.war'
10
+ File.open(@sample_package, 'w') { |f| f << 's' * 100 }
11
+ end
12
+
13
+ def deploy(opts)
14
+ EbDeployer.deploy({:package => @sample_package,
15
+ :bs_driver => @eb_driver,
16
+ :s3_driver => @s3_driver,
17
+ :version_label => 1}.merge(opts))
18
+ end
19
+
20
+
21
+ def test_first_deployment_create_environment
22
+ assert !@eb_driver.environment_exists?('simple', 'production')
23
+ deploy(:application => 'simple', :environment => "production")
24
+ assert @eb_driver.environment_exists?('simple', 'production')
25
+ end
26
+
27
+
28
+ def test_update_environment_with_new_version_should_change_version_that_deployed
29
+ deploy(:application => 'simple',
30
+ :environment => "production",
31
+ :version_label => 1)
32
+ assert_equal '1', @eb_driver.environment_verion_label('simple', 'production')
33
+
34
+ deploy(:application => 'simple',
35
+ :environment => "production",
36
+ :version_label => 2)
37
+
38
+ assert_equal '2', @eb_driver.environment_verion_label('simple', 'production')
39
+ end
40
+
41
+ def test_default_cname_that_deployed_should_app_env_name
42
+ deploy(:application => 'simple',
43
+ :environment => "production",
44
+ :version_label => 42)
45
+ assert_equal "simple-production", @eb_driver.environment_cname_prefix('simple', 'production')
46
+ end
47
+
48
+ def test_cname_prefix_can_be_override
49
+ deploy(:application => 'simple',
50
+ :environment => "production",
51
+ :cname_prefix => 'sports123',
52
+ :version_label => 42)
53
+ assert_equal "sports123", @eb_driver.environment_cname_prefix('simple', 'production')
54
+ end
55
+
56
+
57
+ def test_smoke_test_should_be_run_after_env_created_or_update
58
+ host_for_smoke_test = nil
59
+ deploy(:application => 'simple',
60
+ :environment => "production",
61
+ :cname_prefix => 'foobar',
62
+ :smoke_test => lambda { |host| host_for_smoke_test = host },
63
+ :version_label => 42)
64
+ assert_equal 'foobar.elasticbeanstalk.com', host_for_smoke_test
65
+
66
+ host_for_smoke_test = nil
67
+ deploy(:application => 'simple',
68
+ :environment => "production",
69
+ :cname_prefix => 'foobar',
70
+ :smoke_test => lambda { |host| host_for_smoke_test = host },
71
+ :version_label => 43)
72
+
73
+ assert_equal 'foobar.elasticbeanstalk.com', host_for_smoke_test
74
+ end
75
+
76
+
77
+
78
+ def test_blue_green_deployment_strategy_should_create_blue_env_on_first_deployment
79
+ deploy(:application => 'simple',
80
+ :environment => "production",
81
+ :strategy => 'blue_green',
82
+ :version_label => 42)
83
+
84
+ assert @eb_driver.environment_exists?('simple', 'production-blue')
85
+ assert_equal 'simple-production', @eb_driver.environment_cname_prefix('simple', 'production-blue')
86
+ end
87
+
88
+
89
+ def test_blue_green_deployment_should_create_green_env_if_blue_exists
90
+ deploy(:application => 'simple',
91
+ :environment => "production",
92
+ :strategy => 'blue_green',
93
+ :version_label => 42)
94
+
95
+ deploy(:application => 'simple',
96
+ :environment => "production",
97
+ :strategy => 'blue_green',
98
+ :version_label => 43)
99
+
100
+ assert @eb_driver.environment_exists?('simple', 'production-blue')
101
+ assert @eb_driver.environment_exists?('simple', 'production-green')
102
+ end
103
+
104
+
105
+ def test_blue_green_deployment_should_swap_cname_to_make_active_most_recent_updated_env
106
+ deploy(:application => 'simple',
107
+ :environment => "production",
108
+ :strategy => 'blue_green',
109
+ :version_label => 42)
110
+
111
+ deploy(:application => 'simple',
112
+ :environment => "production",
113
+ :strategy => 'blue_green',
114
+ :version_label => 43)
115
+
116
+ assert_match(/simple-production-inactive/, @eb_driver.environment_cname_prefix('simple', 'production-blue'))
117
+
118
+ assert_equal 'simple-production', @eb_driver.environment_cname_prefix('simple', 'production-green')
119
+
120
+
121
+ deploy(:application => 'simple',
122
+ :environment => "production",
123
+ :strategy => 'blue_green',
124
+ :version_label => 44)
125
+
126
+ assert_match(/simple-production-inactive/, @eb_driver.environment_cname_prefix('simple', 'production-green'))
127
+
128
+ assert_equal 'simple-production', @eb_driver.environment_cname_prefix('simple', 'production-blue')
129
+ end
130
+
131
+
132
+ def test_blue_green_deploy_should_run_smoke_test_before_cname_switch
133
+ smoked_host = []
134
+ smoke_test = lambda { |host| smoked_host << host }
135
+ [42, 43, 44].each do |version_label|
136
+ deploy(:application => 'simple',
137
+ :environment => "production",
138
+ :strategy => 'blue_green',
139
+ :smoke_test => smoke_test,
140
+ :version_label => version_label)
141
+ end
142
+
143
+ assert_equal ['simple-production.elasticbeanstalk.com',
144
+ 'simple-production-inactive.elasticbeanstalk.com',
145
+ 'simple-production-inactive.elasticbeanstalk.com'], smoked_host
146
+ end
147
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: eb_deployer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - wpc
9
+ - betarelease
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2013-06-12 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: aws-sdk
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: minitest
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: Elastic Beanstalk Deployer with different deployment strategies.
48
+ email:
49
+ - alex.hal9000@gmail.com
50
+ - sudhindra.r.rao@gmail.com
51
+ executables: []
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - .gitignore
56
+ - Gemfile
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - eb_deployer.gemspec
61
+ - lib/eb_deployer.rb
62
+ - lib/eb_deployer/beanstalk.rb
63
+ - lib/eb_deployer/cloud_formation_provisioner.rb
64
+ - lib/eb_deployer/deployment_strategy.rb
65
+ - lib/eb_deployer/environment.rb
66
+ - lib/eb_deployer/event_poller.rb
67
+ - lib/eb_deployer/package.rb
68
+ - lib/eb_deployer/s3_driver.rb
69
+ - lib/eb_deployer/version.rb
70
+ - test/aws_driver_stubs.rb
71
+ - test/deploy_test.rb
72
+ homepage: https://github.com/ThoughtWorksStudios/eb_deployer
73
+ licenses: []
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirements: []
91
+ rubyforge_project:
92
+ rubygems_version: 1.8.24
93
+ signing_key:
94
+ specification_version: 3
95
+ summary: Pick strategies like InplaceUpdate, Blue/Green.
96
+ test_files:
97
+ - test/aws_driver_stubs.rb
98
+ - test/deploy_test.rb