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:
- Detect changes in a Git repository (e.g., GitHub).
- Build the content (if necessary).
- Deploy the content to an S3 bucket.
- Invalidate the CloudFront distribution to reflect the new changes.
Overview of the Steps:
- Set up an S3 bucket for the content.
- Set up a CloudFront distribution for caching and serving the content.
- Set up an AWS CodePipeline with:
- Source (GitHub)
- Build (optional, for content build)
- Deploy (S3 and CloudFront invalidation)
- 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 theindex.js
Lambda code and upload it aslambda.zip
.
Step 3: Deploy the Terraform Configuration
- Initialize the Terraform project:
bashCopy codeterraform init
- 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.