From c0bd9b087dbaefbb6a77243d5d3d05d994395631 Mon Sep 17 00:00:00 2001 From: Chris Wright Date: Wed, 10 Jan 2024 10:46:49 +0000 Subject: [PATCH] CodePipeline Codebuild buildspec * Adds the option to specifify a buildspec for the CodePipeline's Codebuild project. By default, it will look for the given filename within the created Codebuild S3 Store, but can optionally be configured to load it from the GitHub repo source instead. * Creates an S3 bucket where Codebuild buildspecs can be stored. This is where the default buildspec is also stored (dalmatian-default.yml). When custom buildspecs are pushed to the S3 bucket, they can then be specified as the `buildspec` filename. * An IAM user is created with a policy that allows S3 read/write permissions, along with an access key that can be used to push custom buildspecs to the bucket. --- README.md | 17 ++- buildspecs/dalmatian-default.yml | 24 +++ data.tf | 13 ++ ...ucture-service-build-pipeline-codebuild.tf | 8 +- ...rvice-build-pipeline-s3-buildspec-store.tf | 142 ++++++++++++++++++ locals.tf | 12 +- policies/s3-object-rw.json.tpl | 21 +++ variables.tf | 31 ++-- 8 files changed, 244 insertions(+), 24 deletions(-) create mode 100644 buildspecs/dalmatian-default.yml create mode 100644 ecs-cluster-infrastructure-service-build-pipeline-s3-buildspec-store.tf create mode 100644 policies/s3-object-rw.json.tpl diff --git a/README.md b/README.md index 95a72ed..a3f09e6 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,10 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_flow_log.infrastructure_vpc_flow_logs_s3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log) | resource | | [aws_glue_catalog_database.infrastructure_vpc_flow_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/glue_catalog_database) | resource | | [aws_glue_catalog_table.infrastructure_vpc_flow_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/glue_catalog_table) | resource | +| [aws_iam_access_key.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_access_key) | resource | +| [aws_iam_group.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group) | resource | +| [aws_iam_group_membership.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_membership) | resource | +| [aws_iam_group_policy.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_group_policy) | resource | | [aws_iam_instance_profile.infrastructure_ecs_cluster](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_draining_ecs_container_instance_state_update_lambda](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.ecs_cluster_infrastructure_draining_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | @@ -83,6 +87,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_codepipeline_codestar_connection](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_service_codepipeline_kms_encrypt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_ecs_cluster_ssm_service_setting_rw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_user.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_user) | resource | | [aws_internet_gateway.infrastructure_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource | | [aws_kms_alias.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_alias) | resource | | [aws_kms_key.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/kms_key) | resource | @@ -112,18 +117,25 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_route_table_association.infrastructure_private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | | [aws_route_table_association.infrastructure_public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | | [aws_s3_bucket.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | | [aws_s3_bucket_lifecycle_configuration.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_lifecycle_configuration.logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | | [aws_s3_bucket_logging.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_logging.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | | [aws_s3_bucket_policy.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | +| [aws_s3_bucket_policy.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_policy.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_public_access_block.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_public_access_block.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_server_side_encryption_configuration.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | | [aws_s3_bucket_versioning.infrastructure_ecs_cluster_service_build_pipeline_artifact_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_versioning.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | | [aws_s3_bucket_versioning.infrastructure_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_object.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store_files](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_object) | resource | | [aws_security_group.infrastructure_ecs_cluster_container_instances](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group.infrastructure_ecs_cluster_efs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | | [aws_security_group_rule.infrastructure_ecs_cluster_container_instances_egress_dns_tcp](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | @@ -143,6 +155,7 @@ This project creates and manages resources within an AWS account for infrastruct | [aws_ami.ecs_cluster_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | | [aws_route53_zone.root](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/route53_zone) | data source | +| [aws_s3_object.ecs_cluster_service_buildspec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/s3_object) | data source | | [external_external.ssm_dhmc_setting](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | ## Inputs @@ -174,8 +187,8 @@ This project creates and manages resources within an AWS account for infrastruct | [infrastructure\_ecs\_cluster\_max\_size](#input\_infrastructure\_ecs\_cluster\_max\_size) | Maximum number of instances for the ECS cluster | `number` | n/a | yes | | [infrastructure\_ecs\_cluster\_min\_size](#input\_infrastructure\_ecs\_cluster\_min\_size) | Minimum number of instances for the ECS cluster | `number` | n/a | yes | | [infrastructure\_ecs\_cluster\_publicly\_avaialble](#input\_infrastructure\_ecs\_cluster\_publicly\_avaialble) | Conditionally launch the ECS cluster EC2 instances into the Public subnet | `bool` | n/a | yes | -| [infrastructure\_ecs\_cluster\_service\_defaults](#input\_infrastructure\_ecs\_cluster\_service\_defaults) | Default values for ECS Cluster Services |
object({
github_v1_source = optional(bool, null)
github_v1_oauth_token = optional(string, null)
codestar_connection_arn = optional(string, null)
github_owner = optional(string, null)
github_repo = optional(string, null)
github_track_revision = optional(string, null)
})
| n/a | yes | -| [infrastructure\_ecs\_cluster\_services](#input\_infrastructure\_ecs\_cluster\_services) | Map of ECS Cluster Services (The key will be the service name). Values in here will override `infrastructure_ecs_cluster_service_defaults` values if set."
{
service-name = {
github\_v1\_source: Conditionally use GitHubV1 for the CodePipeline source (CodeStar will be used by default)
github\_v1\_oauth\_token: If `github_v1_source` is set to true, provide the GitHub OAuthToken here
codestar\_connection\_arn: The CodeStar Connection ARN to use in the CodePipeline source
github\_owner: The GitHub Owner of the repository to be pulled by the CodePipeline source
github\_repo: The GitHub repo name to be pulled by the CodePipeline source
github\_track\_revision: The branch/revision of the GitHub repository to be pulled by the CodePipeline source
}
} |
map(object({
github_v1_source = optional(bool, null)
github_v1_oauth_token = optional(string, null)
codestar_connection_arn = optional(string, null)
github_owner = optional(string, null)
github_repo = optional(string, null)
github_track_revision = optional(string, null)
}))
| n/a | yes | +| [infrastructure\_ecs\_cluster\_service\_defaults](#input\_infrastructure\_ecs\_cluster\_service\_defaults) | Default values for ECS Cluster Services |
object({
github_v1_source = optional(bool, null)
github_v1_oauth_token = optional(string, null)
codestar_connection_arn = optional(string, null)
github_owner = optional(string, null)
github_repo = optional(string, null)
github_track_revision = optional(string, null)
buildspec = optional(string, null)
buildspec_from_github_repo = optional(bool, null)
})
| n/a | yes | +| [infrastructure\_ecs\_cluster\_services](#input\_infrastructure\_ecs\_cluster\_services) | Map of ECS Cluster Services (The key will be the service name). Values in here will override `infrastructure_ecs_cluster_service_defaults` values if set."
{
service-name = {
github\_v1\_source: Conditionally use GitHubV1 for the CodePipeline source (CodeStar will be used by default)
github\_v1\_oauth\_token: If `github_v1_source` is set to true, provide the GitHub OAuthToken here
codestar\_connection\_arn: The CodeStar Connection ARN to use in the CodePipeline source
github\_owner: The GitHub Owner of the repository to be pulled by the CodePipeline source
github\_repo: The GitHub repo name to be pulled by the CodePipeline source
github\_track\_revision: The branch/revision of the GitHub repository to be pulled by the CodePipeline source
buildspec: The filename of the buildspec to use for the CodePipeline build phase, stored within the 'codepipeline buildspec store' S3 bucket
buildspec\_from\_github\_repo: Conditionally use the 'buildspec' filename stored within the GitHub repo as the buildspec
}
} |
map(object({
github_v1_source = optional(bool, null)
github_v1_oauth_token = optional(string, null)
codestar_connection_arn = optional(string, null)
github_owner = optional(string, null)
github_repo = optional(string, null)
github_track_revision = optional(string, null)
buildspec = optional(string, null)
buildspec_from_github_repo = optional(bool, null)
}))
| n/a | yes | | [infrastructure\_ecs\_cluster\_termination\_timeout](#input\_infrastructure\_ecs\_cluster\_termination\_timeout) | The timeout for the terminiation lifecycle hook | `number` | n/a | yes | | [infrastructure\_kms\_encryption](#input\_infrastructure\_kms\_encryption) | Enable infrastructure KMS encryption. This will create a single KMS key to be used across all resources that support KMS encryption. | `bool` | n/a | yes | | [infrastructure\_logging\_bucket\_retention](#input\_infrastructure\_logging\_bucket\_retention) | Retention in days for the infrasrtucture S3 logs. This is for the default S3 logs bucket, where all AWS service logs will be delivered | `number` | n/a | yes | diff --git a/buildspecs/dalmatian-default.yml b/buildspecs/dalmatian-default.yml new file mode 100644 index 0000000..dee09ec --- /dev/null +++ b/buildspecs/dalmatian-default.yml @@ -0,0 +1,24 @@ +version: 0.2 + +phases: + pre_build: + commands: + - echo Build started on `date` + - echo Entered the pre_build phase... + - echo Updating yarn GPG keys ... + - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - + - echo Building the Docker image... + - docker build -t $CONTAINER_NAME:test . + build: + commands: + - IMAGE_TAG=commit-$CODEBUILD_RESOLVED_SOURCE_VERSION + post_build: + commands: + - echo Build completed on `date` +artifacts: + files: + - imagedefinitions.json + secondary-artifacts: + imagedefinitions: + files: + - imagedefinitions.json diff --git a/data.tf b/data.tf index 5ecbf9b..a126473 100644 --- a/data.tf +++ b/data.tf @@ -27,6 +27,19 @@ data "aws_ami" "ecs_cluster_ami" { } } +data "aws_s3_object" "ecs_cluster_service_buildspec" { + for_each = { + for k, service in local.infrastructure_ecs_cluster_services : k => service if service["buildspec_from_github_repo"] == null || service["buildspec_from_github_repo"] == false + } + + bucket = aws_s3_bucket.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store[0].id + key = each.value["buildspec"] != null ? each.value["buildspec"] : "dalmatian-default.yml" + + depends_on = [ + aws_s3_object.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store_files, + ] +} + # aws_ssm_service_setting doesn't yet have a data source, so we need to use # a script to retrieve SSM service settings # https://github.com/hashicorp/terraform-provider-aws/issues/25170 diff --git a/ecs-cluster-infrastructure-service-build-pipeline-codebuild.tf b/ecs-cluster-infrastructure-service-build-pipeline-codebuild.tf index 0475262..4ab059f 100644 --- a/ecs-cluster-infrastructure-service-build-pipeline-codebuild.tf +++ b/ecs-cluster-infrastructure-service-build-pipeline-codebuild.tf @@ -62,9 +62,15 @@ resource "aws_codebuild_project" "infrastructure_ecs_cluster_service_build" { image = "aws/codebuild/standard:5.0" type = "LINUX_CONTAINER" privileged_mode = true + + environment_variable { + name = "CONTAINER_NAME" + value = "${local.resource_prefix}-${each.key}" + } } source { - type = "CODEPIPELINE" + type = "CODEPIPELINE" + buildspec = each.value["buildspec_from_github_repo"] != null || each.value["buildspec_from_github_repo"] == true ? each.value["buildspec"] : data.aws_s3_object.ecs_cluster_service_buildspec[each.key].body } } diff --git a/ecs-cluster-infrastructure-service-build-pipeline-s3-buildspec-store.tf b/ecs-cluster-infrastructure-service-build-pipeline-s3-buildspec-store.tf new file mode 100644 index 0000000..a980c7b --- /dev/null +++ b/ecs-cluster-infrastructure-service-build-pipeline-s3-buildspec-store.tf @@ -0,0 +1,142 @@ +resource "aws_s3_bucket" "infrastructure_ecs_cluster_service_build_pipeline_buildspec_store" { + count = length(local.infrastructure_ecs_cluster_services) != 0 ? 1 : 0 + + bucket = "${local.resource_prefix_hash}-ecs-cluster-service-build-pipeline-buildspec-store" + +} + +resource "aws_s3_bucket_policy" "infrastructure_ecs_cluster_service_build_pipeline_buildspec_store" { + count = length(local.infrastructure_ecs_cluster_services) != 0 ? 1 : 0 + + bucket = aws_s3_bucket.infrastructure_ecs_cluster_service_build_pipeline_buildspec_store[0].id + policy = templatefile( + "${path.module}/policies/s3-bucket-policy.json.tpl", + { + statement = < 0 ? keys(values(var.infrastructure_ecs_cluster_services)[0]) : [] infrastructure_ecs_cluster_services = { - for k, v in var.infrastructure_ecs_cluster_services : k => { - github_v1_source = try(coalesce(v["github_v1_source"], local.infrastructure_ecs_cluster_service_defaults["github_v1_source"]), null) - github_v1_oauth_token = try(coalesce(v["github_v1_oauth_token"], local.infrastructure_ecs_cluster_service_defaults["github_v1_oauth_token"]), null) - codestar_connection_arn = try(coalesce(v["codestar_connection_arn"], local.infrastructure_ecs_cluster_service_defaults["codestar_connection_arn"]), null) - github_owner = try(coalesce(v["github_owner"], local.infrastructure_ecs_cluster_service_defaults["github_owner"]), null) - github_repo = try(coalesce(v["github_repo"], local.infrastructure_ecs_cluster_service_defaults["github_repo"]), null) - github_track_revision = try(coalesce(v["github_track_revision"], local.infrastructure_ecs_cluster_service_defaults["github_track_revision"]), null) - } + for k, v in var.infrastructure_ecs_cluster_services : k => merge({ + for service_key in local.infrastructure_ecs_cluster_services_keys : service_key => try(coalesce(v[service_key], local.infrastructure_ecs_cluster_service_defaults[service_key]), null) + }) } default_tags = { diff --git a/policies/s3-object-rw.json.tpl b/policies/s3-object-rw.json.tpl new file mode 100644 index 0000000..bf90a09 --- /dev/null +++ b/policies/s3-object-rw.json.tpl @@ -0,0 +1,21 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "${bucket_arn}" + ] + }, + { + "Effect": "Allow", + "Action": "s3:*Object", + "Resource": [ + "${bucket_arn}/*" + ] + } + ] +} diff --git a/variables.tf b/variables.tf index 45038be..d329ea1 100644 --- a/variables.tf +++ b/variables.tf @@ -289,14 +289,15 @@ variable "infrastructure_ecs_cluster_autoscaling_time_based_custom" { variable "infrastructure_ecs_cluster_service_defaults" { description = "Default values for ECS Cluster Services" type = object({ - github_v1_source = optional(bool, null) - github_v1_oauth_token = optional(string, null) - codestar_connection_arn = optional(string, null) - github_owner = optional(string, null) - github_repo = optional(string, null) - github_track_revision = optional(string, null) + github_v1_source = optional(bool, null) + github_v1_oauth_token = optional(string, null) + codestar_connection_arn = optional(string, null) + github_owner = optional(string, null) + github_repo = optional(string, null) + github_track_revision = optional(string, null) + buildspec = optional(string, null) + buildspec_from_github_repo = optional(bool, null) }) - } variable "infrastructure_ecs_cluster_services" { @@ -310,16 +311,20 @@ variable "infrastructure_ecs_cluster_services" { github_owner: The GitHub Owner of the repository to be pulled by the CodePipeline source github_repo: The GitHub repo name to be pulled by the CodePipeline source github_track_revision: The branch/revision of the GitHub repository to be pulled by the CodePipeline source + buildspec: The filename of the buildspec to use for the CodePipeline build phase, stored within the 'codepipeline buildspec store' S3 bucket + buildspec_from_github_repo: Conditionally use the 'buildspec' filename stored within the GitHub repo as the buildspec } } EOT type = map(object({ - github_v1_source = optional(bool, null) - github_v1_oauth_token = optional(string, null) - codestar_connection_arn = optional(string, null) - github_owner = optional(string, null) - github_repo = optional(string, null) - github_track_revision = optional(string, null) + github_v1_source = optional(bool, null) + github_v1_oauth_token = optional(string, null) + codestar_connection_arn = optional(string, null) + github_owner = optional(string, null) + github_repo = optional(string, null) + github_track_revision = optional(string, null) + buildspec = optional(string, null) + buildspec_from_github_repo = optional(bool, null) })) }