Skip to content

Major: rework environment configuration of CDK #109

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

Merged
merged 14 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from 12 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
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ on:
branches:
- main

# Only one pull-request triggered run should be executed at a time
# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
build-and-lint:
runs-on: ubuntu-latest
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/integrations.go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ on:
upstream-version:
description: Upstream aws-cdk version to use in tests
required: false

# Only one pull-request triggered run should be executed at a time
# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
Expand All @@ -35,6 +42,7 @@ jobs:
env:
AWS_REGION: ${{ matrix.region }}
AWS_DEFAULT_REGION: ${{ matrix.region }}
AWS_ENVAR_ALLOWLIST: AWS_REGION,AWS_DEFAULT_REGION

steps:
- uses: actions/checkout@v2
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/integrations.node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ on:
upstream-version:
description: Upstream aws-cdk version to use in tests
required: false

# Only one pull-request triggered run should be executed at a time
# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
Expand All @@ -33,6 +40,7 @@ jobs:
env:
AWS_REGION: ${{ matrix.region }}
AWS_DEFAULT_REGION: ${{ matrix.region }}
AWS_ENVAR_ALLOWLIST: AWS_REGION,AWS_DEFAULT_REGION

steps:
- uses: actions/checkout@v2
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/integrations.python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ on:
upstream-version:
description: Upstream aws-cdk version to use in tests
required: false

# Only one pull-request triggered run should be executed at a time
# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
Expand All @@ -33,6 +40,7 @@ jobs:
env:
AWS_REGION: ${{ matrix.region }}
AWS_DEFAULT_REGION: ${{ matrix.region }}
AWS_ENVAR_ALLOWLIST: AWS_REGION,AWS_DEFAULT_REGION

steps:
- uses: actions/checkout@v2
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ on:
type: boolean
default: false

# Only one pull-request triggered run should be executed at a time
# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

env:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Unit tests

on:
push:
branches:
- main
pull_request:
workflow_dispatch:
inputs:
node-version:
required: false
default: "22.x"

# Only one pull-request triggered run should be executed at a time
# (head_ref is only set for PR events, otherwise fallback to run_id which differs for every run).
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: could use the newest version (v4) if there's no specific reason to stay on a lower one


- name: Use Node.js ${{ inputs.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ inputs.node-version }}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: same as above, newest version for that action is v4


- name: Install project
run: |
npm install

- name: Run tests
run: |
npm run test


11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,22 @@ To resolve this, set the `NODE_PATH` variable pointing to your AWS CDK's `node_m
$ export NODE_PATH=$NODE_PATH:/opt/homebrew/Cellar/aws-cdk/<CDK_VERSION>/libexec/lib/node_modules
```

## Version support

`cdklocal` supports all installed versions of the node `aws-cdk` package, however some complications are present for `aws-cdk >= 2.177.0`.

For these CDK versions, we remove AWS configuration environment variables like `AWS_PROFILE` from the shell environment before invoking the `cdk` command, and explicitly set `AWS_ENDPOINT_URL` and `AWS_ENDPOINT_URL_S3` to target LocalStack.

1. We do this because other environment variables may lead to a conflicting set of configuration options, where the wrong region is used to target LocalStack, or `cdklocal` tries to deploy into upstream AWS by mistake. If individual configuration variables are needed for the deploy process (e.g. `AwS_REGION`) these configuration variables can be propagated to the `cdk` command by configuring `AWS_ENVAR_ALLOWLIST`, for example: `AWS_ENVAR_ALLOWLIST=AWS_REGION,AWS_DEFAULT_REGION AWS_REGION=eu-central-1 cdklocal ...`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. We do this because other environment variables may lead to a conflicting set of configuration options, where the wrong region is used to target LocalStack, or `cdklocal` tries to deploy into upstream AWS by mistake. If individual configuration variables are needed for the deploy process (e.g. `AwS_REGION`) these configuration variables can be propagated to the `cdk` command by configuring `AWS_ENVAR_ALLOWLIST`, for example: `AWS_ENVAR_ALLOWLIST=AWS_REGION,AWS_DEFAULT_REGION AWS_REGION=eu-central-1 cdklocal ...`.
1. We do this because other environment variables may lead to a conflicting set of configuration options, where the wrong region is used to target LocalStack, or `cdklocal` tries to deploy into upstream AWS by mistake. If individual configuration variables are needed for the deploy process (e.g. `AWS_REGION`) these configuration variables can be propagated to the `cdk` command by configuring `AWS_ENVAR_ALLOWLIST`, for example: `AWS_ENVAR_ALLOWLIST=AWS_REGION,AWS_DEFAULT_REGION AWS_REGION=eu-central-1 cdklocal ...`.

also: Isn't the AWS_DEFAULT_REGION in the allowlist example a bit confusing since (contrary to AWS_REGION) we don't provide it in the following env arguments?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't understand - what do you mean by "don't provide it in the following env arguments"? Do you mean in the README.md Configurations section? Or in the code? We do allow overriding the AWS_DEFAULT_REGION in the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean

AWS_ENVAR_ALLOWLIST=AWS_REGION,AWS_DEFAULT_REGION AWS_REGION=eu-central-1 cdklocal ...
vs
AWS_ENVAR_ALLOWLIST=AWS_REGION,AWS_DEFAULT_REGION AWS_DEFAULT_REGION=eu-central-1 AWS_REGION=eu-central-1 cdklocal ...

2. If you are manually setting `AWS_ENDPOINT_URL`, the new value will continue to be read from the environment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is confusing me a bit as it seems to conflict with the PR description:

Raise an exception if the user specifies AWS_ENDPOINT_URL without AWS_ENDPOINT_URL_S3 as we cannot automatically determine a configuration that will work. We need to ensure that .s3. is in the domain for S3 requests. If the user customises the endpoint then it may not support subdomains (so we could not just inject .s3. in their URL, and we cannot use the same URL as our S3 detection won't work.


## Configurations

The following environment variables can be configured:

* `AWS_ENDPOINT_URL`: The endpoint URL to connect to (combination of `USE_SSL`/`LOCALSTACK_HOSTNAME`/`EDGE_PORT` below)
* `AWS_ENDPOINT_URL_S3`: The endpoint URL to connect to (combination of `USE_SSL`/`LOCALSTACK_HOSTNAME`/`EDGE_PORT` below) for S3 requests
* `AWS_ENVAR_ALLOWLIST`: Allow specific `AWS_*` environment variables to be used by the CDK
* `EDGE_PORT` (deprecated): Port under which LocalStack edge service is accessible (default: `4566`)
* `LOCALSTACK_HOSTNAME` (deprecated): Target host under which LocalStack edge service is accessible (default: `localhost`)
* `USE_SSL` (deprecated): Whether to use SSL to connect to the LocalStack endpoint, i.e., connect via HTTPS.
Expand Down Expand Up @@ -78,6 +88,7 @@ $ awslocal sns list-topics

## Change Log

* 3.0.0: Sanitise environment of configuration environment variables before deployment
* 2.19.2: Fix SDK compatibility with aws-cdk versions >= 2.177.0
* 2.19.1: Fix SDK compatibility with older CDK versions; Fix patched bucket location in TemplateURL
* 2.19.0: Add support for aws-cdk versions >= `2.167.0`
Expand Down
109 changes: 109 additions & 0 deletions __tests__/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const { configureEnvironment, EnvironmentMisconfigurationError } = require("../src");

describe("configureEnvironment", () => {
test("empty environment", () => {
const env = {};
const allowListStr = "";
configureEnvironment(env, allowListStr);
expect(env).toEqual({
AWS_ACCESS_KEY_ID: "test",
AWS_SECRET_ACCESS_KEY: "test",
AWS_REGION: "us-east-1",
AWS_DEFAULT_REGION: "us-east-1",
AWS_ENDPOINT_URL: "http://localhost.localstack.cloud:4566",
AWS_ENDPOINT_URL_S3: "http://s3.localhost.localstack.cloud:4566",
});
});

test("custom endpoint url", () => {
const env = {
AWS_ENDPOINT_URL: "http://foo.bar:4567",
AWS_ENDPOINT_URL_S3: "http://foo.bar:4567",
};
const allowListStr = "";
configureEnvironment(env, allowListStr);
expect(env).toEqual({
AWS_ACCESS_KEY_ID: "test",
AWS_SECRET_ACCESS_KEY: "test",
AWS_REGION: "us-east-1",
AWS_DEFAULT_REGION: "us-east-1",
AWS_ENDPOINT_URL: "http://foo.bar:4567",
AWS_ENDPOINT_URL_S3: "http://foo.bar:4567",
});
});

test("custom endpoint url without specifying s3 url", () => {
const env = {
AWS_ENDPOINT_URL: "http://foo.bar:4567",
};
const allowListStr = "";
expect(() => configureEnvironment(env, allowListStr)).toThrow(EnvironmentMisconfigurationError);
});

test("strip extra configuration envars", () => {
const env = {
AWS_PROFILE: "my-profile",
};
const allowListStr = "";
configureEnvironment(env, allowListStr);
expect(env).toEqual({
AWS_ACCESS_KEY_ID: "test",
AWS_SECRET_ACCESS_KEY: "test",
AWS_REGION: "us-east-1",
AWS_DEFAULT_REGION: "us-east-1",
AWS_ENDPOINT_URL: "http://localhost.localstack.cloud:4566",
AWS_ENDPOINT_URL_S3: "http://s3.localhost.localstack.cloud:4566",
});
});

test("allowlist of profile", () => {
const env = {
AWS_PROFILE: "my-profile",
};
const allowListStr = "AWS_PROFILE";
configureEnvironment(env, allowListStr);
expect(env).toEqual({
AWS_ACCESS_KEY_ID: "test",
AWS_SECRET_ACCESS_KEY: "test",
AWS_PROFILE: "my-profile",
AWS_REGION: "us-east-1",
AWS_DEFAULT_REGION: "us-east-1",
AWS_ENDPOINT_URL: "http://localhost.localstack.cloud:4566",
AWS_ENDPOINT_URL_S3: "http://s3.localhost.localstack.cloud:4566",
});
});

test("credentials overriding", () => {
const env = {
AWS_ACCESS_KEY_ID: "something",
AWS_SECRET_ACCESS_KEY: "else",
};
const allowListStr = "AWS_PROFILE,AWS_SECRET_ACCESS_KEY,AWS_ACCESS_KEY_ID";
configureEnvironment(env, allowListStr);
expect(env).toEqual({
AWS_ACCESS_KEY_ID: "something",
AWS_SECRET_ACCESS_KEY: "else",
AWS_REGION: "us-east-1",
AWS_DEFAULT_REGION: "us-east-1",
AWS_ENDPOINT_URL: "http://localhost.localstack.cloud:4566",
AWS_ENDPOINT_URL_S3: "http://s3.localhost.localstack.cloud:4566",
});
});

test("region overriding", () => {
const env = {
AWS_REGION: "eu-central-1",
AWS_DEFAULT_REGION: "eu-central-1",
};
const allowListStr = "AWS_REGION,AWS_DEFAULT_REGION";
configureEnvironment(env, allowListStr);
expect(env).toEqual({
AWS_ACCESS_KEY_ID: "test",
AWS_SECRET_ACCESS_KEY: "test",
AWS_REGION: "eu-central-1",
AWS_DEFAULT_REGION: "eu-central-1",
AWS_ENDPOINT_URL: "http://localhost.localstack.cloud:4566",
AWS_ENDPOINT_URL_S3: "http://s3.localhost.localstack.cloud:4566",
});
});
});
17 changes: 4 additions & 13 deletions bin/cdklocal
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ const https = require("https");
const crypto = require("crypto");
const net = require('net');

// constants and custom config values
const { isEnvTrue, EDGE_PORT, PROTOCOL, configureEnvironment } = require("../src");

const isEnvTrue = (envName) => ["1", "true"].includes(process.env[envName]);
// constants and custom config values

const DEFAULT_EDGE_PORT = 4566;
const EDGE_PORT = process.env.EDGE_PORT || DEFAULT_EDGE_PORT;
const DEFAULT_HOSTNAME = "localhost";
const LAMBDA_MOUNT_CODE = isEnvTrue("LAMBDA_MOUNT_CODE");
const PROTOCOL = isEnvTrue("USE_SSL") ? "https" : "http";
const AWS_ENVAR_ALLOWLIST = process.env.AWS_ENVAR_ALLOWLIST || "";


//----------------
Expand Down Expand Up @@ -451,13 +449,6 @@ const patchPre_2_14 = () => {
applyPatches(provider, CdkToolkit, SDK, ToolkitInfo);
};

const configureEnvironment = () => {
// This _must_ use localhost.localstack.cloud as we require valid subdomains of these paths to
// resolve. Unfortunately though `curl` seems to support subdomains of localhost, the CDK does not.
process.env.AWS_ENDPOINT_URL_S3 = process.env.AWS_ENDPOINT_URL_S3 || `${PROTOCOL}://s3.localhost.localstack.cloud:${EDGE_PORT}`;
process.env.AWS_ENDPOINT_URL = process.env.AWS_ENDPOINT_URL || `${PROTOCOL}://localhost.localstack.cloud:${EDGE_PORT}`;
};

const patchPost_2_14 = () => {
var lib = null;
try {
Expand All @@ -484,7 +475,7 @@ const patchPost_2_14 = () => {
break;
case "ERR_PACKAGE_PATH_NOT_EXPORTED":
// post 2.177
configureEnvironment();
configureEnvironment(process.env, AWS_ENVAR_ALLOWLIST);
break;
default:
// a different error
Expand Down
8 changes: 8 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: "node",
verbose: true,
coverageDirectory: "coverage",
collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}"],
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)"],
};

Loading