Skip to content

Commit

Permalink
chore: migrate AWS SDK for JavaScript v2 APIs to v3
Browse files Browse the repository at this point in the history
  • Loading branch information
trivikr committed May 24, 2024
1 parent 4fdb770 commit a051b77
Show file tree
Hide file tree
Showing 7 changed files with 2,010 additions and 2,334 deletions.
4,197 changes: 1,927 additions & 2,270 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
"webpack-cli": "^4.9.2"
},
"dependencies": {
"@aws-sdk/client-cloudformation": "^3.583.0",
"@aws-sdk/client-cognito-identity-provider": "^3.583.0",
"@aws-sdk/client-lambda": "^3.583.0",
"@aws-sdk/client-s3": "^3.583.0",
"@tsconfig/node20": "^20.1.2",
"adm-zip": "^0.5.10",
"aws-jwt-verify": "^2.1.3",
"aws-sdk": "^2.1571.0",
"cookie": "^0.4.1",
"ncp": "^2.0.0",
"s3-spa-upload": "^2.1.5"
Expand Down
9 changes: 4 additions & 5 deletions src/cfn-custom-resources/client-secret-retrieval/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: MIT-0

import { CloudFormationCustomResourceHandler } from "aws-lambda";
import CognitoIdentityServiceProvider from "aws-sdk/clients/cognitoidentityserviceprovider";
import { CognitoIdentityProvider, DescribeUserPoolClientCommandInput } from "@aws-sdk/client-cognito-identity-provider";
import { sendCfnResponse, Status } from "./cfn-response";

async function retrieveClientSecret(
Expand All @@ -17,17 +17,16 @@ async function retrieveClientSecret(
}
const userPoolId = userPoolArn.split("/")[1];
const userPoolRegion = userPoolArn.split(":")[3];
const cognitoClient = new CognitoIdentityServiceProvider({
const cognitoClient = new CognitoIdentityProvider({
region: userPoolRegion,
});
const input: CognitoIdentityServiceProvider.Types.DescribeUserPoolClientRequest =
const input: DescribeUserPoolClientCommandInput =
{
UserPoolId: userPoolId,
ClientId: clientId,
};
const { UserPoolClient } = await cognitoClient
.describeUserPoolClient(input)
.promise();
.describeUserPoolClient(input);
if (!UserPoolClient?.ClientSecret) {
throw new Error(
`User Pool client ${clientId} is not set up with a client secret`
Expand Down
15 changes: 7 additions & 8 deletions src/cfn-custom-resources/lambda-code-update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
CloudFormationCustomResourceDeleteEvent,
CloudFormationCustomResourceUpdateEvent,
} from "aws-lambda";
import Lambda from "aws-sdk/clients/lambda";
import { Lambda } from "@aws-sdk/client-lambda";
import Zip from "adm-zip";
import { writeFileSync, mkdtempSync } from "fs";
import { resolve } from "path";
Expand All @@ -27,15 +27,16 @@ async function updateLambdaCode(
`Adding configuration to Lambda function ${lambdaFunction}:\n${stringifiedConfig}`
);
const region = lambdaFunction.split(":")[3];
const lambdaClient = new Lambda({ region });
const lambdaClient = new Lambda({
region,
});
// Parse the JSON to ensure it's validity (and avoid ugly errors at runtime)
const config = JSON.parse(stringifiedConfig);
// Fetch and extract Lambda zip contents to temporary folder, add configuration.json, and rezip
const { Code } = await lambdaClient
.getFunction({
FunctionName: lambdaFunction,
})
.promise();
});
const data = await fetch(Code!.Location!);
const lambdaZip = new Zip(data);
console.log(
Expand All @@ -61,16 +62,14 @@ async function updateLambdaCode(
FunctionName: lambdaFunction,
ZipFile: newLambdaZip.toBuffer(),
Publish: true,
})
.promise();
});
console.log({ CodeSha256, Version, FunctionArn });
let attempts = 0;
while (++attempts <= 30) {
const { State } = await lambdaClient
.getFunctionConfiguration({
FunctionName: FunctionArn!,
})
.promise();
});
if (!State || State === "Pending") {
console.log(
`Waiting for updated Lambda function to become Active, is: ${State} (attempts: ${attempts})`
Expand Down
92 changes: 54 additions & 38 deletions src/cfn-custom-resources/us-east-1-lambda-stack/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,29 @@ import {
CloudFormationCustomResourceDeleteEvent,
CloudFormationCustomResourceUpdateEvent,
} from "aws-lambda";
import CloudFormation from "aws-sdk/clients/cloudformation";

import {
CloudFormation,
Stack,
waitUntilChangeSetCreateComplete,
waitUntilStackCreateComplete,
waitUntilStackUpdateComplete,
} from "@aws-sdk/client-cloudformation";

import { Lambda } from "@aws-sdk/client-lambda";
import { ListObjectsV2CommandInput, S3 } from "@aws-sdk/client-s3";
import S3 from "aws-sdk/clients/s3";
import Lambda from "aws-sdk/clients/lambda";
import { sendCfnResponse, Status } from "./cfn-response";
import { fetch } from "./https";

const CFN_CLIENT = new CloudFormation();
const CFN_CLIENT_US_EAST_1 = new CloudFormation({ region: "us-east-1" });
const CFN_CLIENT_US_EAST_1 = new CloudFormation({
region: "us-east-1",
});
const LAMBDA_CLIENT = new Lambda();
const S3_CLIENT_US_EAST_1 = new S3({ region: "us-east-1" });
const S3_CLIENT_US_EAST_1 = new S3({
region: "us-east-1",
});

interface CfnTemplateBase {
Resources: {
Expand Down Expand Up @@ -124,7 +137,6 @@ async function ensureUsEast1LambdaStack(props: {
const { Stacks: stacks } = await CFN_CLIENT_US_EAST_1.describeStacks({
StackName: props.stackName,
})
.promise()
.catch(() => ({ Stacks: undefined }));
if (stacks?.length) {
console.log("Deleting us-east-1 stack ...");
Expand All @@ -136,7 +148,7 @@ async function ensureUsEast1LambdaStack(props: {
}
await CFN_CLIENT_US_EAST_1.deleteStack({
StackName: props.stackName,
}).promise();
});
console.log("us-east-1 stack deleted");
} else {
console.log("us-east-1 stack already deleted");
Expand All @@ -154,7 +166,7 @@ async function ensureUsEast1LambdaStack(props: {
const { TemplateBody: originalTemplate } = await CFN_CLIENT.getTemplate({
StackName: props.stackId,
TemplateStage: "Processed",
}).promise();
});
if (!originalTemplate)
throw new Error(
`Failed to get template for stack ${props.stackName} (${props.stackId})`
Expand Down Expand Up @@ -239,18 +251,20 @@ async function ensureLambdaUsEast1Stack(props: {
TemplateBody: props.newTemplate,
ChangeSetType: "UPDATE",
ResourceTypes: ["AWS::Lambda::Function"],
}).promise();
});
if (!changeSetArn)
throw new Error(
"Failed to create change set for lambda handlers deployment"
);
console.log(
"Waiting for completion of change set for adding lambda functions to us-east-1 stack ..."
);
await CFN_CLIENT_US_EAST_1.waitFor("changeSetCreateComplete", {
await waitUntilChangeSetCreateComplete({
client: CFN_CLIENT_US_EAST_1,
maxWaitTime: 200,
}, {
ChangeSetName: changeSetArn,
})
.promise()
.catch((err) =>
console.log(
`Caught exception while waiting for change set create completion: ${err}`
Expand All @@ -259,7 +273,7 @@ async function ensureLambdaUsEast1Stack(props: {
const { Status: status, StatusReason: reason } =
await CFN_CLIENT_US_EAST_1.describeChangeSet({
ChangeSetName: changeSetArn,
}).promise();
});
if (status === "FAILED") {
// The only reason we'll allow a FAILED change set is if there were no changes
if (!reason?.includes("didn't contain changes")) {
Expand All @@ -268,13 +282,13 @@ async function ensureLambdaUsEast1Stack(props: {
// No changes to make to the Lambda@Edge functions, clean up the change set then
await CFN_CLIENT_US_EAST_1.deleteChangeSet({
ChangeSetName: changeSetArn,
}).promise();
});

// Need to get the outputs (Lambda ARNs) from the existing stack then
const { Stacks: existingStacks } =
await CFN_CLIENT_US_EAST_1.describeStacks({
StackName: props.stackName,
}).promise();
});
const existingOutputs = extractOutputsFromStackResponse(existingStacks);
console.log(
`us-east-1 stack unchanged. Stack outputs: ${JSON.stringify(
Expand All @@ -293,16 +307,16 @@ async function ensureLambdaUsEast1Stack(props: {
);
await CFN_CLIENT_US_EAST_1.executeChangeSet({
ChangeSetName: changeSetArn,
}).promise();
});
console.log(
"Waiting for completion of execute change set for adding lambda functions to us-east-1 stack ..."
);
const { Stacks: updatedStacks } = await CFN_CLIENT_US_EAST_1.waitFor(
"stackUpdateComplete",
{
StackName: props.stackName,
}
).promise();
const { Stacks: updatedStacks } = await waitUntilStackUpdateComplete({
client: CFN_CLIENT_US_EAST_1,
maxWaitTime: 200,
}, {
StackName: props.stackName,
});
const outputs = extractOutputsFromStackResponse(updatedStacks);
console.log(
`us-east-1 stack succesfully updated. Stack outputs: ${JSON.stringify(
Expand All @@ -314,7 +328,7 @@ async function ensureLambdaUsEast1Stack(props: {
return outputs as { [key: string]: string };
}

function extractOutputsFromStackResponse(stacks?: CloudFormation.Stack[]) {
function extractOutputsFromStackResponse(stacks?: Stack[]) {
// find the ARNs for all Lambda functions, which will be output from this custom resource

const outputs = LAMBDA_NAMES.reduce((acc, lambdaName) => {
Expand All @@ -341,7 +355,6 @@ async function ensureDeploymentUsEast1Stack(props: {
const { Stacks: usEast1Stacks } = await CFN_CLIENT_US_EAST_1.describeStacks({
StackName: props.stackName,
})
.promise()
.catch(() => ({ Stacks: undefined }));
if (usEast1Stacks?.length) {
const deploymentBucket = usEast1Stacks[0].Outputs?.find(
Expand All @@ -359,7 +372,7 @@ async function ensureDeploymentUsEast1Stack(props: {
console.log("Getting CFN stack tags ...");
const { Stacks: mainRegionStacks } = await CFN_CLIENT.describeStacks({
StackName: props.stackId,
}).promise();
});
if (!mainRegionStacks?.length) {
throw new Error(
`Failed to describe stack ${props.stackName} (${props.stackId})`
Expand All @@ -375,24 +388,27 @@ async function ensureDeploymentUsEast1Stack(props: {
ChangeSetType: "CREATE",
ResourceTypes: ["AWS::S3::Bucket"],
Tags: mainRegionStacks[0].Tags,
}).promise();
});
if (!changeSetArn)
throw new Error("Failed to create change set for bucket deployment");
console.log("Waiting for change set create complete for us-east-1 stack ...");
await CFN_CLIENT_US_EAST_1.waitFor("changeSetCreateComplete", {
await waitUntilChangeSetCreateComplete({
client: CFN_CLIENT_US_EAST_1,
maxWaitTime: 200,
}, {
ChangeSetName: changeSetArn,
}).promise();
});
console.log("Executing change set for us-east-1 stack ...");
await CFN_CLIENT_US_EAST_1.executeChangeSet({
ChangeSetName: changeSetArn,
}).promise();
});
console.log("Waiting for creation of us-east-1 stack ...");
const { Stacks: createdStacks } = await CFN_CLIENT_US_EAST_1.waitFor(
"stackCreateComplete",
{
StackName: props.stackName,
}
).promise();
const { Stacks: createdStacks } = await waitUntilStackCreateComplete({
client: CFN_CLIENT_US_EAST_1,
maxWaitTime: 200,
}, {
StackName: props.stackName,
});
const deploymentBucket = createdStacks?.[0].Outputs?.find(
(output) => output.OutputKey === "DeploymentBucket"
)?.OutputValue;
Expand All @@ -409,7 +425,7 @@ async function copyLambdaCodeToUsEast1(props: {
console.log(`Copying Lambda code: ${JSON.stringify(props, null, 2)}`);
const { Code } = await LAMBDA_CLIENT.getFunction({
FunctionName: props.lambdaArn,
}).promise();
});
console.log(
`Downloading lambda code for ${props.lambdaArn} from ${Code!.Location!}`
);
Expand All @@ -418,18 +434,18 @@ async function copyLambdaCodeToUsEast1(props: {
Bucket: props.toBucket,
Key: props.key,
Body: data,
}).promise();
});
return props;
}

async function emptyBucket(props: { bucket: string }) {
const params: S3.ListObjectsV2Request = {
const params: ListObjectsV2CommandInput = {
Bucket: props.bucket,
};
do {
console.log(`Listing objects in bucket ${props.bucket} ...`);
const { Contents: s3objects, NextContinuationToken } =
await S3_CLIENT_US_EAST_1.listObjectsV2(params).promise();
await S3_CLIENT_US_EAST_1.listObjectsV2(params);

if (!s3objects?.length) break;
console.log(`Deleting ${s3objects.length} S3 objects ...`);
Expand All @@ -439,7 +455,7 @@ async function emptyBucket(props: { bucket: string }) {
Delete: {
Objects: s3objects.filter((o) => !!o.Key).map((o) => ({ Key: o.Key! })),
},
}).promise();
});

if (errors?.length) {
console.log("Failed to delete objects:", JSON.stringify(errors));
Expand Down
19 changes: 11 additions & 8 deletions src/cfn-custom-resources/user-pool-client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import {
CloudFormationCustomResourceHandler,
CloudFormationCustomResourceUpdateEvent,
} from "aws-lambda";
import CognitoIdentityServiceProvider from "aws-sdk/clients/cognitoidentityserviceprovider";
import {
CognitoIdentityProvider,
UpdateUserPoolClientCommandInput,
UserPoolClientType,
} from "@aws-sdk/client-cognito-identity-provider";
import { sendCfnResponse, Status } from "./cfn-response";

const CUSTOM_RESOURCE_CURRENT_VERSION_NAME = "UpdatedUserPoolClientV2";
Expand All @@ -22,7 +26,7 @@ const SENTINEL_DOMAIN = "example.com";
async function getUserPoolClient(props: Props) {
const userPoolId = props.UserPoolArn.split("/")[1];
const userPoolRegion = props.UserPoolArn.split(":")[3];
const cognitoClient = new CognitoIdentityServiceProvider({
const cognitoClient = new CognitoIdentityProvider({
region: userPoolRegion,
});
const input = {
Expand All @@ -31,8 +35,7 @@ async function getUserPoolClient(props: Props) {
};
console.debug("Describing User Pool Client", JSON.stringify(input, null, 4));
const { UserPoolClient } = await cognitoClient
.describeUserPoolClient(input)
.promise();
.describeUserPoolClient(input);
if (!UserPoolClient) {
throw new Error("User Pool Client not found!");
}
Expand All @@ -43,11 +46,11 @@ async function updateUserPoolClient(
props: Props,
redirectUrisSignIn: string[],
redirectUrisSignOut: string[],
existingUserPoolClient: CognitoIdentityServiceProvider.UserPoolClientType
existingUserPoolClient: UserPoolClientType
) {
const userPoolId = props.UserPoolArn.split("/")[1];
const userPoolRegion = props.UserPoolArn.split(":")[3];
const cognitoClient = new CognitoIdentityServiceProvider({
const cognitoClient = new CognitoIdentityProvider({
region: userPoolRegion,
});

Expand Down Expand Up @@ -81,7 +84,7 @@ async function updateUserPoolClient(
delete existingFields.LastModifiedDate;
delete existingFields.ClientSecret;

const input: CognitoIdentityServiceProvider.Types.UpdateUserPoolClientRequest =
const input: UpdateUserPoolClientCommandInput =
{
...existingFields,
AllowedOAuthFlows,
Expand All @@ -93,7 +96,7 @@ async function updateUserPoolClient(
LogoutURLs,
};
console.debug("Updating User Pool Client", JSON.stringify(input, null, 4));
await cognitoClient.updateUserPoolClient(input).promise();
await cognitoClient.updateUserPoolClient(input);
}

async function undoPriorUpdate(
Expand Down
Loading

0 comments on commit a051b77

Please sign in to comment.