CI/CD Pipeline for CloudFront on GIT Update

Creating a CI/CD pipeline using AWS CodePipeline to push content to Amazon CloudFront when a Git repository is updated involves several steps. Below is a detailed process using Terraform to automate the infrastructure setup for this pipeline. The pipeline will:

  1. Detect changes in a Git repository (e.g., GitHub).
  2. Build the content (if necessary).
  3. Deploy the content to an S3 bucket.
  4. Invalidate the CloudFront distribution to reflect the new changes.

Overview of the Steps:

  1. Set up an S3 bucket for the content.
  2. Set up a CloudFront distribution for caching and serving the content.
  3. Set up an AWS CodePipeline with:
    • Source (GitHub)
    • Build (optional, for content build)
    • Deploy (S3 and CloudFront invalidation)
  4. Use Terraform to automate the above steps.

Step 1: Create the Terraform Project Directory

Create a directory to store your Terraform configuration files.

bashCopy codemkdir my-ci-cd-pipeline
cd my-ci-cd-pipeline

Step 2: Set Up Terraform Configuration Files

Create the following Terraform files in the project directory:

1. Provider Configuration (provider.tf)

This file sets up the AWS provider for Terraform.

hclCopy codeprovider "aws" {
  region = "us-east-1"  # Change to your desired region
}

provider "github" {
  token = "<your-github-token>"
  owner = "<your-github-username>"
}

Note: Replace <your-github-token> with your GitHub personal access token and <your-github-username> with your GitHub username.

2. S3 Bucket (s3.tf)

This file defines the S3 bucket where your content will be stored.

hclCopy coderesource "aws_s3_bucket" "content_bucket" {
  bucket = "my-content-bucket"  # Replace with your unique bucket name
  acl    = "public-read"
}

resource "aws_s3_bucket_object" "content_object" {
  bucket = aws_s3_bucket.content_bucket.bucket
  key    = "index.html"  # Example content file
  source = "index.html"  # Path to the local file (e.g., content)
  acl    = "public-read"
}

3. CloudFront Distribution (cloudfront.tf)

This file creates the CloudFront distribution.

hclCopy coderesource "aws_cloudfront_distribution" "content_distribution" {
  origin {
    domain_name = aws_s3_bucket.content_bucket.bucket_regional_domain_name
    origin_id   = "S3-my-content-bucket"
  }

  enabled             = true
  is_ipv6_enabled     = true
  default_root_object = "index.html"

  viewer_certificate {
    cloudfront_default_certificate = true
  }

  default_cache_behavior {
    target_origin_id       = "S3-my-content-bucket"
    viewer_protocol_policy = "allow-all"
    allowed_methods {
      items = ["GET", "HEAD"]
    }
  }
}

4. GitHub Webhook for CodePipeline (github_webhook.tf)

This file sets up the webhook in GitHub to trigger the pipeline.

