├── .gitignore ├── LICENSE ├── README.md ├── components ├── automation │ ├── .terraform-version │ ├── aws_data.tf │ ├── aws_provider.tf │ ├── iam-codepipeline.tf │ ├── iam-tf_deployer.tf │ ├── locals.tf │ ├── module-tf-codepipelines.tf │ ├── outputs.tf │ ├── s3_bucket.tf │ └── variables.tf └── example │ ├── .terraform-version │ ├── aws_data.tf │ ├── aws_provider.tf │ ├── locals.tf │ ├── module-example_test.tf │ ├── outputs.tf │ └── variables.tf ├── etc ├── env_eu-west-1_dev.tfvars ├── env_eu-west-1_nonprod.tfvars ├── eu-west-1.tfvars └── global.tfvars └── modules ├── example ├── aws_data.tf ├── locals.tf ├── outputs.tf └── variables.tf └── tf-codepipeline ├── aws_data.tf ├── cloudwatch_log_groups.tf ├── codebuild-planner.tf ├── codebuild-runner.tf ├── codepipeline.tf ├── iam-codebuild.tf ├── locals.tf ├── outputs.tf ├── templates └── buildspec.tpl └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | 11 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 12 | # .tfvars files are managed as part of configuration and so should be included in 13 | # version control. 14 | # 15 | # example.tfvars 16 | 17 | # Ignore override files as they are usually used to override resources locally and so 18 | # are not checked in 19 | override.tf 20 | override.tf.json 21 | *_override.tf 22 | *_override.tf.json 23 | 24 | # Include override files you do wish to add to version control using negated pattern 25 | # 26 | # !example_override.tf 27 | 28 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 29 | # example: *tfplan* 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 SeboLabs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # codepipeline-tf 2 | 3 | **Info** 4 | ------ 5 | Terraform run with AWS Developers tools. 6 | 7 | **Storis** 8 | ------ 9 | [Terraform deployments with AWS CodePipeline](https://medium.com/@sebolabs/terraform-deployments-with-aws-codepipeline-342074248843) 10 | 11 | **Structure** 12 | ------ 13 | The structe is defined by the [TFScaffold](https://github.com/sebolabs/tfscaffold). 14 | 15 | **How to use it?** 16 | ------ 17 | It can be run with [Docker-TF](https://github.com/sebolabs/docker-tf). 18 | 19 | **Terraform compatibility** 20 | ------ 21 | TF versions tested: 0.12.24 22 | -------------------------------------------------------------------------------- /components/automation/.terraform-version: -------------------------------------------------------------------------------- 1 | 0.12.24 2 | -------------------------------------------------------------------------------- /components/automation/aws_data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_codecommit_repository" "terraform" { 4 | repository_name = var.codecommit_terraform_repo_name 5 | } 6 | 7 | data "aws_ecr_repository" "tfscaffold_runner" { 8 | name = var.tfscaffold_runner_ecr_repo_name 9 | } 10 | -------------------------------------------------------------------------------- /components/automation/aws_provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.aws_region 3 | } 4 | -------------------------------------------------------------------------------- /components/automation/iam-codepipeline.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "codepipeline_assumerole" { 2 | statement { 3 | sid = "AllowCodePipelineAssumeRole" 4 | effect = "Allow" 5 | 6 | actions = [ 7 | "sts:AssumeRole", 8 | ] 9 | 10 | principals { 11 | type = "Service" 12 | 13 | identifiers = [ 14 | "codepipeline.amazonaws.com", 15 | ] 16 | } 17 | } 18 | } 19 | 20 | resource "aws_iam_role" "codepipeline" { 21 | name = "${local.aws_account_level_id}-codepipeline" 22 | assume_role_policy = data.aws_iam_policy_document.codepipeline_assumerole.json 23 | 24 | tags = merge( 25 | local.default_tags, 26 | map( 27 | "Name", "${local.aws_account_level_id}/codepipeline", 28 | ), 29 | ) 30 | } 31 | 32 | data "aws_iam_policy_document" "codepipeline" { 33 | statement { 34 | sid = "AllowS3" 35 | effect = "Allow" 36 | 37 | actions = [ 38 | "s3:GetObject", 39 | "s3:GetObjectVersion", 40 | "s3:GetBucketVersioning", 41 | "s3:PutObject", 42 | ] 43 | 44 | resources = [ 45 | aws_s3_bucket.codepipeline.arn, 46 | "${aws_s3_bucket.codepipeline.arn}/*" 47 | ] 48 | } 49 | 50 | statement { 51 | sid = "AllowCodeCommit" 52 | effect = "Allow" 53 | 54 | actions = [ 55 | "codecommit:CancelUploadArchive", 56 | "codecommit:GetBranch", 57 | "codecommit:GetCommit", 58 | "codecommit:GetUploadArchiveStatus", 59 | "codecommit:UploadArchive", 60 | ] 61 | 62 | resources = [ 63 | data.aws_codecommit_repository.terraform.arn, 64 | ] 65 | } 66 | 67 | statement { 68 | sid = "AllowCodeBuild" 69 | effect = "Allow" 70 | 71 | actions = [ 72 | "codebuild:BatchGetBuilds", # TODO: * 73 | "codebuild:StartBuild", # TODO: specific 74 | ] 75 | 76 | resources = ["*"] 77 | } 78 | 79 | statement { 80 | sid = "AllowECR" 81 | effect = "Allow" 82 | 83 | actions = [ 84 | "ecr:DescribeImages", 85 | ] 86 | 87 | resources = [ 88 | data.aws_codecommit_repository.terraform.arn 89 | ] 90 | } 91 | } 92 | 93 | resource "aws_iam_policy" "codepipeline" { 94 | name = "${local.aws_account_level_id}-codepipeline" 95 | description = "CodePipeline (${upper(local.aws_account_level_id)}) access policy" 96 | policy = data.aws_iam_policy_document.codepipeline.json 97 | } 98 | 99 | resource "aws_iam_role_policy_attachment" "codepipeline" { 100 | role = aws_iam_role.codepipeline.name 101 | policy_arn = aws_iam_policy.codepipeline.arn 102 | } 103 | -------------------------------------------------------------------------------- /components/automation/iam-tf_deployer.tf: -------------------------------------------------------------------------------- 1 | # 2 | # The IAM Role defined here to be used by TF pipelines is just a simplification. 3 | # Ideally, IAM permissions should be crafted in a secure way. 4 | # When deploying to different accounts, roles from those accounts should be used. 5 | # 6 | data "aws_iam_policy_document" "tf_deployer_assumerole" { 7 | statement { 8 | sid = "AllowCodeBuildDockerAssumeRole" 9 | effect = "Allow" 10 | 11 | actions = [ 12 | "sts:AssumeRole", 13 | ] 14 | 15 | principals { 16 | type = "AWS" 17 | 18 | identifiers = [ 19 | data.aws_caller_identity.current.account_id, 20 | ] 21 | } 22 | } 23 | } 24 | 25 | resource "aws_iam_role" "tf_deployer" { 26 | name = "${local.aws_account_level_id}-tf-deployer" 27 | assume_role_policy = data.aws_iam_policy_document.tf_deployer_assumerole.json 28 | 29 | tags = merge( 30 | local.default_tags, 31 | map( 32 | "Name", "${local.aws_account_level_id}/tf-deployer", 33 | ), 34 | ) 35 | } 36 | 37 | resource "aws_iam_role_policy_attachment" "tf_deployer" { 38 | role = aws_iam_role.tf_deployer.name 39 | policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess" 40 | } 41 | -------------------------------------------------------------------------------- /components/automation/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # GENERAL 3 | aws_account_level_id = format( 4 | "%s-%s-%s", 5 | var.project, 6 | var.environment, 7 | var.component, 8 | ) 9 | 10 | aws_global_level_id = format( 11 | "%s-%s-%s-%s", 12 | var.project, 13 | data.aws_caller_identity.current.account_id, 14 | var.environment, 15 | var.component, 16 | ) 17 | 18 | default_tags = { 19 | Project = "${var.project}" 20 | Environment = "${var.environment}" 21 | Component = "${var.component}" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /components/automation/module-tf-codepipelines.tf: -------------------------------------------------------------------------------- 1 | # 2 | # NON-PROD::AUTOMATION (self) 3 | # 4 | module "tf_pipeline_automation_nonprod" { 5 | source = "../../modules/tf-codepipeline" 6 | 7 | # GENERAL 8 | project = var.project 9 | environment = var.environment 10 | component = var.component 11 | default_tags = local.default_tags 12 | 13 | # PIPELINE DETAILS 14 | pipeline_config = { 15 | tf_repo_name = var.codecommit_terraform_repo_name 16 | tf_repo_branch = var.codecommit_terraform_repo_main_branch 17 | tfscaffold_runner_ecr_repo_arn = data.aws_ecr_repository.tfscaffold_runner.arn 18 | tfscaffold_runner_ecr_image_uri = var.tfscaffold_runner_ecr_image_uri 19 | } 20 | 21 | tfscaffold_vars = { 22 | TF_PROJECT = var.project 23 | TF_ENVIRONMENT = var.environment 24 | TF_COMPONENT = var.component 25 | TF_STATE_BUCKET = var.tfscaffold_tfstate_bucket_prefix 26 | AWS_REGION = var.aws_region 27 | } 28 | 29 | # IAM 30 | codepipeline_role_arn = aws_iam_role.codepipeline.arn 31 | tf_deployer_role_arn = aws_iam_role.tf_deployer.arn 32 | 33 | # S3 34 | codepipeline_artifacts_s3_bucket_name = aws_s3_bucket.codepipeline.bucket 35 | tfscaffold_tfstate_bucket_arn = var.tfscaffold_tfstate_bucket_arn 36 | 37 | # LOGS 38 | cwlg_retention_in_days = 7 39 | } 40 | 41 | # 42 | # DEV::EXAMPLE 43 | # 44 | module "tf_pipeline_example_dev" { 45 | source = "../../modules/tf-codepipeline" 46 | 47 | # GENERAL 48 | project = var.project 49 | environment = var.environment 50 | component = var.component 51 | default_tags = local.default_tags 52 | 53 | # PIPELINE DETAILS 54 | pipeline_config = { 55 | tf_repo_name = var.codecommit_terraform_repo_name 56 | tf_repo_branch = var.codecommit_terraform_repo_main_branch 57 | tfscaffold_runner_ecr_repo_arn = data.aws_ecr_repository.tfscaffold_runner.arn 58 | tfscaffold_runner_ecr_image_uri = var.tfscaffold_runner_ecr_image_uri 59 | } 60 | 61 | tfscaffold_vars = { 62 | TF_PROJECT = var.project 63 | TF_ENVIRONMENT = "dev" 64 | TF_COMPONENT = "example" 65 | TF_STATE_BUCKET = var.tfscaffold_tfstate_bucket_prefix 66 | AWS_REGION = var.aws_region 67 | } 68 | 69 | # IAM 70 | codepipeline_role_arn = aws_iam_role.codepipeline.arn 71 | tf_deployer_role_arn = aws_iam_role.tf_deployer.arn 72 | 73 | # S3 74 | codepipeline_artifacts_s3_bucket_name = aws_s3_bucket.codepipeline.bucket 75 | tfscaffold_tfstate_bucket_arn = var.tfscaffold_tfstate_bucket_arn 76 | 77 | # LOGS 78 | cwlg_retention_in_days = 7 79 | } 80 | -------------------------------------------------------------------------------- /components/automation/outputs.tf: -------------------------------------------------------------------------------- 1 | output "__AWS_ACCOUNT_LEVEL_IDENTIFIER__" { 2 | value = upper(local.aws_account_level_id) 3 | } 4 | -------------------------------------------------------------------------------- /components/automation/s3_bucket.tf: -------------------------------------------------------------------------------- 1 | resource "aws_s3_bucket" "codepipeline" { 2 | bucket = "${local.aws_global_level_id}-codepipeline-artifacts" 3 | acl = "private" 4 | 5 | server_side_encryption_configuration { 6 | rule { 7 | apply_server_side_encryption_by_default { 8 | sse_algorithm = "AES256" 9 | } 10 | } 11 | } 12 | 13 | tags = merge( 14 | local.default_tags, 15 | map( 16 | "Name", "${local.aws_global_level_id}-codepipeline-artifacts", 17 | ), 18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /components/automation/variables.tf: -------------------------------------------------------------------------------- 1 | # GENERAL 2 | variable "aws_region" { 3 | type = string 4 | description = "The AWS Region" 5 | default = "eu-west-1" 6 | } 7 | 8 | variable "project" { 9 | type = string 10 | description = "The Project name" 11 | default = "lab" 12 | } 13 | 14 | variable "environment" { 15 | type = string 16 | description = "The environment name" 17 | } 18 | 19 | variable "component" { 20 | type = string 21 | description = "The TF component name" 22 | default = "automation" 23 | } 24 | 25 | # SPECIFIC 26 | variable "tfscaffold_tfstate_bucket_prefix" { 27 | type = string 28 | description = "The TFstate S3 bucket prefix defined to be used by TFScaffold" 29 | } 30 | 31 | variable "tfscaffold_tfstate_bucket_arn" { 32 | type = string 33 | description = "The TFstate S3 bucket ARN" 34 | } 35 | 36 | variable "tfscaffold_runner_ecr_repo_name" { 37 | type = string 38 | description = "The name of the ECR Repository with TFScaffold Docker image" 39 | } 40 | 41 | variable "tfscaffold_runner_ecr_image_uri" { 42 | type = string 43 | description = "The URI of the TFScaffold Docker image" 44 | } 45 | 46 | variable "codecommit_terraform_repo_name" { 47 | type = string 48 | description = "The TF CodeCommit Terraform repo name" 49 | } 50 | 51 | variable "codecommit_terraform_repo_main_branch" { 52 | type = string 53 | description = "The TF CodeCommit Terraform repo main branch" 54 | } 55 | -------------------------------------------------------------------------------- /components/example/.terraform-version: -------------------------------------------------------------------------------- 1 | 0.12.24 2 | -------------------------------------------------------------------------------- /components/example/aws_data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | -------------------------------------------------------------------------------- /components/example/aws_provider.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.aws_region 3 | } 4 | -------------------------------------------------------------------------------- /components/example/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # GENERAL 3 | aws_account_level_id = format( 4 | "%s-%s-%s", 5 | var.project, 6 | var.environment, 7 | var.component, 8 | ) 9 | 10 | aws_global_level_id = format( 11 | "%s-%s-%s-%s", 12 | var.project, 13 | data.aws_caller_identity.current.account_id, 14 | var.environment, 15 | var.component, 16 | ) 17 | 18 | default_tags = { 19 | Project = "${var.project}" 20 | Environment = "${var.environment}" 21 | Component = "${var.component}" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /components/example/module-example_test.tf: -------------------------------------------------------------------------------- 1 | module "test" { 2 | source = "../../modules/example" 3 | 4 | # GENERAL 5 | project = var.project 6 | environment = var.environment 7 | component = var.component 8 | default_tags = local.default_tags 9 | } 10 | -------------------------------------------------------------------------------- /components/example/outputs.tf: -------------------------------------------------------------------------------- 1 | output "__AWS_ACCOUNT_LEVEL_IDENTIFIER__" { 2 | value = upper(local.aws_account_level_id) 3 | } 4 | -------------------------------------------------------------------------------- /components/example/variables.tf: -------------------------------------------------------------------------------- 1 | # GENERAL 2 | variable "aws_region" { 3 | type = string 4 | description = "The AWS Region" 5 | } 6 | 7 | variable "project" { 8 | type = string 9 | description = "The Project name" 10 | } 11 | 12 | variable "environment" { 13 | type = string 14 | description = "The environment name" 15 | } 16 | 17 | variable "component" { 18 | type = string 19 | description = "The TF component name" 20 | default = "example" 21 | } 22 | -------------------------------------------------------------------------------- /etc/env_eu-west-1_dev.tfvars: -------------------------------------------------------------------------------- 1 | # 2 | # GENERAL 3 | # 4 | environment = "dev" 5 | -------------------------------------------------------------------------------- /etc/env_eu-west-1_nonprod.tfvars: -------------------------------------------------------------------------------- 1 | # 2 | # GENERAL 3 | # 4 | environment = "nonprod" 5 | 6 | # 7 | # AUTOMATION 8 | # 9 | tfscaffold_tfstate_bucket_prefix = "tf-sebolabs" 10 | tfscaffold_tfstate_bucket_arn = "arn:aws:s3:::tf-sebolabs-012345678910-eu-west-1" 11 | 12 | tfscaffold_runner_ecr_repo_name = "tfscaffold" 13 | tfscaffold_runner_ecr_image_uri = "012345678910.dkr.ecr.eu-west-1.amazonaws.com/tfscaffold:latest" 14 | 15 | codecommit_terraform_repo_name = "terraform" 16 | codecommit_terraform_repo_main_branch = "master" 17 | -------------------------------------------------------------------------------- /etc/eu-west-1.tfvars: -------------------------------------------------------------------------------- 1 | aws_region = "eu-west-1" 2 | -------------------------------------------------------------------------------- /etc/global.tfvars: -------------------------------------------------------------------------------- 1 | project = "lab" 2 | -------------------------------------------------------------------------------- /modules/example/aws_data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_region" "current" {} 4 | -------------------------------------------------------------------------------- /modules/example/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # GENERAL 3 | aws_account_level_id = format( 4 | "%s-%s-%s", 5 | var.project, 6 | var.environment, 7 | var.component, 8 | ) 9 | 10 | aws_global_level_id = format( 11 | "%s-%s-%s-%s", 12 | var.project, 13 | data.aws_caller_identity.current.account_id, 14 | var.environment, 15 | var.component, 16 | ) 17 | 18 | parent_module = lookup( 19 | var.default_tags, 20 | "Module", 21 | "", 22 | ) 23 | 24 | default_tags = merge( 25 | var.default_tags, 26 | map( 27 | "Module", local.parent_module == "" ? var.module : format( 28 | "%s/%s", 29 | local.parent_module, 30 | var.module, 31 | ), 32 | ), 33 | ) 34 | 35 | # SPECIFIC 36 | # ... 37 | } 38 | -------------------------------------------------------------------------------- /modules/example/outputs.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /modules/example/variables.tf: -------------------------------------------------------------------------------- 1 | # GENERAL 2 | variable "project" { 3 | type = string 4 | description = "The project name" 5 | } 6 | 7 | variable "environment" { 8 | type = string 9 | description = "The environment name" 10 | } 11 | 12 | variable "component" { 13 | type = string 14 | description = "The TF component name" 15 | } 16 | 17 | variable "module" { 18 | type = string 19 | description = "The module name" 20 | default = "example" 21 | } 22 | 23 | variable "default_tags" { 24 | type = map 25 | description = "Default tags to be applied to all taggable resources" 26 | default = {} 27 | } 28 | 29 | # SPECIFIC 30 | # ... 31 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/aws_data.tf: -------------------------------------------------------------------------------- 1 | data "aws_caller_identity" "current" {} 2 | 3 | data "aws_region" "current" {} 4 | 5 | data "aws_s3_bucket" "codepipeline_artifacts" { 6 | bucket = var.codepipeline_artifacts_s3_bucket_name 7 | } 8 | 9 | data "template_file" "codebuild_buildspec" { 10 | template = file("${path.module}/templates/buildspec.tpl") 11 | 12 | vars = { 13 | tfscaffold_deployer_role_arn = var.tf_deployer_role_arn 14 | tfscaffold_output_plan_name = local.codepipeline_name 15 | } 16 | } 17 | 18 | data "aws_codecommit_repository" "terraform" { 19 | repository_name = lookup(var.pipeline_config, "tf_repo_name") 20 | } 21 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/cloudwatch_log_groups.tf: -------------------------------------------------------------------------------- 1 | resource "aws_cloudwatch_log_group" "codepipeline" { 2 | name = "/aws/codepipeline/${local.codepipeline_name}" 3 | 4 | retention_in_days = var.cwlg_retention_in_days 5 | 6 | tags = merge( 7 | local.default_tags, 8 | map( 9 | "Name", "/aws/codepipeline/${local.codepipeline_name}", 10 | ), 11 | ) 12 | } 13 | 14 | resource "aws_cloudwatch_log_group" "codebuild_planner" { 15 | name = "/aws/codebuild/${local.codepipeline_name}-planner" 16 | 17 | retention_in_days = var.cwlg_retention_in_days 18 | 19 | tags = merge( 20 | local.default_tags, 21 | map( 22 | "Name", "/aws/codebuild/${local.codepipeline_name}-planner", 23 | ), 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/codebuild-planner.tf: -------------------------------------------------------------------------------- 1 | resource "aws_codebuild_project" "planner" { 2 | name = "${local.codepipeline_name}-planner" 3 | description = "TF planner for ${local.codepipeline_name} pipeline" 4 | build_timeout = "29" 5 | queued_timeout = "30" 6 | 7 | service_role = aws_iam_role.codebuild.arn 8 | 9 | artifacts { 10 | type = "NO_ARTIFACTS" 11 | } 12 | 13 | environment { 14 | type = "LINUX_CONTAINER" 15 | compute_type = "BUILD_GENERAL1_SMALL" 16 | image = lookup(var.pipeline_config, "tfscaffold_runner_ecr_image_uri") 17 | image_pull_credentials_type = "CODEBUILD" 18 | privileged_mode = false 19 | 20 | dynamic "environment_variable" { 21 | for_each = var.tfscaffold_vars 22 | 23 | content { 24 | name = environment_variable.key 25 | value = environment_variable.value 26 | type = "PLAINTEXT" 27 | } 28 | } 29 | 30 | environment_variable { 31 | name = "TF_ACTION" 32 | value = "plan" 33 | type = "PLAINTEXT" 34 | } 35 | } 36 | 37 | source { 38 | type = "CODECOMMIT" 39 | location = data.aws_codecommit_repository.terraform.clone_url_http 40 | git_clone_depth = 0 # gives branch name instead of commit hash in the UI 41 | 42 | git_submodules_config { 43 | fetch_submodules = false 44 | } 45 | 46 | buildspec = data.template_file.codebuild_buildspec.rendered 47 | } 48 | 49 | source_version = "refs/heads/${lookup(var.pipeline_config, "tf_repo_branch")}" 50 | 51 | logs_config { 52 | cloudwatch_logs { 53 | group_name = aws_cloudwatch_log_group.codebuild_planner.name 54 | } 55 | } 56 | 57 | tags = merge( 58 | local.default_tags, 59 | map( 60 | "Name", "${local.codepipeline_name}-planner", 61 | ), 62 | ) 63 | } 64 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/codebuild-runner.tf: -------------------------------------------------------------------------------- 1 | resource "aws_codebuild_project" "runner" { 2 | name = "${local.codepipeline_name}-runner" 3 | description = "TF runner for ${local.codepipeline_name} pipeline" 4 | build_timeout = "29" 5 | queued_timeout = "30" 6 | 7 | service_role = aws_iam_role.codebuild.arn 8 | 9 | artifacts { 10 | type = "CODEPIPELINE" 11 | name = local.codepipeline_name 12 | packaging = "NONE" 13 | encryption_disabled = "false" 14 | } 15 | 16 | environment { 17 | type = "LINUX_CONTAINER" 18 | compute_type = "BUILD_GENERAL1_SMALL" 19 | image = lookup(var.pipeline_config, "tfscaffold_runner_ecr_image_uri") 20 | image_pull_credentials_type = "CODEBUILD" 21 | privileged_mode = false 22 | 23 | dynamic "environment_variable" { 24 | for_each = var.tfscaffold_vars 25 | 26 | content { 27 | name = environment_variable.key 28 | value = environment_variable.value 29 | type = "PLAINTEXT" 30 | } 31 | } 32 | } 33 | 34 | source { 35 | type = "CODEPIPELINE" 36 | buildspec = data.template_file.codebuild_buildspec.rendered 37 | } 38 | 39 | logs_config { 40 | cloudwatch_logs { 41 | group_name = aws_cloudwatch_log_group.codepipeline.name 42 | } 43 | } 44 | 45 | tags = merge( 46 | local.default_tags, 47 | map( 48 | "Name", "${local.codepipeline_name}-runner", 49 | ), 50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/codepipeline.tf: -------------------------------------------------------------------------------- 1 | resource "aws_codepipeline" "main" { 2 | name = local.codepipeline_name 3 | role_arn = var.codepipeline_role_arn 4 | 5 | artifact_store { 6 | type = "S3" 7 | location = var.codepipeline_artifacts_s3_bucket_name 8 | } 9 | 10 | stage { 11 | name = "Source" 12 | 13 | action { 14 | name = "FetchCode" 15 | category = "Source" 16 | owner = "AWS" 17 | provider = "CodeCommit" 18 | version = "1" 19 | run_order = 1 20 | output_artifacts = ["SourceArtifact"] 21 | 22 | configuration = { 23 | RepositoryName = lookup(var.pipeline_config, "tf_repo_name") 24 | BranchName = lookup(var.pipeline_config, "tf_repo_branch") 25 | PollForSourceChanges = "false" 26 | } 27 | } 28 | } 29 | 30 | stage { 31 | name = "Build" 32 | 33 | action { 34 | name = "TerraformPlan" 35 | category = "Build" 36 | owner = "AWS" 37 | provider = "CodeBuild" 38 | version = "1" 39 | run_order = 1 40 | input_artifacts = ["SourceArtifact"] 41 | output_artifacts = ["TerraformPlanArtifacts"] 42 | 43 | configuration = { 44 | ProjectName = aws_codebuild_project.runner.name 45 | EnvironmentVariables = "[{\"name\":\"TF_ACTION\",\"value\":\"plan\",\"type\":\"PLAINTEXT\"}]" 46 | } 47 | } 48 | } 49 | 50 | stage { 51 | name = "Gate" # TODO: SNS 52 | 53 | action { 54 | name = "TerraformPlanApproval" 55 | category = "Approval" 56 | owner = "AWS" 57 | provider = "Manual" 58 | version = "1" 59 | run_order = 1 60 | 61 | configuration = { 62 | CustomData = "Do you approve the plan?" 63 | } 64 | } 65 | } 66 | 67 | stage { 68 | name = "Deploy" 69 | 70 | action { 71 | name = "TerraformApply" 72 | category = "Build" 73 | owner = "AWS" 74 | provider = "CodeBuild" 75 | version = "1" 76 | run_order = 1 77 | 78 | input_artifacts = [ 79 | "TerraformPlanArtifacts", 80 | "SourceArtifact", 81 | ] 82 | 83 | configuration = { 84 | ProjectName = aws_codebuild_project.runner.name 85 | PrimarySource = "SourceArtifact" 86 | EnvironmentVariables = "[{\"name\":\"TF_ACTION\",\"value\":\"apply\",\"type\":\"PLAINTEXT\"}]" 87 | } 88 | } 89 | } 90 | 91 | tags = merge( 92 | local.default_tags, 93 | map( 94 | "Name", "${local.codepipeline_name}", 95 | ), 96 | ) 97 | } 98 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/iam-codebuild.tf: -------------------------------------------------------------------------------- 1 | data "aws_iam_policy_document" "codebuild_assumerole" { 2 | statement { 3 | sid = "AllowCodeBuildAssumeRole" 4 | effect = "Allow" 5 | 6 | actions = [ 7 | "sts:AssumeRole", 8 | ] 9 | 10 | principals { 11 | type = "Service" 12 | 13 | identifiers = [ 14 | "codebuild.amazonaws.com", 15 | ] 16 | } 17 | } 18 | } 19 | 20 | resource "aws_iam_role" "codebuild" { 21 | name = "${local.codepipeline_name}-codebuild" 22 | assume_role_policy = data.aws_iam_policy_document.codebuild_assumerole.json 23 | 24 | tags = merge( 25 | local.default_tags, 26 | map( 27 | "Name", "${local.codepipeline_name}/codebuild", 28 | ), 29 | ) 30 | } 31 | 32 | data "aws_iam_policy_document" "codebuild" { 33 | statement { 34 | sid = "AllowLogs" 35 | effect = "Allow" 36 | 37 | actions = [ 38 | "logs:CreateLogStream", 39 | "logs:PutLogEvents", 40 | ] 41 | 42 | resources = [ 43 | "${aws_cloudwatch_log_group.codepipeline.arn}:*", 44 | "${aws_cloudwatch_log_group.codebuild_planner.arn}:*", 45 | ] 46 | } 47 | 48 | statement { 49 | sid = "AllowS3" 50 | effect = "Allow" 51 | 52 | actions = [ 53 | "s3:PutObject", 54 | "s3:GetObject", 55 | "s3:GetObjectVersion", 56 | "s3:GetBucketAcl", 57 | "s3:GetBucketLocation", 58 | ] 59 | 60 | resources = [ 61 | data.aws_s3_bucket.codepipeline_artifacts.arn, 62 | "${data.aws_s3_bucket.codepipeline_artifacts.arn}/*", 63 | var.tfscaffold_tfstate_bucket_arn, 64 | "${var.tfscaffold_tfstate_bucket_arn}/*", 65 | ] 66 | } 67 | 68 | statement { 69 | sid = "AllowECR" 70 | effect = "Allow" 71 | 72 | actions = [ 73 | "ecr:List*", 74 | "ecr:Describe*", 75 | "ecr:*Get*", 76 | ] 77 | 78 | resources = [ 79 | lookup(var.pipeline_config, "tfscaffold_runner_ecr_repo_arn"), 80 | ] 81 | } 82 | 83 | statement { 84 | sid = "AllowCodeCommit" 85 | effect = "Allow" 86 | 87 | actions = [ 88 | "codecommit:GitPull", 89 | ] 90 | 91 | resources = [ 92 | data.aws_codecommit_repository.terraform.arn, 93 | ] 94 | } 95 | 96 | statement { 97 | sid = "AllowStsAssumeRole" 98 | effect = "Allow" 99 | 100 | actions = [ 101 | "sts:AssumeRole", 102 | ] 103 | 104 | resources = [ 105 | var.tf_deployer_role_arn, 106 | ] 107 | } 108 | } 109 | 110 | resource "aws_iam_policy" "codebuild" { 111 | name = "${local.codepipeline_name}-codebuild" 112 | description = "CodeBuild (${upper(local.codepipeline_name)}) access policy" 113 | policy = data.aws_iam_policy_document.codebuild.json 114 | } 115 | 116 | resource "aws_iam_role_policy_attachment" "codebuild" { 117 | role = aws_iam_role.codebuild.name 118 | policy_arn = aws_iam_policy.codebuild.arn 119 | } 120 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # GENERAL 3 | aws_account_level_id = format( 4 | "%s-%s-%s", 5 | var.project, 6 | var.environment, 7 | var.component, 8 | ) 9 | 10 | aws_global_level_id = format( 11 | "%s-%s-%s-%s", 12 | var.project, 13 | data.aws_caller_identity.current.account_id, 14 | var.environment, 15 | var.component, 16 | ) 17 | 18 | parent_module = lookup( 19 | var.default_tags, 20 | "Module", 21 | "", 22 | ) 23 | 24 | default_tags = merge( 25 | var.default_tags, 26 | map( 27 | "Module", local.parent_module == "" ? var.module : format( 28 | "%s/%s", 29 | local.parent_module, 30 | var.module, 31 | ), 32 | ), 33 | ) 34 | 35 | # SPECIFIC 36 | codepipeline_name = format("tf-%s-%s-%s", 37 | lookup(var.tfscaffold_vars, "TF_PROJECT"), 38 | lookup(var.tfscaffold_vars, "TF_ENVIRONMENT"), 39 | lookup(var.tfscaffold_vars, "TF_COMPONENT"), 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/outputs.tf: -------------------------------------------------------------------------------- 1 | output "codebuild_runner_arn" { 2 | value = aws_codebuild_project.runner.arn 3 | } 4 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/templates/buildspec.tpl: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | TF_PLAN_NAME: ${tfscaffold_output_plan_name} 6 | TF_DEPLOYER_ROLE_ARN: ${tfscaffold_deployer_role_arn} 7 | TF_CLI_ARGS: "-no-color" 8 | exported-variables: 9 | - TF_CLI_ARGS 10 | 11 | phases: 12 | pre_build: 13 | commands: 14 | - echo '[INFO] Assuming TF Deployer IAM role...' 15 | - TF_DEPLOYER_ROLE=`aws sts assume-role --role-arn $${TF_DEPLOYER_ROLE_ARN} --role-session-name $${TF_PLAN_NAME}` 16 | - export AWS_ACCESS_KEY_ID=`echo "$${TF_DEPLOYER_ROLE}" | jq -r '.Credentials.AccessKeyId'` 17 | - export AWS_SECRET_ACCESS_KEY=`echo "$${TF_DEPLOYER_ROLE}" | jq -r '.Credentials.SecretAccessKey'` 18 | - export AWS_SESSION_TOKEN=`echo "$${TF_DEPLOYER_ROLE}" | jq -r '.Credentials.SessionToken'` 19 | - echo '[INFO] Gathering environment information...' 20 | - bash /opt/tf-bash.sh 21 | - echo '[INFO] Collecting artifacts...' 22 | - if [ $${TF_ACTION} == "apply" ]; then cp $${CODEBUILD_SRC_DIR_TerraformPlanArtifacts}/* /opt/; fi 23 | build: 24 | commands: 25 | - echo "[INFO] Running TFScaffold $${TF_ACTION}..." 26 | - ./bin/terraform.sh -p $${TF_PROJECT} -e $${TF_ENVIRONMENT} -c $${TF_COMPONENT} -b $${TF_STATE_BUCKET} -r $${AWS_REGION} -a $${TF_ACTION} -o /opt/$${TF_PLAN_NAME}.plan 27 | post_build: 28 | commands: 29 | - echo "[INFO] TFScaffold $${TF_ACTION} run successfully." 30 | 31 | artifacts: 32 | files: 33 | - $${TF_PLAN_NAME}.plan 34 | name: TerraformPlanArtifacts 35 | discard-paths: no 36 | base-directory: /opt 37 | -------------------------------------------------------------------------------- /modules/tf-codepipeline/variables.tf: -------------------------------------------------------------------------------- 1 | # GENERAL 2 | variable "project" { 3 | type = string 4 | description = "The project name" 5 | } 6 | 7 | variable "environment" { 8 | type = string 9 | description = "The environment name" 10 | } 11 | 12 | variable "component" { 13 | type = string 14 | description = "The TF component name" 15 | } 16 | 17 | variable "module" { 18 | type = string 19 | description = "The module name" 20 | default = "tf-codepipeline" 21 | } 22 | 23 | variable "default_tags" { 24 | type = map 25 | description = "Default tags to be applied to all taggable resources" 26 | default = {} 27 | } 28 | 29 | # SPECIFIC 30 | variable "pipeline_config" { 31 | type = map(string) 32 | description = "The pipeline required properties" 33 | } 34 | 35 | variable "tfscaffold_vars" { 36 | type = map(string) 37 | description = "The TFScaffold required variables" 38 | } 39 | 40 | variable "codepipeline_role_arn" { 41 | type = string 42 | description = "The IAM service role for CodePipeline" 43 | } 44 | 45 | variable "codepipeline_artifacts_s3_bucket_name" { 46 | type = string 47 | description = "The S3 Bucket name used to store CodePipeline artifacts" 48 | } 49 | 50 | variable "tf_deployer_role_arn" { 51 | type = string 52 | description = "The TF Deployer IAM role ARN to be assumed during CodeBuild run" 53 | } 54 | 55 | variable "cwlg_retention_in_days" { 56 | type = string 57 | description = "Specifies the number of days you want to retain log events" 58 | } 59 | 60 | variable "tfscaffold_tfstate_bucket_arn" { 61 | type = string 62 | description = "The ARN of the TFScaffold TFstate S3 bucket" 63 | } 64 | --------------------------------------------------------------------------------