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

feat(cli): support CloudFormation simplified resource import #32676

Merged
merged 43 commits into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0f8965f
feat(cli): support ImportExstingResources
tmokmss Nov 19, 2023
e42494e
update aws-sdk, but I'm not sure if it's allowed
tmokmss Nov 19, 2023
ca9cd25
Update cli.integtest.ts
tmokmss Nov 19, 2023
e803106
fix integ
tmokmss Nov 20, 2023
a2fa8b4
also update aws-sdk
tmokmss Nov 20, 2023
d66b937
Update deployments.ts
tmokmss Nov 20, 2023
7e04e54
possible refactor to remove dead code
tmokmss Nov 20, 2023
30c1e50
revert refactor; might do this in another PR
tmokmss Nov 20, 2023
86f6ebc
Merge branch 'main' into allow_import_resources
tmokmss Nov 20, 2023
9229d92
Merge branch 'main' into allow_import_resources
tmokmss Dec 7, 2023
3a2a739
Update yarn.lock
tmokmss Dec 7, 2023
2a6aae5
Update README.md
tmokmss Dec 13, 2023
07fa306
Update cli.integtest.ts
tmokmss Dec 13, 2023
526daed
Update README.md
tmokmss Dec 13, 2023
d5f41e6
Update deploy-stack.test.ts
tmokmss Dec 13, 2023
2abaeba
Update deploy-stack.test.ts
tmokmss Dec 13, 2023
3cc542d
reject only when method=direct && importExistingResources==true
tmokmss Dec 14, 2023
e812eaa
Update packages/aws-cdk/README.md
tmokmss Dec 26, 2023
6e8813c
Update packages/aws-cdk/README.md
tmokmss Dec 26, 2023
dd75942
Update packages/aws-cdk/README.md
tmokmss Dec 26, 2023
8b5ea7e
Merge branch 'main' into allow_import_resources
tmokmss Dec 27, 2023
552e0eb
Merge branch 'main' into allow_import_resources
tmokmss Jan 21, 2024
8803a58
Merge branch 'main' into allow_import_resources
tmokmss Feb 3, 2024
1051e8f
Merge branch 'main' into allow_import_resources
tmokmss Feb 13, 2024
af43d96
Merge branch 'main' into allow_import_resources
SankyRed Apr 9, 2024
c8be1f5
merge
tmokmss Sep 17, 2024
8eb1549
Update packages/aws-cdk/README.md
tmokmss Sep 20, 2024
293a61f
Apply suggestions from code review
tmokmss Sep 20, 2024
ced4fb1
Update README.md
tmokmss Sep 20, 2024
5b1db10
add missing periods in docs
tmokmss Oct 1, 2024
5785d59
fix merge conflicts
kaizencc Dec 26, 2024
7a995fd
more files for merge
kaizencc Dec 26, 2024
ade0bbd
Merge branch 'main' into conroy/allow_import_resources
kaizencc Dec 27, 2024
547d145
generated files
kaizencc Dec 27, 2024
7767c13
Merge branch 'main' into conroy/allow_import_resources
kaizencc Dec 30, 2024
6d9606c
Merge branch 'main' into conroy/allow_import_resources
kaizencc Dec 31, 2024
1045640
update readme
kaizencc Dec 31, 2024
d43b0a9
Merge branch 'main' into conroy/allow_import_resources
kaizencc Dec 31, 2024
3a51b70
renames
kaizencc Dec 31, 2024
1e7defd
Merge branch 'conroy/allow_import_resources' of https://github.com/aw…
kaizencc Dec 31, 2024
5003452
camelcase generated file
kaizencc Dec 31, 2024
6d22380
more readme
kaizencc Dec 31, 2024
3a37256
Merge branch 'main' into conroy/allow_import_resources
mergify[bot] Jan 1, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,65 @@ integTest(
}),
);

integTest('deploy with import-existing-resources true', withDefaultFixture(async (fixture) => {
const stackArn = await fixture.cdkDeploy('test-2', {
options: ['--no-execute', '--import-existing-resources'],
captureStderr: false,
});
// verify that we only deployed a single stack (there's a single ARN in the output)
expect(stackArn.split('\n').length).toEqual(1);

const response = await fixture.aws.cloudFormation.send(new DescribeStacksCommand({
StackName: stackArn,
}));
expect(response.Stacks?.[0].StackStatus).toEqual('REVIEW_IN_PROGRESS');

// verify a change set was successfully created
// Here, we do not test whether a resource is actually imported, because that is a CloudFormation feature, not a CDK feature.
const changeSetResponse = await fixture.aws.cloudFormation.send(new ListChangeSetsCommand({
StackName: stackArn,
}));
const changeSets = changeSetResponse.Summaries || [];
expect(changeSets.length).toEqual(1);
expect(changeSets[0].Status).toEqual('CREATE_COMPLETE');
expect(changeSets[0].ImportExistingResources).toEqual(true);
}));

