Skip to content

Commit 897bcf1

Browse files
authored
Cross account deployment - initial support / split Lambda and DB deployment requests (#301)
* Cross account - Fixes #253 * Proxy to parent deployer when configured * Add child deployer construct * Split version controller out into many files * Add DeployVersionLite * Fix build / retain versions * Build fix * Try to fix deploy * Update API.md * Fix ARN * Fix account ID fetching * Minor fixes - Typo - Fix tag name in tests (had `microapp[s]-managed` but it's not supposed to have an `s` - Change `StringEqualsIfExists` to `StringEquals` so it is required that the string be present * Stop publishing if `CreateAlias` fails * Update DeployClient.ts * Log arn base * Implement deployVersionLite and tests * Fix more tests * Add test for proxying of deployVersionLite * Use deployVersionLite from microapps-publish * Pass functionUrl to DeployVersionLite * Check if function version exists in LambdaAlias request * Add hotswap flag on CDK deploys * Cache TypeScript build output * Fix ids * Revert accidental DeployVersion change * Actually skip build if cached
1 parent c5aa77c commit 897bcf1

36 files changed

+3506
-1655
lines changed

.github/workflows/ci.yml

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
path: |
5656
node_modules
5757
packages/**/node_modules
58-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
58+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
5959

6060
- name: Optionally Install Node Modules
6161
if: steps.cache-node-modules.outputs.cache-hit != 'true'
@@ -84,7 +84,17 @@ jobs:
8484
echo 'NEXTJS_DEMO_APP_PACKAGE_VERSION='${NEXTJS_DEMO_APP_PACKAGE_VERSION}
8585
echo 'RELEASE_APP_PACKAGE_VERSION='${RELEASE_APP_PACKAGE_VERSION}
8686
87-
- name: Build All TypeScript
87+
- name: Cache TypeScript Build Output
88+
id: cache-typescript-build
89+
uses: actions/cache@v3
90+
with:
91+
path: |
92+
packages/**/dist
93+
packages/microapps-cdk/lib
94+
key: typescript-build-${{ hashFiles('package.json', 'yarn.lock', 'tsconfig.json', 'tsconfig.packages.json', 'packages/**/tsconfig.json', 'packages/**/*.ts') }}
95+
96+
- name: Optionally Build All TypeScript
97+
if: steps.cache-typescript-build.outputs.cache-hit != 'true'
8898
run: yarn build
8999

90100
- name: Run Lint
@@ -152,13 +162,23 @@ jobs:
152162
path: |
153163
node_modules
154164
packages/**/node_modules
155-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
165+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
156166

157167
- name: Optionally Install Node Modules
158168
if: steps.cache-node-modules.outputs.cache-hit != 'true'
159169
run: yarn install --frozen-lockfile
160170

161-
- name: Build All TypeScript
171+
- name: Cache TypeScript Build Output
172+
id: cache-typescript-build
173+
uses: actions/cache@v3
174+
with:
175+
path: |
176+
packages/**/dist
177+
packages/microapps-cdk/lib
178+
key: typescript-build-${{ hashFiles('package.json', 'yarn.lock', 'tsconfig.json', 'tsconfig.packages.json', 'packages/**/tsconfig.json', 'packages/**/*.ts') }}
179+
180+
- name: Optionally Build All TypeScript
181+
if: steps.cache-typescript-build.outputs.cache-hit != 'true'
162182
run: yarn build
163183

164184
- name: Build Edge-to-Origin for Local Deploy
@@ -217,9 +237,19 @@ jobs:
217237
--context @pwrdrvr/microapps:deployReleaseApp=true \
218238
--require-approval never ${{ matrix.deployName }}
219239
240+
- name: Set Hotswap Flag on PRs
241+
run: |
242+
if [ -n "${PR_NUMBER}" ]; then
243+
echo "HOTSWAP_FLAG=--hotswap" >> $GITHUB_ENV
244+
else
245+
echo "HOTSWAP_FLAG=" >> $GITHUB_ENV
246+
fi
247+
220248
- name: Deploy CDK Stack
221249
run: |
222-
npx cdk deploy --context @pwrdrvr/microapps:deployDemoApp=true \
250+
npx cdk deploy \
251+
${HOTSWAP_FLAG} \
252+
--context @pwrdrvr/microapps:deployDemoApp=true \
223253
--context @pwrdrvr/microapps:deployNexjsDemoApp=true \
224254
--context @pwrdrvr/microapps:deployReleaseApp=true \
225255
--require-approval never ${{ matrix.deployName }}
@@ -419,7 +449,7 @@ jobs:
419449
path: |
420450
node_modules
421451
packages/**/node_modules
422-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
452+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
423453

424454
- name: Optionally Install Node Modules
425455
if: steps.cache-node-modules.outputs.cache-hit != 'true'

.github/workflows/main-build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ jobs:
3939
path: |
4040
node_modules
4141
packages/**/node_modules
42-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
42+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
4343
- name: Install dependencies
4444
if: steps.cache-node-modules.outputs.cache-hit != 'true'
4545
run: yarn install --frozen-lockfile
@@ -136,7 +136,7 @@ jobs:
136136
path: |
137137
node_modules
138138
packages/**/node_modules
139-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
139+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
140140
- name: Optionally Install Node Modules
141141
if: steps.cache-node-modules.outputs.cache-hit != 'true'
142142
run: yarn install --frozen-lockfile

.github/workflows/release.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
path: |
4141
node_modules
4242
packages/**/node_modules
43-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
43+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
4444
- name: Install dependencies
4545
if: steps.cache-node-modules.outputs.cache-hit != 'true'
4646
run: yarn install --frozen-lockfile
@@ -130,7 +130,7 @@ jobs:
130130
path: |
131131
node_modules
132132
packages/**/node_modules
133-
key: node-modules-${{ hashFiles('package.json', '**/yarn.lock') }}
133+
key: node-modules-${{ hashFiles('package.json', 'yarn.lock', '**/yarn.lock') }}
134134
- name: Optionally Install Node Modules
135135
if: steps.cache-node-modules.outputs.cache-hit != 'true'
136136
run: yarn install --frozen-lockfile

packages/cdk/lib/MicroApps.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ export class MicroAppsStack extends Stack {
251251
removalPolicy,
252252
});
253253

