Skip to content

Commit f4b4b08

Browse files
committed
Apply different revision for rollback deployment lifecycle events
1 parent c4cf461 commit f4b4b08

File tree

7 files changed

+189
-41
lines changed

7 files changed

+189
-41
lines changed

lib/instance_agent/plugins/codedeploy/command_executor.rb

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,14 +97,19 @@ def command_method(command_name)
9797

9898
unpack_bundle(cmd, bundle_file, deployment_spec)
9999

100+
FileUtils.mkdir_p(deployment_instructions_dir)
101+
log(:debug, "Instructions directory created at #{deployment_instructions_dir}")
102+
update_most_recent_install(deployment_spec)
100103
nil
101104
end
102105

103106
command "Install" do |cmd, deployment_spec|
104107
log(:debug, "Executing Install command for execution #{cmd.deployment_execution_id}")
105108

106-
FileUtils.mkdir_p(deployment_instructions_dir)
107-
log(:debug, "Instructions directory created at #{deployment_instructions_dir}")
109+
if !File.directory?(deployment_instructions_dir)
110+
FileUtils.mkdir_p(deployment_instructions_dir)
111+
log(:debug, "Instructions directory created at #{deployment_instructions_dir}")
112+
end
108113

109114
installer = Installer.new(:deployment_instructions_dir => deployment_instructions_dir,
110115
:deployment_archive_dir => archive_root_dir(deployment_spec))
@@ -127,8 +132,11 @@ def map
127132
:deployment_id => deployment_spec.deployment_id,
128133
:deployment_group_name => deployment_spec.deployment_group_name,
129134
:deployment_group_id => deployment_spec.deployment_group_id,
135+
:deployment_creator => deployment_spec.deployment_creator,
136+
:deployment_type => deployment_spec.deployment_type,
130137
:deployment_root_dir => deployment_root_dir(deployment_spec),
131138
:last_successful_deployment_dir => last_successful_deployment_dir(deployment_spec.deployment_group_id),
139+
:most_recent_deployment_dir => most_recent_deployment_dir(deployment_spec.deployment_group_id),
132140
:app_spec_path => app_spec_path)
133141
script_log.concat_log(hook_command.execute)
134142
end
@@ -154,11 +162,20 @@ def archive_root_dir(deployment_spec)
154162

155163
private
156164
def last_successful_deployment_dir(deployment_group)
157-
last_install_file_location = last_install_file_path(deployment_group)
158-
return unless File.exist? last_install_file_location
159-
File.open last_install_file_location do |f|
165+
last_successful_install_file_location = last_successful_install_file_path(deployment_group)
166+
return unless File.exist? last_successful_install_file_location
167+
File.open last_successful_install_file_location do |f|
160168
return f.read.chomp
161-
end
169+
end
170+
end
171+
172+
private
173+
def most_recent_deployment_dir(deployment_group)
174+
most_recent_install_file_location = most_recent_install_file_path(deployment_group)
175+
return unless File.exist? most_recent_install_file_location
176+
File.open most_recent_install_file_location do |f|
177+
return f.read.chomp
178+
end
162179
end
163180

164181
private
@@ -169,10 +186,15 @@ def default_app_spec(deployment_spec)
169186
end
170187

171188
private
172-
def last_install_file_path(deployment_group)
189+
def last_successful_install_file_path(deployment_group)
173190
File.join(deployment_instructions_dir, "#{deployment_group}_last_successful_install")
174191
end
175192

193+
private
194+
def most_recent_install_file_path(deployment_group)
195+
File.join(deployment_instructions_dir, "#{deployment_group}_most_recent_install")
196+
end
197+
176198
private
177199
def download_from_s3(deployment_spec, bucket, key, version, etag)
178200
log(:debug, "Downloading artifact bundle from bucket '#{bucket}' and key '#{key}', version '#{version}', etag '#{etag}'")
@@ -330,11 +352,18 @@ def unpack_bundle(cmd, bundle_file, deployment_spec)
330352

