Skip to content

Commit

Permalink
fix(cli): can't bootstrap environment not in app
Browse files Browse the repository at this point in the history
It used to be that if we had an `--app` argument, we would always glob
arguments to `cdk bootstrap <ENV>` through the environments of stacks
in the app.

This makes it super hard/annoying to run `cdk bootstrap
aws://1234/us-somewhere` in a CI/CD project; you have to add a stack
there first and compile before you're allowed to do that, which is
kinda silly.

Change behavior to only glob environment from the environments in the
app if it looks like the user is supplying a glob (if it contains `*`).

If the user is supplying a concrete environment name, just use it
directly.

Also in this commit:

- refactor: lots of places where we were passing around a pair
of `(account, region)`. Replace those by passing a `cxapi.Environment`
instead (most of the changes in this PR).
- refactor: the old and new bootstrapping experience had a lot of
copy/pasta between them. Refactored to make them share code.
- (prerelease) feat: add a version check to the bootstrapping operation,
so that users won't accidentally downgrade a bootstrap stack to an older
version (This happened to 2 devs already, and is an easy mistake to
make. Protect against it.)
  • Loading branch information
rix0rrr committed Apr 24, 2020
1 parent 0ccb549 commit 9566cca
Show file tree
Hide file tree
Showing 29 changed files with 508 additions and 338 deletions.
10 changes: 10 additions & 0 deletions packages/@aws-cdk/cx-api/lib/environment.ts
Expand Up @@ -39,6 +39,16 @@ export class EnvironmentUtils {
return { account, region, name: environment };
}

/**
* Build an environment object from an account and region
*/
public static make(account: string, region: string): Environment {
return { account, region, name: this.format(account, region) };
}

/**
* Format an environment string from an account and region
*/
public static format(account: string, region: string): string {
return `aws://${account}/${region}`;
}
Expand Down
23 changes: 14 additions & 9 deletions packages/aws-cdk/bin/cdk.ts
Expand Up @@ -56,7 +56,8 @@ async function parseCommandLineArguments() {
.option('tags', { type: 'array', alias: 't', desc: 'Tags to add for the stack (KEY=VALUE)', nargs: 1, requiresArg: true, default: [] })
.option('execute', {type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true})
.option('trust', { type: 'array', desc: 'The (space-separated) list of AWS account IDs that should be trusted to perform deployments into this environment', default: [], hidden: true })
.option('cloudformation-execution-policies', { type: 'array', desc: 'The (space-separated) list of Managed Policy ARNs that should be attached to the role performing deployments into this environment. Required if --trust was passed', default: [], hidden: true }),
.option('cloudformation-execution-policies', { type: 'array', desc: 'The (space-separated) list of Managed Policy ARNs that should be attached to the role performing deployments into this environment. Required if --trust was passed', default: [], hidden: true })
.option('force', { alias: 'f', type: 'boolean', desc: 'Always bootstrap even if it would downgrade template version', default: false }),
)
.command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs
.option('build-exclude', { type: 'array', alias: 'E', nargs: 1, desc: 'Do not rebuild asset with the given ID. Can be specified multiple times.', default: [] })
Expand Down Expand Up @@ -209,14 +210,18 @@ async function initCommandLine() {
});

case 'bootstrap':
return await cli.bootstrap(args.ENVIRONMENTS, toolkitStackName, args.roleArn, !!process.env.CDK_NEW_BOOTSTRAP, {
bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']),
kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']),
tags: configuration.settings.get(['tags']),
execute: args.execute,
trustedAccounts: args.trust,
cloudFormationExecutionPolicies: args.cloudformationExecutionPolicies,
});
return await cli.bootstrap(args.ENVIRONMENTS, toolkitStackName,
args.roleArn,
!!process.env.CDK_NEW_BOOTSTRAP,
argv.force,
{
bucketName: configuration.settings.get(['toolkitBucket', 'bucketName']),
kmsKeyId: configuration.settings.get(['toolkitBucket', 'kmsKeyId']),
tags: configuration.settings.get(['tags']),
execute: args.execute,
trustedAccounts: args.trust,
cloudFormationExecutionPolicies: args.cloudformationExecutionPolicies,
});

