Skip to content

Commit bce2e12

Browse files
authored
Merge pull request #8 from reapit/migrate/edge-api
migrate: edge api
2 parents 55e72ee + 8e0a008 commit bce2e12

50 files changed

Lines changed: 3656 additions & 9 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"recommendations": [
33
"orta.vscode-jest",
4-
"dbaeumer.vscode-eslint"
4+
"dbaeumer.vscode-eslint",
5+
"sonarsource.sonarlint-vscode"
56
]
67
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
"scripts": {
99
"test": "jest",
1010
"check": "tsc packages/**/*.ts --noEmit",
11-
"build": "yarn workspaces foreach run build",
12-
"lint": "yarn workspaces foreach run lint",
11+
"build": "yarn workspaces foreach -v run build",
12+
"lint": "yarn workspaces foreach -v run lint",
1313
"root:test": "jest",
1414
"root:check": "tsc"
1515
},
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
module.exports = {
2+
root: true,
3+
extends: ["@reapit-cdk/eslint-config"],
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
src
2+
tests
3+
.eslintrc.js
4+
tsconfig.json
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "@reapit-cdk/cloudfront-invalidation",
3+
"version": "0.0.0",
4+
"homepage": "https://github.com/reapit/ts-cdk-constructs/blob/main/packages/cloudfront-invalidation",
5+
"readme": "https://github.com/reapit/ts-cdk-constructs/blob/main/packages/cloudfront-invalidation/readme.md",
6+
"bugs": {
7+
"url": "https://github.com/reapit/ts-cdk-constructs/issues"
8+
},
9+
"license": "MIT",
10+
"scripts": {
11+
"build": "reapit-cdk-tsup",
12+
"check": "yarn run root:check -p $PWD",
13+
"lint": "reapit-cdk-eslint",
14+
"test": "yarn run root:test $PWD",
15+
"prepack": "reapit-version-package && yarn build"
16+
},
17+
"publishConfig": {
18+
"main": "dist/index.js"
19+
},
20+
"main": "src/index.ts",
21+
"dependencies": {
22+
"@reapit-cdk/common": "workspace:^",
23+
"aws-cdk-lib": "2.96.2",
24+
"constructs": "10.2.70"
25+
},
26+
"devDependencies": {
27+
"@reapit-cdk/eslint-config": "workspace:^",
28+
"@reapit-cdk/tsup": "workspace:^",
29+
"@reapit-cdk/version-package": "workspace:^"
30+
}
31+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# @reapit-cdk/cloudfront-invalidation
2+
CloudFront invalidations are [very error prone](https://github.com/aws/aws-cdk/issues/15891#issuecomment-966456154), making it hard to invalidate distributions reliably. This construct aims to solve this problem by using a step function which is triggered on stack update, and uses exponential backoff to retry the invalidation.
3+
4+
Inspired by https://github.com/aws/aws-cdk/issues/15891#issuecomment-1362163142.
5+
6+
## npm Package Installation:
7+
```sh
8+
yarn add --dev @reapit-cdk/cloudfront-invalidation
9+
# or
10+
npm install @reapit-cdk/cloudfront-invalidation --save-dev
11+
```
12+
13+
## Usage
14+
```ts
15+
import { CfnOutput, Stack, App } from 'aws-cdk-lib'
16+
import { HostedZone } from 'aws-cdk-lib/aws-route53'
17+
import { EmailReceiver } from '@reapit-cdk/cloudfront-invalidation'
18+
19+
const app = new App()
20+
const stack = new Stack(app, 'stack-name', {
21+
env: {
22+
region: 'us-east-1', // region must be specified
23+
},
24+
})
25+
const distribution = new Distribution(stack, 'distribution', {
26+
defaultBehavior: {
27+
origin: new HttpOrigin('example.org'),
28+
},
29+
})
30+
const invalidation = new CloudfrontInvalidation(stack, 'invalidation', {
31+
distribution,
32+
items: ['/index.html', '/config.js'], // path patterns you want invalidated
33+
})
34+
```
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { Duration, Lazy } from 'aws-cdk-lib'
2+
import { IDistribution } from 'aws-cdk-lib/aws-cloudfront'
3+
import { Rule, RuleTargetInput } from 'aws-cdk-lib/aws-events'
4+
import { SfnStateMachine } from 'aws-cdk-lib/aws-events-targets'
5+
import { DefinitionBody, JsonPath, StateMachine } from 'aws-cdk-lib/aws-stepfunctions'
6+
import { CallAwsService } from 'aws-cdk-lib/aws-stepfunctions-tasks'
7+
import { Construct } from 'constructs'
8+
9+
export class CloudfrontInvalidation extends Construct {
10+
constructor(
11+
scope: Construct,
12+
id: string,
13+
{ distribution, items = ['/index.html'] }: { distribution: IDistribution; items?: string[] },
14+
) {
15+
super(scope, id)
16+
17+
const createInvalidation = new CallAwsService(this, 'CreateInvalidation', {
18+
service: 'cloudfront',
19+
action: 'createInvalidation',
20+
parameters: {
21+
DistributionId: distribution.distributionId,
22+
InvalidationBatch: {
23+
CallerReference: JsonPath.entirePayload,
24+
Paths: {
25+
Items: Lazy.list({
26+
produce() {
27+
return items
28+
},
29+
}),
30+
Quantity: Lazy.number({
31+
produce() {
32+
return items.length
33+
},
34+
}),
35+
},
36+
},
37+
},
38+
iamResources: [`arn:aws:cloudfront::${distribution.stack.account}:distribution/${distribution.distributionId}`],
39+
})
40+
41+
const createInvalidationStateMachine = new StateMachine(this, 'CreateInvalidationStateMachine', {
42+
definitionBody: DefinitionBody.fromChainable(
43+
createInvalidation.addRetry({
44+
errors: ['CloudFront.CloudFrontException'],
45+
backoffRate: 2,
46+
interval: Duration.seconds(5),
47+
maxAttempts: 10,
48+
}),
49+
),
50+
})
51+
52+
new Rule(this, 'DeploymentComplete', {
53+
eventPattern: {
54+
source: ['aws.cloudformation'],
55+
detail: {
56+
'stack-id': [distribution.stack.stackId],
57+
'status-details': {
58+
status: ['UPDATE_COMPLETE'],
59+
},
60+
},
61+
},
62+
}).addTarget(
63+
new SfnStateMachine(createInvalidationStateMachine, {
64+
input: RuleTargetInput.fromEventPath('$.id'),
65+
}),
66+
)
67+
}
68+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './cloudfront-invalidation'
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Template } from 'aws-cdk-lib/assertions'
2+
import * as cdk from 'aws-cdk-lib'
3+
import { CloudfrontInvalidation } from '../src'
4+
import { Distribution } from 'aws-cdk-lib/aws-cloudfront'
5+
import { HttpOrigin } from 'aws-cdk-lib/aws-cloudfront-origins'
6+
7+
const synth = (items?: string[]) => {
8+
const app = new cdk.App()
9+
const stack = new cdk.Stack(app, 'stack', {
10+
env: {
11+
region: 'eu-west-2',
12+
},
13+
})
14+
const distribution = new Distribution(stack, 'distribution', {
15+
defaultBehavior: {
16+
origin: new HttpOrigin('example.org'),
17+
},
18+
})
19+
const invalidation = new CloudfrontInvalidation(stack, 'invalidation', {
20+
distribution,
21+
items,
22+
})
23+
const template = () => Template.fromStack(stack)
24+
return {
25+
invalidation,
26+
template,
27+
stack,
28+
}
29+
}
30+
31+
describe('cloudfront-invalidation', () => {
32+
test('synthesizes', () => {
33+
const { invalidation, template } = synth()
34+
expect(invalidation).toBeDefined()
35+
expect(template).toBeDefined()
36+
})
37+
test('invalidates /index.html by default', () => {
38+
const { template } = synth()
39+
template().hasResourceProperties('AWS::StepFunctions::StateMachine', {
40+
DefinitionString: {
41+
'Fn::Join': [
42+
'',
43+
[
44+
'{"StartAt":"CreateInvalidation","States":{"CreateInvalidation":{"End":true,"Retry":[{"ErrorEquals":["CloudFront.CloudFrontException"],"IntervalSeconds":5,"MaxAttempts":10,"BackoffRate":2}],"Type":"Task","Resource":"arn:',
45+
{
46+
Ref: 'AWS::Partition',
47+
},
48+
':states:::aws-sdk:cloudfront:createInvalidation","Parameters":{"DistributionId":"',
49+
{
50+
Ref: 'distribution114A0A2A',
51+
},
52+
'","InvalidationBatch":{"CallerReference.$":"$","Paths":{"Items":["/index.html"],"Quantity":1}}}}}}',
53+
],
54+
],
55+
},
56+
})
57+
})
58+
59+
test('invalidates the items passed in lazily', () => {
60+
const items = ['/index.html']
61+
const { template } = synth(items)
62+
items.push('/config.js')
63+
template().hasResourceProperties('AWS::StepFunctions::StateMachine', {
64+
DefinitionString: {
65+
'Fn::Join': [
66+
'',
67+
[
68+
'{"StartAt":"CreateInvalidation","States":{"CreateInvalidation":{"End":true,"Retry":[{"ErrorEquals":["CloudFront.CloudFrontException"],"IntervalSeconds":5,"MaxAttempts":10,"BackoffRate":2}],"Type":"Task","Resource":"arn:',
69+
{
70+
Ref: 'AWS::Partition',
71+
},
72+
':states:::aws-sdk:cloudfront:createInvalidation","Parameters":{"DistributionId":"',
73+
{
74+
Ref: 'distribution114A0A2A',
75+
},
76+
'","InvalidationBatch":{"CallerReference.$":"$","Paths":{"Items":["/index.html","/config.js"],"Quantity":2}}}}}}',
77+
],
78+
],
79+
},
80+
})
81+
})
82+
})
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@reapit-cdk/tsconfig/base.json",
3+
}

0 commit comments

Comments
 (0)