Skip to content

Commit 5d5830e

Browse files
sreesree
sree
authored and
sree
committed
add eslint
0 parents  commit 5d5830e

15 files changed

+6229
-0
lines changed

.eslintrc.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
module.exports = {
2+
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
3+
parserOptions: {
4+
ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features
5+
sourceType: "module", // Allows for the use of imports
6+
ecmaFeatures: {
7+
jsx: true // Allows for the parsing of JSX
8+
}
9+
},
10+
settings: {
11+
react: {
12+
version: "detect" // Tells eslint-plugin-react to automatically detect the version of React to use
13+
}
14+
},
15+
extends: [
16+
"plugin:prettier/recommended" // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array.
17+
],
18+
rules: {
19+
// Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs
20+
// e.g. "@typescript-eslint/explicit-function-return-type": "off",
21+
},
22+
};

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
*.env
2+
lib/*.js
3+
lib/*.d.ts
4+
bin/*.d.ts
5+
bin/*.js
6+
test/*.js
7+
test/*.d.ts
8+
node_modules/
9+
*.log
10+
!sample.env

.npmignore

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out

.prettierrc.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = {
2+
semi: true,
3+
trailingComma: "all",
4+
singleQuote: true,
5+
printWidth: 120,
6+
tabWidth: 4
7+
};

README.md

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Amplify CDK + slack notifications on build
2+
3+
This is a project for setting up an Amplify app for NextJS application with CDK.
4+
This also sets up deploy notifications for every build triggered on amplify with the status of the build.
5+
A lambda function is triggered whenever a build is triggered. Currently the lambda is configured to post to a SLACK WEBHOOK.
6+
7+
It uses cdk 1.*.* currently, will migrate this to v2 shortly.
8+
9+
The `cdk.json` file tells the CDK Toolkit how to execute your app.
10+
11+
## Useful commands
12+
13+
* `yarn run build` compile typescript to js
14+
* `yarn run watch` watch for changes and compile
15+
* `yarn run test` perform the jest unit tests
16+
* `cdk deploy` deploy this stack to your default AWS account/region
17+
* `cdk diff` compare deployed stack with current state
18+
* `cdk synth` emits the synthesized CloudFormation template

bin/main.ts

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/usr/bin/env node
2+
import 'source-map-support/register';
3+
import * as cdk from '@aws-cdk/core';
4+
import { MainInfraStack } from '../lib/main';
5+
6+
const dotenv = require('dotenv');
7+
dotenv.config();
8+
9+
const app = new cdk.App();
10+
11+
new MainInfraStack(app, 'MainInfraStack', {
12+
/* If you don't specify 'env', this stack will be environment-agnostic.
13+
* Account/Region-dependent features and context lookups will not work,
14+
* but a single synthesized template can be deployed anywhere. */
15+
16+
/* Uncomment the next line to specialize this stack for the AWS Account
17+
* and Region that are implied by the current CLI configuration. */
18+
env: { account: process.env.ACCOUNT_ID, region: process.env.AWS_REGION },
19+
20+
/* Uncomment the next line if you know exactly what Account and Region you
21+
* want to deploy the stack to. */
22+
// env: { account: '123456789012', region: 'us-east-1' },
23+
24+
/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
25+
});

cdk.json

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/main.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
21+
"@aws-cdk/core:stackRelativeExports": true,
22+
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
23+
"@aws-cdk/aws-lambda:recognizeVersionProps": true,
24+
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
25+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
26+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
27+
"@aws-cdk/core:target-partitions": [
28+
"aws",
29+
"aws-cn"
30+
]
31+
}
32+
}

jest.config.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};

