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

chore(cli): statically guarantee that assets aren't forgotten around makeBodyParameter #31750

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

rix0rrr
Copy link
Contributor

@rix0rrr rix0rrr commented Oct 14, 2024

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.

After merging with #31904, this also moves the allowCrossAccountAssetPublishing check into ToolkitInfo, next to all the other bootstrap stack routines. In practice it is indirected through EnvironmentResources, 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

…`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.
@rix0rrr rix0rrr requested a review from a team October 14, 2024 11:04
@aws-cdk-automation aws-cdk-automation requested a review from a team October 14, 2024 11:05
@github-actions github-actions bot added the p2 label Oct 14, 2024
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label Oct 14, 2024
Copy link
Collaborator

@aws-cdk-automation aws-cdk-automation left a 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.

@aws-cdk-automation aws-cdk-automation added the pr/needs-cli-test-run This PR needs CLI tests run against it. label Oct 14, 2024
Comment on lines +222 to +231
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;
}
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines +250 to +251
* This is only allowed in one location: the function `deployStack` in
* `deploy-stack.ts`. Do not call this function anywhere but there.
Copy link
Contributor

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?

Copy link
Contributor Author

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);
Copy link
Contributor

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?

Copy link
Contributor Author

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

@mrgrain mrgrain left a 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.

@rix0rrr
Copy link
Contributor Author

rix0rrr commented Oct 29, 2024

Bad: we now make every caller of makeBodyParameter implement the (more or less) same switch (bodyAction.type) {}.

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.

@rix0rrr
Copy link
Contributor Author

rix0rrr commented Oct 30, 2024

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.

@rix0rrr rix0rrr added the pr-linter/cli-integ-tested Assert that any CLI changes have been integ tested label Nov 6, 2024
@aws-cdk-automation aws-cdk-automation dismissed their stale review November 6, 2024 10:43

✅ Updated pull request passes all PRLinter validations. Dismissing previous PRLinter review.

@aws-cdk-automation aws-cdk-automation removed the pr/needs-cli-test-run This PR needs CLI tests run against it. label Nov 6, 2024
@rix0rrr rix0rrr self-assigned this Nov 6, 2024
@aws-cdk-automation
Copy link
Collaborator

➡️ PR build request submitted to test-main-pipeline ⬅️

A maintainer must now check the pipeline and add the pr-linter/cli-integ-tested label once the pipeline succeeds.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildv2Project1C6BFA3F-wQm2hXv2jqQv
  • Commit ID: 2a81ee4
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS. p2 pr-linter/cli-integ-tested Assert that any CLI changes have been integ tested
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants