man_eb_deployer 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/release.yml +31 -0
  3. data/.github/workflows/test.yml +16 -0
  4. data/.gitignore +12 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +143 -0
  8. data/Gemfile +10 -0
  9. data/LICENSE +22 -0
  10. data/README.md +138 -0
  11. data/Rakefile +12 -0
  12. data/TODOS.md +11 -0
  13. data/bin/eb_deploy +13 -0
  14. data/eb_deployer.gemspec +22 -0
  15. data/lib/eb_deployer/application.rb +96 -0
  16. data/lib/eb_deployer/aws_driver/beanstalk.rb +158 -0
  17. data/lib/eb_deployer/aws_driver/cloud_formation_driver.rb +53 -0
  18. data/lib/eb_deployer/aws_driver/s3_driver.rb +35 -0
  19. data/lib/eb_deployer/aws_driver.rb +8 -0
  20. data/lib/eb_deployer/cf_event_source.rb +26 -0
  21. data/lib/eb_deployer/cloud_formation_provisioner.rb +120 -0
  22. data/lib/eb_deployer/component.rb +45 -0
  23. data/lib/eb_deployer/config_loader.rb +64 -0
  24. data/lib/eb_deployer/default_component.rb +32 -0
  25. data/lib/eb_deployer/default_config.rb +20 -0
  26. data/lib/eb_deployer/default_config.yml +159 -0
  27. data/lib/eb_deployer/deployment_strategy/blue_green.rb +79 -0
  28. data/lib/eb_deployer/deployment_strategy/blue_only.rb +45 -0
  29. data/lib/eb_deployer/deployment_strategy/inplace_update.rb +16 -0
  30. data/lib/eb_deployer/deployment_strategy.rb +20 -0
  31. data/lib/eb_deployer/eb_environment.rb +204 -0
  32. data/lib/eb_deployer/eb_event_source.rb +35 -0
  33. data/lib/eb_deployer/environment.rb +60 -0
  34. data/lib/eb_deployer/event_poller.rb +51 -0
  35. data/lib/eb_deployer/package.rb +39 -0
  36. data/lib/eb_deployer/resource_stacks.rb +20 -0
  37. data/lib/eb_deployer/smoke_test.rb +23 -0
  38. data/lib/eb_deployer/tasks.rb +45 -0
  39. data/lib/eb_deployer/throttling_handling.rb +17 -0
  40. data/lib/eb_deployer/utils.rb +33 -0
  41. data/lib/eb_deployer/version.rb +3 -0
  42. data/lib/eb_deployer/version_cleaner.rb +30 -0
  43. data/lib/eb_deployer.rb +339 -0
  44. data/lib/generators/eb_deployer/install/install_generator.rb +82 -0
  45. data/lib/generators/eb_deployer/install/templates/eb_deployer.rake +1 -0
  46. data/lib/generators/eb_deployer/install/templates/eb_deployer.yml.erb +181 -0
  47. data/lib/generators/eb_deployer/install/templates/ebextensions/01_postgres_packages.config +5 -0
  48. data/lib/generators/eb_deployer/install/templates/postgres_rds.json +88 -0
  49. data/test/aws_driver_stubs.rb +350 -0
  50. data/test/beanstalk_test.rb +23 -0
  51. data/test/blue_green_deploy_test.rb +114 -0
  52. data/test/blue_only_deploy_test.rb +78 -0
  53. data/test/cf_event_poller_test.rb +32 -0
  54. data/test/cloud_formation_provisioner_test.rb +47 -0
  55. data/test/config_loader_test.rb +205 -0
  56. data/test/deploy_test.rb +42 -0
  57. data/test/eb_environment_test.rb +120 -0
  58. data/test/eb_event_poller_test.rb +32 -0
  59. data/test/inplace_update_deploy_test.rb +110 -0
  60. data/test/multi_components_deploy_test.rb +164 -0
  61. data/test/rails_generators_test.rb +67 -0
  62. data/test/resources_deploy_test.rb +191 -0
  63. data/test/smoke_test_test.rb +23 -0
  64. data/test/template_deploy_test.rb +13 -0
  65. data/test/test_helper.rb +68 -0
  66. data/test/tier_setting_deploy_test.rb +24 -0
  67. data/test/versions_deploy_test.rb +120 -0
  68. metadata +176 -0
