Skip to content

Commit f0f5bbc

Browse files
Add documentation for AWS CDK Refactor with examples and usage guidance.
1 parent b8eac4a commit f0f5bbc

File tree

9 files changed

+914
-0
lines changed

9 files changed

+914
-0
lines changed

notes.md

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
Plugins
2+
=======
3+
The |cdk| toolkit provides extension points that enable users to augment the features provided by
4+
the toolkit. There is currently only one extension point, allowing users to define custom AWS
5+
credential providers, but other extension points may be added in the future as the needs arise.
6+
Loading Plugins
7+
---------------
8+
Plugins can be loaded by providing the Node module name (or path) to the CDK toolkit:
9+
1. Using the ``--plugin`` command line option (which can be specified multiple times):
10+
.. code-block:: console
11+
$ cdk list --plugin=module
12+
$ cdk deploy --plugin=module_1 --plugin=module_2
13+
2. Adding entries to the ``~/.cdk.json`` or ``cdk.json`` file:
14+
.. code-block:: js
15+
{
16+
// ...
17+
"plugin": [
18+
"module_1",
19+
"module_2"
20+
],
21+
// ...
22+
}
23+
Authoring Plugins
24+
-----------------
25+
Plugins must be authored in TypeScript or Javascript, and are defined by implementing a Node module
26+
that implements the following protocol, and using :js:class:`~aws-cdk.PluginHost` methods:
27+
.. tabs::
28+
.. code-tab:: typescript
29+
import cdk = require('aws-cdk');
30+
export = {
31+
version: '1', // Version of the plugin infrastructure (currently always '1')
32+
init(host: cdk.PluginHost): void {
33+
// Your plugin initialization hook.
34+
// Use methods of ``host`` to register custom code with the CDK toolkit
35+
}
36+
};
37+
.. code-tab:: javascript
38+
export = {
39+
version: '1', // Version of the plugin infrastructure (currently always '1')
40+
init(host) {
41+
// Your plugin initialization hook.
42+
// Use methods of ``host`` to register custom code with the CDK toolkit
43+
}
44+
};
45+
Credential Provider Plugins
46+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
47+
Custom credential providers are classes implementing the
48+
:js:class:`~aws-cdk.CredentialProviderSource` interface, and registered to the toolkit using
49+
the :js:func:`~aws-cdk.PluginHost.registerCredentialProviderSource` method.
50+
.. tabs::
51+
.. code-tab:: typescript
52+
import cdk = require('aws-cdk');
53+
import aws = require('aws-sdk');
54+
class CustomCredentialProviderSource implements cdk.CredentialProviderSource {
55+
public async isAvailable(): Promise<boolean> {
56+
// Return ``false`` if the plugin could determine it cannot be used (for example,
57+
// if it depends on files that are not present on this host).
58+
return true;
59+
}
60+
public async canProvideCredentials(accountId: string): Promise<boolean> {
61+
// Return ``false`` if the plugin is unable to provide credentials for the
62+
// requested account (for example if it's not managed by the credentials
63+
// system that this plugin adds support for).
64+
return true;
65+
}
66+
public async getProvider(accountId: string, mode: cdk.Mode): Promise<aws.Credentials> {
67+
let credentials: aws.Credentials;
68+
// Somehow obtain credentials in ``credentials``, and return those.
69+
return credentials;
70+
}
71+
}
72+
export = {
73+
version = '1',
74+
init(host: cdk.PluginHost): void {
75+
// Register the credential provider to the PluginHost.
76+
host.registerCredentialProviderSource(new CustomCredentialProviderSource());
77+
}
78+
};
79+
.. code-tab:: javascript
80+
class CustomCredentialProviderSource {
81+
async isAvailable() {
82+
// Return ``false`` if the plugin could determine it cannot be used (for example,
83+
// if it depends on files that are not present on this host).
84+
return true;
85+
}
86+
async canProvideCredentials(accountId) {
87+
// Return ``false`` if the plugin is unable to provide credentials for the
88+
// requested account (for example if it's not managed by the credentials
89+
// system that this plugin adds support for).
90+
return true;
91+
}
92+
async getProvider(accountId, mode) {
93+
let credentials;
94+
// Somehow obtain credentials in ``credentials``, and return those.
95+
return credentials;
96+
}
97+
}
98+
export = {
99+
version = '1',
100+
init(host) {
101+
// Register the credential provider to the PluginHost.
102+
host.registerCredentialProviderSource(new CustomCredentialProviderSource());
103+
}
104+
};
105+
Note that the credentials obtained from the providers for a given account and mode will be cached,
106+
and as a consequence it is strongly advised that the credentials objects returned are self-refreshing,
107+
as descibed in the `AWS SDK for Javascript documentation <https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Credentials.html>`_.
108+
Reference
109+
---------
110+
.. js:module:: aws-cdk
111+
CredentialProviderSource *(interface)*
112+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
113+
.. js:class:: CredentialProviderSource
114+
.. js:attribute:: name
115+
A friendly name for the provider, which will be used in error messages, for example.
116+
:type: ``string``
117+
.. js:method:: isAvailable()
118+
Whether the credential provider is even online. Guaranteed to be called before any of the other functions are called.
119+
:returns: ``Promise<boolean>``
120+
.. js:method:: canProvideCredentials(accountId)
121+
Whether the credential provider can provide credentials for the given account.
122+
:param string accountId: the account ID for which credentials are needed.
123+
:returns: ``Promise<boolean>``
124+
.. js:method:: getProvider(accountId, mode)
125+
Construct a credential provider for the given account and the given access mode.
126+
Guaranteed to be called only if canProvideCredentails() returned true at some point.
127+
:param string accountId: the account ID for which credentials are needed.
128+
:param aws-cdk.Mode mode: the kind of operations the credentials are intended to perform.
129+
:returns: ``Promise<aws.Credentials>``
130+
Mode *(enum)*
131+
^^^^^^^^^^^^^
132+
.. js:class:: Mode
133+
.. js:data:: ForReading
134+
Credentials are inteded to be used for read-only scenarios.
135+
.. js:data:: ForWriting
136+
Credentials are intended to be used for read-write scenarios.
137+
Plugin *(interface)*
138+
^^^^^^^^^^^^^^^^^^^^
139+
.. js:class:: Plugin()
140+
.. js:attribute:: version
141+
The version of the plug-in interface used by the plug-in. This will be used by
142+
the plug-in host to handle version changes. Currently, the only valid value for
143+
this attribute is ``'1'``.
144+
:type: ``string``
145+
.. js:method:: init(host)
146+
When defined, this function is invoked right after the plug-in has been loaded,
147+
so that the plug-in is able to initialize itself. It may call methods of the
148+
:js:class:`~aws-cdk.PluginHost` instance it receives to register new
149+
:js:class:`~aws-cdk.CredentialProviderSource` instances.
150+
:param aws-cdk.PluginHost host: The |cdk| plugin host
151+
:returns: ``void``
152+
PluginHost
153+
^^^^^^^^^^
154+
.. js:class:: PluginHost()
155+
.. js:data:: instance
156+
:type: :js:class:`~aws-cdk.PluginHost`
157+
.. js:method:: load(moduleSpec)
158+
Loads a plug-in into this PluginHost.
159+
:param string moduleSpec: the specification (path or name) of the plug-in module to be loaded.
160+
:throws Error: if the provided ``moduleSpec`` cannot be loaded or is not a valid :js:class:`~aws-cdk.Plugin`.
161+
:returns: ``void``
162+
.. js:method:: registerCredentialProviderSource(source)
163+
Allows plug-ins to register new :js:class:`~aws-cdk.CredentialProviderSources`.
164+
:param aws-cdk.CredentialProviderSources source: a new CredentialProviderSource to register.
165+
:returns: ``void``

