eb_deployer_updated 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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +14 -0
  6. data/CHANGELOG.md +127 -0
  7. data/Gemfile +10 -0
  8. data/LICENSE +22 -0
  9. data/README.md +136 -0
  10. data/Rakefile +12 -0
  11. data/TODOS.md +11 -0
  12. data/bin/eb_deploy +13 -0
  13. data/eb_deployer.gemspec +22 -0
  14. data/lib/eb_deployer/application.rb +96 -0
  15. data/lib/eb_deployer/aws_driver/beanstalk.rb +158 -0
  16. data/lib/eb_deployer/aws_driver/cloud_formation_driver.rb +53 -0
  17. data/lib/eb_deployer/aws_driver/s3_driver.rb +35 -0
  18. data/lib/eb_deployer/aws_driver.rb +8 -0
  19. data/lib/eb_deployer/cf_event_source.rb +26 -0
  20. data/lib/eb_deployer/cloud_formation_provisioner.rb +120 -0
  21. data/lib/eb_deployer/component.rb +45 -0
  22. data/lib/eb_deployer/config_loader.rb +64 -0
  23. data/lib/eb_deployer/default_component.rb +32 -0
  24. data/lib/eb_deployer/default_config.rb +20 -0
  25. data/lib/eb_deployer/default_config.yml +159 -0
  26. data/lib/eb_deployer/deployment_strategy/blue_green.rb +79 -0
  27. data/lib/eb_deployer/deployment_strategy/blue_only.rb +45 -0
  28. data/lib/eb_deployer/deployment_strategy/inplace_update.rb +16 -0
  29. data/lib/eb_deployer/deployment_strategy.rb +20 -0
  30. data/lib/eb_deployer/eb_environment.rb +204 -0
  31. data/lib/eb_deployer/eb_event_source.rb +35 -0
  32. data/lib/eb_deployer/environment.rb +60 -0
  33. data/lib/eb_deployer/event_poller.rb +51 -0
  34. data/lib/eb_deployer/package.rb +39 -0
  35. data/lib/eb_deployer/resource_stacks.rb +20 -0
  36. data/lib/eb_deployer/smoke_test.rb +23 -0
  37. data/lib/eb_deployer/tasks.rb +45 -0
  38. data/lib/eb_deployer/throttling_handling.rb +17 -0
  39. data/lib/eb_deployer/utils.rb +33 -0
  40. data/lib/eb_deployer/version.rb +3 -0
  41. data/lib/eb_deployer/version_cleaner.rb +30 -0
  42. data/lib/eb_deployer.rb +339 -0
  43. data/lib/generators/eb_deployer/install/install_generator.rb +82 -0
  44. data/lib/generators/eb_deployer/install/templates/eb_deployer.rake +1 -0
  45. data/lib/generators/eb_deployer/install/templates/eb_deployer.yml.erb +181 -0
  46. data/lib/generators/eb_deployer/install/templates/ebextensions/01_postgres_packages.config +5 -0
  47. data/lib/generators/eb_deployer/install/templates/postgres_rds.json +88 -0
  48. data/test/aws_driver_stubs.rb +350 -0
  49. data/test/beanstalk_test.rb +23 -0
  50. data/test/blue_green_deploy_test.rb +114 -0
  51. data/test/blue_only_deploy_test.rb +78 -0
  52. data/test/cf_event_poller_test.rb +32 -0
  53. data/test/cloud_formation_provisioner_test.rb +47 -0
  54. data/test/config_loader_test.rb +205 -0
  55. data/test/deploy_test.rb +42 -0
  56. data/test/eb_environment_test.rb +120 -0
  57. data/test/eb_event_poller_test.rb +32 -0
  58. data/test/inplace_update_deploy_test.rb +110 -0
  59. data/test/multi_components_deploy_test.rb +164 -0
  60. data/test/rails_generators_test.rb +67 -0
  61. data/test/resources_deploy_test.rb +191 -0
  62. data/test/smoke_test_test.rb +23 -0
  63. data/test/template_deploy_test.rb +13 -0
  64. data/test/test_helper.rb +68 -0
  65. data/test/tier_setting_deploy_test.rb +24 -0
  66. data/test/versions_deploy_test.rb +120 -0
  67. metadata +194 -0
