feat: invalidate cloudfront cache after updating json#71
Conversation
There was a problem hiding this comment.
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.
scripts/src/lambda-handler.ts
Outdated
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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:
- Using CloudFront distribution aliases/CNAMEs if applicable
- Passing the distribution ID directly as an environment variable instead of looking it up by Name tag
- Caching the distribution ID lookup result if this Lambda runs frequently
| CallerReference: `lambda-${Date.now()}`, | ||
| Paths: { | ||
| Quantity: 1, | ||
| Items: [`/${key.replace("website/", "")}`], |
There was a problem hiding this comment.
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.
| Items: [`/${key.replace("website/", "")}`], | |
| Items: [`/${key.startsWith("website/") ? key.substring(8) : key}`], |
| const BUCKET_NAME = process.env.S3_BUCKET_NAME; | ||
| const CLOUDFRONT_DISTRIBUTION_NAME = | ||
| process.env.CLOUDFRONT_DISTRIBUTION_NAME; | ||
| const key = "website/data/rolling-images.json"; |
There was a problem hiding this comment.
[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.
| ARG CLOUDFRONT_DISTRIBUTION_ID | ||
| ENV CLOUDFRONT_DISTRIBUTION_ID=${CLOUDFRONT_DISTRIBUTION_ID} |
There was a problem hiding this comment.
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.
| ARG CLOUDFRONT_DISTRIBUTION_ID | |
| ENV CLOUDFRONT_DISTRIBUTION_ID=${CLOUDFRONT_DISTRIBUTION_ID} | |
| ARG CLOUDFRONT_DISTRIBUTION_NAME | |
| ENV CLOUDFRONT_DISTRIBUTION_NAME=${CLOUDFRONT_DISTRIBUTION_NAME} |
71f0053 to
df030ca
Compare
There was a problem hiding this comment.
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.
| } 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}`, | ||
| }), | ||
| }; | ||
| } |
There was a problem hiding this comment.
Returning a 500 error when CloudFront invalidation fails could be misleading since the S3 upload already succeeded. Consider either:
- Returning a 207 (Multi-Status) or 200 with a warning indicating partial success
- Making CloudFront invalidation non-blocking and logging the error instead
- 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.
| } | ||
|
|
||
| // Invalidate CloudFront cache for the updated file | ||
| let invalidationId = null; |
There was a problem hiding this comment.
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.
| let invalidationId = null; | |
| let invalidationId: string | undefined; |
| const invalidationCommand = new CreateInvalidationCommand({ | ||
| DistributionId: CLOUDFRONT_DISTRIBUTION_ID, | ||
| InvalidationBatch: { | ||
| CallerReference: `lambda-${Date.now()}`, |
There was a problem hiding this comment.
[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)}`,| "dev": "ts-node src/main.ts" | ||
| }, | ||
| "dependencies": { | ||
| "@aws-sdk/client-cloudfront": "^3.927.0", |
There was a problem hiding this comment.
[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",| "@aws-sdk/client-cloudfront": "^3.927.0", | |
| "@aws-sdk/client-cloudfront": "^3.922.0", |
No description provided.