integTest('deploy without import-existing-resources', withDefaultFixture(async (fixture) => {
const stackArn = await fixture.cdkDeploy('test-2', {
options: ['--no-execute'],
captureStderr: false,
});
// verify that we only deployed a single stack (there's a single ARN in the output)
expect(stackArn.split('\n').length).toEqual(1);

const response = await fixture.aws.cloudFormation.send(new DescribeStacksCommand({
StackName: stackArn,
}));
expect(response.Stacks?.[0].StackStatus).toEqual('REVIEW_IN_PROGRESS');

// verify a change set was successfully created and ImportExistingResources = false
const changeSetResponse = await fixture.aws.cloudFormation.send(new ListChangeSetsCommand({
StackName: stackArn,
}));
const changeSets = changeSetResponse.Summaries || [];
expect(changeSets.length).toEqual(1);
expect(changeSets[0].Status).toEqual('CREATE_COMPLETE');
expect(changeSets[0].ImportExistingResources).toEqual(false);
}));

integTest('deploy with method=direct and import-existing-resources fails', withDefaultFixture(async (fixture) => {
const stackName = 'iam-test';
await expect(fixture.cdkDeploy(stackName, {
options: ['--import-existing-resources', '--method=direct'],
})).rejects.toThrow('exited with error');

// Ensure stack was not deployed
await expect(fixture.aws.cloudFormation.send(new DescribeStacksCommand({
StackName: fixture.fullStackName(stackName),
}))).rejects.toThrow('does not exist');
}));

