From 11e2a04ac294a9688f3145c21a8574dee94a0770 Mon Sep 17 00:00:00 2001 From: Janice Huang <60631893+huanjani@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:09:22 -0700 Subject: [PATCH] chore: add CloudFront cache invalidation for Static Site services (#5035) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One of the ways in which CloudFront reduces latency is by caching objects at edge locations, reducing the number of requests that users’ origin servers must respond to directly. For Copilot-managed Static Site services, this caching behavior has the unfortunate side effect of redeployments using cached— and out-of-date— versions of source files. By default, files expire after 24 hours. We want users to benefit from edge caches, so we don’t want to [manage cache expiration](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Expiration.html) by changing TTL values or the [cache policy](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html). Instead, we can allow the caching but [invalidate the cache](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html) each time the Static Site service is redeployed, after (updated) files are uploaded. This way, the updated files are served from the origin server rather than the outdated cached files. We opted to invalidate all files (`/*`) for simplicity, efficiency, and [cost-savings](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html#PayingForInvalidation). If, in the future, customers request the ability to exclude files from invalidation, we can consider adding that config to the workload manifest. Resolves: #5024. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the Apache 2.0 License. --- .../testdata/workloads/static-site.stack.yml | 45 +++++++++++++++++- .../workloads/services/static-site/cf.yml | 46 ++++++++++++++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml index 0a46be73793..d1bc9cfab31 100644 --- a/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml +++ b/internal/pkg/deploy/cloudformation/stack/testdata/workloads/static-site.stack.yml @@ -213,7 +213,7 @@ Resources: Next: CopyFiles CopyFiles: Type: Map - End: true + Next: InvalidateCache ItemsPath: $.GetMappingFile.files ItemProcessor: ProcessorConfig: @@ -251,6 +251,18 @@ Resources: # Required otherwise ContentType won't be applied. # See https://github.com/aws/aws-sdk-js/issues/1092 for more. MetadataDirective: 'REPLACE' + InvalidateCache: + Type: Task + End: true + Resource: arn:aws:states:::aws-sdk:cloudfront:createInvalidation + Parameters: + DistributionId: !Ref CloudFrontDistribution + InvalidationBatch: + CallerReference.$: States.UUID() + Paths: + Quantity: 1 + Items: + - "/*" CopyAssetsStateMachineRole: Metadata: @@ -283,6 +295,37 @@ Resources: Action: - s3:PutObject Resource: !Sub arn:aws:s3:::${Bucket}/* + - PolicyName: CacheInvalidation + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - acm:ListCertificates + - cloudfront:GetDistribution + - cloudfront:GetStreamingDistribution + - cloudfront:GetDistributionConfig + - cloudfront:ListDistributions + - cloudfront:ListCloudFrontOriginAccessIdentities + - cloudfront:CreateInvalidation + - cloudfront:GetInvalidation + - cloudfront:ListInvalidations + - elasticloadbalancing:DescribeLoadBalancers + - iam:ListServerCertificates + - sns:ListSubscriptionsByTopic + - sns:ListTopics + - waf:GetWebACL + - waf:ListWebACLs + Resource: "*" + Condition: + StringEquals: + 'aws:ResourceTag/copilot-application': !Sub '${AppName}' + 'aws:ResourceTag/copilot-environment': !Sub '${EnvName}' + 'aws:ResourceTag/copilot-service': !Sub '${WorkloadName}' + - Effect: Allow + Action: + - s3:ListAllMyBuckets + Resource: arn:aws:s3:::* EnvManagerS3Access: Metadata: diff --git a/internal/pkg/template/templates/workloads/services/static-site/cf.yml b/internal/pkg/template/templates/workloads/services/static-site/cf.yml index fd29fbfa1e7..527932ec323 100644 --- a/internal/pkg/template/templates/workloads/services/static-site/cf.yml +++ b/internal/pkg/template/templates/workloads/services/static-site/cf.yml @@ -245,7 +245,7 @@ Resources: Next: CopyFiles CopyFiles: Type: Map - End: true + Next: InvalidateCache ItemsPath: $.GetMappingFile.files ItemProcessor: ProcessorConfig: @@ -283,6 +283,18 @@ Resources: # Required otherwise ContentType won't be applied. # See https://github.com/aws/aws-sdk-js/issues/1092 for more. MetadataDirective: "REPLACE" + InvalidateCache: + Type: Task + End: true + Resource: arn:aws:states:::aws-sdk:cloudfront:createInvalidation + Parameters: + DistributionId: !Ref CloudFrontDistribution + InvalidationBatch: + CallerReference.$: States.UUID() + Paths: + Quantity: 1 + Items: + - "/*" CopyAssetsStateMachineRole: Metadata: @@ -318,6 +330,38 @@ Resources: Action: - s3:PutObject Resource: !Sub arn:aws:s3:::${Bucket}/* + - PolicyName: CacheInvalidation + # https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/security_iam_id-based-policy-examples.html + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - acm:ListCertificates + - cloudfront:GetDistribution + - cloudfront:GetStreamingDistribution + - cloudfront:GetDistributionConfig + - cloudfront:ListDistributions + - cloudfront:ListCloudFrontOriginAccessIdentities + - cloudfront:CreateInvalidation + - cloudfront:GetInvalidation + - cloudfront:ListInvalidations + - elasticloadbalancing:DescribeLoadBalancers + - iam:ListServerCertificates + - sns:ListSubscriptionsByTopic + - sns:ListTopics + - waf:GetWebACL + - waf:ListWebACLs + Resource: "*" + Condition: + StringEquals: + 'aws:ResourceTag/copilot-application': !Sub '${AppName}' + 'aws:ResourceTag/copilot-environment': !Sub '${EnvName}' + 'aws:ResourceTag/copilot-service': !Sub '${WorkloadName}' + - Effect: Allow + Action: + - s3:ListAllMyBuckets + Resource: arn:aws:s3:::* EnvManagerS3Access: Metadata: