Skip to content

feat: invalidate cloudfront cache after updating json#71

Merged
Perry2004 merged 1 commit intomainfrom
feat/invalidate-cache-after-update
Nov 8, 2025
Merged

feat: invalidate cloudfront cache after updating json#71
Perry2004 merged 1 commit intomainfrom
feat/invalidate-cache-after-update

Conversation

@Perry2004
Copy link
Owner

No description provided.

Copilot AI review requested due to automatic review settings November 8, 2025 18:35
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds CloudFront cache invalidation functionality to the Lambda handler to ensure that updated image data is immediately reflected on the website after being uploaded to S3.

Key Changes:

  • Integrates AWS CloudFront SDK to create cache invalidations after S3 uploads
  • Implements distribution lookup by Name tag for dynamic configuration
  • Updates environment variable configuration for CloudFront distribution identification

Reviewed Changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
scripts/src/lambda-handler.ts Adds CloudFront client, getDistributionIdByName function, and cache invalidation logic after S3 upload
scripts/package.json Adds @aws-sdk/client-cloudfront dependency version 3.927.0
scripts/package-lock.json Updates lock file with CloudFront client dependencies and transitive dependencies
scripts/Dockerfile.script Adds CLOUDFRONT_DISTRIBUTION_ID environment variable configuration
.github/workflows/build-lambda-image.yaml Passes CLOUDFRONT_DISTRIBUTION_ID build argument to Docker build
Files not reviewed (1)
  • scripts/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +35 to +62
for (const distribution of listResponse.DistributionList.Items) {
if (!distribution.ARN || !distribution.Id) {
continue;
}

try {
const tagsCommand = new ListTagsForResourceCommand({
Resource: distribution.ARN,
});
const tagsResponse = await cloudfrontClient.send(tagsCommand);

const nameTag = tagsResponse.Tags?.Items?.find(
(tag) => tag.Key === "Name" && tag.Value === name,
);

if (nameTag) {
console.log(
`Found distribution ID: ${distribution.Id} for Name: ${name}`,
);
return distribution.Id;
}
} catch (tagError) {
console.error(
`Error fetching tags for distribution ${distribution.Id}: ${tagError}`,
);
continue;
}
}
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getDistributionIdByName function makes N+1 API calls by listing all distributions and then making a separate ListTagsForResourceCommand call for each distribution. This is inefficient and can lead to performance issues, especially with many distributions or rate limiting.

Consider alternatives such as:

  1. Using CloudFront distribution aliases/CNAMEs if applicable
  2. Passing the distribution ID directly as an environment variable instead of looking it up by Name tag
  3. Caching the distribution ID lookup result if this Lambda runs frequently

