Skip to content
This repository has been archived by the owner on Feb 1, 2025. It is now read-only.

Commit

Permalink
fix(cdk,cli): correctly read git config, include BUILD_ID in assets, …
Browse files Browse the repository at this point in the history
…calculate zip hases ourselves
  • Loading branch information
true committed Sep 8, 2022
1 parent 1219354 commit 7604d1d
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 24 deletions.
11 changes: 9 additions & 2 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ program
const codeOutputPath = path.resolve(outputFolder, 'code.zip')

// Assets bundle configuration
const buildIdPath = path.resolve(commandCwd, './.next/BUILD_ID')
const generatedStaticContentPath = path.resolve(commandCwd, '.next/static')
const generatedStaticRemapping = '_next/static'
const assetsOutputPath = path.resolve(outputFolder, 'assetsLayer.zip')
Expand All @@ -79,6 +80,11 @@ program
await zipMultipleFoldersOrFiles({
outputName: assetsOutputPath,
inputDefinition: [
{
isFile: true,
name: 'BUILD_ID',
path: buildIdPath,
},
{
path: publicFolder,
},
Expand Down Expand Up @@ -169,8 +175,8 @@ program
.option('-a, --autoPush', 'This will automatically create release branch and tag commit in master.', Boolean, true)
.option('-t, --tagPrefix <prefix>', 'Prefix version with string of your choice.', 'v')
.option('-r, --releaseBranchPrefix <prefix>', 'Prefix for release branch fork.', 'release/')
.option('--gitUser', 'User name to be used for commits.', 'Bender')
.option('--gitEmail', 'User email to be used for commits.', 'bender@bot.eu')
.option('--gitUser <user>', 'User name to be used for commits.', 'Bender')
.option('--gitEmail <email>', 'User email to be used for commits.', 'bender@bot.eu')
.action(async (options) => {
const { tagPrefix, failOnMissingCommit, releaseBranchPrefix, forceBump, gitUser, gitEmail } = options

Expand Down Expand Up @@ -259,6 +265,7 @@ program
.push(remote.name, `${branch.current}:${releaseBranch}`)

// @Note: CI/CD should not be listening for tags in master, it should listen to release branch.
// @TODO: Include commits and commit bodies in release commit so Jira can pick it up.

console.log(`Successfuly tagged and created new branch - ${releaseBranch}`)
})
Expand Down
68 changes: 48 additions & 20 deletions lib/construct.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { HttpApi } from '@aws-cdk/aws-apigatewayv2-alpha'
import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha'
import { AssetHashType, CfnOutput, Duration, RemovalPolicy, Stack, StackProps, SymlinkFollowMode } from 'aws-cdk-lib'
import { AssetHashType, CfnOutput, Duration, RemovalPolicy, Stack, SymlinkFollowMode } from 'aws-cdk-lib'
import { CloudFrontAllowedMethods, CloudFrontWebDistribution, ViewerCertificate } from 'aws-cdk-lib/aws-cloudfront'
import { Code, Function, LayerVersion, Runtime } from 'aws-cdk-lib/aws-lambda'
import { Bucket, BucketAccessControl } from 'aws-cdk-lib/aws-s3'
Expand All @@ -9,58 +9,77 @@ import { Construct } from 'constructs'
import packageJson from '../package.json'

import { imageHandlerZipPath, sharpLayerZipPath, nextLayerZipPath } from './consts'
import { md5FileSync } from './utils'

interface NextConstructProps extends StackProps {
interface NextConstructProps {
// Required paths, output of pack CLI command.
codeZipPath: string
dependenciesZipPath: string
assetsZipPath: string
// Optional for additional customizations.
customServerHandler?: string
customImageHandler?: string

cfnViewerCertificate?: ViewerCertificate

imageHandlerZipPath?: string
sharpLayerZipPath?: string
nextLayerZipPath?: string
codeZipPath: string
dependenciesZipPath: string
assetsZipPath: string
}

export class NextStandaloneStack extends Stack {
export class NextStandaloneConstruct extends Construct {
private readonly cfnDistro: CloudFrontWebDistribution
private readonly serverLambda: Function
private readonly imageLambda: Function
private readonly serverApigatewayProxy: HttpApi
private readonly imageApigatewayProxy: HttpApi
private readonly region: string

constructor(scope: Construct, id: string, props: NextConstructProps) {
super(scope, id, props)
super(scope, id)

const config = {
sharpLayerZipPath: sharpLayerZipPath,
nextLayerZipPath: nextLayerZipPath,
imageHandlerZipPath: imageHandlerZipPath,
...props,
}

this.region = Stack.of(scope).region

const depsLayer = new LayerVersion(this, 'DepsLayer', {
code: Code.fromAsset(props.dependenciesZipPath),
code: Code.fromAsset(props.dependenciesZipPath, {
assetHash: md5FileSync(props.dependenciesZipPath),
assetHashType: AssetHashType.CUSTOM,
}),
})

const sharpLayer = new LayerVersion(this, 'SharpLayer', {
code: Code.fromAsset(props.sharpLayerZipPath ?? sharpLayerZipPath, {
assetHash: `static-sharp-${packageJson.version}`,
code: Code.fromAsset(config.sharpLayerZipPath, {
assetHash: md5FileSync(config.sharpLayerZipPath),
assetHashType: AssetHashType.CUSTOM,
}),
})

const nextLayer = new LayerVersion(this, 'NextLayer', {
code: Code.fromAsset(props.nextLayerZipPath ?? nextLayerZipPath, {
assetHash: `static-next-${packageJson.version}`,
code: Code.fromAsset(config.nextLayerZipPath, {
assetHash: md5FileSync(config.nextLayerZipPath),
assetHashType: AssetHashType.CUSTOM,
}),
})

const assetsBucket = new Bucket(this, 'NextAssetsBucket', {
// Those settings are necessary for bucket to be removed on stack removal.
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
// @NOTE: Considering not having public ACL.
publicReadAccess: true,
autoDeleteObjects: true,
removalPolicy: RemovalPolicy.DESTROY,
})

this.serverLambda = new Function(this, 'DefaultNextJs', {
code: Code.fromAsset(props.codeZipPath, { followSymlinks: SymlinkFollowMode.NEVER }),
code: Code.fromAsset(config.codeZipPath, {
followSymlinks: SymlinkFollowMode.NEVER,
assetHash: md5FileSync(config.codeZipPath),
assetHashType: AssetHashType.CUSTOM,
}),
runtime: Runtime.NODEJS_16_X,
handler: props.customServerHandler ?? 'handler.handler',
layers: [depsLayer, nextLayer],
Expand All @@ -70,7 +89,10 @@ export class NextStandaloneStack extends Stack {
})

this.imageLambda = new Function(this, 'ImageOptimizationNextJs', {
code: Code.fromAsset(props.imageHandlerZipPath ?? imageHandlerZipPath),
code: Code.fromAsset(config.imageHandlerZipPath, {
assetHash: md5FileSync(config.imageHandlerZipPath),
assetHashType: AssetHashType.CUSTOM,
}),
runtime: Runtime.NODEJS_16_X,
handler: props.customImageHandler ?? 'index.handler',
layers: [sharpLayer, nextLayer],
Expand Down Expand Up @@ -152,18 +174,24 @@ export class NextStandaloneStack extends Stack {
// This can be handled by `aws s3 sync` but we need to ensure invalidation of Cfn after deploy.
new BucketDeployment(this, 'PublicFilesDeployment', {
destinationBucket: assetsBucket,
sources: [Source.asset(props.assetsZipPath)],
accessControl: BucketAccessControl.PUBLIC_READ,
sources: [
Source.asset(config.assetsZipPath, {
assetHashType: AssetHashType.CUSTOM,
assetHash: md5FileSync(config.assetsZipPath),
}),
],
// Invalidate all paths after deployment.
distributionPaths: ['/*'],
distribution: this.cfnDistro,
distributionPaths: ['/*'],
})

new CfnOutput(this, 'cfnDistroUrl', { value: this.cfnDistro.distributionDomainName })
new CfnOutput(this, 'cfnDistroId', { value: this.cfnDistro.distributionId })
new CfnOutput(this, 'defaultApiGwUrl', { value: this.serverApigatewayProxy.apiEndpoint })
new CfnOutput(this, 'imagesApiGwUrl', { value: this.imageApigatewayProxy.apiEndpoint })
new CfnOutput(this, 'assetsBucketUrl', { value: assetsBucket.bucketDomainName })
new CfnOutput(this, 'nextConstructRegion', { value: this.region })
}

get cloudfrontDistribution() {
Expand Down
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { NextStandaloneStack } from './construct'
export { NextStandaloneConstruct } from './construct'
export { imageHandlerZipPath, serverHandlerZipPath, sharpLayerZipPath } from './consts'

export { handler as serverHandler } from './standalone/server-handler'
Expand Down
24 changes: 23 additions & 1 deletion lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
import archiver from 'archiver'
import { createWriteStream, readFileSync, symlinkSync } from 'fs'
import { closeSync, createWriteStream, openSync, readFileSync, readSync, symlinkSync } from 'fs'
import { IOptions as GlobOptions } from 'glob'
import { IncomingMessage, ServerResponse } from 'http'
import { NextUrlWithParsedQuery } from 'next/dist/server/request-meta'
import { replaceInFileSync } from 'replace-in-file'
import { Readable } from 'stream'
import crypto from 'crypto'

// Make header keys lowercase to ensure integrity.
export const normalizeHeaders = (headers: Record<string, any>) =>
Expand Down Expand Up @@ -254,3 +255,24 @@ interface SymlinkProps {
}

export const createSymlink = ({ linkLocation, sourcePath }: SymlinkProps) => symlinkSync(sourcePath, linkLocation)

const BUFFER_SIZE = 8192

export const md5FileSync = (path: string) => {
const fd = openSync(path, 'r')
const hash = crypto.createHash('md5')
const buffer = Buffer.alloc(BUFFER_SIZE)

try {
let bytesRead

do {
bytesRead = readSync(fd, buffer, 0, BUFFER_SIZE, null)
hash.update(buffer.subarray(0, bytesRead))
} while (bytesRead === BUFFER_SIZE)
} finally {
closeSync(fd)
}

return hash.digest('hex')
}
1 change: 1 addition & 0 deletions scripts/pack-nextjs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ mkdir -p $ASSETS_FOLDER
mkdir -p $ASSETS_FOLDER/_next/static
cp -r $MY_ROOT/.next/static/* $ASSETS_FOLDER/_next/static/
cp -r $MY_ROOT/$PUBLIC_FOLDER/* $ASSETS_FOLDER/
cp .next/BUILD_ID $ASSETS_FOLDER/

echo "Zipping assets."
cd $ASSETS_FOLDER
Expand Down

0 comments on commit 7604d1d

Please sign in to comment.