From 33f903277fbe3054ba45d7165e09765e68fcb488 Mon Sep 17 00:00:00 2001 From: Pedro Arantes Date: Sat, 5 Dec 2020 21:23:53 -0300 Subject: [PATCH] feat: add versions to static app deployments --- .../src/deploy/addDefaults.cloudFormation.ts | 61 +++++++++++-------- packages/cli/src/deploy/s3.ts | 2 +- .../deploy/staticApp/staticApp.template.ts | 50 +++++++-------- .../cli/src/deploy/staticApp/staticApp.ts | 7 ++- packages/website/api/vars.ts | 1 - .../pages/docs/usage/deploy-static-app.tsx | 11 +--- 6 files changed, 67 insertions(+), 65 deletions(-) diff --git a/packages/cli/src/deploy/addDefaults.cloudFormation.ts b/packages/cli/src/deploy/addDefaults.cloudFormation.ts index 302622d..8090d5f 100644 --- a/packages/cli/src/deploy/addDefaults.cloudFormation.ts +++ b/packages/cli/src/deploy/addDefaults.cloudFormation.ts @@ -117,30 +117,41 @@ const addLogGroupToResources = ( return template; }; -// const addEnvironmentsToLambdaResources = ( -// template: CloudFormationTemplate, -// ): CloudFormationTemplate => { -// const environment = getEnvironment(); - -// const { Resources } = template; - -// const resourcesEntries = Object.entries(Resources); - -// resourcesEntries.forEach(([, resource]) => { -// if (resource.Type === 'AWS::Lambda::Function') { -// const { Properties } = resource; -// if (!Properties.Environment) { -// Properties.Environment = {}; -// } -// if (!Properties.Environment.Variables) { -// Properties.Environment.Variables = {}; -// } -// Properties.Environment.Variables.ENVIRONMENT = environment; -// } -// }); - -// return template; -// }; +const addEnvironmentsToLambdaResources = ( + template: CloudFormationTemplate, +): CloudFormationTemplate => { + const environment = getEnvironment(); + + const { Resources } = template; + + const resourcesEntries = Object.entries(Resources); + + resourcesEntries.forEach(([, resource]) => { + if (resource.Type === 'AWS::Lambda::Function') { + const { Properties } = resource; + + /** + * Lambda@Edege does not support environment variables. + * https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-requirements-limits.html#lambda-requirements-lambda-function-configuration + * Then every function that has "Lambda@Edge" in its description will not + * have the variables passed to Environment.Variables. + */ + if (((Properties.Description as string) || '').includes('Lambda@Edge')) { + return; + } + + if (!Properties.Environment) { + Properties.Environment = {}; + } + if (!Properties.Environment.Variables) { + Properties.Environment.Variables = {}; + } + Properties.Environment.Variables.ENVIRONMENT = environment; + } + }); + + return template; +}; export const addDefaults = async ({ params, @@ -149,7 +160,7 @@ export const addDefaults = async ({ const newTemplate = await [ addDefaultParametersToTemplate, addLogGroupToResources, - // addEnvironmentsToLambdaResources, + addEnvironmentsToLambdaResources, ].reduce(async (acc, addFn) => addFn(await acc), Promise.resolve(template)); return { diff --git a/packages/cli/src/deploy/s3.ts b/packages/cli/src/deploy/s3.ts index 08dab49..6ce11d3 100644 --- a/packages/cli/src/deploy/s3.ts +++ b/packages/cli/src/deploy/s3.ts @@ -225,7 +225,7 @@ export const uploadDirectoryToS3 = async ({ groupOfFiles.map((file) => uploadFileToS3({ bucket, - key: path.relative(directory, file), + key: path.join(bucketKey, path.relative(directory, file)), filePath: file, }), ), diff --git a/packages/cli/src/deploy/staticApp/staticApp.template.ts b/packages/cli/src/deploy/staticApp/staticApp.template.ts index 240c041..ff08dff 100644 --- a/packages/cli/src/deploy/staticApp/staticApp.template.ts +++ b/packages/cli/src/deploy/staticApp/staticApp.template.ts @@ -5,7 +5,12 @@ * https://gist.github.com/jed/56b1f58297d374572bc51c59394c7e7f */ import { NAME } from '../../config'; -import { CloudFormationTemplate, Resource, Output } from '../../utils'; +import { + CloudFormationTemplate, + Resource, + Output, + getPackageVersion, +} from '../../utils'; const STATIC_APP_BUCKET_LOGICAL_ID = 'StaticBucket'; @@ -37,22 +42,23 @@ exports.handler = (event, context) => { } `.trim(); -const LAMBDA_EDGE_ORIGIN_REQUEST_LOGICAL_ID = 'LambdaEdgeOriginRequest'; +const LAMBDA_EDGE_VIEWER_REQUEST_LOGICAL_ID = 'LambdaEdgeOriginRequest'; -const LAMBDA_EDGE_VERSION_ORIGIN_REQUEST_LOGICAL_ID = +const LAMBDA_EDGE_VERSION_VIEWER_REQUEST_LOGICAL_ID = 'LambdaEdgeVersionOriginRequest'; -const LAMBDA_EDGE_ORIGIN_REQUEST_ZIP_FILE = ` +const LAMBDA_EDGE_VIEWER_REQUEST_ZIP_FILE = ` exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; - const uri = request.uri; - if (uri.endsWith('/')) { + if (request.uri.endsWith('/')) { request.uri += 'index.html'; - } else if (!uri.includes('.')) { + } else if (!request.uri.includes('.')) { request.uri += '.html'; } + request.uri = "/${getPackageVersion()}" + request.uri; + callback(null, request); }; `.trim(); @@ -62,12 +68,6 @@ const LAMBDA_EDGE_ORIGIN_RESPONSE_LOGICAL_ID = 'LambdaEdgeOriginResponse'; const LAMBDA_EDGE_VERSION_ORIGIN_RESPONSE_LOGICAL_ID = 'LambdaEdgeVersionOriginResponse'; -/** - * Matches strings that: - * - contains substring '/static/' - */ -export const originCacheExpression = '/static/'; - const defaultScp = [ "default-src 'self'", "img-src 'self'", @@ -82,8 +82,6 @@ const defaultScp = [ * * - Add some headers to improve security * {@link https://aws.amazon.com/blogs/networking-and-content-delivery/adding-http-security-headers-using-lambdaedge-and-amazon-cloudfront/}. - * - * @param param.spa tells if the static app is a SPA. */ export const getLambdaEdgeOriginResponseZipFile = ({ scp = defaultScp, @@ -94,15 +92,13 @@ exports.handler = (event, context, callback) => { const request = event.Records[0].cf.request; const response = event.Records[0].cf.response; const headers = response.headers; - - const cacheRegex = new RegExp('${originCacheExpression}'); - const maxAge = cacheRegex.test(request.uri) ? 60 * 60 * 24 * 365 : 60; + const maxAge = 150; headers['cache-control'] = [ { key: 'Cache-Control', - value: \`public, max-age=\${maxAge}, immutable\` + value: \`max-age=\${maxAge}\` } ]; headers['strict-transport-security'] = [ @@ -347,17 +343,17 @@ const getCloudFrontEdgeLambdas = ({ }; /** - * If not SPA, then add Lambda@Edge origin request, which handle the received + * If not SPA, then add Lambda@Edge viewer request, which handle the received * URI and convert to final files to be retrieved from AWS S3. */ if (!spa) { lambdaEdgeResources = { ...lambdaEdgeResources, - [LAMBDA_EDGE_ORIGIN_REQUEST_LOGICAL_ID]: { + [LAMBDA_EDGE_VIEWER_REQUEST_LOGICAL_ID]: { Type: 'AWS::Lambda::Function', Properties: { - Code: { ZipFile: LAMBDA_EDGE_ORIGIN_REQUEST_ZIP_FILE }, - Description: 'Lambda@Edge function serving as origin request.', + Code: { ZipFile: LAMBDA_EDGE_VIEWER_REQUEST_ZIP_FILE }, + Description: 'Lambda@Edge function serving as viewer request.', Handler: 'index.handler', MemorySize: 128, Role: { 'Fn::GetAtt': `${LAMBDA_EDGE_IAM_ROLE_LOGICAL_ID}.Arn` }, @@ -365,11 +361,11 @@ const getCloudFrontEdgeLambdas = ({ Timeout: 5, }, }, - [LAMBDA_EDGE_VERSION_ORIGIN_REQUEST_LOGICAL_ID]: { + [LAMBDA_EDGE_VERSION_VIEWER_REQUEST_LOGICAL_ID]: { Type: 'Custom::LatestLambdaVersion', Properties: { FunctionName: { - Ref: LAMBDA_EDGE_ORIGIN_REQUEST_LOGICAL_ID, + Ref: LAMBDA_EDGE_VIEWER_REQUEST_LOGICAL_ID, }, Nonce: `${Date.now()}`, ServiceToken: { @@ -467,9 +463,9 @@ const getCloudFrontTemplate = ({ ? [] : [ { - EventType: 'origin-request', + EventType: 'viewer-request', LambdaFunctionARN: { - 'Fn::GetAtt': `${LAMBDA_EDGE_VERSION_ORIGIN_REQUEST_LOGICAL_ID}.FunctionArn`, + 'Fn::GetAtt': `${LAMBDA_EDGE_VERSION_VIEWER_REQUEST_LOGICAL_ID}.FunctionArn`, }, }, ]), diff --git a/packages/cli/src/deploy/staticApp/staticApp.ts b/packages/cli/src/deploy/staticApp/staticApp.ts index ee4491a..2340b61 100644 --- a/packages/cli/src/deploy/staticApp/staticApp.ts +++ b/packages/cli/src/deploy/staticApp/staticApp.ts @@ -2,6 +2,8 @@ import { CloudFormation, CloudFront } from 'aws-sdk'; import log from 'npmlog'; +import { getPackageVersion } from '../../utils'; + import { cloudFormation, deploy } from '../cloudFormation'; import { uploadDirectoryToS3, emptyS3Directory } from '../s3'; import { getStackName } from '../stackName'; @@ -32,8 +34,9 @@ export const uploadBuiltAppToS3 = async ({ buildFolder: string; bucket: string; }) => { - await emptyS3Directory({ bucket }); - await uploadDirectoryToS3({ bucket, directory }); + const version = getPackageVersion(); + await emptyS3Directory({ bucket, directory: version }); + await uploadDirectoryToS3({ bucket, bucketKey: version, directory }); }; export const invalidateCloudFront = async ({ diff --git a/packages/website/api/vars.ts b/packages/website/api/vars.ts index a43dc83..6c48f93 100644 --- a/packages/website/api/vars.ts +++ b/packages/website/api/vars.ts @@ -3,5 +3,4 @@ export { getLambdaLayerTemplate } from 'carlin/dist/deploy/lambdaLayer'; export { getLambdaEdgeOriginResponseZipFile, getStaticAppTemplate, - originCacheExpression, } from 'carlin/dist/deploy/staticApp/staticApp.template'; diff --git a/packages/website/pages/docs/usage/deploy-static-app.tsx b/packages/website/pages/docs/usage/deploy-static-app.tsx index f07b2f9..a4145fe 100644 --- a/packages/website/pages/docs/usage/deploy-static-app.tsx +++ b/packages/website/pages/docs/usage/deploy-static-app.tsx @@ -22,7 +22,7 @@ export const getStaticProps = async () => { })(); const cloudfront = (() => { - const options = { cloudfront: true, spa: false }; + const options = { cloudfront: true, spa: false, scp: [] }; const template = getJsonYamlTemplates( apiVars.getStaticAppTemplate(options), ); @@ -31,7 +31,6 @@ export const getStaticProps = async () => { 'deploy/staticApp/staticApp.template.js', originCacheExpression: 'deploy/staticApp/staticApp.template.js', }); - const { originCacheExpression } = apiVars; const getLambdaEdgeOriginResponseZipFile = apiVars.getLambdaEdgeOriginResponseZipFile( options, ); @@ -47,7 +46,6 @@ export const getStaticProps = async () => { template, comments, customScp, - originCacheExpression, getLambdaEdgeOriginResponseZipFile, getLambdaEdgeOriginResponseZipFileWithScp, }; @@ -90,12 +88,7 @@ const DocsUsageDeployStaticApp = ({ root, onlyS3, cloudfront }: Props) => { functionality is explained below: - {[ - cloudfront.comments.getLambdaEdgeOriginResponseZipFile, - '\n', - cloudfront.comments.originCacheExpression, - `const originCacheExpression = '${cloudfront.originCacheExpression}';`, - ].join('\n')} + {[cloudfront.comments.getLambdaEdgeOriginResponseZipFile].join('\n')} The Lambda@Edge code is show below: