Skip to content

Commit

Permalink
feat(cli): Extend cdk import support to all resource types supported …
Browse files Browse the repository at this point in the history
…by CloudFormation
  • Loading branch information
tomas-mazak committed Nov 27, 2021
1 parent 9569124 commit 9e4c131
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 143 deletions.
10 changes: 5 additions & 5 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ and might have breaking changes in the future.

#### Import existing resources

**Important:** This is a work in progress, only S3 buckets are currently supported
**Important:** This is a work in progress

Sometimes, it is beneficial to import (enroll/adopt/...) AWS resources, that were
created manually (or by different means), into a CDK stack. Some resources can simply be
Expand All @@ -452,13 +452,13 @@ To import an existing resource to a CDK stack:
- add corresponding constructs for the resources to be added in your stack - for example,
for an S3 bucket, add something like `new s3.Bucket(this, 'ImportedS3Bucket', {});` -
**no other changes must be done to the stack before the import is completed**
- run `cdk deploy` with `--import-resources` argument to instruct CDK to start the import
operation
- if resource definition contains all information needed for the import, this happens
- run `cdk import` command - if there are multiple stacks in the CDK app, pass a specific
stack name as an argument
- if the resource definition contains all information needed for the import, this happens
automatically (e.g. an `s3.Bucket` construct has an explicit `bucketName` set),
otherwise, CDK will prompt user to provide neccessary identification information (e.g.
the bucket name)
- after cdk deploy reports success, the resource is managed by CDK. Any subsequent
- after cdk import reports success, the resource is managed by CDK. Any subsequent
changes in the construct configuration will be reflected on the resource


Expand Down
24 changes: 21 additions & 3 deletions packages/aws-cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,16 @@ async function parseCommandLineArguments() {
desc: 'Continuously observe the project files, ' +
'and deploy the given stack(s) automatically when changes are detected. ' +
'Implies --hotswap by default',
})
.option('import-resources', { type: 'boolean', alias: 'i', desc: 'Import existing resources in the stack' }),
}),
)
.command('import [STACK]', 'Import existing resource(s) into the given STACK', yargs => yargs
.option('execute', { type: 'boolean', desc: 'Whether to execute ChangeSet (--no-execute will NOT execute the ChangeSet)', default: true })
.option('change-set-name', { type: 'string', desc: 'Name of the CloudFormation change set to create' })
.option('rollback', {
type: 'boolean',
desc: "Rollback stack to stable state on failure. Defaults to 'true', iterate more rapidly with --no-rollback or -R. " +
'Note: do **not** disable this flag for deployments with resource replacements, as that will always fail',
}),
)
.command('watch [STACKS..]', "Shortcut for 'deploy --watch'", yargs => yargs
// I'm fairly certain none of these options, present for 'deploy', make sense for 'watch':
Expand Down Expand Up @@ -376,7 +384,17 @@ async function initCommandLine() {
rollback: configuration.settings.get(['rollback']),
hotswap: args.hotswap,
watch: args.watch,
importResources: args['import-resources'],
});

case 'import':
return cli.import({
selector,
toolkitStackName,
roleArn: args.roleArn,
execute: args.execute,
changeSetName: args.changeSetName,
progress: configuration.settings.get(['progress']),
rollback: configuration.settings.get(['rollback']),
});

case 'watch':
Expand Down
16 changes: 13 additions & 3 deletions packages/aws-cdk/lib/api/cloudformation-deployments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { publishAssets } from '../util/asset-publishing';
import { Mode, SdkProvider } from './aws-auth';
import { deployStack, DeployStackResult, destroyStack } from './deploy-stack';
import { ToolkitInfo } from './toolkit-info';
import { CloudFormationStack, Template, ResourcesToImport } from './util/cloudformation';
import { CloudFormationStack, Template, ResourcesToImport, ResourceIdentifierSummaries } from './util/cloudformation';
import { StackActivityProgress } from './util/cloudformation/stack-activity-monitor';

/**
Expand Down Expand Up @@ -198,13 +198,23 @@ export class CloudFormationDeployments {
return stack.template();
}

public async getTemplateSummary(stackArtifact: cxapi.CloudFormationStackArtifact): Promise<ResourceIdentifierSummaries> {
debug(`Retrieving template summary for stack ${stackArtifact.displayName}.`);
const { stackSdk } = await this.prepareSdkFor(stackArtifact, undefined, Mode.ForReading);
const cfn = stackSdk.cloudFormation();

return CloudFormationStack.templateSummary(cfn, stackArtifact.template);
}

public async deployStack(options: DeployStackOptions): Promise<DeployStackResult> {
const { stackSdk, resolvedEnvironment, cloudFormationRoleArn } = await this.prepareSdkFor(options.stack, options.roleArn);

const toolkitInfo = await ToolkitInfo.lookup(resolvedEnvironment, stackSdk, options.toolkitStackName);

// Publish any assets before doing the actual deploy
await this.publishStackAssets(options.stack, toolkitInfo);
// Publish any assets before doing the actual deploy (do not publish any assets on import operation)
if (options.resourcesToImport === undefined) {
await this.publishStackAssets(options.stack, toolkitInfo);
}

// Do a verification of the bootstrap stack version
await this.validateBootstrapStackVersion(
Expand Down
12 changes: 12 additions & 0 deletions packages/aws-cdk/lib/api/util/cloudformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ interface TemplateParameter {
}

export type ResourcesToImport = CloudFormation.ResourcesToImport;
export type ResourceIdentifierSummaries = CloudFormation.ResourceIdentifierSummaries;

/**
* Represents an (existing) Stack in CloudFormation
Expand All @@ -37,6 +38,17 @@ export class CloudFormationStack {
}
}

/**
* Retrieve the stack template's summary with the information about resource import identifiers
*/
public static async templateSummary(cfn: CloudFormation, template: any): Promise<ResourceIdentifierSummaries> {
const response = await cfn.getTemplateSummary({ TemplateBody: JSON.stringify(template) }).promise();
if (!response.ResourceIdentifierSummaries) {
debug('GetTemplateSummary API call did not return "ReousrceIdentifierSummaries"');
}
return response.ResourceIdentifierSummaries ?? [];
}

/**
* Return a copy of the given stack that does not exist
*
Expand Down
Loading

0 comments on commit 9e4c131

Please sign in to comment.