Setting up AWS CodePipeline with Terraform

A comprehensive guide to configuring AWS CodePipeline for continuous delivery using Terraform Infrastructure as Code

Setting up AWS CodePipeline with Terraform

AWS CodePipeline is a fully managed continuous delivery service. This guide shows how to set up CodePipeline using Terraform.

Prerequisites

  • AWS CLI configured
  • Terraform installed
  • Source code repository
  • Build and deployment requirements defined

Project Structure

aws-codepipeline-terraform/
├── main.tf
├── variables.tf
├── outputs.tf
└── terraform.tfvars

Basic CodePipeline Configuration

# main.tf
provider "aws" {
  region = var.aws_region
}

# S3 Bucket for Artifacts
resource "aws_s3_bucket" "artifacts" {
  bucket = "${var.project_name}-pipeline-artifacts"

  tags = {
    Environment = var.environment
  }
}

# CodePipeline Role
resource "aws_iam_role" "codepipeline_role" {
  name = "${var.project_name}-pipeline-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "codepipeline.amazonaws.com"
        }
      }
    ]
  })
}

# CodePipeline Policy
resource "aws_iam_role_policy" "codepipeline_policy" {
  name = "${var.project_name}-pipeline-policy"
  role = aws_iam_role.codepipeline_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:GetObjectVersion",
          "s3:GetBucketVersioning",
          "s3:PutObject"
        ]
        Resource = [
          aws_s3_bucket.artifacts.arn,
          "${aws_s3_bucket.artifacts.arn}/*"
        ]
      },
      {
        Effect = "Allow"
        Action = [
          "codecommit:CancelUploadArchive",
          "codecommit:GetBranch",
          "codecommit:GetCommit",
          "codecommit:GetUploadArchiveStatus",
          "codecommit:UploadArchive"
        ]
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "codebuild:BatchGetBuilds",
          "codebuild:StartBuild"
        ]
        Resource = "*"
      }
    ]
  })
}

# CodeBuild Project
resource "aws_codebuild_project" "main" {
  name          = "${var.project_name}-build"
  description   = "Build project for ${var.project_name}"
  build_timeout = "30"
  service_role  = aws_iam_role.codebuild_role.arn

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                      = "aws/codebuild/standard:5.0"
    type                       = "LINUX_CONTAINER"
    image_pull_credentials_type = "CODEBUILD"

    environment_variable {
      name  = "ENVIRONMENT"
      value = var.environment
    }
  }

  source {
    type = "CODEPIPELINE"
    buildspec = "buildspec.yml"
  }

  tags = {
    Environment = var.environment
  }
}

# CodePipeline
resource "aws_codepipeline" "main" {
  name     = "${var.project_name}-pipeline"
  role_arn = aws_iam_role.codepipeline_role.arn

  artifact_store {
    location = aws_s3_bucket.artifacts.bucket
    type     = "S3"
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        RepositoryName = var.repository_name
        BranchName     = var.branch_name
      }
    }
  }

  stage {
    name = "Build"

    action {
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      input_artifacts = ["source_output"]
      version         = "1"

      configuration = {
        ProjectName = aws_codebuild_project.main.name
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CloudFormation"
      input_artifacts = ["build_output"]
      version         = "1"

      configuration = {
        ActionMode     = "CREATE_UPDATE"
        StackName      = "${var.project_name}-stack"
        TemplatePath   = "build_output::template.yaml"
        Capabilities   = "CAPABILITY_IAM"
        RoleArn        = aws_iam_role.cloudformation_role.arn
      }
    }
  }
}

CodeBuild Role Configuration

# CodeBuild Role
resource "aws_iam_role" "codebuild_role" {
  name = "${var.project_name}-build-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "codebuild.amazonaws.com"
        }
      }
    ]
  })
}

# CodeBuild Policy
resource "aws_iam_role_policy" "codebuild_policy" {
  name = "${var.project_name}-build-policy"
  role = aws_iam_role.codebuild_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Resource = [
          "*"
        ]
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
      },
      {
        Effect = "Allow"
        Action = [
          "s3:GetObject",
          "s3:GetObjectVersion",
          "s3:GetBucketVersioning",
          "s3:PutObject"
        ]
        Resource = [
          aws_s3_bucket.artifacts.arn,
          "${aws_s3_bucket.artifacts.arn}/*"
        ]
      }
    ]
  })
}

Variables Configuration

# variables.tf
variable "aws_region" {
  description = "AWS region"
  type        = string
  default     = "us-west-2"
}

variable "project_name" {
  description = "Project name"
  type        = string
}

variable "environment" {
  description = "Environment name"
  type        = string
  default     = "dev"
}

variable "repository_name" {
  description = "CodeCommit repository name"
  type        = string
}

variable "branch_name" {
  description = "Branch name"
  type        = string
  default     = "main"
}

Best Practices

  1. Pipeline Management

    • Use meaningful stage names
    • Implement proper error handling
    • Configure notifications
    • Regular pipeline reviews
  2. Security

    • Implement proper IAM roles
    • Use encryption for artifacts
    • Enable logging
    • Regular security reviews
  3. Cost Optimization

    • Monitor build minutes
    • Clean up artifacts
    • Use appropriate build environments
    • Regular cost reviews
  4. Performance

    • Optimize build steps
    • Use caching effectively
    • Monitor build times
    • Regular performance reviews

Pipeline with Multiple Environments