331353
private
332354
def update_last_successful_install(deployment_spec)
333-
File.open(last_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
355+
File.open(last_successful_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
334356
f.write deployment_root_dir(deployment_spec)
335357
end
336358
end
337359

360+
private
361+
def update_most_recent_install(deployment_spec)
362+
File.open(most_recent_install_file_path(deployment_spec.deployment_group_id), 'w+') do |f|
363+
f.write deployment_root_dir(deployment_spec)
364+
end
365+
end
366+
338367
private
339368
def cleanup_old_archives(deployment_spec)
340369
deployment_group = deployment_spec.deployment_group_id

lib/instance_agent/plugins/codedeploy/deployment_specification.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module InstanceAgent
66
module Plugins
77
module CodeDeployPlugin
88
class DeploymentSpecification
9-
attr_accessor :deployment_id, :deployment_group_id, :deployment_group_name, :revision, :revision_source, :application_name
9+
attr_accessor :deployment_id, :deployment_group_id, :deployment_group_name, :revision, :revision_source, :application_name, :deployment_type, :deployment_creator
1010
attr_accessor :bucket, :key, :bundle_type, :version, :etag
1111
attr_accessor :external_account, :repository, :commit_id, :anonymous, :external_auth_token
1212
class << self
@@ -40,6 +40,8 @@ def initialize(data)
4040
@deployment_id = data["DeploymentId"]
4141
end
4242
@deployment_group_id = data["DeploymentGroupId"]
43+
@deployment_creator = data["DeploymentCreator"] || "user"
44+
@deployment_type = data["DeploymentType"] || "IN_PLACE"
4345

4446
raise 'Must specify a revison' unless data["Revision"]
4547
@revision_source = data["Revision"]["RevisionType"]

lib/instance_agent/plugins/codedeploy/hook_executor.rb

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def to_json
5959

6060
class HookExecutor
6161

62-
LAST_SUCCESSFUL_DEPLOYMENT = "OldOrIgnore"
62+
LAST_SUCCESSFUL_DEPLOYMENT = "LastSuccessfulOrIgnore"
63+
MOST_RECENT_DEPLOYMENT = "MostRecentOrIgnore"
6364
CURRENT = "New"
6465
def initialize(arguments = {})
6566
#check arguments
@@ -69,13 +70,17 @@ def initialize(arguments = {})
6970
raise "App Spec Path Required " if arguments[:app_spec_path].nil?
7071
raise "Application name required" if arguments[:application_name].nil?
7172
raise "Deployment Group name required" if arguments[:deployment_group_name].nil?
73+
raise "Deployment creator required" if arguments[:deployment_creator].nil?
74+
raise "Deployment type required" if arguments[:deployment_type].nil?
7275
@lifecycle_event = arguments[:lifecycle_event]
7376
@deployment_id = arguments[:deployment_id]
7477
@application_name = arguments[:application_name]
7578
@deployment_group_name = arguments[:deployment_group_name]
7679
@deployment_group_id = arguments[:deployment_group_id]
80+
@deployment_creator = arguments[:deployment_creator]
81+
@deployment_type = arguments[:deployment_type]
7782
@current_deployment_root_dir = arguments[:deployment_root_dir]
78-
select_correct_deployment_root_dir(arguments[:deployment_root_dir], arguments[:last_successful_deployment_dir])
83+
select_correct_deployment_root_dir(arguments[:deployment_root_dir], arguments[:last_successful_deployment_dir], arguments[:most_recent_deployment_dir])
7984
return if @deployment_root_dir.nil?
8085
@deployment_archive_dir = File.join(@deployment_root_dir, 'deployment-archive')
8186
@app_spec_path = arguments[:app_spec_path]
@@ -183,14 +188,25 @@ def parse_app_spec
183188
end
184189

185190
private
186-
def select_correct_deployment_root_dir(current_deployment_root_dir, last_successful_deployment_root_dir)
191+
def select_correct_deployment_root_dir(current_deployment_root_dir, last_successful_deployment_root_dir, most_recent_deployment_dir)
187192
@deployment_root_dir = current_deployment_root_dir
188193
hook_deployment_mapping = mapping_between_hooks_and_deployments
189-
if(hook_deployment_mapping[@lifecycle_event] == LAST_SUCCESSFUL_DEPLOYMENT && !File.exist?(File.join(@deployment_root_dir, 'deployment-archive')))
194+
if(select_correct_mapping_for_hooks == LAST_SUCCESSFUL_DEPLOYMENT && !File.exist?(File.join(@deployment_root_dir, 'deployment-archive')))
190195
@deployment_root_dir = last_successful_deployment_root_dir
196+
elsif(select_correct_mapping_for_hooks == MOST_RECENT_DEPLOYMENT && !File.exists?(File.join(@deployment_root_dir, 'deployment-archive')))
197+
@deployment_root_dir = most_recent_deployment_dir
191198
end
192199
end
193200

201+
private
202+
def select_correct_mapping_for_hooks
203+
hook_deployment_mapping = mapping_between_hooks_and_deployments
204+
if((@deployment_creator.eql? "codeDeployRollback") && (@deployment_type.eql? "BLUE_GREEN"))
205+
hook_deployment_mapping = rollback_deployment_mapping_between_hooks_and_deployments
206+
end
207+
hook_deployment_mapping[@lifecycle_event]
208+
end
209+
194210
private
195211
def mapping_between_hooks_and_deployments
196212
{ "BeforeBlockTraffic"=>LAST_SUCCESSFUL_DEPLOYMENT,
@@ -204,6 +220,19 @@ def mapping_between_hooks_and_deployments
204220
"ValidateService"=>CURRENT}
205221
end
206222

223+
private
224+
def rollback_deployment_mapping_between_hooks_and_deployments
225+
{ "BeforeBlockTraffic"=>MOST_RECENT_DEPLOYMENT,
226+
"AfterBlockTraffic"=>MOST_RECENT_DEPLOYMENT,
227+
"ApplicationStop"=>LAST_SUCCESSFUL_DEPLOYMENT,
228+
"BeforeInstall"=>CURRENT,
229+
"AfterInstall"=>CURRENT,
230+
"ApplicationStart"=>CURRENT,
231+
"BeforeAllowTraffic"=>LAST_SUCCESSFUL_DEPLOYMENT,
232+
"AfterAllowTraffic"=>LAST_SUCCESSFUL_DEPLOYMENT,
233+
"ValidateService"=>CURRENT}
234+
end
235+
207236
private
208237
def description
209238
self.class.to_s

test/instance_agent/plugins/codedeploy/codedeploy_control_test.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ class CodeDeployControlTest < InstanceAgentTestCase
1212
ENV['AWS_SECRET_ACCESS_KEY'] = "Test Secret Access Key"
1313
ENV['AWS_REGION'] = nil
1414
ENV['AWSDEPLOY_CONTROL_ENDPOINT'] = "https://tempuri"
15+
ENV['DEPLOYMENT_CREATOR'] = "User"
16+
ENV['DEPLOYMENT_TYPE'] = "IN_PLACE"
1517
end
1618

1719
context "with region, endpoint and credentials" do
@@ -25,7 +27,10 @@ class CodeDeployControlTest < InstanceAgentTestCase
2527
should "raise an exception" do
2628
assert_raise {
2729
codedeploy_control_client = CodeDeployControl.new()
28-
codedeploy_control_client.get_client
30+
codedeploy_control_client.get_client.put_host_command_complete(
31+
:command_status => 'Succeeded',
32+
:diagnostics => nil,
33+
:host_command_identifier => "TestCommand")
2934
}
3035
end
3136
end

test/instance_agent/plugins/codedeploy/command_executor_test.rb

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def generate_signed_message_for(map)
4747
@deployment_group_name = "TestDeploymentGroup"
4848
@application_name = "TestApplicationName"
4949
@deployment_group_id = "foo"
50+
@deployment_creator = "User"
51+
@deployment_type = "IN_PLACE"
5052
@s3Revision = {
5153
"Bucket" => "mybucket",
5254
"Key" => "mykey",
@@ -57,6 +59,8 @@ def generate_signed_message_for(map)
5759
"DeploymentGroupId" => @deployment_group_id,
5860
"ApplicationName" => @application_name,
5961
"DeploymentGroupName" => @deployment_group_name,
62+
"DeploymentCreator" => @deployment_creator,
63+
"DeploymentType" => @deployment_type,
6064
"Revision" => {
6165
"RevisionType" => "S3",
6266
"S3Revision" => @s3Revision
@@ -73,7 +77,8 @@ def generate_signed_message_for(map)
7377

7478
FileUtils.stubs(:mkdir_p)
7579
File.stubs(:directory?).with(@deployment_root_dir).returns(true)
76-
@previous_install_file_location = File.join(@deployment_instructions_dir, "#{@deployment_group_id}_last_successful_install")
80+
@last_successful_install_file_location = File.join(@deployment_instructions_dir, "#{@deployment_group_id}_last_successful_install")
81+
@most_recent_install_file_location = File.join(@deployment_instructions_dir, "#{@deployment_group_id}_most_recent_install")
7782
end
7883

7984
context "when executing an unknown command" do
@@ -130,10 +135,11 @@ def generate_signed_message_for(map)
130135
InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::ApplicationSpecification.stubs(:parse).returns(@app_spec)
131136
@installer = stub("installer", :install => nil)
132137
Installer.stubs(:new).returns(@installer)
133-
File.stubs(:exist?).with(@previous_install_file_location).returns(true)
138+
File.stubs(:directory?).with(@deployment_instructions_dir).returns(true)
139+
File.stubs(:exist?).with(@last_successful_install_file_location).returns(true)
134140
File.stubs(:exist?).with(@archive_root_dir).returns(true)
135-
File.stubs(:open).with(@previous_install_file_location, 'w+')
136-
File.stubs(:open).with(@previous_install_file_location)
141+
File.stubs(:open).with(@last_successful_install_file_location, 'w+')
142+
File.stubs(:open).with(@last_successful_install_file_location)
137143

138144
@app_spec = mock("parsed application specification")
139145
File.
@@ -143,12 +149,6 @@ def generate_signed_message_for(map)
143149
ApplicationSpecification::ApplicationSpecification.stubs(:parse).with("APP SPEC").returns(@app_spec)
144150
end
145151

146-
should "idempotently create the instructions directory" do
147-
FileUtils.expects(:mkdir_p).with(@deployment_instructions_dir)
148-
149-
@command_executor.execute_command(@command, @deployment_spec)
150-
end
151-
152152
should "create an appropriate Installer" do
153153
Installer.
154154
expects(:new).
@@ -167,7 +167,7 @@ def generate_signed_message_for(map)
167167

168168
should "write the archive root dir to the install instructions file" do
169169
mock_file = mock
170-
File.expects(:open).with(@previous_install_file_location, 'w+').yields(mock_file)
170+
File.expects(:open).with(@last_successful_install_file_location, 'w+').yields(mock_file)
171171
mock_file.expects(:write).with(@deployment_root_dir)
172172

173173
@command_executor.execute_command(@command, @deployment_spec)
@@ -355,6 +355,20 @@ def generate_signed_message_for(map)
355355
InstanceAgent::LinuxUtil.expects(:extract_tar).in_sequence(call_sequence)
356356
@command_executor.execute_command(@command, @deployment_spec)
357357
end
358+
359+
should "idempotently create the instructions directory" do
360+
FileUtils.expects(:mkdir_p).with(@deployment_instructions_dir)
361+
362+
@command_executor.execute_command(@command, @deployment_spec)
363+
end
364+
365+
should "write the archive root dir to the install instructions file" do
366+
mock_file = mock
367+
File.expects(:open).with(@most_recent_install_file_location, 'w+').yields(mock_file)
368+
mock_file.expects(:write).with(@deployment_root_dir)
369+
370+
@command_executor.execute_command(@command, @deployment_spec)
371+
end
358372
end
359373

360374
context "I have an empty app spec (for script mapping)" do
@@ -366,8 +380,11 @@ def generate_signed_message_for(map)
366380
:deployment_id => @deployment_id,
367381
:deployment_group_name => @deployment_group_name,
368382
:deployment_group_id => @deployment_group_id,
383+
:deployment_creator => @deployment_creator,
384+
:deployment_type => @deployment_type,
369385
:deployment_root_dir => @deployment_root_dir,
370386
:last_successful_deployment_dir => nil,
387+
:most_recent_deployment_dir => nil,
371388
:app_spec_path => 'appspec.yml'}
372389
@mock_hook_executor = mock
373390
end
@@ -505,7 +522,10 @@ def generate_signed_message_for(map)
505522
:deployment_id => @deployment_id,
506523
:deployment_group_name => @deployment_group_name,
507524
:deployment_group_id => @deployment_group_id,
525+
:deployment_creator => @deployment_creator,
526+
:deployment_type => @deployment_type,
508527
:last_successful_deployment_dir => nil,
528+
:most_recent_deployment_dir => nil,
509529
:app_spec_path => 'appspec.yml'}
510530
@hook_executor_constructor_hash_1 = hook_executor_constructor_hash.merge({:lifecycle_event => "lifecycle_event_1"})
511531
@hook_executor_constructor_hash_2 = hook_executor_constructor_hash.merge({:lifecycle_event => "lifecycle_event_2"})
@@ -522,12 +542,11 @@ def generate_signed_message_for(map)
522542

523543
context "when the first script is forced to fail" do
524544
setup do
525-
HookExecutor.stubs(:new).with(@hook_executor_constructor_hash_1).raises("failed to create hook caommand")
526-
545+
HookExecutor.stubs(:new).with(@hook_executor_constructor_hash_1).raises("failed to create hook command")
527546
end
528547

529-
should "calls lifecycle event 1 and fails but not 2" do
530-
assert_raised_with_message('failed to create hook caommand') do
548+
should "calls lifecycle event 1 and fails but not lifecycle event 2" do
549+
assert_raised_with_message('failed to create hook command') do
531550
@command_executor.execute_command(@command, @deployment_spec)
532551
end
533552
HookExecutor.expects(:new).with(@hook_executor_constructor_hash_2).never

0 commit comments

Comments
 (0)