Skip to content
Open
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
23 changes: 23 additions & 0 deletions packages/@aws-cdk-testing/cli-integ/resources/cdk-apps/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,28 @@ class DockerStackWithCustomFile extends cdk.Stack {
}
}

class MultipleDockerAssetsStack extends cdk.Stack {
constructor(parent, id, props) {
super(parent, id, props);

new docker.DockerImageAsset(this, 'image1', {
directory: path.join(__dirname, 'docker-concurrent/image1')
});
new docker.DockerImageAsset(this, 'image2', {
directory: path.join(__dirname, 'docker-concurrent/image2')
});
new docker.DockerImageAsset(this, 'image3', {
directory: path.join(__dirname, 'docker-concurrent/image3')
});

// Add at least a single resource (WaitConditionHandle), otherwise this stack will never
// be deployed (and its assets never built)
new cdk.CfnResource(this, 'Handle', {
type: 'AWS::CloudFormation::WaitConditionHandle'
});
}
}

/**
* A stack that will never succeed deploying (done in a way that CDK cannot detect but CFN will complain about)
*/
Expand Down Expand Up @@ -921,6 +943,7 @@ switch (stackSet) {
new DockerStack(app, `${stackPrefix}-docker`);
new DockerInUseStack(app, `${stackPrefix}-docker-in-use`);
new DockerStackWithCustomFile(app, `${stackPrefix}-docker-with-custom-file`);
new MultipleDockerAssetsStack(app, `${stackPrefix}-multiple-docker-assets`);

new NotificationArnsStack(app, `${stackPrefix}-notification-arns`);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM public.ecr.aws/docker/library/alpine:latest
RUN echo "image1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM public.ecr.aws/docker/library/alpine:latest
RUN echo "image2"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FROM public.ecr.aws/docker/library/alpine:latest
RUN echo "image3"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { integTest, withDefaultFixture } from '../../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'deploy stack with multiple docker assets',
withDefaultFixture(async (fixture) => {
await fixture.cdkDeploy('multiple-docker-assets', {
options: ['--asset-parallelism', '--asset-build-concurrency', '3'],
});
}),
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* eslint-disable import/no-extraneous-dependencies */
import * as toolkit from '@aws-cdk/toolkit-lib';
import { assemblyFromCdkAppDir, toolkitFromFixture } from './toolkit-helpers';
import { integTest, withDefaultFixture } from '../../lib';

jest.setTimeout(2 * 60 * 60_000); // Includes the time to acquire locks, worst-case single-threaded runtime

integTest(
'toolkit deploy stack with multiple docker assets',
withDefaultFixture(async (fixture) => {
const tk = toolkitFromFixture(fixture);

const assembly = await assemblyFromCdkAppDir(tk, fixture);

const stacks: toolkit.StackSelector = {
strategy: toolkit.StackSelectionStrategy.PATTERN_MUST_MATCH_SINGLE,
patterns: [fixture.fullStackName('multiple-docker-assets')],
};

await tk.deploy(assembly, {
stacks,
assetParallelism: true,
assetBuildConcurrency: 3,
});
await tk.destroy(assembly, { stacks });
}),
);
9 changes: 9 additions & 0 deletions packages/@aws-cdk/toolkit-lib/lib/actions/deploy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ export interface DeployOptions extends BaseDeployOptions {
*/
readonly assetParallelism?: boolean;

/**
* Maximum number of asset builds to run in parallel
*
* This setting only has an effect if `assetParallelism` is set to `true`.
*
* @default 1
*/
readonly assetBuildConcurrency?: number;

/**
* When to build assets
*
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ export class Toolkit extends CloudAssemblySourceBuilder {

const graphConcurrency: Concurrency = {
'stack': concurrency,
'asset-build': 1, // This will be CPU-bound/memory bound, mostly matters for Docker builds
'asset-build': (options.assetParallelism ?? true) ? options.assetBuildConcurrency ?? 1 : 1, // This will be CPU-bound/memory bound, mostly matters for Docker builds
'asset-publish': (options.assetParallelism ?? true) ? 8 : 1, // This will be I/O-bound, 8 in parallel seems reasonable
};

Expand Down
78 changes: 78 additions & 0 deletions packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StackParameters } from '../../lib/actions/deploy';
import type { DeployStackOptions, DeployStackResult } from '../../lib/api/deployments';
import * as deployments from '../../lib/api/deployments';
import { WorkGraphBuilder } from '../../lib/api/work-graph';
import { Toolkit } from '../../lib/toolkit';
import { builderFixture, cdkOutFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';

Expand Down Expand Up @@ -161,6 +162,83 @@ IAM Statement Changes
forcePublish: true,
}));
});

describe('assetBuildConcurrency', () => {
let buildSpy: jest.SpyInstance;

afterEach(() => {
buildSpy?.mockRestore();
});

test('is passed when assetParallelism is true', async () => {
const mockWorkGraph = {
doParallel: jest.fn().mockResolvedValue(undefined),
removeUnnecessaryAssets: jest.fn().mockResolvedValue(undefined),
};
buildSpy = jest.spyOn(WorkGraphBuilder.prototype, 'build').mockReturnValue(mockWorkGraph as any);

const cx = await builderFixture(toolkit, 'stack-with-asset');

await toolkit.deploy(cx, {
assetParallelism: true,
assetBuildConcurrency: 4,
});

expect(mockWorkGraph.doParallel).toHaveBeenCalledWith(
expect.objectContaining({
'asset-build': 4,
}),
expect.anything(),
);
});

test('is ignored when assetParallelism is false', async () => {
const mockWorkGraph = {
doParallel: jest.fn().mockResolvedValue(undefined),
removeUnnecessaryAssets: jest.fn().mockResolvedValue(undefined),
};
buildSpy = jest.spyOn(WorkGraphBuilder.prototype, 'build').mockReturnValue(mockWorkGraph as any);

const cx = await builderFixture(toolkit, 'stack-with-asset');

await toolkit.deploy(cx, {
assetParallelism: false,
assetBuildConcurrency: 4,
});

expect(mockWorkGraph.doParallel).toHaveBeenCalledWith(
expect.objectContaining({
'asset-build': 1,
}),
expect.anything(),
);
});

test.each([
true,
false,
undefined,
])('defaults to 1 when assetParallelism=%s and assetBuildConcurrency is not specified', async (assetParallelism) => {
const mockWorkGraph = {
doParallel: jest.fn().mockResolvedValue(undefined),
removeUnnecessaryAssets: jest.fn().mockResolvedValue(undefined),
};
buildSpy = jest.spyOn(WorkGraphBuilder.prototype, 'build').mockReturnValue(mockWorkGraph as any);

const cx = await builderFixture(toolkit, 'stack-with-asset');

await toolkit.deploy(cx, {
assetParallelism,
});

expect(mockWorkGraph.doParallel).toHaveBeenCalledWith(
expect.objectContaining({
'asset-build': 1,
}),
expect.anything(),
);
});
});
});

describe('deployment results', () => {
Expand Down
11 changes: 10 additions & 1 deletion packages/aws-cdk/lib/cli/cdk-toolkit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ export class CdkToolkit {

const graphConcurrency: Concurrency = {
'stack': concurrency,
'asset-build': 1, // This will be CPU-bound/memory bound, mostly matters for Docker builds
'asset-build': (options.assetParallelism ?? true) ? options.assetBuildConcurrency ?? 1 : 1, // This will be CPU-bound/memory bound, mostly matters for Docker builds
'asset-publish': (options.assetParallelism ?? true) ? 8 : 1, // This will be I/O-bound, 8 in parallel seems reasonable
};

Expand Down Expand Up @@ -1766,6 +1766,15 @@ export interface DeployOptions extends CfnDeployOptions, WatchOptions {
*/
readonly assetParallelism?: boolean;

/**
* Maximum number of asset builds to run in parallel
*
* This setting only has an effect if `assetParallelism` is set to `true`.
*
* @default 1
*/
readonly assetBuildConcurrency?: number;

/**
* When to build assets
*
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli/cli-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export async function makeConfig(): Promise<CliConfig> {
},
'concurrency': { type: 'number', desc: 'Maximum number of simultaneous deployments (dependency permitting) to execute.', default: 1, requiresArg: true },
'asset-parallelism': { type: 'boolean', desc: 'Whether to build/publish assets in parallel' },
'asset-build-concurrency': { type: 'number', desc: 'Maximum number of asset builds to run in parallel', default: 1, requiresArg: true },
'asset-prebuild': { type: 'boolean', desc: 'Whether to build all assets before deploying the first stack (useful for failing Docker builds)', default: true },
'ignore-no-stacks': { type: 'boolean', desc: 'Whether to deploy if the app contains no stacks', default: false },
},
Expand Down
6 changes: 6 additions & 0 deletions packages/aws-cdk/lib/cli/cli-type-registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,12 @@
"type": "boolean",
"desc": "Whether to build/publish assets in parallel"
},
"asset-build-concurrency": {
"type": "number",
"desc": "Maximum number of asset builds to run in parallel",
"default": 1,
"requiresArg": true
},
"asset-prebuild": {
"type": "boolean",
"desc": "Whether to build all assets before deploying the first stack (useful for failing Docker builds)",
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
traceLogs: args.logs,
concurrency: args.concurrency,
assetParallelism: configuration.settings.get(['assetParallelism']),
assetBuildConcurrency: configuration.settings.get(['assetBuildConcurrency']),
assetBuildTime: configuration.settings.get(['assetPrebuild'])
? AssetBuildTime.ALL_BEFORE_DEPLOY
: AssetBuildTime.JUST_IN_TIME,
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-cdk/lib/cli/convert-to-user-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export function convertYargsToUserInput(args: any): UserInput {
logs: args.logs,
concurrency: args.concurrency,
assetParallelism: args.assetParallelism,
assetBuildConcurrency: args.assetBuildConcurrency,
assetPrebuild: args.assetPrebuild,
ignoreNoStacks: args.ignoreNoStacks,
STACKS: args.STACKS,
Expand Down Expand Up @@ -425,6 +426,7 @@ export function convertConfigToUserInput(config: any): UserInput {
logs: config.deploy?.logs,
concurrency: config.deploy?.concurrency,
assetParallelism: config.deploy?.assetParallelism,
assetBuildConcurrency: config.deploy?.assetBuildConcurrency,
assetPrebuild: config.deploy?.assetPrebuild,
ignoreNoStacks: config.deploy?.ignoreNoStacks,
};
Expand Down
6 changes: 6 additions & 0 deletions packages/aws-cdk/lib/cli/parse-command-line-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,12 @@ export function parseCommandLineArguments(args: Array<string>): any {
type: 'boolean',
desc: 'Whether to build/publish assets in parallel',
})
.option('asset-build-concurrency', {
default: 1,
type: 'number',
desc: 'Maximum number of asset builds to run in parallel',
requiresArg: true,
})
.option('asset-prebuild', {
default: true,
type: 'boolean',
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/cli/user-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ export async function commandLineArgumentsToSettings(ioHelper: IoHelper, argv: A
rollback: argv.rollback,
notices: argv.notices,
assetParallelism: argv['asset-parallelism'],
assetBuildConcurrency: argv['asset-build-concurrency'],
assetPrebuild: argv['asset-prebuild'],
ignoreNoStacks: argv['ignore-no-stacks'],
hotswap: {
Expand Down
7 changes: 7 additions & 0 deletions packages/aws-cdk/lib/cli/user-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -911,6 +911,13 @@ export interface DeployOptions {
*/
readonly assetParallelism?: boolean;

/**
* Maximum number of asset builds to run in parallel
*
* @default - 1
*/
readonly assetBuildConcurrency?: number;

/**
* Whether to build all assets before deploying the first stack (useful for failing Docker builds)
*
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/legacy/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ function commandLineArgumentsToSettings(argv: Arguments): Settings {
rollback: argv.rollback,
notices: argv.notices,
assetParallelism: argv['asset-parallelism'],
assetBuildConcurrency: argv['asset-build-concurrency'],
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
assetBuildConcurrency: argv['asset-build-concurrency'],

Maybe we don't need this?

assetPrebuild: argv['asset-prebuild'],
ignoreNoStacks: argv['ignore-no-stacks'],
hotswap: {
Expand Down
Loading
Loading