v2/guide/DELETE_refactor.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# CDK refactor and cross-stack references
2+
3+
## Executive summary
4+
5+
CloudFormation’s stack refactoring API does not allow modifications (other than resource moves). But we want to give CDK users a more flexible experience, by allowing them to move resources while also adding and removing other resources. These additions and removals will be ignored by the toolkit, and only what has moved will be sent to the API for refactoring. One implication of this flexibility is that the refactor opeartion may leave the infrastructure in a state that is not the same as the CDK application.
6+
7+
This becomes a problem when this putative intermediate state has cross-stack references that don’t exist in the CDK application. Due to the way CloudFormation deals with references and exports, we would either not be able to create this kind of reference, or create it in a way that blocks subsequent deployments.
8+
9+
The proposed solution in this document is to change the matching algorithm to only allow a refactor if the deployed stack and the CDK output are exactly the same, up to resource locations. So, instead of building a more flexible experience on top of CFN, we impose the same constraints they do. In exchange, we gain predictability and also, when we refuse to do a refactor, it's easy to explain to the user why.
10+
11+
## What's wrong
12+
13+
Suppose you have two resources, with logical IDs `Foo` and `Bar`, living in the same stack. `Foo` has a reference to `Bar`:
14+
15+
```
16+
Resources:
17+
Foo:
18+
...
19+
Properties:
20+
SomeProp:
21+
Ref: Bar # <-- in-stack reference
22+
23+
Bar: ...
24+
```
25+
26+
Using CloudFormation's stack refactoring API, you can move `Bar` to another stack, as long as you keep `Foo` pointing to it. The way to represent this cross-stack reference in CloudFormation is to create an output in the target stack referencing the resource you want. This output must have an export name which can then be imported using the `Fn::ImportValue` intrinsic function in the consuming stack:
27+
28+
```
29+
# Original stack
30+
Resources:
31+
Foo:
32+
...
33+
Properties:
34+
SomeProp:
35+
Fn::ImportValue: RefBarExport # <-- replaced with a cross-stack one
36+
```
37+
38+
```
39+
# New stack
40+
Resources:
41+
Bar: ...
42+
Outputs:
43+
Value:
44+
Ref: Bar
45+
Export:
46+
Name: RefBarExport
47+
```
48+
49+
Converting an in-stack reference into a cross-stack reference like this poses no problems when using CloudFormation templates in isolation. But when the templates are the output of a CDK application, there is a case where we may run into problems. Here is an example:
50+
[Image: Untitled-2025-06-24-1119.excalidraw.png]
51+
Suppose you have a Lambda function and a DynamoDB table in the same stack, with the function referencing the table (via a `Ref`, for example). This is the deployed state of the infrastructure. Then, in their CDK application, the user moves the table to another stack and, at the same time, replaces the function with another one, `Lambda'`. The matching algorithm will detect that the table was moved from Stack X to Stack Y. But since `Lambda'` is different from `Lambda`, the function is not going to be part of the mappings sent to the stack refactoring API. The result of this refactor is that the table will be moved to the other stack, but the function will stay where it was. Since the stack refactoring API doesn’t allow modifications, `Lambda` still needs to reference `Table`, even if they are in different stacks. The natural thing to do would be to convert the `Ref` into an `Fn::ImportValue`, as described above.
52+
53+
But here is the problem: `Fn::ImportValue` needs a corresponding export. And, in this case, there is none. Note how Stack Y, in the CDK output has no need to export anything, even after the move. We could create an ad-hoc export just for the refactor, but that would cause another problem: the next deploy would fail, as it would try to remove the export while an import for it (in `Lambda`) still exists. We could then try to solve that, by doing a corrective deployment immediately after. First deploying Stack X, thus removing the importer, then deploying Stack Y, which would safely remove the export and add the new function. But in the period between the two deploys, there would be no Lambda function at all, potentially causing downtime for the customer.
54+
55+
In summary: **if a refactor would create cross-stack references that don’t exist in the local CDK output, that refactor is invalid, and cannot be executed.**
56+
57+
## Possible solutions
58+
59+
### 1. Break references before deployment
60+
61+
This is a muti-step solution. Before using the stack refactor API, we will first do a deploy to break all references between resources. Here, “breaking a reference” means replacing CloudFormation intrinsic function calls with the value those functions resolve to:
62+
63+
* For `Fn::ImportValue`, we can get the export value from `describeStacks`.
64+
* For `Ref`, we can get the physical ID from `describeStackResources`.
65+
* For `Fn::GetAtt`, we need an additional step. After getting the physical ID from `describeStackResources`, we use CCAPI’s `getResource` method to read the value of the attribute.
66+
* For `Fn::Sub`, we replace each variable that is not in the map, using either the method for `Ref` or `Fn::GetAtt` described in the two previous points.
67+
68+
The next step is the refactor itself. Since there are no references before the refactor, there won’t be any after, either. And then the next deploy is safe because there is no risk of it trying to remove an export that is in use. Note that the next deploy will reinstate all the references that exist in the CDK app. To recap, these are the states the stacks would go through in the process, in the example above:
69+
70+
71+
[Image: states-with-ref-breaking.excalidraw.png]
72+
73+
The problem with this option is that, in the last deployment, if we pick the wrong order, we might cause downtime for customers. In this example, if we deploy stack X before stack Y, there will be a period of time in which no Lambda function is available. This is much higher risk than causing a deployment failure.
74+
75+
### 2. Stop the refactor and give an error message
76+
77+
If we don't have an output to use (and knowing that we can't create one), stop the process, and let the user know that this is an impossible situation. We can marginally reduce the chance of this happening by adding some heuristics to the matching algorithm. These heuristics can exploit patterns in the construct tree. For example, in certain cases we can detect groups of resources that should be moved together because they belong to the same high-level construct.
78+
79+
But there will always be cases that fall outside any of these patterns, such as in the example above.
80+
81+
### 3. Overdo the refactor
82+
83+
This is essentially taking the idea of moving resources together to the extreme, with the following algorithm:
84+
85+
```
86+
1. Produce an initial list of mappings;
87+
2. Flag all the problematic references that would be created by this refactor;
88+
3. While there are flagged references:
89+
4. For each flagged reference, include its source in the list of mappings;
90+
5. Re-flag all the references as necessary;
91+
```
92+
93+
Line 4 would guarantee that, instead of generating a problematic cross-stack reference, we would preserve the original in-stack reference (only moved wholesale to another stack). By doing this, however, we could create new problematic references, so we repeat the process until the graph is cleared up.
94+
95+
The downside of this solution is that we may end up refactoring more than the user intended to. For example, moving large numbers of resources across stacks, when the user only wanted a single resource moved.
96+
97+
### 4. Forbid any changes (preferred)
98+
99+
We could go to the other extreme, and forbid any additions, deletions or modifications in the local state compared to the deployed state. We would sacrifice flexibility, but the problem would go away.
100+
101+
### 5. Trust mode for refactoring
102+
103+
The reason this problem arises in the first place is that CloudFormation’s refactor API does not allow modifications. This forces us to compute a new state that includes the resource moves the user has made, but no other changes (additions, deletions or modifications of resources). A successful refactor operation would move the user’s stacks to this new state. But this also creates a mismatch between what is deployed and the CDK output, as in the case of the example above.
104+
105+
If CloudFormation had a “trust mode”, with which clients could include modifications (and therefore CloudFormation made fewer validations), we could send the current state of the CDK application, as is, without having to compute this intermediate state. This trust mode could be enabled with a flag in the API.
106+
107+
*This is the cleanest solution, and the one we would prefer. But most of the work would have to be done on the CloudFormation service. To our knowledge, this is not even in their plans.*
108+
109+
### 6. Direct cross-stack references
110+
111+
Contrary to the old saying, we could solve this problem by *removing* a layer of indirection. Instead of having an output mediating the reference between two resources, CloudFormation could allow direct references, maybe using the existing `Ref` and `Fn::GetAtt` intrinsic functions with fully qualified names (e.g., `{Ref: "SomeStack.SomeResource"}`) or, if necessary, by introducing new functions that work across stacks.
112+
113+
Not only would this simplify the implementation a lot and reduce the risk of bugs, it would also completely solve the problem discussed in this document. If there is no output to create and/or delete, there is no risk of users getting their resources stuck in a deadly embrace. And we could do exactly the refactor the user wants: no more and no less.
114+
115+
The CloudFormation team has already done some [investigation](https://quip-amazon.com/r0TpAvT4CD83/CloudFormation-Cross-AccountRegion-Reference-Solutions) on the problem of cross-stack referencing, and are planning to deliver a new intrinsic function this year. When they deliver this, we can reassess whether we want to switch to the new intrinsic function.

v2/guide/book.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ include::chapter-deploy.adoc[leveloffset=+1]
5757

5858
include::blueprints.adoc[leveloffset=+1]
5959

60+
include::refactor.adoc[leveloffset=+1]
61+
6062
include::plugins.adoc[leveloffset=+1]
6163

6264
include::toolkit-library.adoc[leveloffset=+1]

v2/guide/doc-history.adoc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ The table below represents significant documentation milestones. We fix errors a
2323
[.updates]
2424
== Updates
2525

26+
[.update,date="2025-09-08"]
27+
=== Add documentation for preserving deployed resources when refactoring CDK code
28+
29+
With {aws} Cloud Development Kit ({aws} CDK) refactoring, you can refactor your CDK code, such as renaming constructs, moving resources between stacks, and reorganizing your application, while preserving your deployed resources instead of replacing them. For more information, see link:https://docs.aws.amazon.com/cdk/v2/guide/refactor.html[Preserve deployed resources when refactoring CDK code].
30+
31+
[.update,date="2025-08-29"]
32+
=== Add documentation for opting out of CDK CLI telemetry
33+
Learn how to opt out of CDK CLI telemetry in advance of its release using the CDK CLI, context values, or an environment variable. For more information, see link:https://docs.aws.amazon.com/cdk/v2/guide/cli-telemetry.html[Configure {aws} CDK CLI telemetry].
34+
35+
[.update,date="2025-08-20"]
36+
=== Add documentation for CDK feature flag configuration
37+
38+
Use the {aws} CDK CLI `cdk flags` command to view your current feature flag configurations and modify them as needed. For more information, see link:https://docs.aws.amazon.com/cdk/v2/guide/ref-cli-cmd-flags.html[`cdk flags`].
39+
2640
[.update,date="2025-03-27"]
2741
=== Document bootstrap template versions v26 and v27
2842

0 commit comments

Comments
 (0)