diff --git a/buildspec.yml b/buildspec.yml new file mode 100644 index 0000000..9aa035c --- /dev/null +++ b/buildspec.yml @@ -0,0 +1,29 @@ +version: 0.2 + +phases: + install: + runtime-versions: + nodejs: 18 + commands: + - echo cloning repository... + - git clone https://git.falsy.cat/falsycat/imbusy/ . + - find + + - echo installing dependencies... + - cd frontend && npm ci && cd .. + - cd backend && npm ci && cd .. + build: + commands: + - echo building frontend... + - cd frontend && npm run build && cd .. + + - echo building backend... + - cd backend && npm run build && cd .. + post_build: + commands: + - echo deploying frontend... + - aws s3 sync frontend/dist/ s3://$IMBUSY_FE_BUCKET --delete + + - echo deploying backend... + - cd backend/dist && zip -r ../../backend.zip . && cd ../.. + - aws s3 cp backend.zip s3://$IMBUSY_BE_BUCKET diff --git a/infra/backend.ts b/infra/backend.ts index 38e0dc1..f9e196d 100644 --- a/infra/backend.ts +++ b/infra/backend.ts @@ -7,18 +7,11 @@ import * as common from "./common"; const tags = common.tags; const prefix = `${common.prefix}-backend`; -console.log("building backend..."); -esbuild.buildSync({ - entryPoints: ["../backend/src/index.ts"], - outfile: "../backend/dist/index.js", - bundle: true, - platform: "node", - target: "node18", - minify: true, -}); +// ---- program bucket ---- +export const bucket = new aws.s3.Bucket(`${prefix}-bucket`); -// ---- backend lambda ---- +// ---- api lambda ---- const lambdaRole = new aws.iam.Role(`${prefix}-lambda-role`, { tags, assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ @@ -35,9 +28,8 @@ const lambda = new aws.lambda.Function(`${prefix}-lambda`, { runtime: "nodejs18.x", handler: "index.main", role: lambdaRole.arn, - code: new pulumi.asset.AssetArchive({ - ".": new pulumi.asset.FileArchive("../backend/dist/"), - }), + s3Bucket: bucket.arn, + s3Key: "lambda.zip", }); new aws.cloudwatch.LogGroup(`${prefix}-lambda-log-group`, { name: pulumi.interpolate`/aws/lambda/${lambda.name}`, @@ -60,7 +52,7 @@ new aws.dynamodb.Table(`${prefix}-db`, { // ---- API Gateway ---- -const api = new aws.apigatewayv2.Api(`${prefix}-api`, { +export const api = new aws.apigatewayv2.Api(`${prefix}-api`, { tags, protocolType: "WEBSOCKET", routeSelectionExpression: "$request.body.action", @@ -105,8 +97,6 @@ new aws.iam.RolePolicy(`${prefix}-api-role-policy`, { }`, }); -export const endpoint = pulumi.interpolate`${api.apiEndpoint}`; - // ---- cognito ---- const userPool = new aws.cognito.UserPool(`${prefix}-cognito-userpool`, { diff --git a/infra/common.ts b/infra/common.ts index ebb2ca4..d76f421 100644 --- a/infra/common.ts +++ b/infra/common.ts @@ -1,4 +1,5 @@ import * as pulumi from "@pulumi/pulumi"; +import * as aws from "@pulumi/aws"; export const tags = { project: pulumi.getProject(), @@ -6,3 +7,5 @@ export const tags = { }; export const prefix = `${tags.project}-${tags.env}`; + +export const logGroupPrefix = `/falsycat/${tags.project}/${tags.env}/`; diff --git a/infra/deployment.ts b/infra/deployment.ts new file mode 100644 index 0000000..a985bf8 --- /dev/null +++ b/infra/deployment.ts @@ -0,0 +1,79 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as aws from "@pulumi/aws"; + +import * as fs from "fs"; + +import * as common from "./common"; + +import * as backend from "./backend"; +import * as frontend from "./frontend"; + +const tags = common.tags; +const prefix = `${common.prefix}-deployment`; + + +// ---- role ---- +const role = new aws.iam.Role(`${prefix}-role`, { + tags, + assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({ Service: "codebuild.amazonaws.com" }), +}); +new aws.iam.RolePolicyAttachment(`${prefix}-policy-codebuild`, { + role: role.name, + policyArn: aws.iam.ManagedPolicies.AWSCodeBuildDeveloperAccess, +}); +new aws.iam.RolePolicy(`${prefix}-role-policy`, { + role: role.name, + policy: pulumi.all([backend.bucket.arn, frontend.bucket.arn]).apply(([be, fe]) => JSON.stringify({ + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Action: [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + Resource: [ `${be}/*`, `${fe}/*`, ], + }, + { + Effect: "Allow", + Action: [ + "s3:ListBucket", + ], + Resource: [ be, fe, ], + }, + { + Effect: "Allow", + Action: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + Resource: "*", + }, + ] + })), +}); + + +// ---- codebuild ---- +const codebuild = new aws.codebuild.Project(`${prefix}-codebuild`, { + tags, + source: { + type: "NO_SOURCE", + buildspec: fs.readFileSync("../buildspec.yml", "utf-8"), + }, + environment: { + computeType: "BUILD_GENERAL1_SMALL", + image: "aws/codebuild/standard:7.0", + type: "LINUX_CONTAINER", + environmentVariables: [ + { name: "IMBUSY_BE_BUCKET", value: backend.bucket.bucket, }, + { name: "IMBUSY_FE_BUCKET", value: frontend.bucket.bucket, }, + ], + }, + serviceRole: role.arn, + artifacts: { + type: "NO_ARTIFACTS", + }, +}); diff --git a/infra/frontend.ts b/infra/frontend.ts index 6442602..6a5053c 100644 --- a/infra/frontend.ts +++ b/infra/frontend.ts @@ -10,33 +10,18 @@ import * as common from "./common"; const tags = common.tags; const prefix = `${common.prefix}-frontend`; -// ---- infra resouces ---- -const bucket = new aws.s3.Bucket(`${prefix}-bucket`, {tags}); - -const oai = new aws.cloudfront.OriginAccessIdentity(`${prefix}-oai`); - -const bucketPolicy = new aws.s3.BucketPolicy(`${prefix}-bucket-policy`, { - bucket: bucket.bucket, - policy: pulumi.all([oai.iamArn, bucket.bucket]).apply(([a, b]) => JSON.stringify({ - Version: "2012-10-17", - Statement: [{ - Effect: "Allow", - Principal: { - AWS: a, - }, - Action: ["s3:GetObject"], - Resource: [`arn:aws:s3:::${b}/*`], - }], - })), -}); +// ---- bucket ---- +export const bucket = new aws.s3.Bucket(`${prefix}-bucket`, {tags}); +const bucketOai = new aws.cloudfront.OriginAccessIdentity(`${prefix}-oai`); +// ---- cloudfront ---- export const cloudfront = new aws.cloudfront.Distribution(`${prefix}-cloudfront`, { tags, origins: [{ domainName: bucket.bucketRegionalDomainName, originId: bucket.arn, s3OriginConfig: { - originAccessIdentity: oai.cloudfrontAccessIdentityPath, + originAccessIdentity: bucketOai.cloudfrontAccessIdentityPath, }, }], enabled: true, @@ -73,23 +58,3 @@ export const cloudfront = new aws.cloudfront.Distribution(`${prefix}-cloudfront` cloudfrontDefaultCertificate: true, }, }); - -// ---- file uploads ---- -(async () => { - const cwd = "../frontend/dist/"; - - const stats = await fs.stat(cwd); - if (!stats.isDirectory()) { - throw new Error("build the frontend firstly"); - } - - const files = await fg("**/*", {cwd, dot: true}); - for (const file of files) { - new aws.s3.BucketObject(file, { - bucket: bucket, - source: new pulumi.asset.FileAsset(cwd+file), - contentType: mime.getType(file) || "application/octet-stream", - key: file, - }); - } -})(); diff --git a/infra/index.ts b/infra/index.ts index d57c324..1b31f60 100644 --- a/infra/index.ts +++ b/infra/index.ts @@ -4,5 +4,7 @@ import * as aws from "@pulumi/aws"; import * as backend from "./backend"; import * as frontend from "./frontend"; +import "./deployment"; + export const frontendDomain = frontend.cloudfront.domainName; -export const backendEndpoint = backend.endpoint; +export const backendEndpoint = backend.api.apiEndpoint;