eb_deployer 0.0.1

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