254+
const appVersion = (demoApp.lambdaFunction as lambda.Function).currentVersion;
255+
appVersion.applyRemovalPolicy(RemovalPolicy.RETAIN);
256+
254257
new CfnOutput(this, 'demo-app-func-name', {
255258
value: `${demoApp.lambdaFunction.functionName}`,
256259
exportName: `${this.stackName}-demo-app-func-name`,
@@ -266,6 +269,7 @@ export class MicroAppsStack extends Stack {
266269
});
267270

268271
const appVersion = (app.lambdaFunction as lambda.Function).currentVersion;
272+
appVersion.applyRemovalPolicy(RemovalPolicy.RETAIN);
269273

270274
new CfnOutput(this, 'release-app-func-name', {
271275
value: `${app.lambdaFunction.functionName}`,
@@ -288,6 +292,7 @@ export class MicroAppsStack extends Stack {
288292
});
289293

290294
const appVersion = (app.lambdaFunction as lambda.Function).currentVersion;
295+
appVersion.applyRemovalPolicy(RemovalPolicy.RETAIN);
291296

292297
new CfnOutput(this, 'nextjs-demo-app-func-name', {
293298
value: `${app.lambdaFunction.functionName}`,

packages/microapps-cdk/API.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,17 @@ Optional asset name suffix.
14791479

14801480
---
14811481

1482+
##### `deployerTimeout`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsSvcsProps.deployerTimeout"></a>
1483+
1484+
- *Type:* [`aws-cdk-lib.Duration`](#aws-cdk-lib.Duration)
1485+
- *Default:* 2 minutes
1486+
1487+
Deployer timeout.
1488+
1489+
For larger applications this needs to be set up to 2-5 minutes for the S3 copy
1490+
1491+
---
1492+
14821493
##### `httpApi`<sup>Optional</sup> <a name="@pwrdrvr/microapps-cdk.MicroAppsSvcsProps.httpApi"></a>
14831494

14841495
- *Type:* [`@aws-cdk/aws-apigatewayv2-alpha.HttpApi`](#@aws-cdk/aws-apigatewayv2-alpha.HttpApi)
@@ -1503,7 +1514,7 @@ Note: if set to DESTROY the S3 buckes will have `autoDeleteObjects` set to `true
15031514
- *Type:* `boolean`
15041515
- *Default:* true
15051516

1506-
Require IAM auth on API Gateway.
1517+
Require IAM auth on API Gateway and Lambda Function URLs.
15071518

15081519
---
15091520

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { existsSync } from 'fs';
2+
import * as path from 'path';
3+
import { Aws, Duration, RemovalPolicy } from 'aws-cdk-lib';
4+
import * as iam from 'aws-cdk-lib/aws-iam';
5+
import * as lambda from 'aws-cdk-lib/aws-lambda';
6+
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
7+
import * as logs from 'aws-cdk-lib/aws-logs';
8+
import { Construct } from 'constructs';
9+
10+
/**
11+
* Properties to initialize an instance of `MicroAppsChildDeployer`.
12+
*/
13+
export interface MicroAppsChildDeployerProps {
14+
/**
15+
* ARN of the parent Deployer Lambda Function
16+
*/
17+
readonly parentDeployerLambdaARN: string;
18+
19+
/**
20+
* RemovalPolicy override for child resources
21+
*
22+
* Note: if set to DESTROY the S3 buckes will have `autoDeleteObjects` set to `true`
23+
*
24+
* @default - per resource default
25+
*/
26+
readonly removalPolicy?: RemovalPolicy;
27+
28+
/**
29+
* Application environment, passed as `NODE_ENV`
30+
* to the Router and Deployer Lambda functions
31+
*/
32+
readonly appEnv: string;
33+
34+
/**
35+
* Optional asset name root
36+
*
37+
* @example microapps
38+
* @default - resource names auto assigned
39+
*/
40+
readonly assetNameRoot?: string;
41+
42+
/**
43+
* Optional asset name suffix
44+
*
45+
* @example -dev-pr-12
46+
* @default none
47+
*/
48+
readonly assetNameSuffix?: string;
49+
50+
/**
51+
* Deployer timeout
52+
*
53+
* For larger applications this needs to be set up to 2-5 minutes for the S3 copy
54+
*
55+
* @default 2 minutes
56+
*/
57+
readonly deployerTimeout?: Duration;
58+
}
59+
60+
/**
61+
* Represents a MicroApps Child Deployer
62+
*/
63+
export interface IMicroAppsChildDeployer {
64+
/**
65+
* Lambda function for the Deployer
66+
*/
67+
readonly deployerFunc: lambda.IFunction;
68+
}
69+
70+
/**
71+
* Create a new MicroApps Child Deployer construct.
72+
*/
73+
export class MicroAppsChildDeployer extends Construct implements IMicroAppsChildDeployer {
74+
private _deployerFunc: lambda.Function;
75+
public get deployerFunc(): lambda.IFunction {
76+
return this._deployerFunc;
77+
}
78+
79+
constructor(scope: Construct, id: string, props?: MicroAppsChildDeployerProps) {
80+
super(scope, id);
81+
82+
if (props === undefined) {
83+
throw new Error('props cannot be undefined');
84+
}
85+
86+
const {
87+
appEnv,
88+
deployerTimeout = Duration.minutes(2),
89+
assetNameRoot,
90+
assetNameSuffix,
91+
removalPolicy,
92+
parentDeployerLambdaARN,
93+
} = props;
94+
95+
//
96+
// Deployer Lambda Function
97+
//
98+
99+
// Create Deployer Lambda Function
100+
const deployerFuncName = assetNameRoot
101+
? `${assetNameRoot}-deployer${assetNameSuffix}`
102+
: undefined;
103+
const deployerFuncProps: Omit<lambda.FunctionProps, 'handler' | 'code'> = {
104+
functionName: deployerFuncName,
105+
memorySize: 1769,
106+
logRetention: logs.RetentionDays.ONE_MONTH,
107+
runtime: lambda.Runtime.NODEJS_16_X,
108+
timeout: deployerTimeout,
109+
environment: {
110+
NODE_ENV: appEnv,
111+
AWS_NODEJS_CONNECTION_REUSE_ENABLED: '1',
112+
PARENT_DEPLOYER_LAMBDA_ARN: parentDeployerLambdaARN,
113+
},
114+
};
115+
if (
116+
process.env.NODE_ENV === 'test' &&
117+
existsSync(path.join(__dirname, '..', '..', 'microapps-deployer', 'dist', 'index.js'))
118+
) {
119+
// This is for local dev
120+
this._deployerFunc = new lambda.Function(this, 'deployer-func', {
121+
code: lambda.Code.fromAsset(path.join(__dirname, '..', '..', 'microapps-deployer', 'dist')),
122+
handler: 'index.handler',
123+
...deployerFuncProps,
124+
});
125+
} else if (existsSync(path.join(__dirname, 'microapps-deployer', 'index.js'))) {
126+
// This is for built apps packaged with the CDK construct
127+
this._deployerFunc = new lambda.Function(this, 'deployer-func', {
128+
code: lambda.Code.fromAsset(path.join(__dirname, 'microapps-deployer')),
129+
handler: 'index.handler',
130+
...deployerFuncProps,
131+
});
132+
} else {
133+
this._deployerFunc = new lambdaNodejs.NodejsFunction(this, 'deployer-func', {
134+
entry: path.join(__dirname, '..', '..', 'microapps-deployer', 'src', 'index.ts'),
135+
handler: 'handler',
136+
bundling: {
137+
minify: true,
138+
sourceMap: true,
139+
},
140+
...deployerFuncProps,
141+
});
142+
}
143+
if (removalPolicy !== undefined) {
144+
this._deployerFunc.applyRemovalPolicy(removalPolicy);
145+
}
146+
147+
// Grant full control over lambdas that indicate they are microapps
148+
const policyAPIManageLambdas = new iam.PolicyStatement({
149+
effect: iam.Effect.ALLOW,
150+
actions: ['lambda:*'],
151+
resources: [
152+
`arn:aws:lambda:${Aws.REGION}:${Aws.ACCOUNT_ID}:function:*`,
153+
`arn:aws:lambda:${Aws.REGION}:${Aws.ACCOUNT_ID}:function:*:*`,
154+
],
155+
conditions: {
156+
StringEquals: { 'aws:ResourceTag/microapp-managed': 'true' },
157+
},
158+
});
159+
this._deployerFunc.addToRolePolicy(policyAPIManageLambdas);
160+
}
161+
}

packages/microapps-cdk/src/MicroAppsSvcs.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as path from 'path';
33
import * as apigwy from '@aws-cdk/aws-apigatewayv2-alpha';
44
import * as apigwyAuth from '@aws-cdk/aws-apigatewayv2-authorizers-alpha';
55
import * as apigwyint from '@aws-cdk/aws-apigatewayv2-integrations-alpha';
6-
import { Aws, Duration, PhysicalName, RemovalPolicy, Stack } from 'aws-cdk-lib';
6+
import { Aws, Duration, PhysicalName, RemovalPolicy, Stack, Tags } from 'aws-cdk-lib';
77
import * as cf from 'aws-cdk-lib/aws-cloudfront';
88
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
99
import * as iam from 'aws-cdk-lib/aws-iam';
@@ -148,7 +148,7 @@ export interface MicroAppsSvcsProps {
148148
readonly rootPathPrefix?: string;
149149

150150
/**
151-
* Require IAM auth on API Gateway
151+
* Require IAM auth on API Gateway and Lambda Function URLs
152152
*
153153
* @default true
154154
*/
@@ -169,6 +169,15 @@ export interface MicroAppsSvcsProps {
169169
* @default created by construct
170170
*/
171171
readonly table?: dynamodb.ITable;
172+
173+
/**
174+
* Deployer timeout
175+
*
176+
* For larger applications this needs to be set up to 2-5 minutes for the S3 copy
177+
*
178+
* @default 2 minutes
179+
*/
180+
readonly deployerTimeout?: Duration;
172181
}
173182

174183
/**
@@ -223,6 +232,7 @@ export class MicroAppsSvcs extends Construct implements IMicroAppsSvcs {
223232
bucketApps,
224233
bucketAppsOAI,
225234
bucketAppsStaging,
235+
deployerTimeout = Duration.minutes(2),
226236
s3PolicyBypassAROAs = [],
227237
s3PolicyBypassPrincipalARNs = [],
228238
s3StrictBucketPolicy = false,
@@ -284,7 +294,7 @@ export class MicroAppsSvcs extends Construct implements IMicroAppsSvcs {
284294
memorySize: 1769,
285295
logRetention: logs.RetentionDays.ONE_MONTH,
286296
runtime: lambda.Runtime.NODEJS_16_X,
287-
timeout: Duration.seconds(15),
297+
timeout: deployerTimeout,
288298
environment: {
289299
NODE_ENV: appEnv,
290300
...(httpApi ? { APIGWY_ID: httpApi.httpApiId } : {}),
@@ -331,8 +341,11 @@ export class MicroAppsSvcs extends Construct implements IMicroAppsSvcs {
331341
this._table.grantReadWriteData(this._deployerFunc);
332342
this._table.grant(this._deployerFunc, 'dynamodb:DescribeTable');
333343

344+
// Add Tags to Deployer
345+
Tags.of(this._deployerFunc).add('microapps-deployer', 'true');
346+
334347
//
335-
// Deloyer upload temp role
348+
// Deployer upload temp role
336349
// Deployer assumes this role with a limited policy to generate
337350
// an STS temp token to return to microapps-publish for the upload.
338351
//

0 commit comments

Comments
 (0)