hclCopy coderesource "aws_codepipeline" "pipeline" {
  name     = "my-cicd-pipeline"
  role_arn = aws_iam_role.pipeline_role.arn

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

  stage {
    name = "Source"

    action {
      name             = "GitHub_Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["SourceOutput"]
      configuration = {
        "Owner"    = "<your-github-username>"
        "Repo"     = "<your-repository-name>"
        "Branch"   = "main"
        "OAuthToken" = "<your-oauth-token>"
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name             = "Deploy_to_S3"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "S3"
      input_artifacts  = ["SourceOutput"]
      output_artifacts = []
      configuration = {
        "BucketName" = aws_s3_bucket.content_bucket.bucket
        "Extract"    = "true"
      }
    }

    action {
      name             = "Invalidate_CloudFront"
      category         = "Invoke"
      owner            = "AWS"
      provider         = "Lambda"
      input_artifacts  = []
      configuration = {
        "FunctionName" = aws_lambda_function.invalidate_function.arn
      }
    }
  }
}

Note: Replace <your-github-username>, <your-repository-name>, and <your-oauth-token> with appropriate values.

5. IAM Role and Policies (iam.tf)

This file defines the IAM role for the pipeline.

hclCopy coderesource "aws_iam_role" "pipeline_role" {
  name = "codepipeline_role"

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

resource "aws_iam_policy" "codepipeline_policy" {
  name        = "codepipeline-policy"
  description = "Policy for CodePipeline access"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action    = ["s3:GetObject", "s3:PutObject"]
        Effect    = "Allow"
        Resource  = "${aws_s3_bucket.content_bucket.arn}/*"
      },
      {
        Action    = "cloudfront:CreateInvalidation"
        Effect    = "Allow"
        Resource  = aws_cloudfront_distribution.content_distribution.arn
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "pipeline_policy_attach" {
  policy_arn = aws_iam_policy.codepipeline_policy.arn
  role_arn   = aws_iam_role.pipeline_role.arn
}

6. Lambda Function for CloudFront Invalidation (lambda.tf)

Here is the Lambda function code that invalidates the CloudFront cache when the content is updated.

hclCopy coderesource "aws_lambda_function" "invalidate_function" {
  filename      = "lambda.zip"
  function_name = "InvalidateCloudFront"
  role          = aws_iam_role.lambda_execution_role.arn
  handler       = "index.handler"
  runtime       = "nodejs14.x"

  source_code_hash = filebase64sha256("lambda.zip")
}

resource "aws_iam_role" "lambda_execution_role" {
  name = "lambda_execution_role"

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

resource "aws_iam_policy" "lambda_policy" {
  name        = "LambdaCloudFrontPolicy"
  description = "Lambda policy to invalidate CloudFront cache"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action    = "cloudfront:CreateInvalidation"
        Effect    = "Allow"
        Resource  = aws_cloudfront_distribution.content_distribution.arn
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
  role       = aws_iam_role.lambda_execution_role.name
  policy_arn = aws_iam_policy.lambda_policy.arn
}

Lambda Function Code (index.js):

jsCopy codeconst AWS = require('aws-sdk');
const cloudfront = new AWS.CloudFront();

exports.handler = async (event) => {
    const params = {
        DistributionId: 'YOUR_CLOUDFRONT_DISTRIBUTION_ID', // Replace with your CloudFront Distribution ID
        InvalidationBatch: {
            Paths: {
                Quantity: 1,
                Items: ["/*"]  // Invalidate all content
            },
            CallerReference: `invalidate-${Date.now()}`
        }
    };
    
    try {
        const result = await cloudfront.createInvalidation(params).promise();
        console.log("Invalidation result:", result);
        return {
            statusCode: 200,
            body: JSON.stringify('Invalidation success'),
        };
    } catch (error) {
        console.log("Error invalidating CloudFront:", error);
        return {
            statusCode: 500,
            body: JSON.stringify('Invalidation failed'),
        };
    }
};

Note: Create a .zip file of the index.js Lambda code and upload it as lambda.zip.


Step 3: Deploy the Terraform Configuration

  1. Initialize the Terraform project:
bashCopy codeterraform init
  1. Apply the configuration:
bashCopy codeterraform apply

This will create all the resources (S3 bucket, CloudFront distribution, CodePipeline, IAM roles, Lambda, etc.).


Step 4: Triggering the Pipeline

Once the Git repository is updated (i.e., new content is pushed), the pipeline will automatically be triggered by the GitHub webhook. It will deploy the new content to the S3 bucket, and the Lambda function will invalidate the CloudFront cache to reflect the new changes.


Summary

In this tutorial, we created a CI/CD pipeline using AWS CodePipeline to automatically deploy content to CloudFront from a Git repository. We used Terraform to automate the creation of resources such as S3, CloudFront, IAM roles, and Lambda functions. The key steps included setting up a webhook for GitHub to trigger the pipeline, creating stages for source, build (if needed), and deploy actions, and using Lambda for cache invalidation.