From d3b8ea38809b3662c73b49f206bd059bd6d4aef2 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Thu, 6 Feb 2025 11:08:54 +0000 Subject: [PATCH 01/13] Move checks to start of deploy script --- codebase-pipelines/buildspec-deploy.yml | 37 ++++++++++++------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/codebase-pipelines/buildspec-deploy.yml b/codebase-pipelines/buildspec-deploy.yml index 44ece690..4fe8b272 100644 --- a/codebase-pipelines/buildspec-deploy.yml +++ b/codebase-pipelines/buildspec-deploy.yml @@ -22,10 +22,24 @@ phases: commands: - set -e + # Check if the specified image tag exists + - | + if ! aws ecr describe-images --repository-name "${REPOSITORY_NAME}" --image-ids "imageTag=${IMAGE_TAG}" > /dev/null 2>&1; then + echo "Error: image tag ${IMAGE_TAG} not found in repository ${REPOSITORY_NAME}" + exit 1 + fi + + # Check environment exists in config + - | + if [ $(echo $ENV_CONFIG | jq 'has('\"${ENVIRONMENT}\"')') == "false" ]; then + echo "Error: environment ${ENVIRONMENT} not listed in environment config" + exit 1 + fi + # Extract timestamp from image config and check if it exists - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com - # construct Slack message env vars + # Construct Slack message env vars - SLACK_REF=$(regctl image config "${REPOSITORY_URL}:${IMAGE_TAG}" | jq -r '.config.Labels."uk.gov.trade.digital.build.timestamp"') - | if [ "${SLACK_REF}" = "null" ] || [ -z "${SLACK_REF}" ]; then @@ -37,7 +51,7 @@ phases: - UPPERCASE_SERVICE=$(echo "${SERVICE}" | tr '[:lower:]' '[:upper:]') - UPPERCASE_TAG=$(echo "${IMAGE_TAG}" | tr '[:lower:]' '[:upper:]') - # Extract the pipeline name from CODEBUILD_INITIATOR default env var + # Extract the pipeline name from CODEBUILD_INITIATOR default env var - | if [[ "${CODEBUILD_INITIATOR}" == codepipeline/* ]]; then PIPELINE_NAME=$(echo "${CODEBUILD_INITIATOR}" | cut -d'/' -f2) @@ -50,8 +64,6 @@ phases: # Construct the pipeline execution URL - PIPELINE_EXECUTION_URL="https://${AWS_REGION}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${PIPELINE_NAME}/executions/${PIPELINE_EXECUTION_ID}" - - echo "Pipeline execution URL ${PIPELINE_EXECUTION_URL}" - - BUILD_ID_PREFIX=$(echo $CODEBUILD_BUILD_ID | cut -d':' -f1) - echo "BUILD_ID_PREFIX - ${BUILD_ID_PREFIX}" @@ -60,20 +72,7 @@ phases: - platform-helper notify add-comment "${SLACK_CHANNEL_ID}" "${SLACK_TOKEN}" "${SLACK_REF}" "${MESSAGE}" - # Check if the specified image tag exists - - | - if ! aws ecr describe-images --repository-name "${REPOSITORY_NAME}" --image-ids "imageTag=${IMAGE_TAG}" > /dev/null 2>&1; then - echo "Error: image tag ${IMAGE_TAG} not found in repository ${REPOSITORY_NAME}" - exit 1 - fi - - # Check environment exists in config - - | - if [ $(echo $ENV_CONFIG | jq 'has('\"${ENVIRONMENT}\"')') == "false" ]; then - echo "Error: environment ${ENVIRONMENT} not listed in environment config" - exit 1 - fi - + # Build image uri - task_family="${APPLICATION}-${ENVIRONMENT}-${SERVICE}" - cluster="${APPLICATION}-${ENVIRONMENT}" - image_uri="${REPOSITORY_URL}:${IMAGE_TAG}" @@ -162,8 +161,6 @@ phases: STATUS_TEXT="STOP_REQUESTED/STOPPED/PENDING/IN_PROGRESS" ;; esac - - - echo "Status emoji set to ${STATUS_EMOJI}" - MESSAGE="${STATUS_EMOJI} ${STATUS_TEXT} - Deployment of ${UPPERCASE_TAG} to ${UPPERCASE_SERVICE} service - ${deploy_status} - <${PIPELINE_EXECUTION_URL}|Pipeline execution url>" From 66f3a2504a92e4f9859b4a747d8acb7af652fe55 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Thu, 6 Feb 2025 11:11:51 +0000 Subject: [PATCH 02/13] Move tools install into build phase --- codebase-pipelines/buildspec-deploy.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/codebase-pipelines/buildspec-deploy.yml b/codebase-pipelines/buildspec-deploy.yml index 4fe8b272..805fd652 100644 --- a/codebase-pipelines/buildspec-deploy.yml +++ b/codebase-pipelines/buildspec-deploy.yml @@ -7,17 +7,6 @@ env: SLACK_TOKEN: /codebuild/slack_oauth_token phases: - install: - commands: - - echo "Starting deployment script for ${SERVICE} service" - - pip install yq --quiet - - echo "Installing platform-helper" - - pip install dbt-platform-helper --quiet - - platform-helper --version - - echo "Installing regclient" - - curl -s -L https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 > /usr/local/bin/regctl - - chmod +x /usr/local/bin/regctl - build: commands: - set -e @@ -36,6 +25,16 @@ phases: exit 1 fi + # Install tools + - echo "Starting deployment script for ${SERVICE} service" + - pip install yq --quiet + - echo "Installing platform-helper" + - pip install dbt-platform-helper --quiet + - platform-helper --version + - echo "Installing regclient" + - curl -s -L https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 > /usr/local/bin/regctl + - chmod +x /usr/local/bin/regctl + # Extract timestamp from image config and check if it exists - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com From cbe75a1b0517ebecea6f27e3c311b608906dee3c Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Thu, 6 Feb 2025 11:15:17 +0000 Subject: [PATCH 03/13] Clean up build log --- codebase-pipelines/buildspec-deploy.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/codebase-pipelines/buildspec-deploy.yml b/codebase-pipelines/buildspec-deploy.yml index 805fd652..4383946e 100644 --- a/codebase-pipelines/buildspec-deploy.yml +++ b/codebase-pipelines/buildspec-deploy.yml @@ -26,12 +26,7 @@ phases: fi # Install tools - - echo "Starting deployment script for ${SERVICE} service" - - pip install yq --quiet - - echo "Installing platform-helper" - - pip install dbt-platform-helper --quiet - - platform-helper --version - - echo "Installing regclient" + - pip install yq dbt-platform-helper --quiet - curl -s -L https://github.com/regclient/regclient/releases/latest/download/regctl-linux-amd64 > /usr/local/bin/regctl - chmod +x /usr/local/bin/regctl @@ -45,7 +40,6 @@ phases: echo "Image contains no timestamp label" exit 1 fi - echo "Found image timestamp ${SLACK_REF}" - UPPERCASE_SERVICE=$(echo "${SERVICE}" | tr '[:lower:]' '[:upper:]') - UPPERCASE_TAG=$(echo "${IMAGE_TAG}" | tr '[:lower:]' '[:upper:]') @@ -54,17 +48,14 @@ phases: - | if [[ "${CODEBUILD_INITIATOR}" == codepipeline/* ]]; then PIPELINE_NAME=$(echo "${CODEBUILD_INITIATOR}" | cut -d'/' -f2) - echo "Pipeline name is ${PIPELINE_NAME}" else echo "ERROR: Build not triggered by CodePipeline." exit 1 fi # Construct the pipeline execution URL - - PIPELINE_EXECUTION_URL="https://${AWS_REGION}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${PIPELINE_NAME}/executions/${PIPELINE_EXECUTION_ID}" - + - PIPELINE_EXECUTION_URL="https://${AWS_REGION}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${PIPELINE_NAME}/executions/${PIPELINE_EXECUTION_ID}" - BUILD_ID_PREFIX=$(echo $CODEBUILD_BUILD_ID | cut -d':' -f1) - - echo "BUILD_ID_PREFIX - ${BUILD_ID_PREFIX}" - | MESSAGE=":rocket: STARTED - Deployment of ${UPPERCASE_SERVICE} service - Tag: ${UPPERCASE_TAG} - | <${PIPELINE_EXECUTION_URL}|Pipeline execution url>" From 00e8d16d9fc00c6affca595e08c6a27336a01b01 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Thu, 6 Feb 2025 12:15:14 +0000 Subject: [PATCH 04/13] Add additional sleep after initiating deployment --- codebase-pipelines/buildspec-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codebase-pipelines/buildspec-deploy.yml b/codebase-pipelines/buildspec-deploy.yml index 4383946e..76a901a4 100644 --- a/codebase-pipelines/buildspec-deploy.yml +++ b/codebase-pipelines/buildspec-deploy.yml @@ -113,6 +113,7 @@ phases: - start=$( date +%s ) - deploy_status="IN_PROGRESS" - aws ecs update-service --cluster "${cluster}" --service "${service_name}" --task-definition "${task_family}:${new_revision}" --desired-count ${count} > /dev/null 2>&1 + - sleep 10 # Check deployment status - | @@ -153,7 +154,6 @@ phases: esac - MESSAGE="${STATUS_EMOJI} ${STATUS_TEXT} - Deployment of ${UPPERCASE_TAG} to ${UPPERCASE_SERVICE} service - ${deploy_status} - <${PIPELINE_EXECUTION_URL}|Pipeline execution url>" - - platform-helper notify add-comment "${SLACK_CHANNEL_ID}" "${SLACK_TOKEN}" "${SLACK_REF}" "${MESSAGE}" # Exit with an error code if deployment did not succeed From ad11cbde2f6f5fef42b181e87cdb6b6f8ab2d3f0 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Fri, 14 Feb 2025 17:30:30 +0000 Subject: [PATCH 05/13] Fix conditional resources --- codebase-pipelines/artifactstore.tf | 5 +-- codebase-pipelines/eventbridge.tf | 6 ++- codebase-pipelines/locals.tf | 6 +-- codebase-pipelines/tests/unit.tftest.hcl | 54 +++++++++++++++--------- codebase-pipelines/variables.tf | 3 +- 5 files changed, 43 insertions(+), 31 deletions(-) diff --git a/codebase-pipelines/artifactstore.tf b/codebase-pipelines/artifactstore.tf index 79dbb800..493270f2 100644 --- a/codebase-pipelines/artifactstore.tf +++ b/codebase-pipelines/artifactstore.tf @@ -53,10 +53,7 @@ data "aws_iam_policy_document" "artifact_store_bucket_policy" { effect = "Allow" principals { type = "AWS" - identifiers = [ - for env in local.pipeline_environments : - "arn:aws:iam::${env.account}:role/${var.application}-${env.name}-codebase-pipeline-deploy" - ] + identifiers = [for id in local.deploy_account_ids : "arn:aws:iam::${id}:root"] } actions = [ "s3:*" diff --git a/codebase-pipelines/eventbridge.tf b/codebase-pipelines/eventbridge.tf index 757da703..b5238275 100644 --- a/codebase-pipelines/eventbridge.tf +++ b/codebase-pipelines/eventbridge.tf @@ -19,18 +19,20 @@ resource "aws_cloudwatch_event_target" "codepipeline" { for_each = local.pipeline_map rule = aws_cloudwatch_event_rule.ecr_image_publish[each.key].name arn = aws_codepipeline.codebase_pipeline[each.key].arn - role_arn = aws_iam_role.event_bridge_pipeline_trigger.arn + role_arn = aws_iam_role.event_bridge_pipeline_trigger[""].arn } resource "aws_iam_role" "event_bridge_pipeline_trigger" { + for_each = toset(length(local.pipeline_map) > 0 ? [""] : []) name = "${var.application}-${var.codebase}-event-bridge-pipeline-trigger" assume_role_policy = data.aws_iam_policy_document.assume_event_bridge_policy.json tags = local.tags } resource "aws_iam_role_policy" "event_bridge_pipeline_trigger" { + for_each = toset(length(local.pipeline_map) > 0 ? [""] : []) name = "event-bridge-access" - role = aws_iam_role.event_bridge_pipeline_trigger.name + role = aws_iam_role.event_bridge_pipeline_trigger[""].name policy = data.aws_iam_policy_document.event_bridge_pipeline_trigger.json } diff --git a/codebase-pipelines/locals.tf b/codebase-pipelines/locals.tf index 266128d7..5bdc9231 100644 --- a/codebase-pipelines/locals.tf +++ b/codebase-pipelines/locals.tf @@ -9,7 +9,7 @@ locals { ecr_name = "${var.application}/${var.codebase}" prefixed_repository_name = "uktrade/${var.application}" - repository_url = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}" + repository_url = coalesce(var.additional_ecr_repository, "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}") pipeline_branches = distinct([ for pipeline in var.pipelines : pipeline.branch if lookup(pipeline, "branch", null) != null @@ -33,10 +33,6 @@ locals { }) } - pipeline_environments = distinct(flatten([ - for pipeline in local.pipeline_map : [for env in pipeline.environments : env] - ])) - services = sort(flatten([ for run_group in var.services : [for service in flatten(values(run_group)) : service] ])) diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index 0f22ba44..dbc74b9d 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -84,10 +84,9 @@ variables { } } } - application = "my-app" - codebase = "my-codebase" - repository = "my-repository" - additional_ecr_repository = "my-additional-repository" + application = "my-app" + codebase = "my-codebase" + repository = "my-repository" services = [ { "run_group_1" : [ @@ -171,7 +170,7 @@ run "test_artifact_store" { error_message = "Should be: AWS" } assert { - condition = flatten([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].principals : el.identifiers]) == ["arn:aws:iam::000123456789:role/my-app-dev-codebase-pipeline-deploy", "arn:aws:iam::000123456789:role/my-app-staging-codebase-pipeline-deploy", "arn:aws:iam::123456789000:role/my-app-prod-codebase-pipeline-deploy"] + condition = flatten([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].principals : el.identifiers]) == ["arn:aws:iam::000123456789:root", "arn:aws:iam::123456789000:root"] error_message = "Bucket policy principals incorrect" } assert { @@ -231,14 +230,6 @@ run "test_codebuild_images" { condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[3].value == "/fake/slack/channel" error_message = "Should be: '/fake/slack/channel'" } - assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].name == "ADDITIONAL_ECR_REPOSITORY" - error_message = "Should be: 'ADDITIONAL_ECR_REPOSITORY'" - } - assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "my-additional-repository" - error_message = "Should be: 'my-additional-repository'" - } assert { condition = aws_codebuild_project.codebase_image_build[""].logs_config[0].cloudwatch_logs[ 0 @@ -348,6 +339,31 @@ run "test_codebuild_images_not_required" { } } +run "test_additional_ecr_repository" { + command = plan + + variables { + additional_ecr_repository = "my-additional-repository" + } + + assert { + condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].name == "ADDITIONAL_ECR_REPOSITORY" + error_message = "Should be: 'ADDITIONAL_ECR_REPOSITORY'" + } + assert { + condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "my-additional-repository" + error_message = "Should be: 'my-additional-repository'" + } + assert { + condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + error_message = "Configuration environment variables incorrect" + } + assert { + condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + error_message = "Configuration environment variables incorrect" + } +} + run "test_main_branch_filter" { command = plan @@ -1386,27 +1402,27 @@ run "test_event_bridge" { # IAM roles assert { - condition = aws_iam_role.event_bridge_pipeline_trigger.name == "my-app-my-codebase-event-bridge-pipeline-trigger" + condition = aws_iam_role.event_bridge_pipeline_trigger[""].name == "my-app-my-codebase-event-bridge-pipeline-trigger" error_message = "Should be: 'my-app-my-codebase-event-bridge-pipeline-trigger'" } assert { - condition = aws_iam_role.event_bridge_pipeline_trigger.assume_role_policy == "{\"Sid\": \"AssumeEventBridge\"}" + condition = aws_iam_role.event_bridge_pipeline_trigger[""].assume_role_policy == "{\"Sid\": \"AssumeEventBridge\"}" error_message = "Should be: {\"Sid\": \"AssumeEventBridge\"}" } assert { - condition = jsonencode(aws_iam_role.event_bridge_pipeline_trigger.tags) == jsonencode(var.expected_tags) + condition = jsonencode(aws_iam_role.event_bridge_pipeline_trigger[""].tags) == jsonencode(var.expected_tags) error_message = "Should be: ${jsonencode(var.expected_tags)}" } assert { - condition = aws_iam_role_policy.event_bridge_pipeline_trigger.name == "event-bridge-access" + condition = aws_iam_role_policy.event_bridge_pipeline_trigger[""].name == "event-bridge-access" error_message = "Should be: 'event-bridge-access'" } assert { - condition = aws_iam_role_policy.event_bridge_pipeline_trigger.role == "my-app-my-codebase-event-bridge-pipeline-trigger" + condition = aws_iam_role_policy.event_bridge_pipeline_trigger[""].role == "my-app-my-codebase-event-bridge-pipeline-trigger" error_message = "Should be: 'my-app-my-codebase-event-bridge-pipeline-trigger'" } assert { - condition = aws_iam_role_policy.event_bridge_pipeline_trigger.policy == "{\"Sid\": \"EventBridgePipelineTrigger\"}" + condition = aws_iam_role_policy.event_bridge_pipeline_trigger[""].policy == "{\"Sid\": \"EventBridgePipelineTrigger\"}" error_message = "Unexpected policy" } diff --git a/codebase-pipelines/variables.tf b/codebase-pipelines/variables.tf index e0974afb..0481c442 100644 --- a/codebase-pipelines/variables.tf +++ b/codebase-pipelines/variables.tf @@ -11,7 +11,8 @@ variable "repository" { } variable "additional_ecr_repository" { - type = string + type = string + default = null } variable "pipelines" { From a5a29cad31cbe0d5bd49a8a97adb99f6669e36ea Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Fri, 14 Feb 2025 17:58:39 +0000 Subject: [PATCH 06/13] Do image check regardless of ECR type; Remove unnecessary parameters --- codebase-pipelines/buildspec-deploy.yml | 8 +++----- codebase-pipelines/codepipeline.tf | 4 ---- codebase-pipelines/locals.tf | 1 - codebase-pipelines/tests/unit.tftest.hcl | 20 ++++++++++---------- 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/codebase-pipelines/buildspec-deploy.yml b/codebase-pipelines/buildspec-deploy.yml index aa8ddf01..6224b5b5 100644 --- a/codebase-pipelines/buildspec-deploy.yml +++ b/codebase-pipelines/buildspec-deploy.yml @@ -18,9 +18,10 @@ phases: - set -e # Check if the specified image tag exists + - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com - | - if ! aws ecr describe-images --repository-name "${REPOSITORY_NAME}" --image-ids "imageTag=${IMAGE_TAG}" > /dev/null 2>&1; then - echo "Error: image tag ${IMAGE_TAG} not found in repository ${REPOSITORY_NAME}" + if ! regctl manifest head "${REPOSITORY_URL}:${IMAGE_TAG}" > /dev/null 2>&1; then + echo "Error: image tag ${IMAGE_TAG} not found in repository ${REPOSITORY_URL}" exit 1 fi @@ -31,9 +32,6 @@ phases: exit 1 fi - # Extract timestamp from image config and check if it exists - - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com - # Construct Slack message env vars - SLACK_REF=$(regctl image config "${REPOSITORY_URL}:${IMAGE_TAG}" | jq -r '.config.Labels."uk.gov.trade.digital.build.timestamp"') - | diff --git a/codebase-pipelines/codepipeline.tf b/codebase-pipelines/codepipeline.tf index f8209972..de4ccc23 100644 --- a/codebase-pipelines/codepipeline.tf +++ b/codebase-pipelines/codepipeline.tf @@ -80,9 +80,7 @@ resource "aws_codepipeline" "codebase_pipeline" { { name : "ENVIRONMENT", value : stage.value.name }, { name : "IMAGE_TAG", value : "#{variables.IMAGE_TAG}" }, { name : "PIPELINE_EXECUTION_ID", value : "#{codepipeline.PipelineExecutionId}" }, - { name : "PREFIXED_REPOSITORY_NAME", value : local.prefixed_repository_name }, { name : "REPOSITORY_URL", value : local.repository_url }, - { name : "REPOSITORY_NAME", value : local.ecr_name }, { name : "SERVICE", value : action.value.name }, { name : "SLACK_CHANNEL_ID", value : var.slack_channel, type : "PARAMETER_STORE" }, ]) @@ -168,9 +166,7 @@ resource "aws_codepipeline" "manual_release_pipeline" { { name : "ENVIRONMENT", value : "#{variables.ENVIRONMENT}" }, { name : "IMAGE_TAG", value : "#{variables.IMAGE_TAG}" }, { name : "PIPELINE_EXECUTION_ID", value : "#{codepipeline.PipelineExecutionId}" }, - { name : "PREFIXED_REPOSITORY_NAME", value : local.prefixed_repository_name }, { name : "REPOSITORY_URL", value : local.repository_url }, - { name : "REPOSITORY_NAME", value : local.ecr_name }, { name : "SERVICE", value : action.value.name }, { name : "SLACK_CHANNEL_ID", value : var.slack_channel, type : "PARAMETER_STORE" }, ]) diff --git a/codebase-pipelines/locals.tf b/codebase-pipelines/locals.tf index 5bdc9231..e8ebd69a 100644 --- a/codebase-pipelines/locals.tf +++ b/codebase-pipelines/locals.tf @@ -8,7 +8,6 @@ locals { account_region = "${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}" ecr_name = "${var.application}/${var.codebase}" - prefixed_repository_name = "uktrade/${var.application}" repository_url = coalesce(var.additional_ecr_repository, "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}") pipeline_branches = distinct([ diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index dbc74b9d..5ea49909 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -355,11 +355,11 @@ run "test_additional_ecr_repository" { error_message = "Should be: 'my-additional-repository'" } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } } @@ -1023,7 +1023,7 @@ run "test_main_pipeline" { } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } @@ -1062,7 +1062,7 @@ run "test_main_pipeline" { error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } @@ -1100,7 +1100,7 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"staging\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"staging\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } @@ -1115,7 +1115,7 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"staging\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"staging\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { @@ -1161,7 +1161,7 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { @@ -1175,7 +1175,7 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[2].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[2].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { @@ -1314,7 +1314,7 @@ run "test_manual_release_pipeline" { error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { @@ -1352,7 +1352,7 @@ run "test_manual_release_pipeline" { error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"PREFIXED_REPOSITORY_NAME\",\"value\":\"uktrade/my-app\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"REPOSITORY_NAME\",\"value\":\"my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { From c93750ab05cb5ee49047429c9916fc1f8a36a1e2 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Fri, 14 Feb 2025 18:07:10 +0000 Subject: [PATCH 07/13] Formatting --- codebase-pipelines/artifactstore.tf | 2 +- codebase-pipelines/eventbridge.tf | 8 ++++---- codebase-pipelines/locals.tf | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/codebase-pipelines/artifactstore.tf b/codebase-pipelines/artifactstore.tf index 493270f2..c5a8e6ae 100644 --- a/codebase-pipelines/artifactstore.tf +++ b/codebase-pipelines/artifactstore.tf @@ -52,7 +52,7 @@ data "aws_iam_policy_document" "artifact_store_bucket_policy" { statement { effect = "Allow" principals { - type = "AWS" + type = "AWS" identifiers = [for id in local.deploy_account_ids : "arn:aws:iam::${id}:root"] } actions = [ diff --git a/codebase-pipelines/eventbridge.tf b/codebase-pipelines/eventbridge.tf index b5238275..ff8bd429 100644 --- a/codebase-pipelines/eventbridge.tf +++ b/codebase-pipelines/eventbridge.tf @@ -23,7 +23,7 @@ resource "aws_cloudwatch_event_target" "codepipeline" { } resource "aws_iam_role" "event_bridge_pipeline_trigger" { - for_each = toset(length(local.pipeline_map) > 0 ? [""] : []) + for_each = toset(length(local.pipeline_map) > 0 ? [""] : []) name = "${var.application}-${var.codebase}-event-bridge-pipeline-trigger" assume_role_policy = data.aws_iam_policy_document.assume_event_bridge_policy.json tags = local.tags @@ -31,9 +31,9 @@ resource "aws_iam_role" "event_bridge_pipeline_trigger" { resource "aws_iam_role_policy" "event_bridge_pipeline_trigger" { for_each = toset(length(local.pipeline_map) > 0 ? [""] : []) - name = "event-bridge-access" - role = aws_iam_role.event_bridge_pipeline_trigger[""].name - policy = data.aws_iam_policy_document.event_bridge_pipeline_trigger.json + name = "event-bridge-access" + role = aws_iam_role.event_bridge_pipeline_trigger[""].name + policy = data.aws_iam_policy_document.event_bridge_pipeline_trigger.json } data "aws_iam_policy_document" "assume_event_bridge_policy" { diff --git a/codebase-pipelines/locals.tf b/codebase-pipelines/locals.tf index e8ebd69a..6c8d4386 100644 --- a/codebase-pipelines/locals.tf +++ b/codebase-pipelines/locals.tf @@ -7,8 +7,8 @@ locals { account_region = "${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}" - ecr_name = "${var.application}/${var.codebase}" - repository_url = coalesce(var.additional_ecr_repository, "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}") + ecr_name = "${var.application}/${var.codebase}" + repository_url = coalesce(var.additional_ecr_repository, "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}") pipeline_branches = distinct([ for pipeline in var.pipelines : pipeline.branch if lookup(pipeline, "branch", null) != null From d46829393045900d98ba027d5a1df58b2c7ca686 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Mon, 17 Feb 2025 18:16:12 +0000 Subject: [PATCH 08/13] Fix format for additional_ecr_repo for public and private repositories --- codebase-pipelines/buildspec-deploy.yml | 2 +- codebase-pipelines/iam.tf | 70 +++++++------ codebase-pipelines/locals.tf | 8 +- codebase-pipelines/tests/unit.tftest.hcl | 126 ++++++++++++++++------- 4 files changed, 134 insertions(+), 72 deletions(-) diff --git a/codebase-pipelines/buildspec-deploy.yml b/codebase-pipelines/buildspec-deploy.yml index 6224b5b5..eb51f653 100644 --- a/codebase-pipelines/buildspec-deploy.yml +++ b/codebase-pipelines/buildspec-deploy.yml @@ -20,7 +20,7 @@ phases: # Check if the specified image tag exists - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com - | - if ! regctl manifest head "${REPOSITORY_URL}:${IMAGE_TAG}" > /dev/null 2>&1; then + if ! docker manifest inspect "${REPOSITORY_URL}:${IMAGE_TAG}" > /dev/null 2>&1; then echo "Error: image tag ${IMAGE_TAG} not found in repository ${REPOSITORY_URL}" exit 1 fi diff --git a/codebase-pipelines/iam.tf b/codebase-pipelines/iam.tf index f4cbe488..147590fd 100644 --- a/codebase-pipelines/iam.tf +++ b/codebase-pipelines/iam.tf @@ -73,32 +73,6 @@ data "aws_iam_policy_document" "ecr_access_for_codebuild_images" { ] } - statement { - effect = "Allow" - actions = [ - "ecr-public:DescribeImageScanFindings", - "ecr-public:GetLifecyclePolicyPreview", - "ecr-public:GetDownloadUrlForLayer", - "ecr-public:BatchGetImage", - "ecr-public:DescribeImages", - "ecr-public:ListTagsForResource", - "ecr-public:BatchCheckLayerAvailability", - "ecr-public:GetLifecyclePolicy", - "ecr-public:GetRepositoryPolicy", - "ecr-public:PutImage", - "ecr-public:InitiateLayerUpload", - "ecr-public:UploadLayerPart", - "ecr-public:CompleteLayerUpload", - "ecr-public:BatchDeleteImage", - "ecr-public:DescribeRepositories", - "ecr-public:ListImages" - ] - resources = [ - # We have to wildcard the repository name because we currently expect the repository URL and it's not possible to get the ARN from that - "arn:aws:ecr-public::${data.aws_caller_identity.current.account_id}:repository/*" - ] - } - statement { effect = "Allow" actions = [ @@ -119,9 +93,40 @@ data "aws_iam_policy_document" "ecr_access_for_codebuild_images" { "ecr:DescribeRepositories", "ecr:ListImages" ] - resources = [ - aws_ecr_repository.this.arn - ] + resources = compact([ + "arn:aws:ecr:${local.account_region}:repository/${local.ecr_name}", + local.additional_private_repo_arn + ]) + } + + dynamic "statement" { + for_each = toset(local.is_additional_repo_public ? [""] : []) + + content { + effect = "Allow" + actions = [ + "ecr-public:DescribeImageScanFindings", + "ecr-public:GetLifecyclePolicyPreview", + "ecr-public:GetDownloadUrlForLayer", + "ecr-public:BatchGetImage", + "ecr-public:DescribeImages", + "ecr-public:ListTagsForResource", + "ecr-public:BatchCheckLayerAvailability", + "ecr-public:GetLifecyclePolicy", + "ecr-public:GetRepositoryPolicy", + "ecr-public:PutImage", + "ecr-public:InitiateLayerUpload", + "ecr-public:UploadLayerPart", + "ecr-public:CompleteLayerUpload", + "ecr-public:BatchDeleteImage", + "ecr-public:DescribeRepositories", + "ecr-public:ListImages" + ] + resources = [ + # We have to wildcard the repository name because we currently expect the repository URL and it's not possible to get the ARN from that + "arn:aws:ecr-public::${data.aws_caller_identity.current.account_id}:repository/*" + ] + } } } @@ -184,9 +189,10 @@ data "aws_iam_policy_document" "ecr_access_for_codebase_pipeline" { "ecr:BatchGetImage", "ecr:GetDownloadUrlForLayer" ] - resources = [ - aws_ecr_repository.this.arn - ] + resources = compact([ + "arn:aws:ecr:${local.account_region}:repository/${local.ecr_name}", + local.additional_private_repo_arn + ]) } statement { effect = "Allow" diff --git a/codebase-pipelines/locals.tf b/codebase-pipelines/locals.tf index 6c8d4386..4caaf426 100644 --- a/codebase-pipelines/locals.tf +++ b/codebase-pipelines/locals.tf @@ -7,8 +7,12 @@ locals { account_region = "${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}" - ecr_name = "${var.application}/${var.codebase}" - repository_url = coalesce(var.additional_ecr_repository, "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/${local.ecr_name}") + ecr_name = "${var.application}/${var.codebase}" + private_repo_url = "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com" + is_additional_repo_public = var.additional_ecr_repository != null ? strcontains(var.additional_ecr_repository, "public.ecr.aws") : false + additional_ecr_url = var.additional_ecr_repository != null ? local.is_additional_repo_public ? var.additional_ecr_repository : "${local.private_repo_url}/${var.additional_ecr_repository}" : null + repository_url = coalesce(local.additional_ecr_url, "${local.private_repo_url}/${local.ecr_name}") + additional_private_repo_arn = var.additional_ecr_repository != null && !local.is_additional_repo_public ? "arn:aws:ecr:${local.account_region}:repository/${var.additional_ecr_repository}" : "" pipeline_branches = distinct([ for pipeline in var.pipelines : pipeline.branch if lookup(pipeline, "branch", null) != null diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index 5ea49909..96442f1c 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -63,6 +63,13 @@ override_data { } } +override_data { + target = data.aws_iam_policy_document.ecr_access_for_codebase_pipeline + values = { + json = "{\"Sid\": \"PipelineECRAccess\"}" + } +} + variables { env_config = { "*" = { @@ -331,7 +338,7 @@ run "test_codebuild_images_not_required" { } assert { condition = aws_cloudwatch_event_rule.ecr_image_publish[0].event_pattern == "{\"detail\":{\"action-type\":[\"PUSH\"],\"image-tag\":[\"latest\"],\"repository-name\":[\"my-app/my-codebase\"],\"result\":[\"SUCCESS\"]},\"detail-type\":[\"ECR Image Action\"],\"source\":[\"aws.ecr\"]}" - error_message = "Event pattern is incorrect ${jsonencode(aws_cloudwatch_event_rule.ecr_image_publish[0].event_pattern)}" + error_message = "Event pattern is incorrect" } assert { condition = aws_cloudwatch_event_rule.ecr_image_publish[1].event_pattern == "{\"detail\":{\"action-type\":[\"PUSH\"],\"image-tag\":[\"latest\"],\"repository-name\":[\"my-app/my-codebase\"],\"result\":[\"SUCCESS\"]},\"detail-type\":[\"ECR Image Action\"],\"source\":[\"aws.ecr\"]}" @@ -339,29 +346,95 @@ run "test_codebuild_images_not_required" { } } -run "test_additional_ecr_repository" { +run "test_additional_private_ecr_repository" { command = plan variables { - additional_ecr_repository = "my-additional-repository" + additional_ecr_repository = "repository-namespace/repository-name" } + assert { + condition = local.is_additional_repo_public == false + error_message = "Should be: false" + } assert { condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].name == "ADDITIONAL_ECR_REPOSITORY" error_message = "Should be: 'ADDITIONAL_ECR_REPOSITORY'" } assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "my-additional-repository" - error_message = "Should be: 'my-additional-repository'" + condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "repository-namespace/repository-name" + error_message = "Should be: repository-namespace/repository-name" } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"my-additional-repository\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" error_message = "Configuration environment variables incorrect" } + assert { + condition = length(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources) == 2 + error_message = "Unexpected resources" + } + assert { + condition = length(data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources) == 2 + error_message = "Unexpected resources" + } +} + +run "test_additional_ecr_repository_public" { + command = plan + + variables { + additional_ecr_repository = "public.ecr.aws/repository-namespace/repository-name" + } + + assert { + condition = local.is_additional_repo_public == true + error_message = "Should be: true" + } + assert { + condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "public.ecr.aws/repository-namespace/repository-name" + error_message = "Should be: 'public.ecr.aws/repository-namespace/repository-name'" + } + assert { + condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"public.ecr.aws/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + error_message = "Configuration environment variables incorrect" + } + assert { + condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"public.ecr.aws/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" + error_message = "Configuration environment variables incorrect" + } + assert { + condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[2].effect == "Allow" + error_message = "Should be: Allow" + } + assert { + condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[2].actions == toset([ + "ecr-public:DescribeImageScanFindings", + "ecr-public:GetLifecyclePolicyPreview", + "ecr-public:GetDownloadUrlForLayer", + "ecr-public:BatchGetImage", + "ecr-public:DescribeImages", + "ecr-public:ListTagsForResource", + "ecr-public:BatchCheckLayerAvailability", + "ecr-public:GetLifecyclePolicy", + "ecr-public:GetRepositoryPolicy", + "ecr-public:PutImage", + "ecr-public:InitiateLayerUpload", + "ecr-public:UploadLayerPart", + "ecr-public:CompleteLayerUpload", + "ecr-public:BatchDeleteImage", + "ecr-public:DescribeRepositories", + "ecr-public:ListImages" + ]) + error_message = "Unexpected actions" + } + assert { + condition = one(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[2].resources) == "arn:aws:ecr-public::${data.aws_caller_identity.current.account_id}:repository/*" + error_message = "Unexpected resources" + } } run "test_main_branch_filter" { @@ -635,35 +708,6 @@ run "test_iam_documents" { } assert { condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].actions == toset([ - "ecr-public:DescribeImageScanFindings", - "ecr-public:GetLifecyclePolicyPreview", - "ecr-public:GetDownloadUrlForLayer", - "ecr-public:BatchGetImage", - "ecr-public:DescribeImages", - "ecr-public:ListTagsForResource", - "ecr-public:BatchCheckLayerAvailability", - "ecr-public:GetLifecyclePolicy", - "ecr-public:GetRepositoryPolicy", - "ecr-public:PutImage", - "ecr-public:InitiateLayerUpload", - "ecr-public:UploadLayerPart", - "ecr-public:CompleteLayerUpload", - "ecr-public:BatchDeleteImage", - "ecr-public:DescribeRepositories", - "ecr-public:ListImages" - ]) - error_message = "Unexpected actions" - } - assert { - condition = one(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources) == "arn:aws:ecr-public::${data.aws_caller_identity.current.account_id}:repository/*" - error_message = "Unexpected resources" - } - assert { - condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[2].effect == "Allow" - error_message = "Should be: Allow" - } - assert { - condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[2].actions == toset([ "ecr:DescribeImageScanFindings", "ecr:GetLifecyclePolicyPreview", "ecr:GetDownloadUrlForLayer", @@ -683,6 +727,10 @@ run "test_iam_documents" { ]) error_message = "Unexpected actions" } + assert { + condition = length(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources) == 1 + error_message = "Unexpected resources" + } # Codestar connection assert { @@ -787,6 +835,10 @@ run "test_iam_documents" { ]) error_message = "Unexpected actions" } + assert { + condition = length(data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources) == 1 + error_message = "Unexpected resources" + } # SSM access assert { @@ -854,7 +906,7 @@ run "test_codebuild_deploy" { } assert { condition = one(aws_codebuild_project.codebase_deploy.environment).environment_variable[0].name == "ENV_CONFIG" - error_message = "Should be: 'ENV_CONFIG' ${jsonencode(one(aws_codebuild_project.codebase_deploy.environment).environment_variable[0])}" + error_message = "Should be: 'ENV_CONFIG'" } assert { condition = one(aws_codebuild_project.codebase_deploy.environment).environment_variable[0].value == "{\"dev\":{\"account\":\"000123456789\"},\"prod\":{\"account\":\"123456789000\"},\"staging\":{\"account\":\"000123456789\"}}" From acbb63493df54553728be5ec6f39957af6b15f46 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Tue, 18 Feb 2025 09:29:39 +0000 Subject: [PATCH 09/13] Tighten artifact store IAM access --- codebase-pipelines/artifactstore.tf | 8 ++++++++ codebase-pipelines/tests/unit.tftest.hcl | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/codebase-pipelines/artifactstore.tf b/codebase-pipelines/artifactstore.tf index c5a8e6ae..4e5a46ad 100644 --- a/codebase-pipelines/artifactstore.tf +++ b/codebase-pipelines/artifactstore.tf @@ -55,6 +55,14 @@ data "aws_iam_policy_document" "artifact_store_bucket_policy" { type = "AWS" identifiers = [for id in local.deploy_account_ids : "arn:aws:iam::${id}:root"] } + condition { + test = "ArnLike" + values = [ + for id in local.deploy_account_ids : + "arn:aws:iam::${id}:role/${var.application}-*-codebase-pipeline-deploy" + ] + variable = "aws:PrincipalArn" + } actions = [ "s3:*" ] diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index 96442f1c..cdb4dc76 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -180,6 +180,10 @@ run "test_artifact_store" { condition = flatten([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].principals : el.identifiers]) == ["arn:aws:iam::000123456789:root", "arn:aws:iam::123456789000:root"] error_message = "Bucket policy principals incorrect" } + assert { + condition = flatten([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].condition : el.values]) == ["arn:aws:iam::000123456789:role/my-app-*-codebase-pipeline-deploy", "arn:aws:iam::123456789000:role/my-app-*-codebase-pipeline-deploy"] + error_message = "Bucket policy condition incorrect" + } assert { condition = [for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].actions : el][0] == "s3:*" error_message = "Should be: s3:*" From d0e76e9f92672ed62b37268f80d5e9e7a5e3da9a Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Tue, 18 Feb 2025 10:32:33 +0000 Subject: [PATCH 10/13] Refactor env var tests to test individual values --- codebase-pipelines/tests/unit.tftest.hcl | 186 +++++++++++++++++++---- 1 file changed, 157 insertions(+), 29 deletions(-) diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index cdb4dc76..2523e922 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -370,12 +370,14 @@ run "test_additional_private_ecr_repository" { error_message = "Should be: repository-namespace/repository-name" } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name" + error_message = "REPOSITORY_URL environment variable incorrect" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name" + error_message = "REPOSITORY_URL environment variable incorrect" } assert { condition = length(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources) == 2 @@ -403,12 +405,14 @@ run "test_additional_ecr_repository_public" { error_message = "Should be: 'public.ecr.aws/repository-namespace/repository-name'" } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"public.ecr.aws/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "REPOSITORY_URL"]) == "public.ecr.aws/repository-namespace/repository-name" + error_message = "REPOSITORY_URL environment variable incorrect" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"public.ecr.aws/repository-namespace/repository-name\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "REPOSITORY_URL"]) == "public.ecr.aws/repository-namespace/repository-name" + error_message = "REPOSITORY_URL environment variable incorrect" } assert { condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[2].effect == "Allow" @@ -1077,12 +1081,56 @@ run "test_main_pipeline" { condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.ProjectName == "my-app-my-codebase-codebase-pipeline-deploy" error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } - assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "APPLICATION"]) == "my-app" + error_message = "APPLICATION environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "AWS_REGION"]) == "${data.aws_region.current.name}" + error_message = "AWS_REGION environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "AWS_ACCOUNT_ID"]) == "${data.aws_caller_identity.current.account_id}" + error_message = "AWS_ACCOUNT_ID environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "dev" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "IMAGE_TAG"]) == "#{variables.IMAGE_TAG}" + error_message = "IMAGE_TAG environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "PIPELINE_EXECUTION_ID"]) == "#{codepipeline.PipelineExecutionId}" + error_message = "PIPELINE_EXECUTION_ID environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase" + error_message = "REPOSITORY_URL environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-1" + error_message = "SERVICE environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "SLACK_CHANNEL_ID"]) == "/fake/slack/channel" + error_message = "SLACK_CHANNEL_ID environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : + var.type if var.name == "SLACK_CHANNEL_ID"]) == "PARAMETER_STORE" + error_message = "SLACK_CHANNEL_ID environment variable type is incorrect" } - assert { condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[0].run_order == 2 error_message = "Run order incorrect" @@ -1118,10 +1166,15 @@ run "test_main_pipeline" { error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } assert { - condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "dev" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-2" + error_message = "SERVICE environment variable incorrect" } - assert { condition = aws_codepipeline.codebase_pipeline[0].stage[1].action[1].run_order == 3 error_message = "Run order incorrect" @@ -1156,10 +1209,15 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"staging\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "staging" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-1" + error_message = "SERVICE environment variable incorrect" } - assert { condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[0].run_order == 2 error_message = "Run order incorrect" @@ -1168,11 +1226,17 @@ run "test_tagged_pipeline" { # Deploy service-2 action assert { condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[1].name == "service-2" - error_message = "Should be: service-1" + error_message = "Should be: service-2" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"staging\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "staging" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-2" + error_message = "SERVICE environment variable incorrect" } assert { condition = aws_codepipeline.codebase_pipeline[1].stage[1].action[1].run_order == 3 @@ -1217,8 +1281,14 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[2].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "prod" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[2].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-1" + error_message = "SERVICE environment variable incorrect" } assert { condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[1].run_order == 2 @@ -1231,8 +1301,14 @@ run "test_tagged_pipeline" { error_message = "Should be: service-1" } assert { - condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[2].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[2].action[2].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "prod" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[1].stage[2].action[2].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-2" + error_message = "SERVICE environment variable incorrect" } assert { condition = aws_codepipeline.codebase_pipeline[1].stage[2].action[2].run_order == 3 @@ -1370,8 +1446,54 @@ run "test_manual_release_pipeline" { error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-1\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "APPLICATION"]) == "my-app" + error_message = "APPLICATION environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "AWS_REGION"]) == "${data.aws_region.current.name}" + error_message = "AWS_REGION environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "AWS_ACCOUNT_ID"]) == "${data.aws_caller_identity.current.account_id}" + error_message = "AWS_ACCOUNT_ID environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "#{variables.ENVIRONMENT}" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "IMAGE_TAG"]) == "#{variables.IMAGE_TAG}" + error_message = "IMAGE_TAG environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "PIPELINE_EXECUTION_ID"]) == "#{codepipeline.PipelineExecutionId}" + error_message = "PIPELINE_EXECUTION_ID environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase" + error_message = "REPOSITORY_URL environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-1" + error_message = "SERVICE environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.value if var.name == "SLACK_CHANNEL_ID"]) == "/fake/slack/channel" + error_message = "SLACK_CHANNEL_ID environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : + var.type if var.name == "SLACK_CHANNEL_ID"]) == "PARAMETER_STORE" + error_message = "SLACK_CHANNEL_ID environment variable type is incorrect" } assert { condition = aws_codepipeline.manual_release_pipeline.stage[1].action[0].run_order == 2 @@ -1408,8 +1530,14 @@ run "test_manual_release_pipeline" { error_message = "Should be: my-app-my-codebase-codebase-pipeline-deploy" } assert { - condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables == "[{\"name\":\"APPLICATION\",\"value\":\"my-app\"},{\"name\":\"AWS_REGION\",\"value\":\"${data.aws_region.current.name}\"},{\"name\":\"AWS_ACCOUNT_ID\",\"value\":\"${data.aws_caller_identity.current.account_id}\"},{\"name\":\"ENVIRONMENT\",\"value\":\"#{variables.ENVIRONMENT}\"},{\"name\":\"IMAGE_TAG\",\"value\":\"#{variables.IMAGE_TAG}\"},{\"name\":\"PIPELINE_EXECUTION_ID\",\"value\":\"#{codepipeline.PipelineExecutionId}\"},{\"name\":\"REPOSITORY_URL\",\"value\":\"${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/my-app/my-codebase\"},{\"name\":\"SERVICE\",\"value\":\"service-2\"},{\"name\":\"SLACK_CHANNEL_ID\",\"type\":\"PARAMETER_STORE\",\"value\":\"/fake/slack/channel\"}]" - error_message = "Configuration environment variables incorrect" + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "ENVIRONMENT"]) == "#{variables.ENVIRONMENT}" + error_message = "ENVIRONMENT environment variable incorrect" + } + assert { + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[1].configuration.EnvironmentVariables) : + var.value if var.name == "SERVICE"]) == "service-2" + error_message = "SERVICE environment variable incorrect" } assert { condition = aws_codepipeline.manual_release_pipeline.stage[1].action[1].run_order == 3 From 2447e55d73693ccf8e80e24ab67345e55f8351aa Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Tue, 18 Feb 2025 10:37:57 +0000 Subject: [PATCH 11/13] Duplicate test should be manual release pipeline test --- codebase-pipelines/tests/unit.tftest.hcl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index 2523e922..dd400b87 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -370,12 +370,12 @@ run "test_additional_private_ecr_repository" { error_message = "Should be: repository-namespace/repository-name" } assert { - condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[0].configuration.EnvironmentVariables) : var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name" error_message = "REPOSITORY_URL environment variable incorrect" } assert { - condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name" error_message = "REPOSITORY_URL environment variable incorrect" } @@ -410,7 +410,7 @@ run "test_additional_ecr_repository_public" { error_message = "REPOSITORY_URL environment variable incorrect" } assert { - condition = one([for var in jsondecode(aws_codepipeline.codebase_pipeline[0].stage[1].action[1].configuration.EnvironmentVariables) : + condition = one([for var in jsondecode(aws_codepipeline.manual_release_pipeline.stage[1].action[0].configuration.EnvironmentVariables) : var.value if var.name == "REPOSITORY_URL"]) == "public.ecr.aws/repository-namespace/repository-name" error_message = "REPOSITORY_URL environment variable incorrect" } From f5c3984dd3bf777b799d3e69110367adf8ed7ff6 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Tue, 18 Feb 2025 16:58:01 +0000 Subject: [PATCH 12/13] Refactor codebuild env var tests; Add missing resource tests --- codebase-pipelines/tests/unit.tftest.hcl | 56 ++++++++++++++++-------- 1 file changed, 38 insertions(+), 18 deletions(-) diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index dd400b87..e391dd9c 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -180,6 +180,14 @@ run "test_artifact_store" { condition = flatten([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].principals : el.identifiers]) == ["arn:aws:iam::000123456789:root", "arn:aws:iam::123456789000:root"] error_message = "Bucket policy principals incorrect" } + assert { + condition = one([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].condition : el.test]) == "ArnLike" + error_message = "Bucket policy condition incorrect" + } + assert { + condition = one([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].condition : el.variable]) == "aws:PrincipalArn" + error_message = "Bucket policy condition incorrect" + } assert { condition = flatten([for el in data.aws_iam_policy_document.artifact_store_bucket_policy.statement[1].condition : el.values]) == ["arn:aws:iam::000123456789:role/my-app-*-codebase-pipeline-deploy", "arn:aws:iam::123456789000:role/my-app-*-codebase-pipeline-deploy"] error_message = "Bucket policy condition incorrect" @@ -226,19 +234,13 @@ run "test_codebuild_images" { error_message = "Should be: 'public.ecr.aws/uktrade/ci-image-builder:tag-latest'" } assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[1].name == "ECR_REPOSITORY" - error_message = "Should be: 'ECR_REPOSITORY'" - } - assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[1].value == "my-app/my-codebase" + condition = one([for var in one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable : + var.value if var.name == "ECR_REPOSITORY"]) == "my-app/my-codebase" error_message = "Should be: 'my-app/my-codebase'" } assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[3].name == "SLACK_CHANNEL_ID" - error_message = "Should be: 'SLACK_CHANNEL_ID'" - } - assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[3].value == "/fake/slack/channel" + condition = one([for var in one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable : + var.value if var.name == "SLACK_CHANNEL_ID"]) == "/fake/slack/channel" error_message = "Should be: '/fake/slack/channel'" } assert { @@ -362,11 +364,8 @@ run "test_additional_private_ecr_repository" { error_message = "Should be: false" } assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].name == "ADDITIONAL_ECR_REPOSITORY" - error_message = "Should be: 'ADDITIONAL_ECR_REPOSITORY'" - } - assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "repository-namespace/repository-name" + condition = one([for var in one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable : + var.value if var.name == "ADDITIONAL_ECR_REPOSITORY"]) == "repository-namespace/repository-name" error_message = "Should be: repository-namespace/repository-name" } assert { @@ -379,10 +378,24 @@ run "test_additional_private_ecr_repository" { var.value if var.name == "REPOSITORY_URL"]) == "${data.aws_caller_identity.current.account_id}.dkr.ecr.${data.aws_region.current.name}.amazonaws.com/repository-namespace/repository-name" error_message = "REPOSITORY_URL environment variable incorrect" } + assert { + condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources == toset([ + "arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase", + "arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/repository-namespace/repository-name" + ]) + error_message = "Unexpected resources" + } assert { condition = length(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources) == 2 error_message = "Unexpected resources" } + assert { + condition = data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources == toset([ + "arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase", + "arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/repository-namespace/repository-name" + ]) + error_message = "Unexpected resources" + } assert { condition = length(data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources) == 2 error_message = "Unexpected resources" @@ -401,7 +414,8 @@ run "test_additional_ecr_repository_public" { error_message = "Should be: true" } assert { - condition = one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable[4].value == "public.ecr.aws/repository-namespace/repository-name" + condition = one([for var in one(aws_codebuild_project.codebase_image_build[""].environment).environment_variable : + var.value if var.name == "ADDITIONAL_ECR_REPOSITORY"]) == "public.ecr.aws/repository-namespace/repository-name" error_message = "Should be: 'public.ecr.aws/repository-namespace/repository-name'" } assert { @@ -735,6 +749,10 @@ run "test_iam_documents" { ]) error_message = "Unexpected actions" } + assert { + condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources == toset(["arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase"]) + error_message = "Unexpected resources" + } assert { condition = length(data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources) == 1 error_message = "Unexpected resources" @@ -843,6 +861,10 @@ run "test_iam_documents" { ]) error_message = "Unexpected actions" } + assert { + condition = data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources == toset(["arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase"]) + error_message = "Unexpected resources" + } assert { condition = length(data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources) == 1 error_message = "Unexpected resources" @@ -853,7 +875,6 @@ run "test_iam_documents" { condition = data.aws_iam_policy_document.deploy_ssm_access.statement[0].effect == "Allow" error_message = "Should be: Allow" } - assert { condition = data.aws_iam_policy_document.deploy_ssm_access.statement[0].actions == toset([ "ssm:GetParameter", @@ -861,7 +882,6 @@ run "test_iam_documents" { ]) error_message = "Unexpected actions" } - assert { condition = one(data.aws_iam_policy_document.deploy_ssm_access.statement[0].resources) == "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/codebuild/slack_*" error_message = "Unexpected resources" From aa584d640cecc3e4b345e2ac27dc69e83d39c92d Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Wed, 19 Feb 2025 09:49:16 +0000 Subject: [PATCH 13/13] Formatting --- codebase-pipelines/tests/unit.tftest.hcl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codebase-pipelines/tests/unit.tftest.hcl b/codebase-pipelines/tests/unit.tftest.hcl index e391dd9c..85669b02 100644 --- a/codebase-pipelines/tests/unit.tftest.hcl +++ b/codebase-pipelines/tests/unit.tftest.hcl @@ -750,7 +750,7 @@ run "test_iam_documents" { error_message = "Unexpected actions" } assert { - condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources == toset(["arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase"]) + condition = data.aws_iam_policy_document.ecr_access_for_codebuild_images.statement[1].resources == toset(["arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase"]) error_message = "Unexpected resources" } assert { @@ -862,7 +862,7 @@ run "test_iam_documents" { error_message = "Unexpected actions" } assert { - condition = data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources == toset(["arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase"]) + condition = data.aws_iam_policy_document.ecr_access_for_codebase_pipeline.statement[0].resources == toset(["arn:aws:ecr:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:repository/my-app/my-codebase"]) error_message = "Unexpected resources" } assert {