-
Notifications
You must be signed in to change notification settings - Fork 4k
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(core): democratize synthesis and introduce artifacts #1889
Conversation
This change enables each construct in the tree to participate in synthesis by overriding the "synthesize" method and emitting files to the session directory. It is a small step towards generalizing how synthesis artifacts are being produced, built and deployed by the CDK. This change moves the code that synthesizes a CloudFormation template from a CDK stack to the `Stack` class and emits a `.template.json` file for each stack. The CLI hasn't been changed to work with these new files yet, so the output of this file is ALSO included in the main manifest (defined `cxapi.OUTFILE_NAME`), which is the only file currently being read by the CLI.
Instead of an empty protected method, use an interface to discover if a construct is synthesizable.
} | ||
|
||
// add an artifact that represents this stack | ||
session.addArtifact(this.node.id, artifact); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't you be using node.uniqueId
instead of node.id
? Same question for dependencies. Are we leaving it up to the caller to ensure the uniqueness of the id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using node.id
for backwards compatibility purposes. We are going to add support for composite apps (#1582) and will address there.
I decided that for now, I'll introduce the low-level API and the caller will be responsible. As you observed, not many constructs are going to participate in synthesis so I believe we can leave it to them to take responsibility and apply a best practice to use node.uniqueId
for now. Happy to iterate after this PR lands to make more implicit/automatic.
properties: { template } | ||
}; | ||
|
||
const deps = this.dependencies().map(s => s.node.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reasoning behind this? Does a construct dependency always imply a droplet dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are stack dependencies, not construct dependencies.
// write the CloudFormation template as a JSON file | ||
session.store.writeJson(template, this.toCloudFormation()); | ||
|
||
const artifact: cxapi.Artifact = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So it looks like staging will use the same format as cloud assembly? How would I communicate something like 'build this folder'? If it's an Artifact
, then how do we plan on distinguishing between synth-only artifacts and cloud-assembly artifacts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The output of App.run()
is an intermediate step towards a cloud assembly. I've added a build: any
to Artifact
as a placeholder, with the idea that the toolkit will use this information to build the intermediate artifact and produce the final assembly.
I am not in love with this design, but maybe it's the pragmatic thing to do, especially given the simple case (CloudFormation template) is a no-op between staging=>assembly.
Happy to hear your thoughts. Maybe we can start with this and iterate and see where it takes us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this some more. I think you are right... Something is still not clicking. Crap.
NEED MORE THINKING.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, here's where I am at right now.
Let's say that app.run()
produces two files: manifest.json
, which is the cloud assembly manifest proper and build.json
which build instructions for the toolkit.
Let's assume that manifest.json
is not touched by the toolkit and that the build system (which is not modeled yet), will produce outputs that are pre-referenced by the emitted manifest.
As you can see in the example below, the artifact MyHandlerCode
is of type aws:s3:object
(because this is the resource that will eventually be created) and the filePath
property points to file://3cdf5efc05e1254da7d751257c344e74.zip
, which doesn't exist when app.run()
ends. The file location is always relative to the assembly root.
{
"artifacts": {
"MyStack": {
"type": "aws:cloudformation:stack",
"environment": "aws://account/region",
"properties": {
"name": "my-stack-name",
"parameters": {
"MyHandlerLocation": "s3://${MyHandlerCode.bucket}/${MyHandlerCode.key}"
},
"template": "file://${assembly}/my-stack.template.json"
}
},
"MyHandlerCode": {
"type": "aws:s3:object",
"properties": {
"filePath": "file://3cdf5efc05e1254da7d751257c344e74.zip"
}
}
}
}
Completely decoupled from that, we also emit build.json
(which is not modeled yet) but basically tells the toolkit how to produce 3cdf5efc05e1254da7d751257c344e74.zip
. This is where we can get wild with your declarative build system.
So, for the above example, let's say the handler is a Java project and we use lambda builders, then build.json
will model the following process:
- (future) Generate some magical code into the project directory (only referenced in
build.json
, not needed bymanifest.json
). - Run lambda-builders-java for the project directory inside the docker image
- Zip up the resulting output (say it's not zipped by lambda-builders, I don't know)
- Output to
3cdf5efc05e1254da7d751257c344e74.zip
It is important that artifacts and build commands are completely decoupled, and only connect when a build output is used as an artifact's input. For example, the build output name is based on the content hash of the project directory, so if multiple assets use the same project directory, it's the same build output.
} | ||
} | ||
|
||
export interface ISessionStore { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would I make a project directory available for a build? Would I symlink or manually copy each file into the store?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am thinking that project directories will be referenced directly in the Artifact's build
field without copying them to the staging area.
I think that code generation and build will need to happen in-place in order to allow IDE integration to be sane. Otherwise, I can't see how users will be able to sanely consume any generated code.
You mentioned something yesterday about generating complete libraries and not code, and I wanted to loop back on that and tell you that this was not my intention when we talked about code generation last week, and I actually don't think it's very pragmatic to generate full libraries. All I was saying is that you should assume that users never edit or check in any generated code, so if you want users to consume something that you generated, they would do something like:
import { GeneratedClass } from '../cdk.generated';
// use GeneratedClass
Now, the CDK can generate it's code into cdk.generated
.
@sam-goodwin I've updated the PR with support for I think I will slightly change the structure of the output directory to look like this:
This way, the |
Thanks so much for taking the time to contribute to the AWS CDK ❤️ We will shortly assign someone to review this pull request and help get it
|
This change enables each construct in the tree to participate in synthesis
by overriding the
synthesize
and add "artifacts" and write files to a synthesis session.At the moment, the only construct that implements
synthesize
isStack
which emits anaws:cloudformation:stack
artifact which includes a reference to a template file stored into the synthesis session as well.App.run()
traverses the tree and invokessynthesize
on all constructs.When synthesis is finalized, a manifest is produced and stored in the root
manifest.json
file. This is accordance with the draft specification for cloud assemblies as authored by @RomainMuller and included in this change.For backwards compatibility, and until we implement the required changes in the toolkit, the output also contains
cdk.out
which is 100% compatible with the current output.Related #1716
Related #1893
Pull Request Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license.