@@ -0,0 +1,159 @@
1
+ # application name
2
+ application: <%= app_name %>
3
+
4
+ # common settings for all environments
5
+ common:
6
+ # Solution stack for elastic beanstalk, default is 64bit tomcat 7 for JAVA app
7
+ # solution_stack_name: 64bit Amazon Linux 2014.03 v1.0.3 running Tomcat 7 Java 7
8
+
9
+ # Tier name for environments. Current supported values are WebServer and Worker
10
+ # tier: WebServer
11
+
12
+ # AWS region to deploy. Default to us-east-1
13
+ # region: us-west-1
14
+
15
+ # There are three deployment strategies: 'blue-green', 'blue-only', or 'inplace-update'.
16
+ # Blue green deployments keep two elastic beanstalk environments and always deploy to
17
+ # inactive one, to achieve zero downtime.
18
+ # Blue only deployments do everything that the blue green deployments do except for the final
19
+ # inactive to active CNAME swap leaving the newly deployed application on the inactive
20
+ # "blue" instance.
21
+ # Inplace-update strategy will only keep one environment, and update the version inplace on
22
+ # deploy. Inplace-update will save resources but will suffer from downtime.
23
+ # (All old environments need be destroyed when you switching between strategies.)
24
+ # Default strategy is 'blue-green'.
25
+ # strategy: blue-green
26
+
27
+ # Name of s3 bucket where uploaded application packages will be stored.
28
+ # Note that the string ".packages" will be added as a suffix to your bucket.
29
+ # So, if "thoughtworks.simple" is passed as the bucket name, the actual s3 bucket
30
+ # name will be thoughtworks.simple.packages. Default to application name.
31
+ # package_bucket: my-s3-bucket
32
+
33
+ # If phoenix mode is turned on, it will terminate the old elastic
34
+ # beanstalk environment and recreate on deploy. For blue-green
35
+ # deployment it terminates the inactive environment first then
36
+ # recreate it. This is useful to avoid configuration drift and
37
+ # accumulating state on the ec2 instances. Default is off but we recommend
38
+ # it to be turned on for production environment.
39
+ # phoenix_mode: off
40
+
41
+ # The tags you would like to be associated with your resources.
42
+ # These tags will only be used when you first launch an environment. If you are using
43
+ # phoenix_mode set as true each time you deploy you will get a new environment and therefore
44
+ # any changes to your tags. If phoenix_mode is false then it will only use your tags on the
45
+ # initial deploy.
46
+ # tags:
47
+ # my_tag_key: my_tag_value
48
+
49
+ # Specifies the maximum number of versions to keep. Older versions are removed
50
+ # and deleted from the S3 source bucket as well. If specified as zero or not
51
+ # specified, all versions will be kept. If a version_prefix is given, only removes
52
+ # version starting with the prefix.
53
+ # keep_latest: 200
54
+
55
+ # Specifies a prefix to prepend to the version label.
56
+ # This can be useful if you want to use different binaries for different
57
+ # environments.
58
+ # version_prefix:
59
+
60
+ # Generating version label for package to be deployed. A readable version label will
61
+ # provide better traceablity of your deployment process.
62
+ # By default setting is:
63
+ # version_label: <%%= package_digest %>
64
+ # which means using MD5 digest of the package file. If you deploy using build
65
+ # pipeline tool such as GO, switching to pipline counter is highly suggested to
66
+ # increase the readability. Following example will read pipeline counter from environment
67
+ # variable with a fall back to digest for local deployment:
68
+ # version_label: <%%= ENV['GO_PIPELINE_COUNTER'] || package_digest %>
69
+
70
+
71
+ # Smoke test value should be a piece of ruby code with access to single variable
72
+ # "host_name" -- environment DNS name. Smoke test snippet will be evaluated at
73
+ # the end of the deployment for inplace-update deployment. For blue-green
74
+ # deployment it will run after inactive environment update is completed and before
75
+ # switching over.
76
+ # Defining a smoke test is highly recommended for serious usage. By default we use
77
+ # The simplest one that just be checking server landing page using curl, e.g.
78
+ smoke_test: |
79
+ curl_http_code = "curl -s -o /dev/null -w \"%{http_code}\" http://#{host_name}"
80
+ Timeout.timeout(600) do
81
+ until ['200', '301', '302'].include?(`#{curl_http_code}`.strip)
82
+ sleep 5
83
+ end
84
+ end
85
+
86
+ # List of Elastic Beanstalk health states that will be considered healthy when deploying.
87
+ # You may want to override default 'Green' with that list, if for example you are using
88
+ # Elastic Beanstalk worker tier and you don't want eb_deployer to fail if you have messages
89
+ # in your SQS queue.
90
+ # By default eb_deployer only considers 'Green' as the healthy state.
91
+ # For a list of all Elastic Beanstalk health states refer to:
92
+ # http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features.healthstatus.html#using-features.healthstatus.colors
93
+ # accepted_healthy_states:
94
+ # - Green
95
+ # - Yellow
96
+
97
+ # Elastic Beanstalk settings that will apply to the environments you are
98
+ # deploying.
99
+ # For all available options take a look at
100
+ # http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html
101
+ option_settings:
102
+ # Following is an example of set EC2 ssh key name. This allow you ssh into the ec2
103
+ # instance. See http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-key-pairs.html
104
+ # - namespace: aws:autoscaling:launchconfiguration
105
+ # option_name: EC2KeyName
106
+ # value: <my-ec2-key-name>
107
+
108
+ # Following is an example which changes EC2 instance type
109
+ # from t1.micro (default) to m1.small. Instances with t1.micro type sometime
110
+ # are not very responsible, so m1.small is suggested for saving time.
111
+ # But if you care about the marginal cost difference you can comment this out to
112
+ # go back to t1.micro.
113
+ - namespace: aws:autoscaling:launchconfiguration
114
+ option_name: InstanceType
115
+ value: m1.small
116
+
117
+
118
+ # If resources specified, eb_deployer will use the CloudFormation
119
+ # template you provide to create a default CloudFormation stack with
120
+ # name <application_name>-<env-name> for the environment current
121
+ # deploying. And Outputs of the CloudFormation can be mapped to Elastic Beanstalk
122
+ # options settings.
123
+ # keys:
124
+ # template => CloudFormation template file with JSON format
125
+ # policy => CloudFormation policy file with JSON format
126
+ # override_policy => (false) If override_policy is true and a policy file is provided then the policy will temporarily override any existing policy on the resource stack during this update,
127
+ # otherwise the provided policy will replace any existing policy on the resource stack
128
+ # inputs => A Hash, input values for the CloudFormation template
129
+ # outputs => A Hash with key map to your CloudFormation template outputs and value as elastic beanstalk settings namespace and option_name.
130
+ # capabilities => An array. You need set it to ['CAPABILITY_IAM'] if the
131
+ # template include IAM Instance Profile.
132
+ resources:
133
+
134
+ # For example creating a RDS instance for blue green deployment(check jruby-rails4
135
+ # sample project which has a working example):
136
+ # template: config/my_rds.json
137
+ # inputs:
138
+ # DBPassword: <%%= ENV["MYDBPASSWORD"] %>
139
+ # outputs:
140
+ # RDSPassSecurityGroup:
141
+ # namespace: aws:autoscaling:launchconfiguration
142
+ # option_name: SecurityGroups
143
+ # RDSDatabaseConfig:
144
+ # namespace: aws:elasticbeanstalk:application:environment
145
+ # option_name: databaseConfig
146
+
147
+
148
+ # You can define environment here. Each environment can overriden any common settings
149
+ environments:
150
+ dev:
151
+ # example for overriding common settings
152
+ # strategy: inplace-update
153
+ production:
154
+ option_settings:
155
+ # example for overriding common option_settings: providing least redundancy
156
+ # in production environment.
157
+ # - namespace: aws:autoscaling:asg
158
+ # option_name: MinSize
159
+ # value: "2"
@@ -0,0 +1,79 @@
1
+ module EbDeployer
2
+ module DeploymentStrategy
3
+ class BlueGreen
4
+ def initialize(component)
5
+ @component = component
6
+ end
7
+
8
+ def test_compatibility(env_create_options)
9
+ @create_opts = env_create_options
10
+ tier = env_create_options[:tier]
11
+
12
+ if tier && tier.downcase == 'worker'
13
+ raise "Blue green deployment is not supported for Worker tier"
14
+ end
15
+ end
16
+
17
+ def deploy(version_label, env_settings, inactive_settings=[])
18
+
19
+ if !ebenvs.any?(&method(:active_ebenv?))
20
+ ebenv('a', @component.cname_prefix).
21
+ deploy(version_label, env_settings)
22
+ return
23
+ end
24
+
25
+ active_ebenv = ebenvs.detect(&method(:active_ebenv?))
26
+ inactive_ebenv = ebenvs.reject(&method(:active_ebenv?)).first
27
+
28
+ inactive_ebenv.deploy(version_label, env_settings)
29
+ active_ebenv.swap_cname_with(inactive_ebenv)
30
+
31
+ blue_green_terminate_inactive = @create_opts[:blue_green_terminate_inactive]
32
+ blue_green_terminate_inactive_wait = @create_opts[:blue_green_terminate_inactive_wait]
33
+ blue_green_terminate_inactive_sleep = @create_opts[:blue_green_terminate_inactive_sleep]
34
+
35
+ if blue_green_terminate_inactive
36
+ active_ebenv.log("Waiting #{blue_green_terminate_inactive_wait}s before terminating environment...")
37
+
38
+ # Loop until timeout reached or environment becomes Red
39
+ count = 0
40
+ loop do
41
+ break if count >= blue_green_terminate_inactive_wait or inactive_ebenv.health_state != 'Green'
42
+ sleep blue_green_terminate_inactive_sleep
43
+ count += blue_green_terminate_inactive_sleep
44
+ end
45
+
46
+ if inactive_ebenv.health_state == 'Green'
47
+ active_ebenv.log("Active environment healthy, terminating inactive (black) environment")
48
+ active_ebenv.terminate
49
+ else
50
+ active_ebenv.log("Active environment changed state to unhealthy. Existing (black) environment will not be terminated")
51
+ end
52
+
53
+ end
54
+
55
+ unless inactive_settings.empty? || blue_green_terminate_inactive
56
+ active_ebenv.log("applying inactive settings...")
57
+ active_ebenv.apply_settings(inactive_settings)
58
+ end
59
+ end
60
+
61
+ private
62
+ def active_ebenv?(ebenv)
63
+ ebenv.cname_prefix == @component.cname_prefix
64
+ end
65
+
66
+ def ebenvs
67
+ [ebenv('a'), ebenv('b')]
68
+ end
69
+
70
+ def ebenv(suffix, cname_prefix=nil)
71
+ @component.new_eb_env(suffix, cname_prefix || inactive_cname_prefix)
72
+ end
73
+
74
+ def inactive_cname_prefix
75
+ "#{@component.cname_prefix}-inactive"
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,45 @@
1
+ module EbDeployer
2
+ module DeploymentStrategy
3
+ class BlueOnly
4
+ def initialize(component)
5
+ @component = component
6
+ end
7
+
8
+ def test_compatibility(env_create_options)
9
+ tier = env_create_options[:tier]
10
+ if tier && tier.downcase == 'worker'
11
+ raise "Blue only deployment is not supported for Worker tier"
12
+ end
13
+ end
14
+
15
+ def deploy(version_label, env_settings, inactive_settings=[])
16
+ if !ebenvs.any?(&method(:active_ebenv?))
17
+ ebenv('a', @component.cname_prefix).
18
+ deploy(version_label, env_settings)
19
+ return
20
+ end
21
+
22
+ inactive_ebenv = ebenvs.reject(&method(:active_ebenv?)).first
23
+
24
+ inactive_ebenv.deploy(version_label, env_settings)
25
+ end
26
+
27
+ private
28
+ def active_ebenv?(ebenv)
29
+ ebenv.cname_prefix == @component.cname_prefix
30
+ end
31
+
32
+ def ebenvs
33
+ [ebenv('a'), ebenv('b')]
34
+ end
35
+
36
+ def ebenv(suffix, cname_prefix=nil)
37
+ @component.new_eb_env(suffix, cname_prefix || inactive_cname_prefix)
38
+ end
39
+
40
+ def inactive_cname_prefix
41
+ "#{@component.cname_prefix}-inactive"
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ module EbDeployer
2
+ module DeploymentStrategy
3
+ class InplaceUpdate
4
+ def initialize(component)
5
+ @component = component
6
+ end
7
+
8
+ def test_compatibility(env_create_opts)
9
+ end
10
+
11
+ def deploy(version_label, env_settings, inactive_settings)
12
+ @component.new_eb_env.deploy(version_label, env_settings)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'eb_deployer/deployment_strategy/inplace_update'
2
+ require 'eb_deployer/deployment_strategy/blue_green'
3
+ require 'eb_deployer/deployment_strategy/blue_only'
4
+
5
+ module EbDeployer
6
+ module DeploymentStrategy
7
+ def self.create(component, strategy_name)
8
+ case strategy_name.to_s
9
+ when 'inplace_update', 'inplace-update'
10
+ InplaceUpdate.new(component)
11
+ when 'blue_green', 'blue-green'
12
+ BlueGreen.new(component)
13
+ when 'blue_only', 'blue-only'
14
+ BlueOnly.new(component)
15
+ else
16
+ raise 'strategy_name: ' + strategy_name.to_s + ' not supported'
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,204 @@
1
+ module EbDeployer
2
+ class EbEnvironment
3
+ include Utils
4
+
5
+ attr_reader :app, :name
6
+ attr_writer :event_poller
7
+
8
+ def self.unique_ebenv_name(env_name, app_name)
9
+ raise "Environment name #{env_name} is too long, it must be under 32 chars" if env_name.size > 32
10
+ digest = Digest::SHA1.hexdigest(app_name + '-' + env_name)[0..6]
11
+ "#{env_name}-#{digest}"
12
+ end
13
+
14
+ def initialize(app, name, eb_driver, creation_opts={})
15
+ @app = app
16
+ @name = self.class.unique_ebenv_name(name, app)
17
+ @bs = eb_driver
18
+ @creation_opts = default_create_options.merge(reject_nil(creation_opts))
19
+ @accepted_healthy_states = @creation_opts[:accepted_healthy_states]
20
+ @event_poller = nil
21
+ end
22
+
23
+ def deploy(version_label, settings={})
24
+ terminate if @creation_opts[:phoenix_mode]
25
+
26
+ if @bs.environment_exists?(@app, @name)
27
+ update_eb_env(settings, version_label)
28
+ else
29
+ create_eb_env(settings, version_label)
30
+ end
31
+
32
+ smoke_test
33
+ wait_for_env_become_healthy
34
+ end
35
+
36
+ def apply_settings(settings)
37
+ raise "Env #{self.name} not exists for applying settings" unless @bs.environment_exists?(@app, @name)
38
+ wait_for_env_status_to_be_ready
39
+ with_polling_events(/Successfully deployed new configuration to environment/i) do
40
+ @bs.update_environment_settings(@app, @name, settings)
41
+ end
42
+ end
43
+
44
+ def cname_prefix
45
+ @bs.environment_cname_prefix(@app, @name)
46
+ end
47
+
48
+ def swap_cname_with(another)
49
+ log("Swap CNAME with env #{another.name}")
50
+ with_polling_events(/Completed swapping CNAMEs for environments/i) do
51
+ @bs.environment_swap_cname(self.app, self.name, another.name)
52
+ end
53
+ end
54
+
55
+ def log(msg)
56
+ puts "[#{Time.now.utc}][environment:#{@name}] #{msg}"
57
+ end
58
+
59
+ def terminate
60
+ if @bs.environment_exists?(@app, @name)
61
+ with_polling_events(/terminateEnvironment completed successfully/i) do
62
+ @bs.delete_environment(@app, @name)
63
+ end
64
+ end
65
+ end
66
+
67
+ def health_state
68
+ @bs.environment_health_state(@app, @name)
69
+ end
70
+
71
+ private
72
+
73
+ def configured_tier
74
+ @creation_opts[:tier]
75
+ end
76
+
77
+ def template_name
78
+ @creation_opts[:template_name]
79
+ end
80
+
81
+ def has_cname?
82
+ !configured_tier || configured_tier.downcase == 'webserver'
83
+ end
84
+
85
+ def configured_cname_prefix
86
+ @creation_opts[:cname_prefix]
87
+ end
88
+
89
+ def create_eb_env(settings, version_label)
90
+ solution_stack = @creation_opts[:solution_stack]
91
+ tags = convert_tags_hash_to_array(@creation_opts.delete(:tags))
92
+ validate_solutions_stack(solution_stack)
93
+ with_polling_events(/Successfully launched environment/i) do
94
+ @bs.create_environment(@app,
95
+ @name,
96
+ solution_stack,
97
+ has_cname? ? configured_cname_prefix : nil,
98
+ version_label,
99
+ configured_tier,
100
+ tags,
101
+ settings,
102
+ template_name)
103
+ end
104
+ end
105
+
106
+ def update_eb_env(settings, version_label)
107
+ with_polling_events(/Successfully deployed new configuration to environment/i) do
108
+ @bs.update_environment(@app,
109
+ @name,
110
+ version_label,
111
+ configured_tier,
112
+ settings,
113
+ template_name)
114
+ end
115
+ end
116
+
117
+ def validate_solutions_stack(stack_name)
118
+ names = @bs.list_solution_stack_names
119
+ raise "'#{stack_name}' is not a valid solution stack name, available solution stack names are: #{names.join(', ')}" unless names.include?(stack_name)
120
+ end
121
+
122
+ def smoke_test
123
+ host_name = @bs.environment_cname(@app, @name)
124
+ SmokeTest.new(@creation_opts[:smoke_test]).run(host_name, self)
125
+ end
126
+
127
+ def with_polling_events(terminate_pattern, &block)
128
+ anchor = event_poller.get_anchor
129
+ yield
130
+ event_poller.poll(anchor) do |event|
131
+ if event[:message] =~ /Failed to deploy application/
132
+ raise event[:message]
133
+ end
134
+
135
+ if event[:message] =~ /Command failed on instance/
136
+ raise "Elasticbeanstalk instance provision failed (maybe a problem with your .ebextension files). The original message: #{event[:message]}"
137
+ end
138
+
139
+ if event[:message] =~ /complete, but with errors/
140
+ raise event[:message]
141
+ end
142
+
143
+ if event[:message] =~ /However, there were issues during launch\. See event log for details\./
144
+ raise "Environment launched, but with errors. The original message: #{event[:message]}"
145
+ end
146
+
147
+ log_event(event)
148
+ break if event[:message] =~ terminate_pattern
149
+ end
150
+ end
151
+
152
+ def convert_tags_hash_to_array tags
153
+ tags ||= {}
154
+ tags.inject([]) do |arr, (k, v)|
155
+ arr << {:key => k, :value => v}
156
+ arr
157
+ end
158
+ end
159
+
160
+ def wait_for_env_status_to_be_ready
161
+ Timeout.timeout(600) do
162
+ current_status = @bs.environment_status(@app, @name)
163
+
164
+ while current_status.downcase != 'ready'
165
+ log("Environment status: #{current_status}")
166
+ sleep 15
167
+ current_status = @bs.environment_status(@app, @name)
168
+ end
169
+
170
+ log("Environment status: #{current_status}")
171
+ end
172
+ end
173
+
174
+ def wait_for_env_become_healthy
175
+ Timeout.timeout(600) do
176
+ current_health_status = @bs.environment_health_state(@app, @name)
177
+ while !@accepted_healthy_states.include?(current_health_status)
178
+ log("health status: #{current_health_status}")
179
+ sleep 15
180
+ current_health_status = @bs.environment_health_state(@app, @name)
181
+ end
182
+
183
+ log("health status: #{current_health_status}")
184
+ end
185
+ end
186
+
187
+ def event_poller
188
+ @event_poller || EventPoller.new(EbEventSource.new(@app, @name, @bs))
189
+ end
190
+
191
+ def default_create_options
192
+ {
193
+ :solution_stack => "64bit Amazon Linux 2014.09 v1.1.0 running Tomcat 7 Java 7",
194
+ :smoke_test => Proc.new {},
195
+ :tier => 'WebServer',
196
+ :accepted_healthy_states => ['Green']
197
+ }
198
+ end
199
+
200
+ def log_event(event)
201
+ puts "[#{event[:event_date]}][environment:#{@name}] #{event[:message]}"
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,35 @@
1
+ module EbDeployer
2
+ class EbEventSource
3
+ def initialize(app, env, eb_driver)
4
+ @app, @env, @eb_driver = app, env, eb_driver
5
+ end
6
+
7
+ def get_anchor
8
+ events, _ = fetch_events_from_eb(:max_records => 1)
9
+ events.first
10
+ end
11
+
12
+ def fetch_events(from_anchor, &block)
13
+ options = {}
14
+ if from_anchor && from_anchor[:event_date]
15
+ options[:start_time] = from_anchor[:event_date].iso8601
16
+ end
17
+ events, next_token = fetch_events_from_eb(options)
18
+ should_continue = yield(events)
19
+ fetch_next(next_token, &block) if next_token && should_continue
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_next(next_token, &block)
25
+ events, next_token = fetch_events_from_eb(:next_token => next_token)
26
+ should_continue = yield(events)
27
+ fetch_next(next_token, &block) if next_token && should_continue
28
+ end
29
+
30
+ def fetch_events_from_eb(options)
31
+ @eb_driver.fetch_events(@app, @env, options)
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,60 @@
1
+ module EbDeployer
2
+ class Environment
3
+ include Utils
4
+ attr_accessor :creation_opts, :strategy_name
5
+
6
+ attr_writer :resource_stacks, :settings, :inactive_settings, :component_under_deploy
7
+
8
+ attr_reader :name
9
+
10
+ def initialize(app, name, stack_name, eb_driver, &block)
11
+ @app = app
12
+ @name = name
13
+ @stack_name = stack_name
14
+ @eb_driver = eb_driver
15
+ @creation_opts = {}
16
+ @settings = []
17
+ @inactive_settings = []
18
+ @strategy_name = :blue_green
19
+ @components = nil
20
+ yield(self) if block_given?
21
+ unless @components
22
+ @components = [DefaultComponent.new(self, @creation_opts, @strategy_name, @eb_driver)]
23
+ end
24
+ end
25
+
26
+ def app_name
27
+ @app.name
28
+ end
29
+
30
+ def deploy(version_label)
31
+ resource_settings = @resource_stacks.provision(@stack_name)
32
+ components_to_deploy.each do |component|
33
+ component.deploy(version_label, @settings + resource_settings, @inactive_settings)
34
+ end
35
+ end
36
+
37
+ def components=(components_attrs)
38
+ return unless components_attrs
39
+ @components = components_attrs.map do |attrs|
40
+ attrs = symbolize_keys(attrs)
41
+ Component.new(attrs.delete(:name), self, attrs, @eb_driver)
42
+ end
43
+ end
44
+
45
+ private
46
+ def components_to_deploy
47
+ if @component_under_deploy
48
+ component = component_named(@component_under_deploy)
49
+ raise "'#{@component_under_deploy}' is not in the configuration. Available components are #{@components.map(&:name) }" unless component
50
+ [component]
51
+ else
52
+ @components
53
+ end
54
+ end
55
+
56
+ def component_named(name)
57
+ @components.detect { |c| c.name == name }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ module EbDeployer
2
+ class EventPoller
3
+ include Utils
4
+ POLL_INTERVAL = 15
5
+
6
+ def initialize(event_source)
7
+ @event_source = event_source
8
+ end
9
+
10
+ def get_anchor
11
+ @event_source.get_anchor
12
+ end
13
+
14
+ def poll(from_anchor, &block)
15
+ handled = Set.new
16
+ loop do
17
+ @event_source.fetch_events(from_anchor) do |events|
18
+ # events from event source is latest first order
19
+ to_be_handled = []
20
+ reached_anchor = false
21
+
22
+ events.each do |event|
23
+ if digest(event) == digest(from_anchor)
24
+ reached_anchor = true
25
+ end
26
+
27
+ if !handled.include?(digest(event)) && !reached_anchor
28
+ to_be_handled << event
29
+ end
30
+ end
31
+
32
+ to_be_handled.reverse.each do |event|
33
+ yield(event)
34
+ handled << digest(event)
35
+ end
36
+
37
+ !reached_anchor
38
+ end
39
+ sleep POLL_INTERVAL
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def digest(event)
46
+ return nil unless event
47
+ event = event.to_h if event.respond_to?(:to_h)
48
+ event.to_json
49
+ end
50
+ end
51
+ end