@@ -0,0 +1,53 @@
1
+ module EbDeployer
2
+ module AWSDriver
3
+ class CloudFormationDriver
4
+
5
+ def initialize
6
+ @client = Aws::CloudFormation::Client.new
7
+ end
8
+
9
+ def stack_exists?(name)
10
+ describe_stack(name)
11
+ true
12
+ rescue Aws::CloudFormation::Errors::ValidationError
13
+ false
14
+ end
15
+
16
+ def create_stack(name, template, opts)
17
+ @client.create_stack(opts.merge(:stack_name => name,
18
+ :template_body => template,
19
+ :parameters => convert_parameters(opts[:parameters])))
20
+ end
21
+
22
+ def update_stack(name, template, opts)
23
+ @client.update_stack(opts.merge(:stack_name => name,
24
+ :template_body => template,
25
+ :parameters => convert_parameters(opts[:parameters])))
26
+ end
27
+
28
+ def query_output(name, key)
29
+ output = describe_stack(name)[:outputs].find { |o| o[:output_key] == key }
30
+ output && output[:output_value]
31
+ end
32
+
33
+ def fetch_events(name, options={})
34
+ response = @client.describe_stack_events(options.merge(:stack_name => name))
35
+ return response.stack_events, response.next_token
36
+ end
37
+
38
+ private
39
+
40
+ def describe_stack(name)
41
+ @client.describe_stacks(:stack_name => name)[:stacks].first
42
+ end
43
+
44
+ def log(msg)
45
+ puts "[#{Time.now.utc}][cloud_formation_driver] #{msg}"
46
+ end
47
+
48
+ def convert_parameters(params)
49
+ params.map { |k, v| {:parameter_key => k, :parameter_value => v}}
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ module EbDeployer
2
+ module AWSDriver
3
+ class S3Driver
4
+ def create_bucket(bucket_name)
5
+ s3.create_bucket(:bucket => bucket_name)
6
+ end
7
+
8
+ def bucket_exists?(bucket_name)
9
+ s3.bucket(bucket_name).exists?
10
+ end
11
+
12
+ def object_length(bucket_name, obj_name)
13
+ obj(bucket_name, obj_name).content_length rescue nil
14
+ end
15
+
16
+ def upload_file(bucket_name, obj_name, file)
17
+ o = obj(bucket_name, obj_name)
18
+ o.upload_file(file)
19
+ end
20
+
21
+ private
22
+ def s3
23
+ Aws::S3::Resource.new(client: Aws::S3::Client.new)
24
+ end
25
+
26
+ def obj(bucket_name, obj_name)
27
+ s3.bucket(bucket_name).object(obj_name)
28
+ end
29
+
30
+ def buckets
31
+ s3.buckets
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ require 'eb_deployer/aws_driver/s3_driver'
2
+ require 'eb_deployer/aws_driver/beanstalk'
3
+ require 'eb_deployer/aws_driver/cloud_formation_driver'
4
+
5
+ module EbDeployer
6
+ module AWSDriver
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ module EbDeployer
2
+ class CfEventSource
3
+ def initialize(stack_name, cf_driver)
4
+ @stack_name = stack_name
5
+ @cf_driver = cf_driver
6
+ end
7
+
8
+ def get_anchor
9
+ events, _ = @cf_driver.fetch_events(@stack_name)
10
+ events.first
11
+ end
12
+
13
+ def fetch_events(from_anchor, &block)
14
+ events, next_token = @cf_driver.fetch_events(@stack_name)
15
+ should_continue = yield(events)
16
+ fetch_next(next_token, &block) if next_token && should_continue
17
+ end
18
+
19
+ private
20
+ def fetch_next(next_token, &block)
21
+ events, next_token = @cf_driver.fetch_events(@stack_name, :next_token => next_token)
22
+ should_continue = yield(events)
23
+ fetch_next(next_token, &block) if next_token && should_continue
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,120 @@
1
+ module EbDeployer
2
+ class ResourceNotInReadyState < StandardError
3
+ end
4
+
5
+ class CloudFormationProvisioner
6
+ SUCCESS_STATS = ["CREATE_COMPLETE", "UPDATE_COMPLETE"]
7
+ FAILED_STATS = ["CREATE_FAILED", "UPDATE_FAILED", "UPDATE_ROLLBACK_COMPLETE"]
8
+
9
+ def initialize(stack_name, cf_driver)
10
+ @stack_name = stack_name
11
+ @cf_driver = cf_driver
12
+ @poller = EventPoller.new(CfEventSource.new(@stack_name, @cf_driver))
13
+ end
14
+
15
+ def provision(resources, tags)
16
+ resources = symbolize_keys(resources)
17
+ template = File.read(resources[:template])
18
+ capabilities = resources[:capabilities] || []
19
+ params = resources[:inputs] || resources[:parameters] || {}
20
+ policy = File.read(resources[:policy]) if resources[:policy]
21
+ override_policy = resources[:override_policy] || false
22
+ anchor = nil
23
+ begin
24
+ if stack_exists?
25
+ anchor = @poller.get_anchor
26
+ update_stack(template, params, capabilities, policy, override_policy, tags)
27
+ else
28
+ create_stack(template, params, capabilities, policy, tags)
29
+ end
30
+ rescue Aws::CloudFormation::Errors::ValidationError => e
31
+ if e.message =~ /No updates are to be performed/
32
+ log(e.message)
33
+ return
34
+ else
35
+ raise
36
+ end
37
+ end
38
+ wait_for_stack_op_terminate(anchor)
39
+ log("Resource stack provisioned successfully")
40
+ end
41
+
42
+ def transform_outputs(resources)
43
+ resources = symbolize_keys(resources)
44
+ outputs = resources[:outputs] || {}
45
+ transforms = resources[:transforms] || {}
46
+ transform_output_to_settings(convert_to_transforms(outputs).merge(transforms))
47
+ end
48
+
49
+ def output(key)
50
+ @cf_driver.query_output(@stack_name, key)
51
+ rescue Aws::CloudFormation::Errors::ValidationError
52
+ raise ResourceNotInReadyState.new("Resource stack not in ready state yet, perhaps you should provision it first?")
53
+ end
54
+
55
+ private
56
+
57
+ #todo: remove duplication
58
+ def symbolize_keys(hash)
59
+ hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
60
+ end
61
+
62
+
63
+ def convert_to_transforms(outputs)
64
+ outputs.inject({}) do |memo, (key, value)|
65
+ memo[key] = lambda { |output_value| value.merge('value' => output_value) }
66
+ memo
67
+ end
68
+ end
69
+
70
+ def update_stack(template, params, capabilities, policy, override_policy, tags)
71
+ opts = {:capabilities => capabilities, :parameters => params, :tags => tags}
72
+ if (policy)
73
+ opts[:stack_policy_during_update_body] = policy if override_policy
74
+ log("Using temporary stack policy to apply resource stack updates") if override_policy
75
+ opts[:stack_policy_body] = policy unless override_policy
76
+ log("Applying new stack policy to existing resource stack") unless override_policy
77
+ end
78
+ @cf_driver.update_stack(@stack_name, template, opts)
79
+ end
80
+
81
+ def stack_exists?
82
+ @cf_driver.stack_exists?(@stack_name)
83
+ end
84
+
85
+ def create_stack(template, params, capabilities, policy, tags)
86
+ opts = {:disable_rollback => true, :capabilities => capabilities, :parameters => params, :tags => tags}
87
+ opts[:stack_policy_body] = policy if policy
88
+ log("Applying stack policy to new resource stack") if policy
89
+ @cf_driver.create_stack(@stack_name, template, opts)
90
+ end
91
+
92
+ def transform_output_to_settings(transforms)
93
+ (transforms || []).inject([]) do |settings, pair|
94
+ key, transform = pair
95
+ settings << transform.call(output(key))
96
+ settings
97
+ end.flatten
98
+ end
99
+
100
+ def wait_for_stack_op_terminate(anchor)
101
+ @poller.poll(anchor) do |event|
102
+ log_event(event)
103
+ if FAILED_STATS.include?(event.resource_status)
104
+ raise "Resource stack update failed!"
105
+ end
106
+
107
+ break if event.logical_resource_id == @stack_name && SUCCESS_STATS.include?(event.resource_status)
108
+ end
109
+ end
110
+
111
+ def log_event(event)
112
+ puts "[#{event.timestamp}][cloud_formation_provisioner] #{event.resource_type}(#{event.logical_resource_id}) #{event.resource_status} \"#{event.resource_status_reason}\""
113
+ end
114
+
115
+
116
+ def log(msg)
117
+ puts "[#{Time.now.utc}][cloud_formation_provisioner] #{msg}"
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,45 @@
1
+ module EbDeployer
2
+ class Component
3
+ attr_reader :name
4
+
5
+ def initialize(name, env, options, eb_driver)
6
+ @name = name
7
+ @env = env
8
+ @eb_driver = eb_driver
9
+ @options = options.dup
10
+ @component_eb_settings = @options.delete(:option_settings) || []
11
+ @component_inactive_settings = @options.delete(:inactive_settings) || []
12
+ strategy_name = @options[:strategy] || @env.strategy_name
13
+ @strategy = DeploymentStrategy.create(self, strategy_name)
14
+ end
15
+
16
+ def cname_prefix
17
+ @options[:cname_prefix] || default_cname_prefix
18
+ end
19
+
20
+ def deploy(version_label, eb_settings, inactive_settings=[])
21
+ @strategy.test_compatibility(create_options)
22
+ @strategy.deploy(version_label,
23
+ eb_settings + @component_eb_settings,
24
+ inactive_settings + @component_inactive_settings)
25
+ end
26
+
27
+ def new_eb_env(suffix=nil, cname_prefix_overriding=nil)
28
+ EbEnvironment.new(@env.app_name,
29
+ [@env.name, @name, suffix].compact.join('-'),
30
+ @eb_driver,
31
+ create_options.merge(:cname_prefix => cname_prefix_overriding || cname_prefix))
32
+
33
+ end
34
+
35
+ private
36
+
37
+ def create_options
38
+ @env.creation_opts.merge(@options)
39
+ end
40
+
41
+ def default_cname_prefix
42
+ [@env.app_name, @env.name, @name].join('-')
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,64 @@
1
+ require 'securerandom'
2
+ require 'digest'
3
+ require 'yaml'
4
+
5
+ module EbDeployer
6
+ class ConfigLoader
7
+ include Utils
8
+
9
+ class EvalBinding
10
+ attr_reader :environment, :package_digest
11
+ def initialize(package_digest, env)
12
+ @package_digest = package_digest
13
+ @environment = env
14
+ end
15
+
16
+ def random_hash
17
+ SecureRandom.hex[0..9]
18
+ end
19
+ end
20
+
21
+ def load(options)
22
+ options = options.dup
23
+ package_digest = package_digest(options[:package])
24
+ config_file = options.delete(:config_file)
25
+
26
+ env = options[:environment]
27
+ config_settings = load_config_settings(config_file, package_digest, env)
28
+
29
+ app_name = config_settings[:application]
30
+
31
+ common_settings = symbolize_keys(config_settings[:common])
32
+ common_settings[:version_label] ||= package_digest
33
+
34
+ envs = config_settings[:environments]
35
+ raise "Environment #{env} is not defined in #{config_file}" unless envs.has_key?(env)
36
+ env_settings = symbolize_keys(envs[env] || {})
37
+ env_option_settings = env_settings.delete(:option_settings) || []
38
+
39
+ ret = options.merge(common_settings).merge(env_settings)
40
+ ret[:application] = app_name
41
+ ret[:option_settings] ||= []
42
+ ret[:option_settings] += env_option_settings
43
+ ret
44
+ end
45
+
46
+ private
47
+
48
+ def load_config_settings(config_file, package_digest, env)
49
+ yaml = ERB.new(File.read(config_file)).
50
+ result(eval_binding(package_digest, env))
51
+ symbolize_keys(YAML.load(yaml))
52
+ end
53
+
54
+ def eval_binding(package_digest, env)
55
+ EvalBinding.new(package_digest, env).instance_eval { binding }
56
+ end
57
+
58
+ def package_digest(package)
59
+ return nil unless package
60
+ return package unless File.exist?(package)
61
+ Digest::MD5.file(package).hexdigest
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,32 @@
1
+ module EbDeployer
2
+ class DefaultComponent
3
+ def initialize(env, creation_opts, strategy_name, eb_driver)
4
+ @env = env
5
+ @eb_driver = eb_driver
6
+ @creation_opts = creation_opts
7
+ @strategy = DeploymentStrategy.create(self, strategy_name)
8
+ end
9
+
10
+ def cname_prefix
11
+ @creation_opts[:cname_prefix] || default_cname_prefix
12
+ end
13
+
14
+ def deploy(version_label, eb_settings, inactive_settings=[])
15
+ @strategy.test_compatibility(@creation_opts)
16
+ @strategy.deploy(version_label, eb_settings, inactive_settings)
17
+ end
18
+
19
+ def new_eb_env(suffix=nil, cname_prefix_overriding=nil)
20
+ EbEnvironment.new(@env.app_name,
21
+ [@env.name, suffix].compact.join('-'),
22
+ @eb_driver,
23
+ @creation_opts.merge(:cname_prefix => cname_prefix_overriding || cname_prefix))
24
+ end
25
+
26
+ private
27
+
28
+ def default_cname_prefix
29
+ [@env.app_name, @env.name].join('-')
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ module EbDeployer
2
+ class DefaultConfig
3
+ attr_reader :app_name
4
+
5
+ def initialize(app_name)
6
+ @app_name = app_name.gsub('_', '-')
7
+ end
8
+
9
+ def write_to(path)
10
+ FileUtils.mkdir_p(File.dirname(path))
11
+ File.open(path, 'w') { |f| f << ERB.new(File.read(config_template)).result(binding) }
12
+ end
13
+
14
+ private
15
+
16
+ def config_template
17
+ File.expand_path("../default_config.yml", __FILE__)
18
+ end
19
+ end
20
+ end
@@ -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