lib/main.ts

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import * as cdk from '@aws-cdk/core';
2+
import * as amplify from '@aws-cdk/aws-amplify';
3+
import * as iam from '@aws-cdk/aws-iam';
4+
import * as events from '@aws-cdk/aws-events';
5+
import * as targets from '@aws-cdk/aws-events-targets';
6+
import * as sns from '@aws-cdk/aws-sns';
7+
import * as lambda from '@aws-cdk/aws-lambda';
8+
import * as snsSubscription from '@aws-cdk/aws-sns-subscriptions';
9+
import { BuildSpec } from 'aws-cdk-lib/aws-codebuild';
10+
import { Branch } from '@aws-cdk/aws-amplify';
11+
import { EventField } from '@aws-cdk/aws-events';
12+
import * as fs from 'fs';
13+
import { ServicePrincipal } from '@aws-cdk/aws-iam';
14+
15+
export class MainInfraStack extends cdk.Stack {
16+
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
17+
super(scope, id, props);
18+
19+
// load env
20+
const appName = process.env.APP_NAME || '';
21+
const owner = process.env.OWNER || '';
22+
const repository = process.env.REPOSITORY || '';
23+
const token = process.env.GITHUB_TOKEN || '';
24+
const baseDomain = process.env.DOMAIN || '';
25+
const projectName = process.env.PROJECT_NAME || '';
26+
const slackWebhookURL = process.env.SLACK_WEBHOOK_URL || '';
27+
28+
const amplifyApp = this.createAmplifyApp(appName, owner, repository, token, baseDomain, projectName);
29+
this.addDeployNotificationStack(amplifyApp, appName, slackWebhookURL);
30+
}
31+
32+
private createAmplifyApp(
33+
appName: string,
34+
owner: string,
35+
repository: string,
36+
token: string,
37+
baseDomain: string,
38+
projectName: string,
39+
) {
40+
const amplifyApp = new amplify.App(this, appName, {
41+
sourceCodeProvider: new amplify.GitHubSourceCodeProvider({
42+
owner: owner,
43+
repository: repository,
44+
oauthToken: cdk.SecretValue.plainText(token),
45+
}),
46+
autoBranchCreation: {
47+
patterns: ['develop', 'main', 'release*'],
48+
},
49+
autoBranchDeletion: true,
50+
role: this.createRole(projectName),
51+
buildSpec: this.getBuildSpec(),
52+
});
53+
54+
const mainBranch = amplifyApp.addBranch('main');
55+
const developBranch = amplifyApp.addBranch('develop');
56+
57+
amplifyApp.addCustomRule({
58+
source: '/<*>',
59+
target: '/index.html',
60+
status: amplify.RedirectStatus.NOT_FOUND_REWRITE,
61+
});
62+
63+
const productionDomain = `${projectName}.${baseDomain}`;
64+
const stagingDomain = `staging-${productionDomain}`;
65+
this.createDomains(amplifyApp, mainBranch, productionDomain);
66+
this.createDomains(amplifyApp, developBranch, stagingDomain);
67+
68+
return amplifyApp;
69+
}
70+
71+
private createDomains(amplifyApp: amplify.App, branch: Branch, branchDomainName: string) {
72+
const domain = amplifyApp.addDomain(`${branchDomainName}`, {
73+
enableAutoSubdomain: false,
74+
});
75+
domain.mapRoot(branch); // map master branch to domain root
76+
domain.mapSubDomain(branch, 'www');
77+
}
78+
79+
private createRole(projectName: string): iam.IRole {
80+
const role = new iam.Role(this, `cdk-amplify-role-${projectName}`, {
81+
assumedBy: new iam.ServicePrincipal('amplify.amazonaws.com'),
82+
description: `Custom role permitting resources creation from Amplify for ${projectName}`,
83+
});
84+
85+
const iManagedPolicy = iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess-Amplify');
86+
87+
role.addManagedPolicy(iManagedPolicy);
88+
return role;
89+
}
90+
91+
private getBuildSpec(): BuildSpec {
92+
return BuildSpec.fromObjectToYaml({
93+
version: '1.0',
94+
frontend: {
95+
phases: {
96+
preBuild: {
97+
commands: ['yarn'],
98+
},
99+
build: {
100+
commands: ['yarn run build'],
101+
},
102+
},
103+
artifacts: {
104+
baseDirectory: '.next',
105+
files: -'**/*',
106+
},
107+
cache: {
108+
paths: ['node_modules/**/*'],
109+
},
110+
},
111+
});
112+
}
113+
114+
private addDeployNotificationStack(amplifyApp: amplify.App, appName: string, slackWebhookURL: string) {
115+
const rule = new events.Rule(this, 'rule', {
116+
eventPattern: {
117+
source: ['aws.amplify'],
118+
detail: {
119+
appId: [amplifyApp.appId],
120+
jobStatus: ['SUCCEED', 'FAILED', 'STARTED'],
121+
},
122+
detailType: ['Amplify Deployment Status Change'],
123+
},
124+
});
125+
126+
const snsTopic = new sns.Topic(this, `sns-${appName}-deploy-topic`, {
127+
displayName: `${appName}`,
128+
});
129+
130+
const lambdaFunction = this.createNotifyLambdaFunction(slackWebhookURL, appName, snsTopic);
131+
snsTopic.addSubscription(new snsSubscription.LambdaSubscription(lambdaFunction));
132+
133+
this.createDeployNotificationRole(appName, snsTopic);
134+
rule.addTarget(
135+
new targets.SnsTopic(snsTopic, {
136+
message: events.RuleTargetInput.fromText(
137+
`Hi, the build status for ${EventField.fromPath(
138+
'$.detail.branchName',
139+
)} branch with build ID ${EventField.fromPath(
140+
'$.detail.jobId',
141+
)} on ${appName} is ${EventField.fromPath('$.detail.jobStatus')}.`,
142+
),
143+
}),
144+
);
145+
}
146+
147+
private createNotifyLambdaFunction(slackWebhookURL: string, appName: string, snsTopic: sns.Topic): lambda.Function {
148+
const lambdaCode = fs.readFileSync('static/lambda-notify.js');
149+
150+
const lambdaRole = new iam.Role(this, `iam-role-lambda-send-slack-notification-${appName}`, {
151+
roleName: `iam-role-lambda-send-slack-notification-${appName}`,
152+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
153+
});
154+
155+
// policy to allow assume role TenantUserRole
156+
lambdaRole.addToPolicy(
157+
new iam.PolicyStatement({
158+
resources: ['*'],
159+
actions: ['sts:AssumeRole'],
160+
}),
161+
);
162+
163+
const fn = new lambda.Function(this, `sendSlackNotification${appName}`, {
164+
runtime: lambda.Runtime.NODEJS_12_X,
165+
handler: 'index.handler',
166+
code: lambda.Code.fromInline(`${lambdaCode.toString()}`),
167+
environment: { WEBHOOK_URL: slackWebhookURL },
168+
role: lambdaRole,
169+
});
170+
171+
fn.addPermission(`sendSlack${appName}-execution-permission`, {
172+
action: 'lambda:InvokeFunction',
173+
principal: new ServicePrincipal('sns.amazonaws.com'),
174+
sourceArn: snsTopic.topicArn,
175+
});
176+
177+
return fn;
178+
}
179+
180+
private createDeployNotificationRole(appName: string, snsTopic: sns.Topic): iam.IRole {
181+
const role = new iam.Role(this, `cdk-amplify-notification-role-${appName}`, {
182+
assumedBy: new iam.ServicePrincipal('events.amazonaws.com'),
183+
description: `Custom role permitting event publishing from Amplify for ${appName}`,
184+
});
185+
186+
const inlinePolicy = new iam.Policy(this, `cdk-amplify-deploy-notification-policy-${appName}`, {
187+
statements: [
188+
new iam.PolicyStatement({
189+
actions: ['SNS:Publish'],
190+
resources: [snsTopic.topicArn],
191+
}),
192+
],
193+
});
194+
195+
role.attachInlinePolicy(inlinePolicy);
196+
return role;
197+
}
198+
}

