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

bug(cli): bootstrapping multiple environments with same bootstrap-bucket-name causes inconsistent stack creation #96

Open
iankhou opened this issue Feb 21, 2025 · 0 comments
Labels

Comments

@iankhou
Copy link
Contributor

iankhou commented Feb 21, 2025

Bootstrap fails when multiple environments are being bootstrapped with a custom bucketName

Description

Users can pass in BootstrappingParameters to modify how resources are created during bootstrap:

export interface BootstrappingParameters {
/**
* The name to be given to the CDK Bootstrap bucket.
*
* @default - a name is generated by CloudFormation.
*/
readonly bucketName?: string;

If bucketName is passed in, bootstrap will attempt to create the bucket. If the bucket already exists, the bootstrap operation will fail.

When bootstrapping multiple environments, it is possible to pass in bucketName. However, this will cause at least one environment to fail bootstrapping. Assuming the bucket name is not already taken, the first environment that is bootstrapped will use that bucket name and succeed. All other environments will fail to bootstrap, since the bucket name is already taken.

Reproduction

Command-line:

cdk bootstrap --all \
  --bootstrap-bucket-name my-custom-parameters-bucket-481665128236 \
  aws://481665128236/us-east-2 \
  aws://481665128236/us-west-1

Result:

Only one environment is bootstrapped successfully.

Image Image

Proposed fix

BootstrappingParameters should be passed in per-environment. It should remain optional. The current CLI implementation is as follows:

public async bootstrap(
userEnvironmentSpecs: string[],
options: BootstrapEnvironmentOptions,
): Promise<void> {
const bootstrapper = new Bootstrapper(options.source, { ioHost: this.ioHost, action: 'bootstrap' });
// If there is an '--app' argument and an environment looks like a glob, we
// select the environments from the app. Otherwise, use what the user said.
const environments = await this.defineEnvironments(userEnvironmentSpecs);
const limit = pLimit(20);
// eslint-disable-next-line @cdklabs/promiseall-no-unbounded-parallelism
await Promise.all(environments.map((environment) => limit(async () => {
success(' ⏳ Bootstrapping environment %s...', chalk.blue(environment.name));
try {
const result = await bootstrapper.bootstrapEnvironment(environment, this.props.sdkProvider, options);
const message = result.noOp
? ' ✅ Environment %s bootstrapped (no changes).'
: ' ✅ Environment %s bootstrapped.';
success(message, chalk.blue(environment.name));
} catch (e) {
error(' ❌ Environment %s failed bootstrapping: %s', chalk.blue(environment.name), e);
throw e;
}
})));
}

BootstrapEnvironmentOptions is bootstrap-action-scoped (i.e. account-scoped), but it is passed into bootstrapEnvironment, an environment-scoped (i.e. region-scoped) function.

Consider something like the following as a param for bootstrap:

interface EnvironmentBootstrapConfig {
  readonly environment: string;  // The environment descriptor (e.g. 'aws://123456789012/us-east-1')
  readonly parameters?: BootstrappingParameters;
  // Other environment-specific options that were previously in BootstrapOptions
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant