diff --git a/build/buildspec.yml b/build/buildspec.yml index 738e14e..bfac828 100644 --- a/build/buildspec.yml +++ b/build/buildspec.yml @@ -3,26 +3,10 @@ version: 0.2 phases: pre_build: commands: - - pip install awscli --upgrade --user - - echo `aws --version` - - echo Logging into Amazon ECR... - - $(aws ecr get-login --region ${region} --no-include-email) - - REPOSITORY_URI=${repository_url} - - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - - echo Entered the pre_build phase... - build: - commands: - - echo Build started on `date` - - echo Building the Docker image... - - docker build -t $REPOSITORY_URI:latest -f ${dockerfile} ${dockerfile_path} - - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG + - apt-get update && apt-get install jq -y post_build: commands: - - echo Build completed on `date` - - echo Pushing the Docker images... - - docker push $REPOSITORY_URI:latest - - docker push $REPOSITORY_URI:$IMAGE_TAG - - echo Writing image definitions file... - - printf '[{"name":"${container_name}","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json + - REPOSITORY_URI=$(cat imageDetail.json | jq .ImageURI) + - printf '[{"name":"${container_name}","imageUri":%s}]' $REPOSITORY_URI > imagedefinitions.json artifacts: files: imagedefinitions.json diff --git a/cloudwatch/ecr-source-event.json b/cloudwatch/ecr-source-event.json new file mode 100644 index 0000000..4220137 --- /dev/null +++ b/cloudwatch/ecr-source-event.json @@ -0,0 +1,24 @@ +{ + "source": [ + "aws.ecr" + ], + "detail-type": [ + "AWS API Call via CloudTrail" + ], + "detail": { + "eventSource": [ + "ecr.amazonaws.com" + ], + "eventName": [ + "PutImage" + ], + "requestParameters": { + "repositoryName": [ + "${ecr_repository_name}" + ], + "imageTag": [ + "latest" + ] + } + } +} diff --git a/examples/basic/README.md b/examples/basic/README.md index 4e3401a..cfcbed9 100644 --- a/examples/basic/README.md +++ b/examples/basic/README.md @@ -2,16 +2,12 @@ ## Usage -First update both `repo_name` and `repo_owner` with a Github repository and valid username respectively - -> This probably will change in the future by setting the source as the [ECR repository](https://aws.amazon.com/about-aws/whats-new/2018/11/the-aws-developer-tools-improve-continuous-delivery-support-for-aws-fargate-and-amazon-ecs/) instead of a Github repository - -And then, to run this example you need to execute: +To run this example you need to execute: ```bash $ terraform init $ terraform plan -$ GITHUB_TOKEN= terraform apply +$ terraform apply ``` Note that this example create resources which can cost money (AWS Fargate Services, for example). Run `terraform destroy` when you don't need these resources. diff --git a/examples/basic/main.tf b/examples/basic/main.tf index e6f01dc..faee927 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -3,7 +3,7 @@ terraform { } provider "aws" { - version = "~> 1.52.0" + version = "~> 1.54.0" region = "us-east-1" profile = "playground" } @@ -13,9 +13,6 @@ module "fargate" { name = "basic-example" - repo_name = "" # CHANGE THIS - repo_owner = "" # CHANGE THIS - services = { api = { task_definition = "api.json" @@ -26,9 +23,6 @@ module "fargate" { registry_retention_count = 15 # Optional. 20 by default logs_retention_days = 14 # Optional. 30 by default - - dockerfile = "Dockerfile" # Optional. Dockerfile by default - dockerfile_path = "." # Optional. '.' by default } } } diff --git a/main.tf b/main.tf index e100074..59b6def 100644 --- a/main.tf +++ b/main.tf @@ -270,11 +270,7 @@ data "template_file" "buildspec" { template = "${file("${path.module}/build/buildspec.yml")}" vars { - container_name = "${element(keys(var.services), count.index)}" - repository_url = "${element(aws_ecr_repository.this.*.repository_url, count.index)}" - region = "${var.region}" - dockerfile = "${lookup(var.services[element(keys(var.services), count.index)], "dockerfile", var.dockerfile_default_name)}" - dockerfile_path = "${lookup(var.services[element(keys(var.services), count.index)], "dockerfile_path", var.dockerfile_default_path)}" + container_name = "${element(keys(var.services), count.index)}" } } @@ -306,30 +302,37 @@ resource "aws_codebuild_project" "this" { # CODEPIPELINE resource "aws_iam_role" "codepipeline" { - name = "${var.name}-${terraform.workspace}-codepipeline-role" + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + name = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-codepipeline-role" assume_role_policy = "${file("${path.module}/policies/codepipeline-role.json")}" } data "template_file" "codepipeline" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + template = "${file("${path.module}/policies/codepipeline-role-policy.json")}" vars { - aws_s3_bucket_arn = "${aws_s3_bucket.this.arn}" + aws_s3_bucket_arn = "${aws_s3_bucket.this.arn}" + ecr_repository_arn = "${element(aws_ecr_repository.this.*.arn, count.index)}" } } resource "aws_iam_role_policy" "codepipeline" { - name = "${var.name}-${terraform.workspace}-codepipeline-role-policy" + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + name = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-codepipeline-role-policy" role = "${aws_iam_role.codepipeline.id}" - policy = "${data.template_file.codepipeline.rendered}" + policy = "${element(data.template_file.codepipeline.*.rendered, count.index)}" } resource "aws_codepipeline" "this" { count = "${length(var.services) > 0 ? length(var.services) : 0}" name = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-pipeline" - role_arn = "${aws_iam_role.codepipeline.arn}" + role_arn = "${element(aws_iam_role.codepipeline.*.arn, count.index)}" artifact_store { location = "${aws_s3_bucket.this.bucket}" @@ -342,15 +345,14 @@ resource "aws_codepipeline" "this" { action { name = "Source" category = "Source" - owner = "ThirdParty" - provider = "GitHub" + owner = "AWS" + provider = "ECR" version = "1" output_artifacts = ["source"] configuration { - Owner = "${var.repo_owner}" - Repo = "${var.repo_name}" - Branch = "${terraform.workspace == "default" ? "master" : terraform.workspace}" + RepositoryName = "${element(aws_ecr_repository.this.*.name, count.index)}" + ImageTag = "latest" } } } @@ -394,3 +396,64 @@ resource "aws_codepipeline" "this" { depends_on = ["aws_iam_role_policy.codebuild", "aws_ecs_service.this"] } + +### Remove after ECR as CodePipeline Source gets fully integrated with AWS Provider + +resource "aws_iam_role" "events" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + name = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-events-role" + + assume_role_policy = "${file("${path.module}/policies/events-role.json")}" +} + +data "template_file" "events" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + template = "${file("${path.module}/policies/events-role-policy.json")}" + + vars { + codepipeline_arn = "${element(aws_codepipeline.this.*.arn, count.index)}" + } +} + +resource "aws_iam_role_policy" "events" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + name = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-events-role-policy" + role = "${element(aws_iam_role.events.*.id, count.index)}" + policy = "${element(data.template_file.events.*.rendered, count.index)}" +} + +data "template_file" "ecr_event" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + template = "${file("${path.module}/cloudwatch/ecr-source-event.json")}" + + vars { + ecr_repository_name = "${element(aws_ecr_repository.this.*.name, count.index)}" + } +} + +resource "aws_cloudwatch_event_rule" "this" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + name = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-ecr-event" + description = "Amazon CloudWatch Events rule to automatically start your pipeline when a change occurs in the Amazon ECR image tag." + + event_pattern = "${element(data.template_file.ecr_event.*.rendered, count.index)}" + + depends_on = ["aws_codepipeline.this"] +} + +resource "aws_cloudwatch_event_target" "this" { + count = "${length(var.services) > 0 ? length(var.services) : 0}" + + rule = "${element(aws_cloudwatch_event_rule.this.*.name, count.index)}" + target_id = "${var.name}-${terraform.workspace}-${element(keys(var.services), count.index)}-codepipeline" + arn = "${element(aws_codepipeline.this.*.arn, count.index)}" + role_arn = "${element(aws_iam_role.events.*.arn, count.index)}" +} + +### End Remove + diff --git a/policies/codepipeline-role-policy.json b/policies/codepipeline-role-policy.json index c9e8ddd..eeddf9c 100644 --- a/policies/codepipeline-role-policy.json +++ b/policies/codepipeline-role-policy.json @@ -23,6 +23,15 @@ ], "Resource": "*" }, + { + "Effect": "Allow", + "Action": [ + "ecr:DescribeImages" + ], + "Resource": [ + "${ecr_repository_arn}" + ] + }, { "Action": [ "ecs:*", diff --git a/policies/events-role-policy.json b/policies/events-role-policy.json new file mode 100644 index 0000000..40fd5bd --- /dev/null +++ b/policies/events-role-policy.json @@ -0,0 +1,14 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "codepipeline:StartPipelineExecution" + ], + "Resource": [ + "${codepipeline_arn}" + ] + } + ] +} diff --git a/policies/events-role.json b/policies/events-role.json new file mode 100644 index 0000000..68c4a60 --- /dev/null +++ b/policies/events-role.json @@ -0,0 +1,12 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} diff --git a/variables.tf b/variables.tf index 0d2179b..706ed66 100644 --- a/variables.tf +++ b/variables.tf @@ -11,15 +11,6 @@ variable "region" { default = "us-east-1" } -variable "repo_name" { - description = "Name of the repository. Needed for Continious Deployment for ECS services" -} - -variable "repo_owner" { - description = "Owner of the repository" - default = "strvcom" -} - variable "development_mode" { description = "Whether or not create a most robust production-ready infrastructure with ALBs and more than 1 replica" default = false @@ -61,13 +52,3 @@ variable "ecr_default_retention_count" { variable "services" { type = "map" } - -## Docker - -variable "dockerfile_default_name" { - default = "Dockerfile" -} - -variable "dockerfile_default_path" { - default = "." -}