package.json

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"name": "cdk-amplify",
3+
"version": "0.1.0",
4+
"bin": {
5+
"main": "bin/main.js"
6+
},
7+
"scripts": {
8+
"build": "tsc",
9+
"watch": "tsc -w",
10+
"test": "jest",
11+
"cdk": "cdk",
12+
"lint": "eslint '*/**/*.{js,ts,tsx}' --quiet --fix"
13+
},
14+
"devDependencies": {
15+
"@types/jest": "^26.0.10",
16+
"@types/node": "10.17.27",
17+
"@typescript-eslint/eslint-plugin": "^5.13.0",
18+
"@typescript-eslint/parser": "^5.13.0",
19+
"aws-cdk": "2.15.0",
20+
"eslint": "^8.10.0",
21+
"eslint-config-prettier": "^8.5.0",
22+
"eslint-plugin-prettier": "^4.0.0",
23+
"jest": "^26.4.2",
24+
"prettier": "^2.5.1",
25+
"ts-jest": "^26.2.0",
26+
"ts-node": "^9.0.0",
27+
"typescript": "~3.9.7"
28+
},
29+
"dependencies": {
30+
"@aws-cdk/aws-amplify": "^1.147.0",
31+
"@aws-cdk/aws-amplify-alpha": "2.15.0-alpha.0",
32+
"@aws-cdk/aws-events": "^1.147.0",
33+
"@aws-cdk/aws-events-targets": "^1.147.0",
34+
"@aws-cdk/aws-iam": "^1.147.0",
35+
"@aws-cdk/aws-lambda": "^1.147.0",
36+
"@aws-cdk/aws-sns": "^1.147.0",
37+
"@aws-cdk/aws-sns-subscriptions": "^1.147.0",
38+
"@aws-cdk/core": "^1.147.0",
39+
"aws-cdk-lib": "2.15.0",
40+
"constructs": "^10.0.0",
41+
"dotenv": "^16.0.0",
42+
"module": "^1.2.5",
43+
"source-map-support": "^0.5.16"
44+
}
45+
}

0 commit comments

Comments
 (0)