Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with AWS-CDK >= 2.167.0 #96

Merged
merged 4 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ $ awslocal sns list-topics

## Change Log

* 2.19.0: Add support for aws-cdk versions >= `2.167.0`
* 2.18.1: Throw better exception if `aws-cdk` not found
* 2.18.0: Add support for AWS_ENDPOINT_URL, USE_SSL, and BUCKET_MARKER_LOCAL configurations
* 2.17.0: Fix IPv4 fallback check to prevent IPv6 connection issue with `localhost` on macOS
Expand Down
86 changes: 63 additions & 23 deletions bin/cdklocal
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const getLocalHost = async () => {
// Issue: https://github.com/localstack/aws-cdk-local/issues/78
if (hostname === "localhost") {
try {
const options = { host: hostname, port: EDGE_PORT };
const options = {host: hostname, port: EDGE_PORT};
await checkTCPConnection(options);
} catch (e) {
hostname = "127.0.0.1";
Expand Down Expand Up @@ -131,14 +131,15 @@ const getTemplateBody = (params) => {
// small import util function

const modulePrefix = "aws-cdk/node_modules";
const importLib = function importLib (libPath) {
const importLib = function importLib(libPath) {
try {
return require(path.join(modulePrefix, libPath));
} catch (exc) {
return require(path.join(libPath));
}
};

// this isn't doing anything for current versions (e.g. 2.167.1)
const setSdkOptions = async (options, setHttpOptions) => {
if (!useLocal(options)) {
return;
Expand All @@ -155,10 +156,13 @@ const setSdkOptions = async (options, setHttpOptions) => {
const patchProviderCredentials = (provider) => {
const origConstr = provider.SdkProvider.withAwsCliCompatibleDefaults;
provider.SdkProvider.withAwsCliCompatibleDefaults = async (options = {}) => {
await setSdkOptions(options, true);
const localEndpoint = await getLocalEndpoint();
await setSdkOptions(options, true); // legacy
const result = await origConstr(options);
result.sdkOptions = result.sdkOptions || {};
await setSdkOptions(result.sdkOptions);
result.sdkOptions = result.sdkOptions || {}; // legacy
await setSdkOptions(result.sdkOptions); // legacy
result.requestHandler.endpoint = localEndpoint;
result.requestHandler.forcePathStyle = true;
return result;
};

Expand All @@ -172,7 +176,7 @@ const patchCdkToolkit = (CdkToolkit) => {
const CdkToolkitClass = CdkToolkit.ToolkitInfo || CdkToolkit;
getMethods(CdkToolkitClass.prototype).forEach((meth) => {
const original = CdkToolkitClass.prototype[meth];
CdkToolkitClass.prototype[meth] = async function methFunc (...args) {
CdkToolkitClass.prototype[meth] = async function methFunc(...args) {
await setSdkOptions(this.props.sdkProvider.sdkOptions);
return original.bind(this).apply(this, args);
};
Expand All @@ -181,14 +185,14 @@ const patchCdkToolkit = (CdkToolkit) => {

const patchCurrentAccount = (SDK) => {
const currentAccountOrig = SDK.prototype.currentAccount;
SDK.prototype.currentAccount = async function currentAccount () {
SDK.prototype.currentAccount = async function currentAccount() {
const {config} = this;
await setSdkOptions(config);
return currentAccountOrig.bind(this)();
};

const forceCredentialRetrievalOrig = SDK.prototype.forceCredentialRetrieval;
SDK.prototype.forceCredentialRetrieval = function forceCredentialRetrieval () {
SDK.prototype.forceCredentialRetrieval = function forceCredentialRetrieval() {
if (!this._credentials.getPromise) {
this._credentials.getPromise = () => this._credentials;
}
Expand All @@ -201,9 +205,9 @@ const patchToolkitInfo = (ToolkitInfo) => {
BUCKET_NAME_OUTPUT, BUCKET_DOMAIN_NAME_OUTPUT
} = require("aws-cdk/lib/api/bootstrap/bootstrap-props");

const setBucketUrl = function setBucketUrl (object) {
const setBucketUrl = function setBucketUrl(object) {
Object.defineProperty(object, "bucketUrl", {
async get () {
async get() {
const bucket = this.requireOutput(BUCKET_NAME_OUTPUT);
const domain = this.requireOutput(BUCKET_DOMAIN_NAME_OUTPUT) || await getLocalHost();
return `https://${domain.replace(`${bucket}.`, "")}:${EDGE_PORT}/${bucket}`;
Expand Down Expand Up @@ -236,19 +240,14 @@ const patchLambdaMounting = (CdkToolkit) => {
// modify asset paths to enable local Lambda code mounting

const lookupLambdaForAsset = (template, paramName) => {
const result = Object.keys(template.Resources).
map((key) => template.Resources[key]).
filter((res) => res.Type === "AWS::Lambda::Function").
filter((res) => JSON.stringify(res.Properties.Code.S3Key).includes(paramName));
const result = Object.keys(template.Resources).map((key) => template.Resources[key]).filter((res) => res.Type === "AWS::Lambda::Function").filter((res) => JSON.stringify(res.Properties.Code.S3Key).includes(paramName));
const props = result[0].Properties;
const funcName = props.FunctionName;
if (funcName) {
return funcName;
}
const attributes = ["Handler", "Runtime", "Description", "Timeout", "MemorySize", "Environment"];
const valueToHash = attributes.map((attr) => props[attr]).
map((val) => typeof val === "object" ? JSON.stringify(diff.canonicalize(val)) : val ? val : "").
join("|");
const valueToHash = attributes.map((attr) => props[attr]).map((val) => typeof val === "object" ? JSON.stringify(diff.canonicalize(val)) : val ? val : "").join("|");
return md5(valueToHash);
};

Expand Down Expand Up @@ -292,13 +291,13 @@ const patchLambdaMounting = (CdkToolkit) => {
// symlink local Lambda assets if "cdklocal deploy" is called with LAMBDA_MOUNT_CODE=1

const deployStackOrig = deployStackMod.deployStack;
deployStackMod.deployStack = function deployStack (options) {
deployStackMod.deployStack = function deployStack(options) {
options.sdk.cloudFormationOrig = options.sdk.cloudFormationOrig || options.sdk.cloudFormation;
const state = {};
options.sdk.cloudFormation = () => state.instance;
const cfn = state.instance = options.sdk.cloudFormationOrig();
cfn.createChangeSetOrig = cfn.createChangeSetOrig || cfn.createChangeSet;
const createChangeSetAsync = async function createChangeSetAsync (params) {
const createChangeSetAsync = async function createChangeSetAsync(params) {
if (LAMBDA_MOUNT_CODE) {
const template = deserializeStructure(await getTemplateBody(params));
symlinkLambdaAssets(template, params.Parameters);
Expand All @@ -315,7 +314,7 @@ const patchLambdaMounting = (CdkToolkit) => {
const {FileAssetHandler} = importLib("cdk-assets/lib/private/handlers/files");

const handlerPublish = FileAssetHandler.prototype.publish;
FileAssetHandler.prototype.publish = function publish () {
FileAssetHandler.prototype.publish = function publish() {
if (LAMBDA_MOUNT_CODE && this.asset.destination && this.asset.source) {
if (this.asset.source.packaging === "zip") {
// skip uploading this asset - should get mounted via `__file__` into the Lambda container later on
Expand All @@ -328,7 +327,7 @@ const patchLambdaMounting = (CdkToolkit) => {
// symlink local Lambda assets if "cdklocal synth" is called with LAMBDA_MOUNT_CODE=1

const assemblyOrig = CdkToolkit.prototype.assembly;
CdkToolkit.prototype.assembly = async function assembly () {
CdkToolkit.prototype.assembly = async function assembly() {
const result = await assemblyOrig.bind(this)();
if (LAMBDA_MOUNT_CODE) {
result.assembly.artifacts.forEach((art) => {
Expand All @@ -351,7 +350,48 @@ const isEsbuildBundle = () => {
}
};


const patchSdk = (SDK, localEndpoint) => {
getMethods(SDK.prototype).forEach((methodName) => {
if (typeof SDK.prototype[methodName] === 'function') {
const original = SDK.prototype[methodName];
SDK.prototype[methodName] = function methFunc(...args) {
this.config.endpoint = localEndpoint;
this.config.forcePathStyle = true;
return original.apply(this, args);
};
}
});
};

let sdkOverwritten = false;
const patchSdkProvider = (provider, SDK) => {
getMethods(provider.SdkProvider.prototype).forEach((methodName) => {
if (typeof provider.SdkProvider.prototype[methodName] === 'function') {
const original = provider.SdkProvider.prototype[methodName];
provider.SdkProvider.prototype[methodName] = async function methFunc(...args) {
const localEndpoint = await getLocalEndpoint();

if (!sdkOverwritten) {
// the goal is to support `SdkProvider.withAssumedRole`
// since it instantiates a different client (i.e. not from the SDK class)
this.requestHandler.endpoint = localEndpoint;
this.requestHandler.forcePathStyle = true;
// patch SDK class methods (mostly clients) to make sure the config that is created in the constructor
// is updated with the correct configuration
patchSdk(SDK, localEndpoint);
sdkOverwritten = true;
}
return await original.apply(this, args);
};
}
}
);
};

const applyPatches = (provider, CdkToolkit, SDK, ToolkitInfo, patchAssets = true) => {
patchSdkProvider(provider, SDK);
// TODO: a lot of the patches are not really needed for newer versions
patchProviderCredentials(provider);
patchCdkToolkit(CdkToolkit);
patchCurrentAccount(SDK);
Expand All @@ -366,7 +406,7 @@ const patchPre_2_14 = () => {
var provider = null;
try {
provider = require("aws-cdk/lib/api/aws-auth");
} catch(e) {
} catch (e) {
if (e.code == "MODULE_NOT_FOUND") {
console.log(e);
console.error("`aws-cdk` module NOT found! Have you tried adding it to your `NODE_PATH`?");
Expand All @@ -386,7 +426,7 @@ const patchPost_2_14 = () => {
var lib = null;
try {
lib = require("aws-cdk/lib");
} catch(e) {
} catch (e) {
if (e.code == "MODULE_NOT_FOUND") {
console.log(e);
console.log("`aws-cdk` module NOT found! Have you tried to add it to your `NODE_PATH`?");
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"name": "aws-cdk-local",
"description": "CDK Toolkit for use with LocalStack",
"version": "2.18.1",
"version": "2.19.0",
"bin": {
"cdklocal": "bin/cdklocal"
},
"scripts": {
"lint": "eslint bin/cdklocal"
"lint": "eslint bin/cdklocal",
"format": "eslint --fix bin/cdklocal"
},
"author": {
"name": "LocalStack",
Expand Down
Loading