Skip to content

Commit

Permalink
Add custom python script block to set automatic stage rollback
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnStainsby committed Dec 20, 2024
1 parent 0d19cfa commit 4084d0f
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 0 deletions.
21 changes: 21 additions & 0 deletions codebase-pipelines/codepipeline.tf
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,24 @@ resource "aws_codepipeline" "manual_release_pipeline" {

tags = local.tags
}

# This is a temporary workaround until automatic stage rollback is implemented in terraform-provider-aws
# https://github.com/hashicorp/terraform-provider-aws/issues/37244
resource "terraform_data" "update_pipeline" {
provisioner "local-exec" {
command = "python ${path.module}/custom_pipeline_update/update_pipeline.py"
quiet = true
environment = {
PIPELINES = jsonencode(local.pipeline_names)
}
}
triggers_replace = [
aws_codepipeline.codebase_pipeline,
aws_codepipeline.manual_release_pipeline,
file("${path.module}/custom_pipeline_update/update_pipeline.py")
]
depends_on = [
aws_codepipeline.codebase_pipeline,
aws_codepipeline.manual_release_pipeline
]
}
50 changes: 50 additions & 0 deletions codebase-pipelines/custom_pipeline_update/test_update_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import pytest
import unittest
from unittest.mock import MagicMock, patch

from update_pipeline import update_pipeline_stage_failure


class TestUpdatePipeline(unittest.TestCase):

def test_update_pipeline_stage_failure_sets_rollback(self):
with patch("boto3.client") as mock_boto_client:
mock_client = MagicMock()
mock_boto_client.return_value = mock_client

mock_client.get_pipeline.return_value = {
"pipeline": {
"name": "test-pipeline",
"stages": [
{
"name": "Deploy",
}
]
}
}

update_pipeline_stage_failure(["test-pipeline"])

call_args = mock_client.update_pipeline.call_args[1]
assert call_args["pipeline"]["stages"][0]["onFailure"]["result"] == "ROLLBACK"

def test_update_pipeline_stage_failure_does_not_set_rollback(self):
with patch("boto3.client") as mock_boto_client:
mock_client = MagicMock()
mock_boto_client.return_value = mock_client

mock_client.get_pipeline.return_value = {
"pipeline": {
"name": "test-pipeline",
"stages": [
{
"name": "Source",
}
]
}
}

with pytest.raises(ValueError) as error_msg:
update_pipeline_stage_failure(["test-pipeline"])

assert "Stage Deploy not found in pipeline test-pipeline" in str(error_msg.value)
33 changes: 33 additions & 0 deletions codebase-pipelines/custom_pipeline_update/update_pipeline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import os
import json
import boto3
from typing import List


def update_pipeline_stage_failure(pipelines: List[str]):
for pipeline_name in pipelines:
print(f"Updating pipeline {pipeline_name}")

client = boto3.client("codepipeline")

response = client.get_pipeline(name=pipeline_name)
pipeline = response["pipeline"]

stage_found = False
for stage in pipeline["stages"]:
if "Deploy" in stage["name"]:
stage["onFailure"] = {
"result": "ROLLBACK"
}
stage_found = True

if not stage_found:
raise ValueError(f"Stage Deploy not found in pipeline {pipeline_name}")

client.update_pipeline(pipeline=pipeline)
print(f"Updated Deploy stage onFailure property for {pipeline_name}")


if __name__ == "__main__":
pipelines = json.loads(os.environ['PIPELINES'])
update_pipeline_stage_failure(pipelines)
5 changes: 5 additions & 0 deletions codebase-pipelines/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ locals {
]
]
])

pipeline_names = flatten(concat([for pipeline in local.pipeline_map :
"${var.application}-${var.codebase}-${pipeline.name}-codebase-pipeline"],
["${var.application}-${var.codebase}-manual-release-pipeline"]
))
}

0 comments on commit 4084d0f

Please sign in to comment.