Copilot uses AI. Check for mistakes.
CallerReference: `lambda-${Date.now()}`,
Paths: {
Quantity: 1,
Items: [`/${key.replace("website/", "")}`],
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path transformation logic key.replace("website/", "") may produce unexpected results if the key doesn't contain "website/". The current implementation would invalidate /data/rolling-images.json, but if the key format changes or doesn't contain "website/", this could silently fail or invalidate the wrong path.

Consider making this more explicit:

const cloudfrontPath = `/${key.startsWith("website/") ? key.substring(8) : key}`;

Or validate that the key has the expected format before proceeding.

Suggested change
Items: [`/${key.replace("website/", "")}`],
Items: [`/${key.startsWith("website/") ? key.substring(8) : key}`],

Copilot uses AI. Check for mistakes.
const BUCKET_NAME = process.env.S3_BUCKET_NAME;
const CLOUDFRONT_DISTRIBUTION_NAME =
process.env.CLOUDFRONT_DISTRIBUTION_NAME;
const key = "website/data/rolling-images.json";
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The key path "website/data/rolling-images.json" is hardcoded and appears in multiple places (line 98 for the key itself, and line 208 where "website/" is stripped for CloudFront invalidation). This creates a maintainability issue if the path structure needs to change.

Consider extracting this as a constant at the module level:

const S3_KEY = "website/data/rolling-images.json";
const CLOUDFRONT_PATH_PREFIX = "website/";

This would make the relationship between S3 and CloudFront paths more explicit and easier to maintain.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
ARG CLOUDFRONT_DISTRIBUTION_ID
ENV CLOUDFRONT_DISTRIBUTION_ID=${CLOUDFRONT_DISTRIBUTION_ID}
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Environment variable name mismatch detected. The Dockerfile sets CLOUDFRONT_DISTRIBUTION_ID, but the lambda-handler.ts expects CLOUDFRONT_DISTRIBUTION_NAME. This will cause the Lambda function to fail at runtime with a "Missing CloudFront configuration" error.

Either change the Dockerfile to use CLOUDFRONT_DISTRIBUTION_NAME or update the lambda-handler.ts to read CLOUDFRONT_DISTRIBUTION_ID.

Suggested change
ARG CLOUDFRONT_DISTRIBUTION_ID
ENV CLOUDFRONT_DISTRIBUTION_ID=${CLOUDFRONT_DISTRIBUTION_ID}
ARG CLOUDFRONT_DISTRIBUTION_NAME
ENV CLOUDFRONT_DISTRIBUTION_NAME=${CLOUDFRONT_DISTRIBUTION_NAME}

Copilot uses AI. Check for mistakes.
@Perry2004 Perry2004 force-pushed the feat/invalidate-cache-after-update branch from 71f0053 to df030ca Compare November 8, 2025 18:44
@Perry2004 Perry2004 requested a review from Copilot November 8, 2025 18:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • scripts/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +122 to +135
} catch (cfError) {
console.error(`Error creating CloudFront invalidation: ${cfError}`);
return {
statusCode: 500,
body: JSON.stringify({
success: false,
error: cfError instanceof Error ? cfError.message : String(cfError),
message: "Failed to invalidate CloudFront cache",
image_links: imageLinks,
total_count: imageLinks.length,
s3_location: `s3://${BUCKET_NAME}/${key}`,
}),
};
}
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a 500 error when CloudFront invalidation fails could be misleading since the S3 upload already succeeded. Consider either:

  1. Returning a 207 (Multi-Status) or 200 with a warning indicating partial success
  2. Making CloudFront invalidation non-blocking and logging the error instead
  3. Implementing a rollback mechanism to delete the S3 object if invalidation fails

The current approach could cause the Lambda to be retried unnecessarily even though the S3 upload succeeded.

Copilot uses AI. Check for mistakes.
}

// Invalidate CloudFront cache for the updated file
let invalidationId = null;
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable invalidationId should be explicitly typed. Consider changing:

let invalidationId: string | undefined;

This makes the type clearer and avoids relying on null which might not match the API response type.

Suggested change
let invalidationId = null;
let invalidationId: string | undefined;

Copilot uses AI. Check for mistakes.
const invalidationCommand = new CreateInvalidationCommand({
DistributionId: CLOUDFRONT_DISTRIBUTION_ID,
InvalidationBatch: {
CallerReference: `lambda-${Date.now()}`,
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using Date.now() for CallerReference could potentially cause issues if multiple Lambda invocations happen within the same millisecond. While unlikely, consider using a more robust unique identifier like crypto.randomUUID() or combining timestamp with a random component:

CallerReference: `lambda-${Date.now()}-${Math.random().toString(36).substring(7)}`,

Copilot uses AI. Check for mistakes.
"dev": "ts-node src/main.ts"
},
"dependencies": {
"@aws-sdk/client-cloudfront": "^3.927.0",
Copy link

Copilot AI Nov 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The @aws-sdk/client-cloudfront version (^3.927.0) is newer than @aws-sdk/client-s3 (^3.922.0). While this shouldn't cause issues due to the caret (^) range, consider using the same minor version for consistency across AWS SDK packages to avoid potential compatibility issues:

"@aws-sdk/client-cloudfront": "^3.922.0",
Suggested change
"@aws-sdk/client-cloudfront": "^3.927.0",
"@aws-sdk/client-cloudfront": "^3.922.0",

Copilot uses AI. Check for mistakes.
@Perry2004 Perry2004 merged commit 4fc50aa into main Nov 8, 2025
8 checks passed
@Perry2004 Perry2004 deleted the feat/invalidate-cache-after-update branch November 8, 2025 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants