diff --git a/packages/@aws-cdk/aws-lambda-go/README.md b/packages/@aws-cdk/aws-lambda-go/README.md index f79d8c8a862ec..748ab32256ec0 100644 --- a/packages/@aws-cdk/aws-lambda-go/README.md +++ b/packages/@aws-cdk/aws-lambda-go/README.md @@ -183,6 +183,21 @@ new go.GoFunction(this, 'GoFunction', { }); ``` +You can set additional Docker options to configure the build environment: + + ```ts +new go.GoFunction(this, 'GoFunction', { + entry: 'app/cmd/api', + bundling: { + network: 'host', + securityOpt: 'no-new-privileges', + user: 'user:group', + volumesFrom: ['777f7dc92da7'], + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }, +}); +``` + ## Command hooks It is possible to run additional commands by specifying the `commandHooks` prop: diff --git a/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts index d30c753df7839..62645d0ae8cc6 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-go/lib/bundling.ts @@ -78,6 +78,13 @@ export class Bundling implements cdk.BundlingOptions { command: bundling.command, environment: bundling.environment, local: bundling.local, + entrypoint: bundling.entrypoint, + volumes: bundling.volumes, + volumesFrom: bundling.volumesFrom, + workingDirectory: bundling.workingDirectory, + user: bundling.user, + securityOpt: bundling.securityOpt, + network: bundling.network, }, }); } @@ -93,6 +100,13 @@ export class Bundling implements cdk.BundlingOptions { public readonly command: string[]; public readonly environment?: { [key: string]: string }; public readonly local?: cdk.ILocalBundling; + public readonly entrypoint?: string[] + public readonly volumes?: cdk.DockerVolume[]; + public readonly volumesFrom?: string[]; + public readonly workingDirectory?: string; + public readonly user?: string; + public readonly securityOpt?: string; + public readonly network?: string; private readonly relativeEntryPath: string; @@ -131,8 +145,15 @@ export class Bundling implements cdk.BundlingOptions { : cdk.DockerImage.fromRegistry('dummy'); // Do not build if we don't need to const bundlingCommand = this.createBundlingCommand(cdk.AssetStaging.BUNDLING_INPUT_DIR, cdk.AssetStaging.BUNDLING_OUTPUT_DIR); - this.command = ['bash', '-c', bundlingCommand]; + this.command = props.command ?? ['bash', '-c', bundlingCommand]; this.environment = environment; + this.entrypoint = props.entrypoint; + this.volumes = props.volumes; + this.volumesFrom = props.volumesFrom; + this.workingDirectory = props.workingDirectory; + this.user = props.user; + this.securityOpt = props.securityOpt; + this.network = props.network; // Local bundling if (!props.forcedDockerBundling) { // only if Docker is not forced diff --git a/packages/@aws-cdk/aws-lambda-go/lib/types.ts b/packages/@aws-cdk/aws-lambda-go/lib/types.ts index c754cbdbcf664..5dc7beb8f886c 100644 --- a/packages/@aws-cdk/aws-lambda-go/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-go/lib/types.ts @@ -1,16 +1,9 @@ -import { AssetHashType, DockerImage } from '@aws-cdk/core'; +import { AssetHashType, DockerImage, DockerRunOptions } from '@aws-cdk/core'; /** * Bundling options */ -export interface BundlingOptions { - /** - * Environment variables defined when go runs. - * - * @default - no environment variables are defined. - */ - readonly environment?: { [key: string]: string; }; - +export interface BundlingOptions extends DockerRunOptions { /** * Force bundling in a Docker container even if local bundling is * possible. diff --git a/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts index 73dfec99b45c1..134dd0f51716f 100644 --- a/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-go/test/bundling.test.ts @@ -335,3 +335,129 @@ test('with command hooks', () => { }), }); }); + +test('Custom bundling entrypoint', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'], + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'], + }), + }); +}); + +test('Custom bundling volumes', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }), + }); +}); + +test('Custom bundling volumesFrom', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + volumesFrom: ['777f7dc92da7'], + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + volumesFrom: ['777f7dc92da7'], + }), + }); +}); + +test('Custom bundling workingDirectory', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + workingDirectory: '/working-directory', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + workingDirectory: '/working-directory', + }), + }); +}); + +test('Custom bundling user', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + user: 'user:group', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + user: 'user:group', + }), + }); +}); + +test('Custom bundling securityOpt', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + securityOpt: 'no-new-privileges', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + securityOpt: 'no-new-privileges', + }), + }); +}); + +test('Custom bundling network', () => { + Bundling.bundle({ + entry, + moduleDir, + runtime: Runtime.GO_1_X, + architecture: Architecture.X86_64, + forcedDockerBundling: true, + network: 'host', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + network: 'host', + }), + }); +}); diff --git a/packages/@aws-cdk/aws-lambda-nodejs/README.md b/packages/@aws-cdk/aws-lambda-nodejs/README.md index 3953bd225aaca..24d6582d67732 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/README.md +++ b/packages/@aws-cdk/aws-lambda-nodejs/README.md @@ -307,6 +307,20 @@ should also have `npm`, `yarn` or `pnpm` depending on the lock file you're using Use the [default image provided by `@aws-cdk/aws-lambda-nodejs`](https://github.com/aws/aws-cdk/blob/main/packages/%40aws-cdk/aws-lambda-nodejs/lib/Dockerfile) as a source of inspiration. +You can set additional Docker options to configure the build environment: + + ```ts +new nodejs.NodejsFunction(this, 'my-handler', { + bundling: { + network: 'host', + securityOpt: 'no-new-privileges', + user: 'user:group', + volumesFrom: ['777f7dc92da7'], + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }, +}); +``` + ## Asset hash By default the asset hash will be calculated based on the bundled output (`AssetHashType.OUTPUT`). diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts index 2c16103b0863c..8809166a6e57b 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/bundling.ts @@ -73,9 +73,15 @@ export class Bundling implements cdk.BundlingOptions { // Core bundling options public readonly image: cdk.DockerImage; + public readonly entrypoint?: string[] public readonly command: string[]; + public readonly volumes?: cdk.DockerVolume[]; + public readonly volumesFrom?: string[]; public readonly environment?: { [key: string]: string }; public readonly workingDirectory: string; + public readonly user?: string; + public readonly securityOpt?: string; + public readonly network?: string; public readonly local?: cdk.ILocalBundling; private readonly projectRoot: string; @@ -137,11 +143,17 @@ export class Bundling implements cdk.BundlingOptions { tscRunner: 'tsc', // tsc is installed globally in the docker image osPlatform: 'linux', // linux docker image }); - this.command = ['bash', '-c', bundlingCommand]; + this.command = props.command ?? ['bash', '-c', bundlingCommand]; this.environment = props.environment; // Bundling sets the working directory to cdk.AssetStaging.BUNDLING_INPUT_DIR // and we want to force npx to use the globally installed esbuild. - this.workingDirectory = '/'; + this.workingDirectory = props.workingDirectory ?? '/'; + this.entrypoint = props.entrypoint; + this.volumes = props.volumes; + this.volumesFrom = props.volumesFrom; + this.user = props.user; + this.securityOpt = props.securityOpt; + this.network = props.network; // Local bundling if (!props.forceDockerBundling) { // only if Docker is not forced diff --git a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts index c0096404b2511..0d79db703287e 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/lib/types.ts @@ -1,9 +1,9 @@ -import { DockerImage } from '@aws-cdk/core'; +import { DockerImage, DockerRunOptions } from '@aws-cdk/core'; /** * Bundling options */ -export interface BundlingOptions { +export interface BundlingOptions extends DockerRunOptions { /** * Whether to minify files when bundling. * @@ -161,13 +161,6 @@ export interface BundlingOptions { */ readonly charset?: Charset; - /** - * Environment variables defined when bundling runs. - * - * @default - no environment variables are defined. - */ - readonly environment?: { [key: string]: string; }; - /** * Replace global identifiers with constant expressions. * diff --git a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts index 0259180de8a54..83029ec601590 100644 --- a/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-nodejs/test/bundling.test.ts @@ -668,3 +668,137 @@ test('with custom hash', () => { assetHashType: AssetHashType.CUSTOM, })); }); + +test('Custom bundling entrypoint', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'], + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + entrypoint: ['/cool/entrypoint', '--cool-entrypoint-arg'], + }), + }); +}); + +test('Custom bundling volumes', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }), + }); +}); + +test('Custom bundling volumesFrom', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + volumesFrom: ['777f7dc92da7'], + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + volumesFrom: ['777f7dc92da7'], + }), + }); +}); + + +test('Custom bundling workingDirectory', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + workingDirectory: '/working-directory', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + workingDirectory: '/working-directory', + }), + }); +}); + +test('Custom bundling user', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + user: 'user:group', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + user: 'user:group', + }), + }); +}); + +test('Custom bundling securityOpt', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + securityOpt: 'no-new-privileges', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + securityOpt: 'no-new-privileges', + }), + }); +}); + +test('Custom bundling network', () => { + Bundling.bundle({ + entry, + projectRoot, + depsLockFilePath, + runtime: Runtime.NODEJS_14_X, + architecture: Architecture.X86_64, + forceDockerBundling: true, + network: 'host', + }); + + expect(Code.fromAsset).toHaveBeenCalledWith('/project', { + assetHashType: AssetHashType.OUTPUT, + bundling: expect.objectContaining({ + network: 'host', + }), + }); +}); diff --git a/packages/@aws-cdk/aws-lambda-python/README.md b/packages/@aws-cdk/aws-lambda-python/README.md index 04f092e5f2875..f7ca29de39e0a 100644 --- a/packages/@aws-cdk/aws-lambda-python/README.md +++ b/packages/@aws-cdk/aws-lambda-python/README.md @@ -145,6 +145,24 @@ new python.PythonFunction(this, 'function', { }); ``` +You can set additional Docker options to configure the build environment: + + ```ts +const entry = '/path/to/function'; + +new python.PythonFunction(this, 'function', { + entry, + runtime: Runtime.PYTHON_3_8, + bundling: { + network: 'host', + securityOpt: 'no-new-privileges', + user: 'user:group', + volumesFrom: ['777f7dc92da7'], + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }, +}); +``` + ## Custom Bundling with Code Artifact To use a Code Artifact PyPI repo, the `PIP_INDEX_URL` for bundling the function can be customized (requires AWS CLI in the build environment): diff --git a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts index 5302673a5e296..f83756a334fe8 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/bundling.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import { Architecture, AssetCode, Code, Runtime } from '@aws-cdk/aws-lambda'; -import { AssetStaging, BundlingOptions as CdkBundlingOptions, DockerImage } from '@aws-cdk/core'; +import { AssetStaging, BundlingOptions as CdkBundlingOptions, DockerImage, DockerVolume } from '@aws-cdk/core'; import { Packaging, DependenciesFile } from './packaging'; import { BundlingOptions, ICommandHooks } from './types'; @@ -57,8 +57,15 @@ export class Bundling implements CdkBundlingOptions { } public readonly image: DockerImage; + public readonly entrypoint?: string[] public readonly command: string[]; + public readonly volumes?: DockerVolume[]; + public readonly volumesFrom?: string[]; public readonly environment?: { [key: string]: string }; + public readonly workingDirectory?: string; + public readonly user?: string; + public readonly securityOpt?: string; + public readonly network?: string; constructor(props: BundlingProps) { const { @@ -88,8 +95,15 @@ export class Bundling implements CdkBundlingOptions { }, platform: architecture.dockerPlatform, }); - this.command = ['bash', '-c', chain(bundlingCommands)]; + this.command = props.command ?? ['bash', '-c', chain(bundlingCommands)]; + this.entrypoint = props.entrypoint; + this.volumes = props.volumes; + this.volumesFrom = props.volumesFrom; this.environment = props.environment; + this.workingDirectory = props.workingDirectory; + this.user = props.user; + this.securityOpt = props.securityOpt; + this.network = props.network; } private createBundlingCommand(options: BundlingCommandOptions): string[] { diff --git a/packages/@aws-cdk/aws-lambda-python/lib/types.ts b/packages/@aws-cdk/aws-lambda-python/lib/types.ts index ff9608b97e2f1..ad0ff6f8ce09d 100644 --- a/packages/@aws-cdk/aws-lambda-python/lib/types.ts +++ b/packages/@aws-cdk/aws-lambda-python/lib/types.ts @@ -1,10 +1,10 @@ -import { AssetHashType, DockerImage } from '@aws-cdk/core'; +import { AssetHashType, DockerImage, DockerRunOptions } from '@aws-cdk/core'; /** * Options for bundling */ -export interface BundlingOptions { +export interface BundlingOptions extends DockerRunOptions { /** * Whether to export Poetry dependencies with hashes. Note that this can cause builds to fail if not all dependencies @@ -40,13 +40,6 @@ export interface BundlingOptions { */ readonly buildArgs?: { [key: string]: string }; - /** - * Environment variables defined when bundling runs. - * - * @default - no environment variables are defined. - */ - readonly environment?: { [key: string]: string; }; - /** * Determines how asset hash is calculated. Assets will get rebuild and * uploaded only if their hash has changed. diff --git a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts index 402953c197c1c..d6172ccfb705b 100644 --- a/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts +++ b/packages/@aws-cdk/aws-lambda-python/test/bundling.test.ts @@ -278,6 +278,102 @@ test('Bundling with custom environment vars`', () => { })); }); +test('Bundling with volumes from other container', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + volumesFrom: ['777f7dc92da7'], + + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + volumesFrom: ['777f7dc92da7'], + }), + })); +}); + +test('Bundling with custom volume paths', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + volumes: [{ hostPath: '/host-path', containerPath: '/container-path' }], + }), + })); +}); + +test('Bundling with custom working directory', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + workingDirectory: '/my-dir', + + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + workingDirectory: '/my-dir', + }), + })); +}); + +test('Bundling with custom user', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + user: 'user:group', + + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + user: 'user:group', + }), + })); +}); + +test('Bundling with custom securityOpt', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + securityOpt: 'no-new-privileges', + + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + securityOpt: 'no-new-privileges', + }), + })); +}); + +test('Bundling with custom network', () => { + const entry = path.join(__dirname, 'lambda-handler'); + Bundling.bundle({ + entry: entry, + runtime: Runtime.PYTHON_3_7, + network: 'host', + + }); + + expect(Code.fromAsset).toHaveBeenCalledWith(entry, expect.objectContaining({ + bundling: expect.objectContaining({ + network: 'host', + }), + })); +}); + test('Do not build docker image when skipping bundling', () => { const entry = path.join(__dirname, 'lambda-handler'); Bundling.bundle({