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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +127 -0
- data/Gemfile +10 -0
- data/LICENSE +22 -0
- data/README.md +136 -0
- data/Rakefile +12 -0
- data/TODOS.md +11 -0
- data/bin/eb_deploy +13 -0
- data/eb_deployer.gemspec +22 -0
- data/lib/eb_deployer/application.rb +96 -0
- data/lib/eb_deployer/aws_driver/beanstalk.rb +158 -0
- data/lib/eb_deployer/aws_driver/cloud_formation_driver.rb +53 -0
- data/lib/eb_deployer/aws_driver/s3_driver.rb +35 -0
- data/lib/eb_deployer/aws_driver.rb +8 -0
- data/lib/eb_deployer/cf_event_source.rb +26 -0
- data/lib/eb_deployer/cloud_formation_provisioner.rb +120 -0
- data/lib/eb_deployer/component.rb +45 -0
- data/lib/eb_deployer/config_loader.rb +64 -0
- data/lib/eb_deployer/default_component.rb +32 -0
- data/lib/eb_deployer/default_config.rb +20 -0
- data/lib/eb_deployer/default_config.yml +159 -0
- data/lib/eb_deployer/deployment_strategy/blue_green.rb +79 -0
- data/lib/eb_deployer/deployment_strategy/blue_only.rb +45 -0
- data/lib/eb_deployer/deployment_strategy/inplace_update.rb +16 -0
- data/lib/eb_deployer/deployment_strategy.rb +20 -0
- data/lib/eb_deployer/eb_environment.rb +204 -0
- data/lib/eb_deployer/eb_event_source.rb +35 -0
- data/lib/eb_deployer/environment.rb +60 -0
- data/lib/eb_deployer/event_poller.rb +51 -0
- data/lib/eb_deployer/package.rb +39 -0
- data/lib/eb_deployer/resource_stacks.rb +20 -0
- data/lib/eb_deployer/smoke_test.rb +23 -0
- data/lib/eb_deployer/tasks.rb +45 -0
- data/lib/eb_deployer/throttling_handling.rb +17 -0
- data/lib/eb_deployer/utils.rb +33 -0
- data/lib/eb_deployer/version.rb +3 -0
- data/lib/eb_deployer/version_cleaner.rb +30 -0
- data/lib/eb_deployer.rb +339 -0
- data/lib/generators/eb_deployer/install/install_generator.rb +82 -0
- data/lib/generators/eb_deployer/install/templates/eb_deployer.rake +1 -0
- data/lib/generators/eb_deployer/install/templates/eb_deployer.yml.erb +181 -0
- data/lib/generators/eb_deployer/install/templates/ebextensions/01_postgres_packages.config +5 -0
- data/lib/generators/eb_deployer/install/templates/postgres_rds.json +88 -0
- data/test/aws_driver_stubs.rb +350 -0
- data/test/beanstalk_test.rb +23 -0
- data/test/blue_green_deploy_test.rb +114 -0
- data/test/blue_only_deploy_test.rb +78 -0
- data/test/cf_event_poller_test.rb +32 -0
- data/test/cloud_formation_provisioner_test.rb +47 -0
- data/test/config_loader_test.rb +205 -0
- data/test/deploy_test.rb +42 -0
- data/test/eb_environment_test.rb +120 -0
- data/test/eb_event_poller_test.rb +32 -0
- data/test/inplace_update_deploy_test.rb +110 -0
- data/test/multi_components_deploy_test.rb +164 -0
- data/test/rails_generators_test.rb +67 -0
- data/test/resources_deploy_test.rb +191 -0
- data/test/smoke_test_test.rb +23 -0
- data/test/template_deploy_test.rb +13 -0
- data/test/test_helper.rb +68 -0
- data/test/tier_setting_deploy_test.rb +24 -0
- data/test/versions_deploy_test.rb +120 -0
- metadata +194 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Outputs": {
|
|
3
|
+
"RDSHost": {
|
|
4
|
+
"Description": "Database endpoint address",
|
|
5
|
+
"Value": { "Fn::GetAtt": ["RDSDatabase", "Endpoint.Address"] }
|
|
6
|
+
},
|
|
7
|
+
"RDSPort": {
|
|
8
|
+
"Description": "Database endpoint port",
|
|
9
|
+
"Value": { "Fn::GetAtt": ["RDSDatabase", "Endpoint.Port"] }
|
|
10
|
+
},
|
|
11
|
+
"RDSPassSecurityGroup": {
|
|
12
|
+
"Description": "Security group assign to ec2 instance that need access to rds instance",
|
|
13
|
+
"Value": {
|
|
14
|
+
"Ref": "RDSPassSecurityGroup"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
"Parameters": {
|
|
20
|
+
"DBUser": {
|
|
21
|
+
"NoEcho": "false",
|
|
22
|
+
"Description": "The name of master user for the client DB Instance.",
|
|
23
|
+
"Type": "String",
|
|
24
|
+
"ConstraintDescription": "must begin with a letter and contain only alphanumeric characters"
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
"DBName": {
|
|
28
|
+
"NoEcho": "false",
|
|
29
|
+
"Description": "The DB Name of the RDS instance",
|
|
30
|
+
"Type": "String",
|
|
31
|
+
"ConstraintDescription": "must contain only alphanumeric characters"
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
"DBPassword": {
|
|
35
|
+
"NoEcho": "true",
|
|
36
|
+
"Description": "The master password for the DB instance.",
|
|
37
|
+
"Type": "String",
|
|
38
|
+
"ConstraintDescription": "must contain only alphanumeric characters"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
"Resources": {
|
|
43
|
+
"RDSDBSecurityGroup": {
|
|
44
|
+
"Type": "AWS::RDS::DBSecurityGroup",
|
|
45
|
+
"Properties": {
|
|
46
|
+
"GroupDescription": "Enable database access to Beanstalk application",
|
|
47
|
+
"DBSecurityGroupIngress": {
|
|
48
|
+
"EC2SecurityGroupName": {
|
|
49
|
+
"Ref": "RDSPassSecurityGroup"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
"RDSDatabase": {
|
|
56
|
+
"Type": "AWS::RDS::DBInstance",
|
|
57
|
+
"DeletionPolicy": "Delete",
|
|
58
|
+
"Properties": {
|
|
59
|
+
"MasterUsername": {
|
|
60
|
+
"Ref": "DBUser"
|
|
61
|
+
},
|
|
62
|
+
"DBSecurityGroups": [
|
|
63
|
+
{
|
|
64
|
+
"Ref": "RDSDBSecurityGroup"
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"DBInstanceClass": "db.m1.small",
|
|
68
|
+
"AllocatedStorage": "5",
|
|
69
|
+
"MultiAZ": "false",
|
|
70
|
+
"EngineVersion": "9.3.3",
|
|
71
|
+
"DBName": {
|
|
72
|
+
"Ref": "DBName"
|
|
73
|
+
},
|
|
74
|
+
"MasterUserPassword": {
|
|
75
|
+
"Ref": "DBPassword"
|
|
76
|
+
},
|
|
77
|
+
"Engine": "postgres"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
"RDSPassSecurityGroup": {
|
|
82
|
+
"Type": "AWS::EC2::SecurityGroup",
|
|
83
|
+
"Properties": {
|
|
84
|
+
"GroupDescription": "SecurityGroup access RDS database."
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
class EBStub
|
|
2
|
+
attr_reader :envs, :events
|
|
3
|
+
def initialize
|
|
4
|
+
@apps = []
|
|
5
|
+
@envs = {}
|
|
6
|
+
@versions = {}
|
|
7
|
+
@envs_been_deleted = {}
|
|
8
|
+
@versions_deleted = {}
|
|
9
|
+
@event_fetched_times = 0
|
|
10
|
+
@envs_health_states = {}
|
|
11
|
+
@events = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def create_application(app)
|
|
15
|
+
raise 'already exists' if application_exists?(app)
|
|
16
|
+
@apps << app
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def delete_application(app)
|
|
20
|
+
raise "there are environments running for this app" unless environment_names_for_application(app).empty?
|
|
21
|
+
@apps.delete(app)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def application_exists?(app)
|
|
25
|
+
@apps.include?(app)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def create_environment(app, env, solution_stack, cname_prefix, version, tier, tags, settings, template_name)
|
|
29
|
+
raise 'cname prefix is not avaible' if @envs.values.detect { |value| value[:cname_prefix] == cname_prefix }
|
|
30
|
+
raise "env name #{env} is longer than 23 chars" if env.size > 23
|
|
31
|
+
raise "app not exists" unless application_exists?(app)
|
|
32
|
+
@envs[env_key(app, env)] = {
|
|
33
|
+
:name => env,
|
|
34
|
+
:application => app,
|
|
35
|
+
:solution_stack => solution_stack,
|
|
36
|
+
:version => version,
|
|
37
|
+
:cname_prefix => cname_prefix,
|
|
38
|
+
:tier => tier,
|
|
39
|
+
:tags => tags,
|
|
40
|
+
:settings => settings,
|
|
41
|
+
:template_name => template_name }
|
|
42
|
+
set_env_ready(app, env, false)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def delete_environment(app, env)
|
|
46
|
+
@envs.delete(env_key(app, env))
|
|
47
|
+
@envs_been_deleted[app] ||= []
|
|
48
|
+
@envs_been_deleted[app] << env
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def update_environment_settings(app, env, settings)
|
|
52
|
+
raise "not in ready state, consider waiting for previous action finish by pulling envents" unless env_ready?(app, env)
|
|
53
|
+
@envs[env_key(app, env)].merge!(:settings => settings)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def update_environment(app, env, version, tier, settings, template_name)
|
|
58
|
+
raise "not in ready state, consider waiting for previous action finish by pulling envents" unless env_ready?(app, env)
|
|
59
|
+
@envs[env_key(app, env)].merge!(:version => version, :settings => settings, :tier => tier, :template_name => template_name)
|
|
60
|
+
set_env_ready(app, env, false)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def environment_exists?(app_name, env_name)
|
|
64
|
+
@envs.has_key?(env_key(app_name, env_name))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def create_application_version(app_name, version_label, source_bundle)
|
|
68
|
+
@versions[app_name] ||= []
|
|
69
|
+
@versions[app_name] << {
|
|
70
|
+
:version_label => version_label,
|
|
71
|
+
:source_bundle => source_bundle,
|
|
72
|
+
:date_created => Time.now,
|
|
73
|
+
:date_updated => Time.now
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def delete_application_version(app_name, version, delete_source_bundle)
|
|
78
|
+
@versions_deleted[app_name] ||= []
|
|
79
|
+
@versions_deleted[app_name] << version
|
|
80
|
+
@versions[app_name].delete_if { |apv| apv[:version_label] == version }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def application_versions(app_name)
|
|
84
|
+
@versions[app_name]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def application_version_labels(app_name)
|
|
88
|
+
if @versions[app_name]
|
|
89
|
+
@versions[app_name].map { |appv| appv[:version_label]}
|
|
90
|
+
else
|
|
91
|
+
[]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# simulating elasticbeanstalk events api behavior
|
|
96
|
+
# use set_events and append_events to set fake events
|
|
97
|
+
def fetch_events(app_name, env_name, options={})
|
|
98
|
+
@event_fetched_times += 1
|
|
99
|
+
set_env_ready(app_name, env_name, true) # assume env become ready after it spit out all the events
|
|
100
|
+
|
|
101
|
+
unless @events # unrestricted mode for testing if no explicit events set
|
|
102
|
+
return [generate_event_from_messages(['Successfully deployed new configuration to environment',
|
|
103
|
+
'terminateEnvironment completed successfully',
|
|
104
|
+
'Successfully launched environment',
|
|
105
|
+
'Completed swapping CNAMEs for environments'
|
|
106
|
+
], Time.now + @event_fetched_times), nil]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
events = @events[env_key(app_name, env_name)][@event_fetched_times - 1]
|
|
110
|
+
|
|
111
|
+
if options.has_key?(:start_time)
|
|
112
|
+
start_time = Time.parse(options[:start_time])
|
|
113
|
+
events = events.select { |e| e[:event_date] >= start_time }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if limit = options[:max_records]
|
|
117
|
+
events = events[0..limit]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
[events, nil]
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# add fake events for each times of fetch events call
|
|
124
|
+
# message passed in should be old to new order
|
|
125
|
+
# e.g. given set_events("myapp", "test", ['a'], ['b', 'c'])
|
|
126
|
+
# then
|
|
127
|
+
# fetch_events('myapp', 'test') # => [{message: 'a'}] for first time
|
|
128
|
+
# fetch_events('myapp', 'test') # => [{message: 'c'}, {message: 'b'}, {message: 'a'}] for the second time call
|
|
129
|
+
def set_events(app_name, env_name, *messages)
|
|
130
|
+
events_seq = []
|
|
131
|
+
messages.each do |messages_for_call_seq|
|
|
132
|
+
if old_events = events_seq.last
|
|
133
|
+
last_event_date = old_events.first && old_events.first[:event_date]
|
|
134
|
+
events_seq << (generate_event_from_messages(messages_for_call_seq, last_event_date) + old_events)
|
|
135
|
+
else
|
|
136
|
+
events_seq << generate_event_from_messages(messages_for_call_seq)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
@events ||= {}
|
|
140
|
+
@events[env_key(app_name, env_name)] = events_seq
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def environment_cname_prefix(app_name, env_name)
|
|
144
|
+
return unless @envs[env_key(app_name, env_name)]
|
|
145
|
+
@envs[env_key(app_name, env_name)][:cname_prefix]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def environment_cname(app_name, env_name)
|
|
149
|
+
return unless @envs[env_key(app_name, env_name)]
|
|
150
|
+
if cname_prefix = environment_cname_prefix(app_name, env_name)
|
|
151
|
+
cname_prefix + ".us-west-1.elasticbeanstalk.com"
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def environment_swap_cname(app_name, env1_name, env2_name)
|
|
157
|
+
raise "#{env1_name} not in ready state, consider to wait for previous action finsish by pulling events" unless env_ready?(app_name, env1_name)
|
|
158
|
+
|
|
159
|
+
env1, env2 = @envs[env_key(app_name, env1_name)], @envs[env_key(app_name, env2_name)]
|
|
160
|
+
temp = env1[:cname_prefix]
|
|
161
|
+
env1[:cname_prefix] = env2[:cname_prefix]
|
|
162
|
+
env2[:cname_prefix] = temp
|
|
163
|
+
set_env_ready(app_name, env1_name, false)
|
|
164
|
+
set_env_ready(app_name, env2_name, false)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def environment_status(app_name, env_name)
|
|
168
|
+
'ready'
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def environment_health_state(app_name, env_name)
|
|
172
|
+
@envs_health_states.fetch(app_name+env_name, 'Green')
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def environment_verion_label(app_name, env_name)
|
|
176
|
+
@envs[env_key(app_name, env_name)][:version]
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def list_solution_stack_names
|
|
180
|
+
["64bit Amazon Linux 2014.09 v1.1.0 running Tomcat 7 Java 7"]
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
#test only
|
|
184
|
+
|
|
185
|
+
def mark_env_health_state_as(app_name, env_name, state)
|
|
186
|
+
@envs_health_states[app_name+env_name] = state
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def mark_all_envs_ready
|
|
190
|
+
@envs.values.each { |env| set_env_ready(env[:application], env[:name], true) }
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def set_solution_stacks(names)
|
|
194
|
+
@solution_stacks = names
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def environment_tier(app_name, env_name)
|
|
198
|
+
@envs[env_key(app_name, env_name)][:tier]
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def environment_tags(app_name, env_name)
|
|
202
|
+
@envs[env_key(app_name, env_name)][:tags]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def environment_settings(app_name, env_name)
|
|
206
|
+
@envs[env_key(app_name, env_name)][:settings]
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def environment_names_for_application(app)
|
|
210
|
+
@envs.inject([]) do |memo, pair|
|
|
211
|
+
_, env = pair
|
|
212
|
+
memo << env[:name] if env[:application] == app
|
|
213
|
+
memo
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def environments_been_deleted(app)
|
|
218
|
+
@envs_been_deleted[app] || []
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def versions_deleted(app_name)
|
|
222
|
+
@versions_deleted[app_name]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def template_name(app_name, env_name)
|
|
226
|
+
@envs[env_key(app_name, env_name)][:template_name]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
def set_env_ready(app, env, ready)
|
|
232
|
+
if record = @envs[env_key(app, env)]
|
|
233
|
+
record[:ready] = ready
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def env_ready?(app, env)
|
|
238
|
+
@envs[env_key(app, env)][:ready]
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def generate_event_from_messages(messages, start_time=Time.now)
|
|
242
|
+
start_time ||= Time.now
|
|
243
|
+
events = messages.map do |m|
|
|
244
|
+
{ :message => m }
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
events.each_with_index do |e, i|
|
|
248
|
+
e[:event_date] = start_time + (i + 1)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
events.reverse
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def env_key(app, name)
|
|
255
|
+
[app, name].join("-")
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
class S3Stub
|
|
261
|
+
def initialize
|
|
262
|
+
@buckets = {}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def create_bucket(bucket_name)
|
|
266
|
+
@buckets[bucket_name] = {}
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def bucket_exists?(bucket_name)
|
|
270
|
+
@buckets.has_key?(bucket_name)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def objects(bucket_name)
|
|
274
|
+
@buckets[bucket_name]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def object_length(bucket_name, obj_name)
|
|
278
|
+
@buckets[bucket_name][obj_name] && File.size(@buckets[bucket_name][obj_name])
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def upload_file(bucket_name, obj_name, file)
|
|
282
|
+
@buckets[bucket_name][obj_name] = file
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
class CFStub
|
|
288
|
+
def initialize
|
|
289
|
+
@stacks = {}
|
|
290
|
+
@events = {}
|
|
291
|
+
@event_fetch_counter = 0;
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def create_stack(name, template, opts)
|
|
295
|
+
@stacks[name] = {:template => template, :opts => opts }
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def update_stack(name, template, opts)
|
|
299
|
+
@stacks[name] = @stacks[name].merge(:template => template, :opts => opts)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
def stack_status(name)
|
|
303
|
+
@stacks[name] && :update_complete
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def stack_exists?(name)
|
|
307
|
+
@stacks.has_key?(name)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def query_output(name, key)
|
|
311
|
+
raise Aws::CloudFormation::Errors::ValidationError.new(nil, nil) unless stack_exists?(name)
|
|
312
|
+
"value of #{key}"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def stack_config(name)
|
|
316
|
+
@stacks[name][:opts]
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def set_events(name, *messages)
|
|
320
|
+
events_seq = []
|
|
321
|
+
messages.each do |messages_for_call_seq|
|
|
322
|
+
if old_events = events_seq.last
|
|
323
|
+
events_seq << generate_event_from_messages(name, messages_for_call_seq) + old_events
|
|
324
|
+
else
|
|
325
|
+
events_seq << generate_event_from_messages(name, messages_for_call_seq)
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
@events[name] = events_seq
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def fetch_events(name, opts={})
|
|
332
|
+
@event_fetch_counter += 1
|
|
333
|
+
if es = @events[name]
|
|
334
|
+
return es[@event_fetch_counter - 1], nil
|
|
335
|
+
else
|
|
336
|
+
return generate_event_from_messages(name, ["UPDATE_COMPLETE"]), nil
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
private
|
|
341
|
+
|
|
342
|
+
def generate_event_from_messages(stack, messages)
|
|
343
|
+
messages.map do |message|
|
|
344
|
+
OpenStruct.new(timestamp: Time.now,
|
|
345
|
+
resource_type: 'AWS::CloudFormation::Stack',
|
|
346
|
+
logical_resource_id: stack,
|
|
347
|
+
resource_status: message)
|
|
348
|
+
end.reverse
|
|
349
|
+
end
|
|
350
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class BeanstalkTest < Test::Unit::TestCase
|
|
4
|
+
|
|
5
|
+
class StubbedCnameResponder
|
|
6
|
+
def initialize(stubbed_response)
|
|
7
|
+
@stubbed_response = stubbed_response
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def describe_environments(options)
|
|
11
|
+
{:environments => [@stubbed_response.merge(:status => "online")]}
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_cname_prefix_will_parse_both_legacy_and_regionalized_domains
|
|
16
|
+
legacy_domain = "mingle-saas.elasticbeanstalk.com"
|
|
17
|
+
regionalized_domain = "mingle-saas.us-west-1.elasticbeanstalk.com"
|
|
18
|
+
|
|
19
|
+
assert_equal "mingle-saas", EbDeployer::AWSDriver::Beanstalk.new(StubbedCnameResponder.new(:cname => legacy_domain)).environment_cname_prefix("mingle", "saas")
|
|
20
|
+
assert_equal "mingle-saas", EbDeployer::AWSDriver::Beanstalk.new(StubbedCnameResponder.new(:cname => regionalized_domain)).environment_cname_prefix("mingle", "saas")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
require 'deploy_test'
|
|
2
|
+
|
|
3
|
+
class BlueGreenDeployTest < DeployTest
|
|
4
|
+
def test_blue_green_deployment_strategy_should_create_blue_env_on_first_deployment
|
|
5
|
+
do_deploy(42)
|
|
6
|
+
assert @eb.environment_exists?('simple', t('production-a', 'simple'))
|
|
7
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_blue_green_deployment_should_create_green_env_if_blue_exists
|
|
12
|
+
do_deploy(42)
|
|
13
|
+
do_deploy(43)
|
|
14
|
+
assert @eb.environment_exists?('simple', t('production-a', 'simple'))
|
|
15
|
+
assert @eb.environment_exists?('simple', t('production-b', 'simple'))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_blue_green_deployment_should_swap_cname_to_make_active_most_recent_updated_env
|
|
20
|
+
do_deploy(42)
|
|
21
|
+
do_deploy(43)
|
|
22
|
+
assert_match(/simple-production-inactive/, @eb.environment_cname_prefix('simple', t('production-a', 'simple')))
|
|
23
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-b', 'simple'))
|
|
24
|
+
do_deploy(44)
|
|
25
|
+
assert_match(/simple-production-inactive/, @eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
|
26
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_blue_green_deploy_should_run_smoke_test_before_cname_switch
|
|
31
|
+
smoked_host = []
|
|
32
|
+
smoke_test = lambda { |host| smoked_host << host }
|
|
33
|
+
[42, 43, 44].each do |version_label|
|
|
34
|
+
do_deploy(version_label, :smoke_test => smoke_test)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
assert_equal ['simple-production.us-west-1.elasticbeanstalk.com',
|
|
38
|
+
'simple-production-inactive.us-west-1.elasticbeanstalk.com',
|
|
39
|
+
'simple-production-inactive.us-west-1.elasticbeanstalk.com'], smoked_host
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_blue_green_deploy_should_blue_green_terminate_inactive_env_if_blue_green_terminate_inactive_is_enabled
|
|
43
|
+
do_deploy(42, :blue_green_terminate_inactive => true, :blue_green_terminate_inactive_wait => 1, :blue_green_terminate_inactive_sleep => 1)
|
|
44
|
+
do_deploy(43, :blue_green_terminate_inactive => true, :blue_green_terminate_inactive_wait => 0, :blue_green_terminate_inactive_sleep => 0)
|
|
45
|
+
|
|
46
|
+
inactive_env = t('production-a', 'simple')
|
|
47
|
+
assert_equal [inactive_env], @eb.environments_been_deleted('simple')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_blue_green_deployment_should_delete_and_recreate_inactive_env_if_phoenix_mode_is_enabled
|
|
51
|
+
do_deploy(42, :phoenix_mode => true)
|
|
52
|
+
do_deploy(43, :phoenix_mode => true)
|
|
53
|
+
assert_equal [], @eb.environments_been_deleted('simple')
|
|
54
|
+
|
|
55
|
+
inactive_env = t('production-a', 'simple')
|
|
56
|
+
assert_match(/inactive/, @eb.environment_cname_prefix('simple', inactive_env))
|
|
57
|
+
|
|
58
|
+
do_deploy(44, :phoenix_mode => true)
|
|
59
|
+
assert_equal [inactive_env], @eb.environments_been_deleted('simple')
|
|
60
|
+
|
|
61
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', inactive_env)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def test_destroy_should_clean_up_env
|
|
65
|
+
[42, 44].each do |version|
|
|
66
|
+
do_deploy(version)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
destroy(:application => 'simple', :environment => 'production')
|
|
70
|
+
assert !@eb.environment_exists?('simple', t('production-a', 'simple'))
|
|
71
|
+
assert !@eb.environment_exists?('simple', t('production-b', 'simple'))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_can_have_inactive_settings_which_will_be_applied_to_inactive_env
|
|
75
|
+
settings = {:option_settings =>
|
|
76
|
+
[{:namespace => 'aws:autoscaling:launchconfiguration',
|
|
77
|
+
:option_name => 'MinSize',
|
|
78
|
+
:value => 10}],
|
|
79
|
+
:inactive_settings =>
|
|
80
|
+
[{:namespace => 'aws:autoscaling:launchconfiguration',
|
|
81
|
+
:option_name => 'MinSize',
|
|
82
|
+
:value => 1}]}
|
|
83
|
+
|
|
84
|
+
do_deploy(42, settings)
|
|
85
|
+
assert_equal 10, @eb.environment_settings('simple', t('production-a', 'simple')).last[:value]
|
|
86
|
+
assert_equal '42', @eb.environment_verion_label('simple', t('production-a', 'simple'))
|
|
87
|
+
|
|
88
|
+
do_deploy(43, settings)
|
|
89
|
+
assert_equal 1, @eb.environment_settings('simple', t('production-a', 'simple')).last[:value]
|
|
90
|
+
assert_equal '42', @eb.environment_verion_label('simple', t('production-a', 'simple'))
|
|
91
|
+
|
|
92
|
+
assert_equal 10, @eb.environment_settings('simple', t('production-b', 'simple')).last[:value]
|
|
93
|
+
assert_equal '43', @eb.environment_verion_label('simple', t('production-b', 'simple'))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
do_deploy(44, settings)
|
|
97
|
+
assert_equal 10, @eb.environment_settings('simple', t('production-a', 'simple')).last[:value]
|
|
98
|
+
assert_equal '44', @eb.environment_verion_label('simple', t('production-a', 'simple'))
|
|
99
|
+
|
|
100
|
+
assert_equal 1, @eb.environment_settings('simple', t('production-b', 'simple')).last[:value]
|
|
101
|
+
assert_equal '43', @eb.environment_verion_label('simple', t('production-b', 'simple'))
|
|
102
|
+
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def do_deploy(version_label, options={})
|
|
108
|
+
deploy( {:application => 'simple',
|
|
109
|
+
:environment => "production",
|
|
110
|
+
:strategy => 'blue-green',
|
|
111
|
+
}.merge(options).merge(:version_label => version_label))
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require 'deploy_test'
|
|
2
|
+
|
|
3
|
+
class BlueOnlyDeployTest < DeployTest
|
|
4
|
+
def test_blue_only_deployment_strategy_should_create_blue_env_on_first_deployment
|
|
5
|
+
do_deploy(42)
|
|
6
|
+
assert @eb.environment_exists?('simple', t('production-a', 'simple'))
|
|
7
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_blue_only_deployment_should_create_green_env_if_blue_exists
|
|
12
|
+
do_deploy(42)
|
|
13
|
+
do_deploy(43)
|
|
14
|
+
assert @eb.environment_exists?('simple', t('production-a', 'simple'))
|
|
15
|
+
assert @eb.environment_exists?('simple', t('production-b', 'simple'))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_blue_only_deployment_should_not_swap_cname_to_make_active_most_recent_updated_env
|
|
20
|
+
do_deploy(42)
|
|
21
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
|
22
|
+
assert_nil(@eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
|
23
|
+
do_deploy(43)
|
|
24
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
|
25
|
+
assert_match(/simple-production-inactive/, @eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
|
26
|
+
do_deploy(44)
|
|
27
|
+
assert_equal 'simple-production', @eb.environment_cname_prefix('simple', t('production-a', 'simple'))
|
|
28
|
+
assert_match(/simple-production-inactive/, @eb.environment_cname_prefix('simple', t('production-b', 'simple')))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_blue_only_deploy_should_run_smoke_test_before_cname_switch
|
|
33
|
+
smoked_host = []
|
|
34
|
+
smoke_test = lambda { |host| smoked_host << host }
|
|
35
|
+
[42, 43, 44].each do |version_label|
|
|
36
|
+
do_deploy(version_label, :smoke_test => smoke_test)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
assert_equal ['simple-production.us-west-1.elasticbeanstalk.com',
|
|
40
|
+
'simple-production-inactive.us-west-1.elasticbeanstalk.com',
|
|
41
|
+
'simple-production-inactive.us-west-1.elasticbeanstalk.com'], smoked_host
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_blue_only_deployment_should_delete_and_recreate_inactive_env_if_phoenix_mode_is_enabled
|
|
46
|
+
do_deploy(42, :phoenix_mode => true)
|
|
47
|
+
do_deploy(43, :phoenix_mode => true)
|
|
48
|
+
assert_equal [], @eb.environments_been_deleted('simple')
|
|
49
|
+
|
|
50
|
+
inactive_env = t('production-b', 'simple')
|
|
51
|
+
assert_match(/inactive/, @eb.environment_cname_prefix('simple', inactive_env))
|
|
52
|
+
|
|
53
|
+
do_deploy(44, :phoenix_mode => true)
|
|
54
|
+
assert_equal [inactive_env], @eb.environments_been_deleted('simple')
|
|
55
|
+
|
|
56
|
+
assert_match(/inactive/, @eb.environment_cname_prefix('simple', inactive_env))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def test_destroy_should_clean_up_env
|
|
60
|
+
[42, 44].each do |version|
|
|
61
|
+
do_deploy(version)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
destroy(:application => 'simple', :environment => 'production')
|
|
65
|
+
assert !@eb.environment_exists?('simple', t('production-a', 'simple'))
|
|
66
|
+
assert !@eb.environment_exists?('simple', t('production-b', 'simple'))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
def do_deploy(version_label, options={})
|
|
72
|
+
deploy( {:application => 'simple',
|
|
73
|
+
:environment => "production",
|
|
74
|
+
:strategy => 'blue-only',
|
|
75
|
+
}.merge(options).merge(:version_label => version_label))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
class CfEventPollerTest < Test::Unit::TestCase
|
|
4
|
+
def setup
|
|
5
|
+
@cf = CFStub.new
|
|
6
|
+
@poller = EbDeployer::EventPoller.new(EbDeployer::CfEventSource.new("mystack", @cf))
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_run_handle_block_through_all_events_when_there_is_no_from_anchor
|
|
10
|
+
messages_handled = []
|
|
11
|
+
@cf.set_events('mystack', ['a', 'b', nil])
|
|
12
|
+
@poller.poll(nil) do |event|
|
|
13
|
+
break if event.resource_status.nil?
|
|
14
|
+
messages_handled << event.resource_status
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
assert_equal ['a', 'b'], messages_handled
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_can_poll_all_events_after_an_anchor
|
|
22
|
+
@cf.set_events('mystack', ['a', 'b'], ['c', 'd', nil])
|
|
23
|
+
anchor = @poller.get_anchor
|
|
24
|
+
messages_handled = []
|
|
25
|
+
@poller.poll(anchor) do |event|
|
|
26
|
+
break if event.resource_status.nil?
|
|
27
|
+
messages_handled << event.resource_status
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
assert_equal ['c', 'd'], messages_handled
|
|
31
|
+
end
|
|
32
|
+
end
|