integTest(
'update to stack in ROLLBACK_COMPLETE state will delete stack and create a new one',
withDefaultFixture(async (fixture) => {
Expand Down
40 changes: 40 additions & 0 deletions packages/aws-cdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,41 @@ $ cdk deploy --method=prepare-change-set --change-set-name MyChangeSetName
For more control over when stack changes are deployed, the CDK can generate a
CloudFormation change set but not execute it.

#### Import existing resources

You can utilize the AWS CloudFormation
[feature](https://aws.amazon.com/about-aws/whats-new/2023/11/aws-cloudformation-import-parameter-changesets/)
that automatically imports resources in your template that already exist in your account.
To do so, pass the `--import-existing-resources` flag to the `deploy` command:

```console
$ cdk deploy --import-existing-resources
```

This automatically imports resources in your CDK application that represent
unmanaged resources in your account. It reduces the manual effort of import operations and
avoids deployment failures due to naming conflicts with unmanaged resources in your account.

Use the `--method=prepare-change-set` flag to review which resources are imported or not before deploying a changeset.
You can inspect the change set created by CDK from the management console or other external tools.

```console
$ cdk deploy --import-existing-resources --method=prepare-change-set
```

Use the `--exclusively` flag to enable this feature for a specific stack.

```console
$ cdk deploy --import-existing-resources --exclusively StackName
```

Only resources that have custom names can be imported using `--import-existing-resources`.
For more information, see [name type](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html).
To import resources that do not accept custom names, such as EC2 instances,
use the `cdk import` instead.
Visit [Bringing existing resources into CloudFormation management](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resource-import.html)
for more details.

#### Ignore No Stacks

You may have an app with multiple environments, e.g., dev and prod. When starting
Expand Down Expand Up @@ -619,6 +654,11 @@ To import an existing resource to a CDK stack, follow the following steps:
5. When `cdk import` reports success, the resource is managed by CDK. Any subsequent
changes in the construct configuration will be reflected on the resource.

NOTE: You can also import existing resources by passing `--import-existing-resources` to `cdk deploy`.
This parameter only works for resources that support custom physical names,
such as S3 Buckets, DynamoDB Tables, etc...
For more information, see [Request Parameters](https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_CreateChangeSet.html#API_CreateChangeSet_RequestParameters).

#### Limitations

This feature currently has the following limitations:
Expand Down
13 changes: 11 additions & 2 deletions packages/aws-cdk/lib/api/deploy-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,13 @@ export interface ChangeSetDeploymentMethod {
* If not provided, a name will be generated automatically.
*/
readonly changeSetName?: string;

/**
* Indicates if the change set imports resources that already exist.
*
* @default false
*/
readonly importExistingResources?: boolean;
}

export async function deployStack(options: DeployStackOptions): Promise<DeployStackResult> {
Expand Down Expand Up @@ -462,7 +469,8 @@ class FullCloudFormationDeployment {
private async changeSetDeployment(deploymentMethod: ChangeSetDeploymentMethod): Promise<DeployStackResult> {
const changeSetName = deploymentMethod.changeSetName ?? 'cdk-deploy-change-set';
const execute = deploymentMethod.execute ?? true;
const changeSetDescription = await this.createChangeSet(changeSetName, execute);
const importExistingResources = deploymentMethod.importExistingResources ?? false;
const changeSetDescription = await this.createChangeSet(changeSetName, execute, importExistingResources);
await this.updateTerminationProtection();

if (changeSetHasNoChanges(changeSetDescription)) {
Expand Down Expand Up @@ -525,7 +533,7 @@ class FullCloudFormationDeployment {
return this.executeChangeSet(changeSetDescription);
}

private async createChangeSet(changeSetName: string, willExecute: boolean) {
private async createChangeSet(changeSetName: string, willExecute: boolean, importExistingResources: boolean) {
await this.cleanupOldChangeset(changeSetName);

debug(`Attempting to create ChangeSet with name ${changeSetName} to ${this.verb} stack ${this.stackName}`);
Expand All @@ -537,6 +545,7 @@ class FullCloudFormationDeployment {
ResourcesToImport: this.options.resourcesToImport,
Description: `CDK Changeset for execution ${this.uuid}`,
ClientToken: `create${this.uuid}`,
ImportExistingResources: importExistingResources,
...this.commonPrepareOptions(),
});

Expand Down
7 changes: 7 additions & 0 deletions packages/aws-cdk/lib/cli-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,13 @@ export interface DeployOptions {
*/
readonly method?: string;

/**
* Indicates if the stack set imports resources that already exist.
*
* @default - false
*/
readonly importExistingResources?: boolean;

/**
* Always deploy stack even if templates are identical
*
Expand Down
6 changes: 6 additions & 0 deletions packages/aws-cdk/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,27 +292,33 @@ export async function exec(args: string[], synthesizer?: Synthesizer): Promise<n
if (args.changeSetName) {
throw new ToolkitError('--change-set-name cannot be used with method=direct');
}
if (args.importExistingResources) {
throw new Error('--import-existing-resources cannot be enabled with method=direct');
}
deploymentMethod = { method: 'direct' };
break;
case 'change-set':
deploymentMethod = {
method: 'change-set',
execute: true,
changeSetName: args.changeSetName,
importExistingResources: args.importExistingResources,
};
break;
case 'prepare-change-set':
deploymentMethod = {
method: 'change-set',
execute: false,
changeSetName: args.changeSetName,
importExistingResources: args.importExistingResources,
};
break;
case undefined:
deploymentMethod = {
method: 'change-set',
execute: args.execute ?? true,
changeSetName: args.changeSetName,
importExistingResources: args.importExistingResources,
};
break;
}
Expand Down
1 change: 1 addition & 0 deletions packages/aws-cdk/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export async function makeConfig(): Promise<CliConfig> {
requiresArg: true,
desc: 'How to perform the deployment. Direct is a bit faster but lacks progress information',
},
'import-existing-resources': { type: 'boolean', desc: 'Indicates if the stack set imports resources that already exist.', default: false },
'force': { alias: 'f', type: 'boolean', desc: 'Always deploy stack even if templates are identical', default: false },
'parameters': { type: 'array', desc: 'Additional parameters passed to CloudFormation at deploy time (STACK:KEY=VALUE)', default: {} },
'outputs-file': { type: 'string', alias: 'O', desc: 'Path to file where stack outputs will be written as JSON', requiresArg: true },
Expand Down
5 changes: 5 additions & 0 deletions packages/aws-cdk/lib/parse-command-line-arguments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,11 @@ export function parseCommandLineArguments(args: Array<string>): any {
requiresArg: true,
desc: 'How to perform the deployment. Direct is a bit faster but lacks progress information',
})
.option('import-existing-resources', {
default: false,
type: 'boolean',
desc: 'Indicates if the stack set imports resources that already exist.',
})
.option('force', {
default: false,
alias: 'f',
Expand Down
35 changes: 35 additions & 0 deletions packages/aws-cdk/test/api/deploy-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1126,6 +1126,41 @@ describe('disable rollback', () => {
});
});

describe('import-existing-resources', () => {
test('is disabled by default', async () => {
// WHEN
await deployStack({
...standardDeployStackArguments(),
deploymentMethod: {
method: 'change-set',
},
});

// THEN
expect(mockCloudFormationClient).toHaveReceivedCommandWith(CreateChangeSetCommand, {
...expect.anything,
ImportExistingResources: false,
} as CreateChangeSetCommandInput);
});

test('is added to the CreateChangeSetCommandInput', async () => {
// WHEN
await deployStack({
...standardDeployStackArguments(),
deploymentMethod: {
method: 'change-set',
importExistingResources: true,
},
});

// THEN
expect(mockCloudFormationClient).toHaveReceivedCommandWith(CreateChangeSetCommand, {
...expect.anything,
ImportExistingResources: true,
} as CreateChangeSetCommandInput);
});
});

test.each([
// From a failed state, a --no-rollback is possible as long as there is not a replacement
[StackStatus.UPDATE_FAILED, 'no-rollback', 'no-replacement', 'did-deploy-stack'],
Expand Down
Loading