# Pipeline with Staging and Production
resource "aws_codepipeline" "multi_env" {
  name     = "${var.project_name}-multi-env-pipeline"
  role_arn = aws_iam_role.codepipeline_role.arn

  artifact_store {
    location = aws_s3_bucket.artifacts.bucket
    type     = "S3"
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        RepositoryName = var.repository_name
        BranchName     = var.branch_name
      }
    }
  }

  stage {
    name = "Build"

    action {
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      input_artifacts = ["source_output"]
      version         = "1"

      configuration = {
        ProjectName = aws_codebuild_project.main.name
      }
    }
  }

  stage {
    name = "DeployToStaging"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CloudFormation"
      input_artifacts = ["build_output"]
      version         = "1"

      configuration = {
        ActionMode     = "CREATE_UPDATE"
        StackName      = "${var.project_name}-staging"
        TemplatePath   = "build_output::template.yaml"
        Capabilities   = "CAPABILITY_IAM"
        RoleArn        = aws_iam_role.cloudformation_role.arn
        ParameterOverrides = jsonencode({
          Environment = "staging"
        })
      }
    }
  }

  stage {
    name = "ApprovalForProduction"

    action {
      name     = "Approval"
      category = "Approval"
      owner    = "AWS"
      provider = "Manual"
      version  = "1"
    }
  }

  stage {
    name = "DeployToProduction"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CloudFormation"
      input_artifacts = ["build_output"]
      version         = "1"

      configuration = {
        ActionMode     = "CREATE_UPDATE"
        StackName      = "${var.project_name}-production"
        TemplatePath   = "build_output::template.yaml"
        Capabilities   = "CAPABILITY_IAM"
        RoleArn        = aws_iam_role.cloudformation_role.arn
        ParameterOverrides = jsonencode({
          Environment = "production"
        })
      }
    }
  }
}

Monitoring Configuration

# CloudWatch Events Rule
resource "aws_cloudwatch_event_rule" "pipeline_status" {
  name        = "${var.project_name}-pipeline-status"
  description = "Capture pipeline status changes"

  event_pattern = jsonencode({
    source      = ["aws.codepipeline"]
    detail-type = ["CodePipeline Pipeline Execution State Change"]
    detail = {
      pipeline = [aws_codepipeline.main.name]
    }
  })
}

resource "aws_cloudwatch_event_target" "sns" {
  rule      = aws_cloudwatch_event_rule.pipeline_status.name
  target_id = "SendToSNS"
  arn       = var.sns_topic_arn

  input_transformer {
    input_paths = {
      pipeline = "$.detail.pipeline"
      state    = "$.detail.state"
    }
    input_template = "\"Pipeline <pipeline> changed state to <state>\""
  }
}

# CloudWatch Dashboard
resource "aws_cloudwatch_dashboard" "pipeline" {
  dashboard_name = "${var.project_name}-pipeline-dashboard"

  dashboard_body = jsonencode({
    widgets = [
      {
        type   = "metric"
        x      = 0
        y      = 0
        width  = 12
        height = 6

        properties = {
          metrics = [
            ["AWS/CodeBuild", "BuildDuration", "ProjectName", aws_codebuild_project.main.name],
            ["AWS/CodeBuild", "FailedBuilds", "ProjectName", aws_codebuild_project.main.name]
          ]
          period = 300
          stat   = "Average"
          region = var.aws_region
          title  = "CodeBuild Metrics"
        }
      }
    ]
  })
}

Deployment Steps

  1. Initialize Terraform:
terraform init
  1. Plan the deployment:
terraform plan
  1. Apply the configuration:
terraform apply

Clean Up

Remove all resources when done:

terraform destroy

Common Use Cases

  1. Docker Build Pipeline
resource "aws_codebuild_project" "docker" {
  name          = "${var.project_name}-docker-build"
  description   = "Docker build project"
  build_timeout = "30"
  service_role  = aws_iam_role.codebuild_role.arn

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                      = "aws/codebuild/standard:5.0"
    type                       = "LINUX_CONTAINER"
    image_pull_credentials_type = "CODEBUILD"
    privileged_mode            = true

    environment_variable {
      name  = "AWS_DEFAULT_REGION"
      value = var.aws_region
    }

    environment_variable {
      name  = "AWS_ACCOUNT_ID"
      value = data.aws_caller_identity.current.account_id
    }

    environment_variable {
      name  = "IMAGE_REPO_NAME"
      value = var.ecr_repository_name
    }
  }

  source {
    type      = "CODEPIPELINE"
    buildspec = "buildspec.yml"
  }
}
  1. Cross-Account Deployment
resource "aws_codepipeline" "cross_account" {
  # ... other configuration ...

  stage {
    name = "DeployToProd"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CloudFormation"
      input_artifacts = ["build_output"]
      version         = "1"
      region          = var.prod_region
      role_arn        = var.prod_role_arn

      configuration = {
        ActionMode     = "CREATE_UPDATE"
        StackName      = "${var.project_name}-prod"
        TemplatePath   = "build_output::template.yaml"
        Capabilities   = "CAPABILITY_IAM"
        RoleArn        = var.prod_cloudformation_role_arn
      }
    }
  }
}

Conclusion

This setup provides a comprehensive foundation for deploying CodePipeline using Terraform. Remember to:

  • Plan your pipeline stages carefully
  • Implement proper security measures
  • Monitor pipeline performance
  • Keep your configurations versioned
  • Test thoroughly before production deployment

The complete code can be customized based on your specific requirements and use cases.