From a7f701c6f0fbe94ec34a715fdcbcf173b5214391 Mon Sep 17 00:00:00 2001 From: John Stainsby Date: Wed, 15 May 2024 14:54:54 +0100 Subject: [PATCH] feat: DBTP-914 - Environment pipeline terraform apply (#116) Co-authored-by: Anthony Roy Co-authored-by: Will Gibson <8738245+WillGibson@users.noreply.github.com> --- .checkov.baseline | 30 +- environment-pipelines/buildspec-apply.yml | 15 + environment-pipelines/buildspec-plan.yml | 23 + environment-pipelines/buildspec.yml | 34 +- environment-pipelines/codebuild.tf | 89 +++- environment-pipelines/codepipeline.tf | 45 +- environment-pipelines/iam.tf | 502 +++++++++++++++++++- environment-pipelines/locals.tf | 50 +- environment-pipelines/stage_config.yml | 16 +- environment-pipelines/tests/unit.tftest.hcl | 493 ++++++++++++++++++- postgres/lambda.tf | 3 + 11 files changed, 1195 insertions(+), 105 deletions(-) create mode 100644 environment-pipelines/buildspec-apply.yml create mode 100644 environment-pipelines/buildspec-plan.yml diff --git a/.checkov.baseline b/.checkov.baseline index 90d7c08b0..da02a177e 100644 --- a/.checkov.baseline +++ b/.checkov.baseline @@ -6,25 +6,18 @@ { "resource": "module.extensions-staging.module.alb.aws_lb.this", "check_ids": [ - "CKV2_AWS_20", "CKV2_AWS_28", "CKV_AWS_131", "CKV_AWS_150" ] }, { - "resource": "module.extensions-staging.module.alb.aws_lb_listener.alb-listener[\"http\"]", + "resource": "module.extensions-staging.module.alb.aws_lb_listener.alb-listener", "check_ids": [ "CKV_AWS_103", "CKV_AWS_2" ] }, - { - "resource": "module.extensions-staging.module.alb.aws_lb_listener.alb-listener[\"https\"]", - "check_ids": [ - "CKV_AWS_103" - ] - }, { "resource": "module.extensions-staging.module.alb.aws_lb_target_group.http-target-group", "check_ids": [ @@ -32,17 +25,8 @@ ] }, { - "resource": "module.extensions-staging.module.alb.aws_security_group.alb-security-group[\"http\"]", + "resource": "module.extensions-staging.module.alb.aws_security_group.alb-security-group", "check_ids": [ - "CKV2_AWS_5", - "CKV_AWS_23", - "CKV_AWS_260" - ] - }, - { - "resource": "module.extensions-staging.module.alb.aws_security_group.alb-security-group[\"https\"]", - "check_ids": [ - "CKV2_AWS_5", "CKV_AWS_23" ] } @@ -124,12 +108,6 @@ "check_ids": [ "CKV_AWS_158" ] - }, - { - "resource": "aws_codebuild_project.environment_pipeline", - "check_ids": [ - "CKV_AWS_147" - ] } ] }, @@ -353,10 +331,10 @@ "resource": "aws_vpc.vpc", "check_ids": [ "CKV2_AWS_11", - "CKV2_AWS_12" + "CKV2_AWS_12" ] } ] } ] -} +} \ No newline at end of file diff --git a/environment-pipelines/buildspec-apply.yml b/environment-pipelines/buildspec-apply.yml new file mode 100644 index 000000000..0bd0de567 --- /dev/null +++ b/environment-pipelines/buildspec-apply.yml @@ -0,0 +1,15 @@ +version: 0.2 +phases: + install: + commands: + - export PATH="$CODEBUILD_SRC_DIR/build-tools:$PATH" + - mv $CODEBUILD_SRC_DIR_terraform_plan/terraform/${ENVIRONMENT}/plan.tfplan $CODEBUILD_SRC_DIR/terraform/${ENVIRONMENT}/ + build: + commands: + - echo "Terraform Apply Phase" + - echo "Working on environment ${ENVIRONMENT}" + - cd terraform/${ENVIRONMENT} + - terraform init + - terraform apply plan.tfplan +artifacts: + files: [] diff --git a/environment-pipelines/buildspec-plan.yml b/environment-pipelines/buildspec-plan.yml new file mode 100644 index 000000000..28d932647 --- /dev/null +++ b/environment-pipelines/buildspec-plan.yml @@ -0,0 +1,23 @@ +version: 0.2 + +env: + exported-variables: + - BUILD_ID + +phases: + install: + commands: + - export PATH="$CODEBUILD_SRC_DIR/build-tools:$PATH" + build: + commands: + - echo "Terraform Plan Phase" + - echo "Working on environment ${ENVIRONMENT}" + - cd terraform/${ENVIRONMENT} + - terraform init + - terraform plan -out=plan.tfplan + post_build: + commands: + - export BUILD_ID="$CODEBUILD_BUILD_ID" +artifacts: + files: + - terraform/${ENVIRONMENT}/plan.tfplan diff --git a/environment-pipelines/buildspec.yml b/environment-pipelines/buildspec.yml index 77626d2d5..1c4ad8e03 100644 --- a/environment-pipelines/buildspec.yml +++ b/environment-pipelines/buildspec.yml @@ -19,30 +19,22 @@ phases: - COPILOT_VERSION=`cat .copilot-version` - PLATFORM_HELPER_VERSION=`cat .platform-helper-version` - echo "Install Phase" - - yum -y install zip wget which python-pip + - yum -y install python-pip + - mkdir ./build-tools + - cd ./build-tools - curl -s -qL -o terraform_install.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip - - unzip terraform_install.zip -d /usr/bin/ - - chmod +x /usr/bin/terraform - - VERSION_OUTPUT+=$(terraform version | head -n1) - - wget -q https://ecs-cli-v2-release.s3.amazonaws.com/copilot-linux-v${COPILOT_VERSION} -O /usr/bin/copilot - - chmod +x /usr/bin/copilot - - pip install dbt-platform-helper==$PLATFORM_HELPER_VERSION - - VERSION_OUTPUT+="\n$(platform-helper --version)" - - VERSION_OUTPUT+="\n$(copilot --version)" + - unzip terraform_install.zip + - chmod +x terraform + - rm terraform_install.zip + - curl -s -qL -o copilot https://ecs-cli-v2-release.s3.amazonaws.com/copilot-linux-v${COPILOT_VERSION} + - chmod +x copilot + - pip install --quiet --target . dbt-platform-helper==$PLATFORM_HELPER_VERSION + - VERSION_OUTPUT+="\n$(./terraform --version)" + - VERSION_OUTPUT+="\n$(./platform_helper.py --version)" + - VERSION_OUTPUT+="\n$(./copilot --version)" - VERSION_OUTPUT+="\n$(python3 --version)" - VERSION_OUTPUT+="\n$(pip --version)" - echo -e "=============\nTool Versions\n-------------\n$VERSION_OUTPUT" - - build: - commands: - - echo "Terraform Plan Phase" - - echo "Working on environment ${ENVIRONMENT}" - - cd terraform/${ENVIRONMENT} - - terraform init - - terraform plan -out=plan.tfplan - post_build: - commands: - - echo "Post Build Phase" artifacts: files: - - terraform/${ENVIRONMENT}/plan.tfplan + - "**/*" diff --git a/environment-pipelines/codebuild.tf b/environment-pipelines/codebuild.tf index 0a34e9764..794ab0b5f 100644 --- a/environment-pipelines/codebuild.tf +++ b/environment-pipelines/codebuild.tf @@ -1,8 +1,9 @@ -resource "aws_codebuild_project" "environment_pipeline" { - name = "${var.application}-environment-pipeline" - description = "Provisions the ${var.application} application's extensions." - build_timeout = 5 - service_role = aws_iam_role.environment_pipeline_codebuild.arn +resource "aws_codebuild_project" "environment_pipeline_build" { + name = "${var.application}-environment-pipeline-build" + description = "Provisions the ${var.application} application's extensions." + build_timeout = 5 + service_role = aws_iam_role.environment_pipeline_codebuild.arn + encryption_key = module.artifact_store.kms_key_arn artifacts { type = "CODEPIPELINE" @@ -45,3 +46,81 @@ resource "aws_cloudwatch_log_stream" "environment_pipeline_codebuild" { name = "codebuild/${var.application}-environment-terraform/log-stream" log_group_name = aws_cloudwatch_log_group.environment_pipeline_codebuild.name } + +# Terraform plan +resource "aws_codebuild_project" "environment_pipeline_plan" { + name = "${var.application}-environment-pipeline-plan" + description = "Provisions the ${var.application} application's extensions." + build_timeout = 5 + service_role = aws_iam_role.environment_pipeline_codebuild.arn + encryption_key = module.artifact_store.kms_key_arn + + artifacts { + type = "CODEPIPELINE" + } + + cache { + type = "S3" + location = module.artifact_store.bucket_name + } + + environment { + compute_type = "BUILD_GENERAL1_SMALL" + image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0" + type = "LINUX_CONTAINER" + image_pull_credentials_type = "CODEBUILD" + } + + logs_config { + cloudwatch_logs { + group_name = aws_cloudwatch_log_group.environment_pipeline_codebuild.name + stream_name = aws_cloudwatch_log_stream.environment_pipeline_codebuild.name + } + } + + source { + type = "CODEPIPELINE" + buildspec = file("${path.module}/buildspec-plan.yml") + } + + tags = local.tags +} + +# Terraform apply +resource "aws_codebuild_project" "environment_pipeline_apply" { + name = "${var.application}-environment-pipeline-apply" + description = "Provisions the ${var.application} application's extensions." + build_timeout = 60 + service_role = aws_iam_role.environment_pipeline_codebuild.arn + encryption_key = module.artifact_store.kms_key_arn + + artifacts { + type = "CODEPIPELINE" + } + + cache { + type = "S3" + location = module.artifact_store.bucket_name + } + + environment { + compute_type = "BUILD_GENERAL1_SMALL" + image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0" + type = "LINUX_CONTAINER" + image_pull_credentials_type = "CODEBUILD" + } + + logs_config { + cloudwatch_logs { + group_name = aws_cloudwatch_log_group.environment_pipeline_codebuild.name + stream_name = aws_cloudwatch_log_stream.environment_pipeline_codebuild.name + } + } + + source { + type = "CODEPIPELINE" + buildspec = file("${path.module}/buildspec-apply.yml") + } + + tags = local.tags +} diff --git a/environment-pipelines/codepipeline.tf b/environment-pipelines/codepipeline.tf index 77955ddcb..26e622c41 100644 --- a/environment-pipelines/codepipeline.tf +++ b/environment-pipelines/codepipeline.tf @@ -36,31 +36,40 @@ resource "aws_codepipeline" "environment_pipeline" { } } + stage { + name = "Build" + + action { + name = "InstallTools" + category = "Build" + owner = "AWS" + provider = "CodeBuild" + input_artifacts = ["project_deployment_source"] + output_artifacts = ["build_output"] + version = "1" + + configuration = { + ProjectName = "${var.application}-environment-pipeline-build" + PrimarySource = "project_deployment_source" + } + } + } dynamic "stage" { for_each = local.stages content { - name = "Build" + name = stage.value.stage_name action { - name = "InstallTools" - category = "Build" - owner = "AWS" - provider = "CodeBuild" - input_artifacts = ["project_deployment_source"] - output_artifacts = ["terraform_plan"] + name = stage.value.name + category = stage.value.category + owner = stage.value.owner + provider = stage.value.provider + input_artifacts = stage.value.input_artifacts + output_artifacts = stage.value.output_artifacts version = "1" - - configuration = { - ProjectName = "${var.application}-environment-pipeline" - PrimarySource = "project_deployment_source" - EnvironmentVariables = jsonencode([ - { - name = "ENVIRONMENT" - value = stage.value.env - } - ]) - } + configuration = stage.value.configuration + namespace = stage.value.namespace } } } diff --git a/environment-pipelines/iam.tf b/environment-pipelines/iam.tf index fd09bb594..f78e6e8cc 100644 --- a/environment-pipelines/iam.tf +++ b/environment-pipelines/iam.tf @@ -116,7 +116,8 @@ data "aws_iam_policy_document" "state_kms_key_access" { statement { actions = [ "kms:ListKeys", - "kms:Decrypt" + "kms:Decrypt", + "kms:GenerateDataKey" ] resources = [ data.aws_kms_key.state_kms_key.arn @@ -143,7 +144,8 @@ data "aws_iam_policy_document" "ec2_read_access" { actions = [ "ec2:DescribeVpcs", "ec2:DescribeSubnets", - "ec2:DescribeSecurityGroups" + "ec2:DescribeSecurityGroups", + "ec2:DescribeNetworkInterfaces" ] resources = [ "*" @@ -152,7 +154,8 @@ data "aws_iam_policy_document" "ec2_read_access" { statement { actions = [ - "ec2:DescribeVpcAttribute" + "ec2:DescribeVpcAttribute", + "ec2:CreateSecurityGroup" ] resources = [ "arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:vpc/*" @@ -167,7 +170,7 @@ data "aws_ssm_parameter" "central_log_group_parameter" { data "aws_iam_policy_document" "ssm_read_access" { statement { actions = [ - "ssm:GetParameter", + "ssm:GetParameter" ] resources = [ data.aws_ssm_parameter.central_log_group_parameter.arn @@ -178,9 +181,430 @@ data "aws_iam_policy_document" "ssm_read_access" { data "aws_iam_policy_document" "dns_account_assume_role" { statement { actions = [ - "sts:AssumeRole", + "sts:AssumeRole" + ] + resources = local.dns_entries + } +} + +data "aws_iam_policy_document" "load_balancer" { + statement { + actions = [ + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetGroupAttributes", + "elasticloadbalancing:DescribeTags", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeSSLPolicies", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:DescribeRules" + ] + resources = [ + "*" + ] + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "elasticloadbalancing:CreateTargetGroup", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:ModifyTargetGroupAttributes", + "elasticloadbalancing:DeleteTargetGroup" + ] + resources = [ + "arn:aws:elasticloadbalancing:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:targetgroup/${var.application}-${statement.value.name}-http/*" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:AddTags", + "elasticloadbalancing:ModifyLoadBalancerAttributes", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:CreateListener" + ] + resources = [ + "arn:aws:elasticloadbalancing:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:loadbalancer/app/${var.application}-${statement.value.name}/*" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "elasticloadbalancing:AddTags" + ] + resources = [ + "arn:aws:elasticloadbalancing:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:listener/app/${var.application}-${statement.value.name}/*" + ] + } + } +} + +data "aws_iam_policy_document" "certificate" { + statement { + actions = [ + "acm:RequestCertificate", + "acm:AddTagsToCertificate", + "acm:DescribeCertificate", + "acm:ListTagsForCertificate", + "acm:DeleteCertificate" + ] + resources = [ + "arn:aws:acm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:certificate/*" + ] + } +} + +data "aws_iam_policy_document" "security_group" { + statement { + actions = [ + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:RevokeSecurityGroupEgress", + "ec2:DeleteSecurityGroup", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AuthorizeSecurityGroupEgress" + ] + resources = [ + "arn:aws:ec2:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:security-group/*" + ] + } +} + +data "aws_iam_policy_document" "ssm_parameter" { + statement { + actions = [ + "ssm:DescribeParameters" + ] + resources = [ + "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:*" + ] + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "ssm:PutParameter", + "ssm:GetParameter", + "ssm:GetParameters", + "ssm:DeleteParameter", + "ssm:AddTagsToResource", + "ssm:ListTagsForResource" + ] + resources = [ + "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/copilot/${var.application}/${statement.value.name}/secrets/*", + "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/copilot/applications/${var.application}/environments/${statement.value.name}/*" + ] + } + } +} + +data "aws_iam_policy_document" "cloudwatch" { + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "cloudwatch:GetDashboard", + "cloudwatch:PutDashboard", + "cloudwatch:DeleteDashboards" + ] + resources = [ + "arn:aws:cloudwatch::${data.aws_caller_identity.current.account_id}:dashboard/${var.application}-${statement.value.name}-compute" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "resource-groups:GetGroup", + "resource-groups:CreateGroup", + "resource-groups:Tag", + "resource-groups:GetGroupQuery", + "resource-groups:GetGroupConfiguration", + "resource-groups:GetTags", + "resource-groups:DeleteGroup" + ] + resources = [ + "arn:aws:resource-groups:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:group/${var.application}-${statement.value.name}-application-insights-resources" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "applicationinsights:CreateApplication", + "applicationinsights:TagResource", + "applicationinsights:DescribeApplication", + "applicationinsights:ListTagsForResource", + "applicationinsights:DeleteApplication" + ] + resources = [ + "arn:aws:applicationinsights:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:application/resource-group/${var.application}-${statement.value.name}-application-insights-resources" + ] + } + } +} + +data "aws_iam_policy_document" "logs" { + statement { + actions = [ + "logs:DescribeResourcePolicies", + "logs:PutResourcePolicy", + "logs:DeleteResourcePolicy", + "logs:DescribeLogGroups" + ] + resources = [ + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group::log-stream:" + ] + } + + statement { + actions = [ + "logs:PutSubscriptionFilter" + ] + resources = [ + local.central_log_destination_arn + ] + } + + statement { + actions = [ + "logs:PutRetentionPolicy", + "logs:ListTagsLogGroup", + "logs:DeleteLogGroup", + "logs:CreateLogGroup", + "logs:PutSubscriptionFilter", + "logs:DescribeSubscriptionFilters", + "logs:DeleteSubscriptionFilter", + "logs:TagResource" + ] + resources = [ + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/opensearch/*", + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/rds/*", + "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/elasticache/*" + ] + } +} + +data "aws_iam_policy_document" "kms_key" { + statement { + actions = [ + "kms:CreateKey", + "kms:ListAliases" + ] + resources = [ + "*" + ] + } + + statement { + actions = [ + "kms:*" + ] + resources = [ + "arn:aws:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:key/*" + ] + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "kms:CreateAlias", + "kms:DeleteAlias" + ] + resources = [ + "arn:aws:kms:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:alias/${var.application}-${statement.value.name}-key" + ] + } + } +} + + + +data "aws_iam_policy_document" "redis" { + statement { + actions = [ + "elasticache:CreateCacheSubnetGroup", + "elasticache:AddTagsToResource", + "elasticache:DescribeCacheSubnetGroups", + "elasticache:ListTagsForResource", + "elasticache:DeleteCacheSubnetGroup", + "elasticache:CreateReplicationGroup" + ] + resources = [ + "arn:aws:elasticache:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:subnetgroup:*" + ] + } + + statement { + actions = [ + "elasticache:CreateReplicationGroup", + "elasticache:AddTagsToResource", + "elasticache:DescribeReplicationGroups", + "elasticache:ListTagsForResource", + "elasticache:DeleteReplicationGroup" + ] + resources = [ + "arn:aws:elasticache:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:replicationgroup:*", + "arn:aws:elasticache:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parametergroup:*" + ] + } + + statement { + actions = [ + "elasticache:DescribeCacheClusters" + ] + resources = [ + "arn:aws:elasticache:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:cluster:*" + ] + } +} + +data "aws_iam_policy_document" "postgres" { + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "iam:CreateRole", + "iam:GetRole", + "iam:ListRolePolicies", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfilesForRole", + "iam:DeleteRole", + "iam:AttachRolePolicy", + "iam:PutRolePolicy", + "iam:GetRolePolicy", + "iam:PassRole" + ] + resources = [ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${var.application}-${statement.value.name}-*", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/rds-enhanced-monitoring-*" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "lambda:GetFunction", + "lambda:ListVersionsByFunction", + "lambda:GetFunctionCodeSigningConfig" + ] + resources = [ + "arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:function:${var.application}-${statement.value.name}-*" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "rds:CreateDBParameterGroup", + "rds:AddTagsToResource", + "rds:ModifyDBParameterGroup", + "rds:DescribeDBParameterGroups", + "rds:DescribeDBParameters", + "rds:ListTagsForResource", + "rds:CreateDBInstance" + ] + resources = [ + "arn:aws:rds:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:pg:${var.application}-${statement.value.name}-*" + ] + } + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "rds:CreateDBSubnetGroup", + "rds:AddTagsToResource", + "rds:DescribeDBSubnetGroups", + "rds:ListTagsForResource", + "rds:DeleteDBSubnetGroup", + "rds:CreateDBInstance" + ] + resources = [ + "arn:aws:rds:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:subgrp:${var.application}-${statement.value.name}-*" + ] + } + } + + statement { + actions = [ + "rds:DescribeDBInstances" + ] + resources = [ + "arn:aws:rds:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:db:*" + ] + } + + dynamic "statement" { + for_each = var.environments + content { + actions = [ + "rds:CreateDBInstance", + "rds:AddTagsToResource" + ] + resources = [ + "arn:aws:rds:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:db:${var.application}-${statement.value.name}-*" + ] + } + } + + statement { + actions = [ + "secretsmanager:*", + "kms:*" + ] + resources = [ + "arn:aws:secretsmanager:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:secret:rds*" + ] + } +} + +data "aws_iam_policy_document" "s3" { + statement { + actions = [ + "s3:*" + ] + resources = [ + "arn:aws:s3:::*" + ] + } +} + +data "aws_iam_policy_document" "opensearch" { + statement { + actions = [ + "es:CreateElasticsearchDomain", + "es:AddTags", + "es:DescribeDomain", + "es:DescribeDomainConfig", + "es:ListTags", + "es:DeleteDomain", + "es:UpdateDomainConfig" + ] + resources = [ + "arn:aws:es:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:domain/*" ] - resources = tolist(toset([for stage in local.stages : "arn:aws:iam::${stage.accounts.dns.id}:role/sandbox-codebuild-assume-role"])) } } @@ -252,3 +676,69 @@ resource "aws_iam_role_policy" "dns_account_assume_role_for_environment_codebuil role = aws_iam_role.environment_pipeline_codebuild.name policy = data.aws_iam_policy_document.dns_account_assume_role.json } + +resource "aws_iam_role_policy" "load_balancer_for_environment_codebuild" { + name = "${var.application}-load-balancer-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.load_balancer.json +} + +resource "aws_iam_role_policy" "certificate_for_environment_codebuild" { + name = "${var.application}-certificate-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.certificate.json +} + +resource "aws_iam_role_policy" "security_group_for_environment_codebuild" { + name = "${var.application}-security-group-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.security_group.json +} + +resource "aws_iam_role_policy" "ssm_parameter_for_environment_codebuild" { + name = "${var.application}-ssm-parameter-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.ssm_parameter.json +} + +resource "aws_iam_role_policy" "cloudwatch_for_environment_codebuild" { + name = "${var.application}-cloudwatch-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.cloudwatch.json +} + +resource "aws_iam_role_policy" "logs_for_environment_codebuild" { + name = "${var.application}-logs-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.logs.json +} + +resource "aws_iam_role_policy" "kms_key_for_environment_codebuild" { + name = "${var.application}-kms-key-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.kms_key.json +} + +resource "aws_iam_role_policy" "redis_for_environment_codebuild" { + name = "${var.application}-redis-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.redis.json +} + +resource "aws_iam_role_policy" "postgres_for_environment_codebuild" { + name = "${var.application}-postgres-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.postgres.json +} + +resource "aws_iam_role_policy" "s3_for_environment_codebuild" { + name = "${var.application}-s3-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.s3.json +} + +resource "aws_iam_role_policy" "opensearch_for_environment_codebuild" { + name = "${var.application}-opensearch-for-environment-codebuild" + role = aws_iam_role.environment_pipeline_codebuild.name + policy = data.aws_iam_policy_document.opensearch.json +} diff --git a/environment-pipelines/locals.tf b/environment-pipelines/locals.tf index 7bea3ff41..1345d3882 100644 --- a/environment-pipelines/locals.tf +++ b/environment-pipelines/locals.tf @@ -7,5 +7,53 @@ locals { stage_config = yamldecode(file("${path.module}/stage_config.yml")) - stages = [for env in var.environments : { type : "plan", env : env.name, approval : env.requires_approval, accounts : env.accounts }] + # We flatten a list of lists for each env: + initial_stages = flatten( + [for env in var.environments : [ + # The first element of the inner list for an env is the Plan stage. + { + type : "plan", + stage_name : "Plan-${env.name}", + env : env.name, + accounts : env.accounts, + configuration : { + ProjectName : "${var.application}-environment-pipeline-plan" + PrimarySource : "build_output" + EnvironmentVariables : jsonencode([{ name : "ENVIRONMENT", value : env.name }]) + } + }, + # The second element of the inner list for an env is the Approval stage if required, or the empty list otherwise. + coalesce(env.requires_approval, false) ? [{ + type : "approve", + stage_name : "Approve-${env.name}", + env : "" + configuration : { + CustomData : "Review Terraform Plan" + ExternalEntityLink : "https://${data.aws_region.current.name}.console.aws.amazon.com/codesuite/codebuild/${data.aws_caller_identity.current.account_id}/projects/${var.application}-environment-pipeline-tf-plan/build/#{tf-plan.BUILD_ID}" + }, + namespace : null + }] : [], + # The third element of the inner list for an env is the Apply stage. + { + type : "apply", + env : env.name, + stage_name : "Apply-${env.name}", + accounts : env.accounts, + configuration : { + ProjectName : "${var.application}-environment-pipeline-apply" + PrimarySource : "build_output" + EnvironmentVariables : jsonencode([{ name : "ENVIRONMENT", value : env.name }]) + }, + namespace : null + } + ] + ]) + + dns_ids = tolist(toset(flatten([for stage in local.initial_stages : lookup(stage, "accounts", null) != null ? [stage.accounts.dns.id] : []]))) + dns_entries = [for id in local.dns_ids : "arn:aws:iam::${id}:role/sandbox-codebuild-assume-role"] + + # Merge in the stage specific config from the stage_config.yml file: + stages = [for stage in local.initial_stages : merge(stage, local.stage_config[stage["type"]])] + + central_log_destination_arn = "arn:aws:logs:eu-west-2:812359060647:destination:cwl_log_destination" } diff --git a/environment-pipelines/stage_config.yml b/environment-pipelines/stage_config.yml index 236073f0b..ddefa7878 100644 --- a/environment-pipelines/stage_config.yml +++ b/environment-pipelines/stage_config.yml @@ -4,12 +4,13 @@ plan: category: "Build" owner: "AWS" provider: "CodeBuild" - input_artifacts: ["source_output"] - output_artifacts: ["build_output"] + input_artifacts: ["build_output"] + output_artifacts: ["terraform_plan"] + namespace: "tf-plan" version: "1" + approve: - run_order: 1 - name: "AWS-Admin-Approval" + name: "Approval" category: "Approval" owner: "AWS" provider: "Manual" @@ -18,3 +19,10 @@ approve: output_artifacts: [] apply: + name: "Apply" + category: "Build" + owner: "AWS" + provider: "CodeBuild" + input_artifacts: ["build_output", "terraform_plan"] + output_artifacts: [] + version: "1" diff --git a/environment-pipelines/tests/unit.tftest.hcl b/environment-pipelines/tests/unit.tftest.hcl index c2e4d9da6..dd87e4d78 100644 --- a/environment-pipelines/tests/unit.tftest.hcl +++ b/environment-pipelines/tests/unit.tftest.hcl @@ -56,6 +56,83 @@ override_data { } } +override_data { + target = data.aws_iam_policy_document.load_balancer + values = { + json = "{\"Sid\": \"LoadBalancer\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.certificate + values = { + json = "{\"Sid\": \"Certificate\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.security_group + values = { + json = "{\"Sid\": \"SecurityGroup\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.ssm_parameter + values = { + json = "{\"Sid\": \"SSMParameter\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.cloudwatch + values = { + json = "{\"Sid\": \"Cloudwatch\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.logs + values = { + json = "{\"Sid\": \"Logs\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.kms_key + values = { + json = "{\"Sid\": \"KMSKey\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.redis + values = { + json = "{\"Sid\": \"Redis\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.postgres + values = { + json = "{\"Sid\": \"Postgres\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.s3 + values = { + json = "{\"Sid\": \"S3\"}" + } +} + +override_data { + target = data.aws_iam_policy_document.opensearch + values = { + json = "{\"Sid\": \"OpenSearch\"}" + } +} + variables { application = "my-app" repository = "my-repository" @@ -187,21 +264,18 @@ run "test_code_pipeline" { error_message = "Should be: project_deployment_source" } assert { - condition = one(aws_codepipeline.environment_pipeline.stage[1].action[0].output_artifacts) == "terraform_plan" - error_message = "Should be: terraform_plan" + condition = one(aws_codepipeline.environment_pipeline.stage[1].action[0].output_artifacts) == "build_output" + error_message = "Should be: build_output" } assert { - condition = aws_codepipeline.environment_pipeline.stage[1].action[0].configuration.ProjectName == "my-app-environment-pipeline" - error_message = "Should be: my-app-environment-pipeline" + condition = aws_codepipeline.environment_pipeline.stage[1].action[0].configuration.ProjectName == "my-app-environment-pipeline-build" + error_message = "Should be: my-app-environment-pipeline-build" } assert { condition = aws_codepipeline.environment_pipeline.stage[1].action[0].configuration.PrimarySource == "project_deployment_source" error_message = "Should be: project_deployment_source" } - assert { - condition = aws_codepipeline.environment_pipeline.stage[1].action[0].configuration.EnvironmentVariables == jsonencode([{ name : "ENVIRONMENT", value : "dev" }]) - error_message = "Should be: ${jsonencode([{ name : "ENVIRONMENT", value : "dev" }])}" - } + # Tags assert { condition = jsonencode(aws_codepipeline.environment_pipeline.tags) == jsonencode(var.expected_tags) @@ -213,64 +287,64 @@ run "test_codebuild" { command = plan assert { - condition = aws_codebuild_project.environment_pipeline.name == "my-app-environment-pipeline" - error_message = "Should be: my-app-environment-pipeline" + condition = aws_codebuild_project.environment_pipeline_build.name == "my-app-environment-pipeline-build" + error_message = "Should be: my-app-environment-pipeline-build" } assert { - condition = aws_codebuild_project.environment_pipeline.description == "Provisions the my-app application's extensions." + condition = aws_codebuild_project.environment_pipeline_build.description == "Provisions the my-app application's extensions." error_message = "Should be: 'Provisions the my-app application's extensions.'" } assert { - condition = aws_codebuild_project.environment_pipeline.build_timeout == 5 + condition = aws_codebuild_project.environment_pipeline_build.build_timeout == 5 error_message = "Should be: 5" } assert { - condition = one(aws_codebuild_project.environment_pipeline.artifacts).type == "CODEPIPELINE" + condition = one(aws_codebuild_project.environment_pipeline_build.artifacts).type == "CODEPIPELINE" error_message = "Should be: 'CODEPIPELINE'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.cache).type == "S3" + condition = one(aws_codebuild_project.environment_pipeline_build.cache).type == "S3" error_message = "Should be: 'S3'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.cache).location == "my-app-environment-pipeline-artifact-store" + condition = one(aws_codebuild_project.environment_pipeline_build.cache).location == "my-app-environment-pipeline-artifact-store" error_message = "Should be: 'my-app-environment-pipeline-artifact-store'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.environment).compute_type == "BUILD_GENERAL1_SMALL" + condition = one(aws_codebuild_project.environment_pipeline_build.environment).compute_type == "BUILD_GENERAL1_SMALL" error_message = "Should be: 'BUILD_GENERAL1_SMALL'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.environment).image == "aws/codebuild/amazonlinux2-x86_64-standard:5.0" + condition = one(aws_codebuild_project.environment_pipeline_build.environment).image == "aws/codebuild/amazonlinux2-x86_64-standard:5.0" error_message = "Should be: 'aws/codebuild/amazonlinux2-x86_64-standard:5.0'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.environment).type == "LINUX_CONTAINER" + condition = one(aws_codebuild_project.environment_pipeline_build.environment).type == "LINUX_CONTAINER" error_message = "Should be: 'LINUX_CONTAINER'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.environment).image_pull_credentials_type == "CODEBUILD" + condition = one(aws_codebuild_project.environment_pipeline_build.environment).image_pull_credentials_type == "CODEBUILD" error_message = "Should be: 'CODEBUILD'" } assert { - condition = aws_codebuild_project.environment_pipeline.logs_config[0].cloudwatch_logs[0].group_name == "codebuild/my-app-environment-terraform/log-group" + condition = aws_codebuild_project.environment_pipeline_build.logs_config[0].cloudwatch_logs[0].group_name == "codebuild/my-app-environment-terraform/log-group" error_message = "Should be: 'codebuild/my-app-environment-terraform/log-group'" } assert { - condition = aws_codebuild_project.environment_pipeline.logs_config[0].cloudwatch_logs[0].stream_name == "codebuild/my-app-environment-terraform/log-stream" + condition = aws_codebuild_project.environment_pipeline_build.logs_config[0].cloudwatch_logs[0].stream_name == "codebuild/my-app-environment-terraform/log-stream" error_message = "Should be: 'codebuild/my-app-environment-terraform/log-group'" } assert { - condition = one(aws_codebuild_project.environment_pipeline.source).type == "CODEPIPELINE" + condition = one(aws_codebuild_project.environment_pipeline_build.source).type == "CODEPIPELINE" error_message = "Should be: 'CODEPIPELINE'" } assert { - condition = length(regexall(".*echo \"Install Phase\".*", aws_codebuild_project.environment_pipeline.source[0].buildspec)) > 0 + condition = length(regexall(".*echo \"Install Phase\".*", aws_codebuild_project.environment_pipeline_build.source[0].buildspec)) > 0 error_message = "Should contain: 'echo \"Install Phase\"'" } assert { - condition = jsonencode(aws_codebuild_project.environment_pipeline.tags) == jsonencode(var.expected_tags) + condition = jsonencode(aws_codebuild_project.environment_pipeline_build.tags) == jsonencode(var.expected_tags) error_message = "Should be: ${jsonencode(var.expected_tags)}" } @@ -406,6 +480,106 @@ run "test_iam" { error_message = "Should be: 'my-app-environment-pipeline-codebuild'" } # aws_iam_role_policy.dns_account_assume_role_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.load_balancer_for_environment_codebuild.name == "my-app-load-balancer-for-environment-codebuild" + error_message = "Should be: 'my-app-load-balancer-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.load_balancer_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.load_balancer_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.certificate_for_environment_codebuild.name == "my-app-certificate-for-environment-codebuild" + error_message = "Should be: 'my-app-certificate-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.certificate_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.certificate_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.security_group_for_environment_codebuild.name == "my-app-security-group-for-environment-codebuild" + error_message = "Should be: 'my-app-security-group-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.security_group_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.security_group_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.ssm_parameter_for_environment_codebuild.name == "my-app-ssm-parameter-for-environment-codebuild" + error_message = "Should be: 'my-app-ssm-parameter-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.ssm_parameter_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.ssm_parameter_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.cloudwatch_for_environment_codebuild.name == "my-app-cloudwatch-for-environment-codebuild" + error_message = "Should be: 'my-app-cloudwatch-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.cloudwatch_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.cloudwatch_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.logs_for_environment_codebuild.name == "my-app-logs-for-environment-codebuild" + error_message = "Should be: 'my-app-logs-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.logs_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.logs_for_environment_codebuild.policy cannot be tested on a plan + + assert { + condition = aws_iam_role_policy.kms_key_for_environment_codebuild.name == "my-app-kms-key-for-environment-codebuild" + error_message = "Should be: 'my-app-kms-key-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.kms_key_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.kms_key_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.redis_for_environment_codebuild.name == "my-app-redis-for-environment-codebuild" + error_message = "Should be: 'my-app-redis-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.redis_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.redis_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.postgres_for_environment_codebuild.name == "my-app-postgres-for-environment-codebuild" + error_message = "Should be: 'my-app-postgres-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.postgres_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.postgres_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.s3_for_environment_codebuild.name == "my-app-s3-for-environment-codebuild" + error_message = "Should be: 'my-app-s3-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.s3_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.s3_for_environment_codebuild.policy cannot be tested on a plan + assert { + condition = aws_iam_role_policy.opensearch_for_environment_codebuild.name == "my-app-opensearch-for-environment-codebuild" + error_message = "Should be: 'my-app-opensearch-for-environment-codebuild'" + } + assert { + condition = aws_iam_role_policy.opensearch_for_environment_codebuild.role == "my-app-environment-pipeline-codebuild" + error_message = "Should be: 'my-app-environment-pipeline-codebuild'" + } + # aws_iam_role_policy.opensearch_for_environment_codebuild.policy cannot be tested on a plan } run "test_artifact_store" { @@ -418,3 +592,274 @@ run "test_artifact_store" { } } +run "test_stages" { + command = plan + + assert { + condition = length(aws_codepipeline.environment_pipeline.stage) == 7 + error_message = "Should be: 7" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[0].name == "Source" + error_message = "Should be: Source" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[1].name == "Build" + error_message = "Should be: Build" + } + + # Stage: dev plan + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].name == "Plan-dev" + error_message = "Should be: Plan-dev" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].name == "Plan" + error_message = "Action name incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].category == "Build" + error_message = "Action category incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].owner == "AWS" + error_message = "Action owner incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].provider == "CodeBuild" + error_message = "Action provider incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[2].action[0].input_artifacts) == 1 + error_message = "Input artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].input_artifacts[0] == "build_output" + error_message = "Input artifacts incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[2].action[0].output_artifacts) == 1 + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].output_artifacts[0] == "terraform_plan" + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].version == "1" + error_message = "Action Version incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].configuration.ProjectName == "my-app-environment-pipeline-plan" + error_message = "Configuration ProjectName incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].configuration.PrimarySource == "build_output" + error_message = "Configuration PrimarySource incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].configuration.EnvironmentVariables == "[{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"}]" + error_message = "Configuration Env Vars incorrect" + } + + # Stage: dev apply + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].name == "Apply-dev" + error_message = "Should be: Apply-dev" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].name == "Apply" + error_message = "Action name incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].category == "Build" + error_message = "Action category incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].owner == "AWS" + error_message = "Action owner incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].provider == "CodeBuild" + error_message = "Action provider incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[3].action[0].input_artifacts) == 2 + error_message = "Input artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].input_artifacts[0] == "build_output" + error_message = "Input artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].input_artifacts[1] == "terraform_plan" + error_message = "Input artifacts incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[3].action[0].output_artifacts) == 0 + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].version == "1" + error_message = "Action Version incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].configuration.ProjectName == "my-app-environment-pipeline-apply" + error_message = "Configuration ProjectName incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[3].action[0].configuration.PrimarySource == "build_output" + error_message = "Configuration PrimarySource incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[2].action[0].configuration.EnvironmentVariables == "[{\"name\":\"ENVIRONMENT\",\"value\":\"dev\"}]" + error_message = "Configuration Env Vars incorrect" + } + + # Stage: prod Plan + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].name == "Plan-prod" + error_message = "Should be: Plan-prod" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].name == "Plan" + error_message = "Action name incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].category == "Build" + error_message = "Action category incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].owner == "AWS" + error_message = "Action owner incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].provider == "CodeBuild" + error_message = "Action provider incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[4].action[0].input_artifacts) == 1 + error_message = "Input artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].input_artifacts[0] == "build_output" + error_message = "Input artifacts incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[4].action[0].output_artifacts) == 1 + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].output_artifacts[0] == "terraform_plan" + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].version == "1" + error_message = "Action Version incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].configuration.ProjectName == "my-app-environment-pipeline-plan" + error_message = "Configuration ProjectName incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].configuration.PrimarySource == "build_output" + error_message = "Configuration PrimarySource incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[4].action[0].configuration.EnvironmentVariables == "[{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"}]" + error_message = "Configuration Env Vars incorrect" + } + + # Stage: prod approval + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].name == "Approve-prod" + error_message = "Should be: Approve-prod" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].action[0].name == "Approval" + error_message = "Action name incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].action[0].category == "Approval" + error_message = "Action category incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].action[0].owner == "AWS" + error_message = "Action owner incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].action[0].provider == "Manual" + error_message = "Action provider incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[5].action[0].input_artifacts) == 0 + error_message = "Input artifacts incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[5].action[0].output_artifacts) == 0 + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].action[0].version == "1" + error_message = "Action Version incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[5].action[0].configuration.CustomData == "Review Terraform Plan" + error_message = "Configuration CustomData incorrect" + } + + # Stage: prod apply + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].name == "Apply-prod" + error_message = "Should be: Apply-prod" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].name == "Apply" + error_message = "Action name incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].category == "Build" + error_message = "Action category incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].owner == "AWS" + error_message = "Action owner incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].provider == "CodeBuild" + error_message = "Action provider incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[6].action[0].input_artifacts) == 2 + error_message = "Input artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].input_artifacts[0] == "build_output" + error_message = "Input artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].input_artifacts[1] == "terraform_plan" + error_message = "Input artifacts incorrect" + } + assert { + condition = length(aws_codepipeline.environment_pipeline.stage[6].action[0].output_artifacts) == 0 + error_message = "Output artifacts incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].version == "1" + error_message = "Action Version incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].configuration.ProjectName == "my-app-environment-pipeline-apply" + error_message = "Configuration ProjectName incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].configuration.PrimarySource == "build_output" + error_message = "Configuration PrimarySource incorrect" + } + assert { + condition = aws_codepipeline.environment_pipeline.stage[6].action[0].configuration.EnvironmentVariables == "[{\"name\":\"ENVIRONMENT\",\"value\":\"prod\"}]" + error_message = "Configuration Env Vars incorrect" + } +} + diff --git a/postgres/lambda.tf b/postgres/lambda.tf index ce48f524f..8abfedaca 100644 --- a/postgres/lambda.tf +++ b/postgres/lambda.tf @@ -58,6 +58,9 @@ data "archive_file" "lambda" { type = "zip" source_file = "${path.module}/manage_users.py" output_path = "${path.module}/manage_users.zip" + depends_on = [ + aws_iam_role.lambda-execution-role + ] } resource "aws_lambda_function" "lambda" {