case 'deploy':
const parameterMap: { [name: string]: string | undefined } = {};
Expand Down
39 changes: 17 additions & 22 deletions packages/aws-cdk/lib/api/aws-auth/sdk-provider.ts
Expand Up @@ -110,11 +110,10 @@ export class SdkProvider {
/**
* Return an SDK which can do operations in the given environment
*
* The `region` and `accountId` parameters are interpreted as in `resolveEnvironment()` (which is to
* say, `undefined` doesn't do what you expect).
* The `environment` parameter is resolved first (see `resolveEnvironment()`).
*/
public async forEnvironment(accountId: string | undefined, region: string | undefined, mode: Mode): Promise<ISDK> {
const env = await this.resolveEnvironment(accountId, region);
public async forEnvironment(environment: cxapi.Environment, mode: Mode): Promise<ISDK> {
const env = await this.resolveEnvironment(environment);
const creds = await this.obtainCredentials(env.account, mode);
return new SDK(creds, env.region, this.sdkOptions);
}
Expand Down Expand Up @@ -150,30 +149,26 @@ export class SdkProvider {
/**
* Resolve the environment for a stack
*
* `undefined` actually means `undefined`, and is NOT changed to default values! Only the magic values UNKNOWN_REGION
* and UNKNOWN_ACCOUNT will be replaced with looked-up values!
* Replaces the magic values `UNKNOWN_REGION` and `UNKNOWN_ACCOUNT`
* with the defaults for the current SDK configuration (`~/.aws/config` or
* otherwise).
*
* It is an error if `UNKNOWN_ACCOUNT` is used but the user hasn't configured
* any SDK credentials.
*/
public async resolveEnvironment(accountId: string | undefined, region: string | undefined) {
region = region !== cxapi.UNKNOWN_REGION ? region : this.defaultRegion;
accountId = accountId !== cxapi.UNKNOWN_ACCOUNT ? accountId : (await this.defaultAccount())?.accountId;

if (!region) {
throw new Error('AWS region must be configured either when you configure your CDK stack or through the environment');
}
public async resolveEnvironment(env: cxapi.Environment): Promise<cxapi.Environment> {
const region = env.region !== cxapi.UNKNOWN_REGION ? env.region : this.defaultRegion;
const account = env.account !== cxapi.UNKNOWN_ACCOUNT ? env.account : (await this.defaultAccount())?.accountId;

if (!accountId) {
if (!account) {
throw new Error('Unable to resolve AWS account to use. It must be either configured when you define your CDK or through the environment');
}

const environment: cxapi.Environment = {
region, account: accountId, name: cxapi.EnvironmentUtils.format(accountId, region),
return {
region,
account,
name: cxapi.EnvironmentUtils.format(account, region),
};

return environment;
}

public async resolveEnvironmentObject(env: cxapi.Environment) {
return this.resolveEnvironment(env.account, env.region);
}

/**
Expand Down
161 changes: 0 additions & 161 deletions packages/aws-cdk/lib/api/bootstrap-environment.ts

This file was deleted.

65 changes: 65 additions & 0 deletions packages/aws-cdk/lib/api/bootstrap/bootstrap-environment.ts
@@ -0,0 +1,65 @@
import * as cxapi from '@aws-cdk/cx-api';
import * as path from 'path';
import { loadStructuredFile } from '../../serialize';
import { SdkProvider } from '../aws-auth';
import { DeployStackResult } from '../deploy-stack';
import { BootstrapEnvironmentOptions } from './bootstrap-props';
import { deployBootstrapStack } from './deploy-bootstrap';
import { legacyBootstrapTemplate } from './legacy-template';

// tslint:disable:max-line-length

/**
* Deploy legacy bootstrap stack
*
* @experimental
*/
export async function bootstrapEnvironment(environment: cxapi.Environment, sdkProvider: SdkProvider, options: BootstrapEnvironmentOptions): Promise<DeployStackResult> {
const params = options.parameters ?? {};

if (params.trustedAccounts?.length) {
throw new Error('--trust can only be passed for the new bootstrap experience.');
}
if (params.cloudFormationExecutionPolicies?.length) {
throw new Error('--cloudformation-execution-policies can only be passed for the new bootstrap experience.');
}

return deployBootstrapStack(
legacyBootstrapTemplate(params),
{},
environment,
sdkProvider,
options);
}

/**
* Deploy CI/CD-ready bootstrap stack from template
*
* @experimental
*/
export async function bootstrapEnvironment2(
environment: cxapi.Environment,
sdkProvider: SdkProvider,
options: BootstrapEnvironmentOptions): Promise<DeployStackResult> {

const params = options.parameters ?? {};

if (params.trustedAccounts?.length && !params.cloudFormationExecutionPolicies?.length) {
throw new Error('--cloudformation-execution-policies are required if --trust has been passed!');
}

const bootstrapTemplatePath = path.join(__dirname, 'bootstrap-template.yaml');
const bootstrapTemplate = await loadStructuredFile(bootstrapTemplatePath);

return deployBootstrapStack(
bootstrapTemplate,
{
FileAssetsBucketName: params.bucketName,
FileAssetsBucketKmsKeyId: params.kmsKeyId,
TrustedAccounts: params.trustedAccounts?.join(','),
CloudFormationExecutionPolicies: params.cloudFormationExecutionPolicies?.join(','),
},
environment,
sdkProvider,
options);
}
55 changes: 0 additions & 55 deletions packages/aws-cdk/lib/api/bootstrap/bootstrap-environment2.ts

This file was deleted.

0 comments on commit 9566cca

Please sign in to comment.