-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
chore(cli): statically guarantee that assets aren't forgotten around makeBodyParameter #31750
base: main
Are you sure you want to change the base?
Conversation
…`makeBodyParameter` The protocol of `makeBodyParameter` has 2 requirements: - Stack assets must already be uploaded (because the stack template will be among them). - It may produce a new stack template asset to be uploaded. This protocol was easy to misuse, so change the API to make it more resilient to misuse: - Before calling, `makeBodyParameter` now requires an `AssetsPublishedProof` value. This is a value that can only be produced by calling certain functions that publish assets, and cannot be constructed from any other source location than the module that defines it. This ensures that `makeBodyParameter` cannot be called without the uploading of assets. - The return value of `makeBodyParameter` no longer is just CloudFormation API parameters, with the implicit assumption that any assets added to the `AssetManifestBuilder` would be uploaded as well: instead, the return value is either CloudFormation parameters, or an object with a function that trades adding to an `AssetManifestBuilder` for the CloudFormation parameters.
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 pull request linter has failed. See the aws-cdk-automation comment below for failure reasons. If you believe this pull request should receive an exemption, please comment and provide a justification.
A comment requesting an exemption should contain the text Exemption Request
. Additionally, if clarification is needed add Clarification Request
to a comment.
const PUBLISH_ASSET_PROOF_SYM = Symbol('publish_assets_proof'); | ||
|
||
/** | ||
* This interface represents proof that assets have been published. | ||
* | ||
* Objects of this type can only be obtained by calling functions in this module. | ||
*/ | ||
export interface AssetsPublishedProof { | ||
[PUBLISH_ASSET_PROOF_SYM]: true; | ||
} |
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.
Using Symbol
vs Symbol.for()
will prevent anyone else from implementing AssetsPublishedProof
- which might well be what you want. But than why make it an interface and/or export it. This feels like a recipe for confusion in the future.
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.
This is exactly what I want -- I want any bit of code (also in other modules) be able to require an AssetsPublishedProof
, while only this module can generate an instance of one.
* This is only allowed in one location: the function `deployStack` in | ||
* `deploy-stack.ts`. Do not call this function anywhere but there. |
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.
Meh. So this is the Get-out-of-jail-free card if you pinky promise to not misuse it?
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 mean. Yes. It's a pragmatic compromise without completely overhauling the codebase. The name should be clear enough to be flagged in code reviews if used for any other purpose.
@@ -419,27 +419,26 @@ export class Deployments { | |||
const { stackSdk, resolvedEnvironment, envResources } = await this.prepareSdkFor(stackArtifact, undefined, Mode.ForReading); | |||
const cfn = stackSdk.cloudFormation(); | |||
|
|||
await uploadStackTemplateAssets(stackArtifact, this); | |||
const proof = await uploadStackTemplateAssets(stackArtifact, this.sdkProvider, resolvedEnvironment); |
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.
Unrelated, but why does this method even publish assets?
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.
Every API that calls CloudFormation with a template may need to publish assets, because with a LegacyStackSynthesizer
the stack template is not automatically written to disk and published; the CLI will need to do that just-in-time.
resources: EnvironmentResources, | ||
sdk: ISDK, | ||
overrideTemplate?: any, | ||
): Promise<TemplateBodyParameter> { | ||
_assetsPublishedProof: AssetsPublishedProof, |
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.
Unused? Don't we need to check this?
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 fact that you can pass an object that passes the type checker is proof that you have one.
Yes someone could do as any
, but we're not trying to protect against someone who is willingly trying to bypass the system, we are protecting against accidental misuse.
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 like the direction, but I'm not wild about the implementation.
Good: makeBodyParameter
is has fewer side-effects now.
Bad: we now make every caller of makeBodyParameter
implement the (more or less) same switch (bodyAction.type) {}
.
You should also add unit tests to this.
I suppose we could wrap that in a helper function with a callback. But the current code has the advantage of being simple and obvious. |
Note: when merging this from main, there are a lot of things that need to be resolved that conflict with #31904, so we need to merge that first. |
✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.
➡️ PR build request submitted to A maintainer must now check the pipeline and add the |
…nto huijbers/static-guarantees
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
The protocol of
makeBodyParameter
has 2 requirements:This protocol was easy to misuse, so change the API to make it more resilient to misuse:
makeBodyParameter
now requires anAssetsPublishedProof
value. This is a value that can only be produced by calling certain functions that publish assets, and cannot be constructed from any other source location than the module that defines it. This ensures thatmakeBodyParameter
cannot be called without the uploading of assets.makeBodyParameter
no longer is just CloudFormation API parameters, with the implicit assumption that any assets added to theAssetManifestBuilder
would be uploaded as well: instead, the return value is either CloudFormation parameters, or an object with a function that trades adding to anAssetManifestBuilder
for the CloudFormation parameters.After merging with #31904, this also moves the
allowCrossAccountAssetPublishing
check intoToolkitInfo
, next to all the other bootstrap stack routines. In practice it is indirected throughEnvironmentResources
, and has a different implementation for no-environment-resources.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license