Skip to content

Commit

Permalink
Fix delete request from child account (#324)
Browse files Browse the repository at this point in the history
* Fix delete request from child account

- Fixes #323

* Fix delete from child account

* Fix delete in child account

* Real fix

* Fix deployer param

* AppName and SemVer headers

* Minor fix to headers
  • Loading branch information
huntharo authored Mar 28, 2023
1 parent 7d4604e commit f350bb6
Show file tree
Hide file tree
Showing 13 changed files with 638 additions and 87 deletions.
24 changes: 23 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,28 @@ jobs:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID_CHILD }}:role/builder-writeRole
aws-region: ${{ env.AWS_REGION }}

- name: Publish Demo App to Child MicroApps - To Delete
if: ${{ matrix.deployName == 'microapps-core' }}
run: |
npx microapps-publish publish \
--app-name ${DEMO_APP_NAME} \
--type lambda-url \
--startup-type direct \
--new-version 0.0.0-childDelete.1 \
--deployer-lambda-name ${CHILD_DEPLOYER_LAMBDA_ARN} \
--app-lambda-name ${CHILD_DEMO_APP_LAMBDA_NAME} \
--static-assets-path packages/demo-app/static_files \
--default-file index.html \
--overwrite
- name: Delete Demo App from Child MicroApps - To Delete
if: ${{ matrix.deployName == 'microapps-core' }}
run: |
npx microapps-publish delete \
--app-name ${DEMO_APP_NAME} \
--new-version 0.0.0-childDelete.1 \
--deployer-lambda-name ${CHILD_DEPLOYER_LAMBDA_ARN}
- name: Publish Demo App to Child MicroApps
if: ${{ matrix.deployName == 'microapps-core' }}
run: |
Expand All @@ -559,7 +581,7 @@ jobs:
--startup-type direct \
--new-version 0.0.0-child.1 \
--deployer-lambda-name ${CHILD_DEPLOYER_LAMBDA_ARN} \
--app-lambda-name ${CHILD_DEMO_APP_LAMBDA_VERSION_ARN} \
--app-lambda-name ${CHILD_DEMO_APP_LAMBDA_NAME} \
--static-assets-path packages/demo-app/static_files \
--default-file index.html \
--overwrite
Expand Down
16 changes: 11 additions & 5 deletions packages/demo-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ export async function handler(
// eslint-disable-next-line no-console
console.log('event', event);

const standardHeaders = {
'Powered-By': 'demo-app',
'X-MicroApps-AppName': event.headers['x-microapps-appname'] ?? 'unknown',
'X-MicroApps-SemVer': event.headers['x-microapps-semver'] ?? 'unknown',
};

if (event.rawPath.endsWith('/serverIncrement')) {
const currValue = parseInt(event.queryStringParameters?.currValue ?? '0', 10);
const newValue = currValue + 1;

return {
statusCode: 200,
headers: {
...standardHeaders,
'Content-Type': 'application/json',
'Powered-By': 'demo-app',
},
isBase64Encoded: false,
body: {
Expand All @@ -42,8 +48,8 @@ export async function handler(
return {
statusCode: 200,
headers: {
...standardHeaders,
'Content-Type': 'text/html',
'Powered-By': 'demo-app',
},
isBase64Encoded: false,
body: file,
Expand All @@ -55,7 +61,7 @@ export async function handler(
return {
statusCode: 404,
headers: {
'Powered-By': 'demo-app',
...standardHeaders,
},
isBase64Encoded: false,
};
Expand All @@ -65,8 +71,8 @@ export async function handler(
return {
statusCode: 200,
headers: {
...standardHeaders,
'Content-Type': 'text/html',
'Powered-By': 'demo-app',
},
isBase64Encoded: false,
body: html,
Expand All @@ -76,8 +82,8 @@ export async function handler(
return {
statusCode: 404,
headers: {
...standardHeaders,
'Content-Type': 'text/plain',
'Powered-By': 'demo-app',
},
isBase64Encoded: false,
body: `${new Date().toUTCString()} - default failure`,
Expand Down
1 change: 1 addition & 0 deletions packages/microapps-deployer-lib/src/messages/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface IRequestBase {
| 'deployVersion'
| 'deployVersionLite'
| 'deployVersionPreflight'
| 'getVersion'
| 'lambdaAlias';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,12 @@ import { IDeployVersionRequestBase } from './deploy-version';
export interface IDeleteVersionRequest
extends Pick<IDeployVersionRequestBase, 'appName' | 'semVer'> {
readonly type: 'deleteVersion';

/**
* When true the Lambda is not cleaned up (as it resides in a child account)
* but all other tasks are perfomed.
*
* @default false
*/
readonly requestFromChildAccount?: boolean;
}
25 changes: 25 additions & 0 deletions packages/microapps-deployer-lib/src/messages/get-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { IDeployVersionRequestBase } from './deploy-version';
import { IDeployerResponse } from './deployer';

/**
* Represents a Get Version Request
*/
export interface IGetVersionRequest extends Pick<IDeployVersionRequestBase, 'appName' | 'semVer'> {
readonly type: 'getVersion';
}

/**
* Represents a Get Version Response
*/
export interface IGetVersionResponse extends IDeployerResponse {
readonly type: 'getVersion';

readonly version?: {
readonly appName: string;
readonly semVer: string;

readonly type: 'lambda' | 'lambda-url' | 'url' | 'static';

readonly lambdaArn?: string;
};
}
1 change: 1 addition & 0 deletions packages/microapps-deployer-lib/src/messages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from './create-app';
export * from './delete-version';
export * from './deploy-version';
export * from './deployer';
export * from './get-version';
export * from './lambda-alias';
export * from './preflight';
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import * as s3 from '@aws-sdk/client-s3';
import type {
ICreateApplicationRequest,
IDeleteVersionRequest,
IGetVersionRequest,
IGetVersionResponse,
} from '@pwrdrvr/microapps-deployer-lib';
import { DBManager, Version } from '@pwrdrvr/microapps-datalib';
import type * as lambdaTypes from 'aws-lambda';
Expand Down Expand Up @@ -244,4 +246,188 @@ describe('DeleteVersion', () => {
expect(noRecord).toBeUndefined();
});
});

describe('deleteVersion - Child Account', () => {
it('200s and cleans up only the lambda and proxies to the parent', async () => {
const appName = 'newapp';
const semVer = '0.0.0';

theConfig.parentDeployerLambdaARN =
'arn:aws:lambda:us-east-1:123456789:function:parent-deployer';

const parentGetRequest: IGetVersionRequest = {
appName,
semVer,
type: 'getVersion',
};
const parentGetResponse: IGetVersionResponse = {
statusCode: 200,
type: 'getVersion',
version: {
appName,
semVer,
type: 'lambda',
lambdaArn: 'arn:aws:lambda:us-east-1:123456789012:function:my-function:my-alias',
},
};

const parentDeleteRequest: IDeleteVersionRequest = {
appName,
semVer,
type: 'deleteVersion',
requestFromChildAccount: true,
};

lambdaClient
.onAnyCommand()
.rejects()
.on(lambda.InvokeCommand, {
FunctionName: config.parentDeployerLambdaARN,
Qualifier: 'currentVersion',
Payload: Buffer.from(JSON.stringify(parentGetRequest)),
})
.resolves({ Payload: Buffer.from(JSON.stringify(parentGetResponse)) })
.on(lambda.InvokeCommand, {
FunctionName: config.parentDeployerLambdaARN,
Qualifier: 'currentVersion',
Payload: Buffer.from(JSON.stringify(parentDeleteRequest)),
})
.resolves({ Payload: Buffer.from(JSON.stringify({ statusCode: 200 })) })
.on(lambda.GetAliasCommand, {
FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:my-function',
Name: 'my-alias',
})
.resolves({
FunctionVersion: '1',
})
.on(lambda.DeleteAliasCommand, {
FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:my-function',
Name: 'my-alias',
})
.resolves({})
.on(lambda.DeleteFunctionCommand, {
FunctionName: 'arn:aws:lambda:us-east-1:123456789012:function:my-function',
Qualifier: '1',
})
.resolves({});
s3Client.onAnyCommand().rejects();
apigwyClient.onAnyCommand().rejects();

const request: IDeleteVersionRequest = {
appName,
semVer,
type: 'deleteVersion',
};

const response = await handler(request, { awsRequestId: '123' } as lambdaTypes.Context);
expect(response).toBeDefined();
expect(response.statusCode).toBeDefined();
expect(response.statusCode).toEqual(200);
expect(lambdaClient.calls().length).toEqual(5);
expect(apigwyClient.calls().length).toEqual(0);
expect(s3Client.calls().length).toEqual(0);
});
});

describe('deleteVersion - Parent Account', () => {
it('200s and cleans up everything except Lambda', async () => {
const appName = 'newapp';
const semVer = '0.0.0';
const fakeIntegrationID = 'integration123';
const fakeRoute1ID = 'route123';
const fakeRoute2ID = 'route456';

lambdaClient.onAnyCommand().rejects();
s3Client
.onAnyCommand()
.rejects()
// Mock S3 get for staging bucket - return one file name
.on(s3.ListObjectsV2Command, {
Bucket: config.filestore.destinationBucket,
Prefix: `${pathPrefix}${appName}/${semVer}/`,
})
.resolves({
IsTruncated: true,
NextContinuationToken: 'nothing-to-see-here-yet',
})
.on(s3.ListObjectsV2Command, {
ContinuationToken: 'nothing-to-see-here-yet',
Bucket: config.filestore.destinationBucket,
Prefix: `${pathPrefix}${appName}/${semVer}/`,
})
.resolves({
IsTruncated: false,
Contents: [{ Key: `${pathPrefix}${appName}/${semVer}/index.html` }],
})
.on(s3.DeleteObjectsCommand, {
Bucket: config.filestore.destinationBucket,
Delete: {
Objects: [
{
Key: `${pathPrefix}${appName}/${semVer}/index.html`,
},
],
},
})
.resolves({});
apigwyClient
.onAnyCommand()
.rejects()
.on(apigwy.DeleteIntegrationCommand, {
ApiId: config.apigwy.apiId,
IntegrationId: fakeIntegrationID,
})
.resolves({})
.on(apigwy.DeleteRouteCommand, {
ApiId: config.apigwy.apiId,
RouteId: fakeRoute1ID,
})
.resolves({})
.on(apigwy.DeleteRouteCommand, {
ApiId: config.apigwy.apiId,
RouteId: fakeRoute2ID,
})
.resolves({});

const version = new Version({
AppName: appName,
SemVer: semVer,
DefaultFile: '',
IntegrationID: fakeIntegrationID,
RouteIDAppVersion: fakeRoute1ID,
RouteIDAppVersionSplat: fakeRoute2ID,
// Note: Pending is reported as "does not exist"
// So don't set this to pending or the test will fail
Status: 'routed',
Type: 'lambda',
LambdaARN: 'arn:aws:lambda:us-east-1:123456789012:function:my-function:my-alias',
});
await version.Save(dbManager);

const request: IDeleteVersionRequest = {
appName,
semVer,
type: 'deleteVersion',
requestFromChildAccount: true,
};

const response = await handler(request, { awsRequestId: '123' } as lambdaTypes.Context);
expect(response).toBeDefined();
expect(response.statusCode).toBeDefined();
expect(response.statusCode).toEqual(200);
expect(lambdaClient.calls().length).toEqual(0);
expect(apigwyClient.calls().length).toEqual(3);
expect(s3Client.calls().length).toEqual(3);

// Confirm the version record is deleted
const noRecord = await Version.LoadVersion({
dbManager,
key: {
AppName: appName,
SemVer: semVer,
},
});
expect(noRecord).toBeUndefined();
});
});
});
Loading

0 comments on commit f350bb6

Please sign in to comment.