feat(toolkit-lib): simplify deploy approval message and surface auto-confirm#1585
feat(toolkit-lib): simplify deploy approval message and surface auto-confirm#1585sai-ray wants to merge 8 commits into
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1585 +/- ##
==========================================
- Coverage 88.16% 88.06% -0.11%
==========================================
Files 76 76
Lines 10841 10909 +68
Branches 1494 1511 +17
==========================================
+ Hits 9558 9607 +49
- Misses 1255 1274 +19
Partials 28 28
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
d1786c1 to
41c4504
Compare
mrgrain
left a comment
There was a problem hiding this comment.
Need to look at this in more detail. It was by design that toolkit lib doesn't get interactions.
rix0rrr
left a comment
There was a problem hiding this comment.
While wer'e doing this, can we rename it to require-confirmation ?
"Approval" is actually something else than approval.
mrgrain
left a comment
There was a problem hiding this comment.
Right, I don't think I want to encode this into toolkit-lib. We are already ask for confirmation, it is up to the implementor to decide how to proceed here.
For now, we should improve the message/motivation to simply be something like
Changes detected. Do you wish to deploy these changes?
Additionally, the NonInteractiveIoHost should be clearer about what's happening, e.g. print the auto-acceptance to the output like the --yes flag does.
In future, we should have an easy way to drop a specific message:
const ioHost = new NonInteractiveIoHost();
ioHost.on('I5060', (msg) => {
// do nothing
});|
I don't disagree @mrgrain, but then shouldn't we pull this all the way through and require the confirmation always, regardless of the change type? And give enough information for the client to correlate the change impact with the command line flag? |
|
Thanks @mrgrain and @rix0rrr. Dropped the @rix0rrr on your second point : if I understand correctly, you're saying the I5060 payload should give the IoHost enough to make its own decision. |
| // in the non-interactive IoHost, no requests are promptable | ||
| await this.notify(msg); | ||
| const annotation = typeof msg.defaultResponse === 'boolean' | ||
| ? '(auto-confirmed)' |
There was a problem hiding this comment.
You are assuming that all booleans are always true, apparently.
The defaultResponse boolean could have been false, in which case auto-confirmed is not correct. It could have been auto-denied as well, you don't have enough information to tell.
There was a problem hiding this comment.
Fixed. (auto-confirmed) for true, (auto-denied) for false, (auto-responded with default: <value>) for non-boolean.
| const deployMotivation = hasSecurityChanges | ||
| ? '"--require-approval" is enabled and stack includes security-sensitive updates.' | ||
| : '"--require-approval" is enabled and stack includes updates.'; | ||
| const deployMotivation = 'Changes detected.'; |
There was a problem hiding this comment.
I think I prefer the specificy of the original message. If the problem is that you cannot refer to --require-approval, then remove that part. But now you've removed all specificity.
Here is the question I want you to ask yourself, at all times: "What is better/clearer for the user?"
So, now answer that question: why is it better for the user to get a message like:
Changes detected. Do you wish to deploy these changes?
Put yourself in the shoes of the user. You've just made changes. You're now running cdk deploy to deploy your changes. Then the tool triumphantly tells you "there are changes!". What would you think at that point?
There was a problem hiding this comment.
I think the point was: The original message was good and to the point. But the authority that comes up with that message is in the wrong place, because the toolkit-lib has no business talking about command-line arguments.
So:
- You need to produce an accurate message here that does not refer to command line arguments.
- You then need to produce an even more accurate message in the CLI, that does refer to command line arguments.
There was a problem hiding this comment.
Restored the security-vs-non-security branching, just dropped the --require-approval reference. Library now says Stack includes security-sensitive updates. / Stack includes updates..
…d from auto-denied
| ? 'Stack includes security-sensitive updates.' | ||
| : 'Stack includes updates.'; | ||
| const diffOutput = hasSecurityChanges ? securityDiff.formattedDiff : stackDiff.formattedDiff; |
There was a problem hiding this comment.
Please put the messaging about the flags back in the CliIoHost.
| return msg; | ||
| } | ||
|
|
||
| const data = (msg.data ?? {}) as { motivation?: string; permissionChangeType?: string }; |
There was a problem hiding this comment.
Is there not an existing type definition that models the data field here? I feel like we require this.
In fact, there should be a CDK_TOOLKIT_I5060.is(msg) that will enforce a type guard that will automatically make msg.data have the right type.
| let prefix: string; | ||
| switch (this.requireDeployApproval) { | ||
| case RequireApproval.ANYCHANGE: | ||
| prefix = `"--require-approval" is set to '${RequireApproval.ANYCHANGE}'. `; |
There was a problem hiding this comment.
Because you want to reuse the original message, and both need to make sense in their own context, the final sentence here will be very staccato:
require-approval is set to anychange. Stack contains updates. Do you want to continue?
Instead, you can include hasSecurityChanges: true|false into the data object and format a complete sentence here, rather than scrapping about with fragments.
You might have noticed already, but I care quite a lot how CDK "feels". Not just: what the CLI says is technically accurate, but also that it speaks in a normal voice to a normal user. No formal IBM-speak, no vague Microsoft-speak.
| // The library emits I5060 with a flag-free motivation. The CLI is the | ||
| // layer that knows about `--require-approval` and adds it to the | ||
| // user-facing message before any downstream rendering. | ||
| const promptMessage = this.augmentDeployApprovalMessage(msg).message; |
There was a problem hiding this comment.
No need for this function to return a full IoRequest object if all we're going to do is pull out a single field from that object and throw the rest away. You can just have this return a string.
Closes #828.
@aws-cdk/toolkit-lib'sdeploy()previously printed"--require-approval" is enabled and stack includes security-sensitive updates. Do you wish to deploy these changesand then silently auto-confirmed via the defaultNonInteractiveIoHost. The question-shaped text without a visible answer-shaped action confused programmatic users.This PR keeps the I5060 confirmation request in
_deploy(it's the IoHost's job to decide what to do with it), drops the--require-approvalreference from the library's vocabulary, makesNonInteractiveIoHost's auto-response visible, and centralizes the CLI-side--require-approvalframing inCliIoHost.Changes
@aws-cdk/toolkit-lib/lib/toolkit/toolkit.ts: motivation drops the--require-approvalreference but keeps the existing security-sensitive vs non-security-sensitive branching:Stack includes security-sensitive updatesStack includes updates@aws-cdk/toolkit-lib/lib/toolkit/non-interactive-io-host.ts:requestResponseannotates the message based on the actual default response value:defaultResponse: true:(auto-confirmed)defaultResponse: false:(auto-denied)(auto-responded with default: <value>)aws-cdk/lib/cli/io-host/cli-io-host.ts: newaugmentDeployApprovalMessagehelper. WhenCliIoHostreceives an I5060, it reinjects the--require-approvalframing based onrequireDeployApproval:BROADENING + broadening change→ prepends"--require-approval" is enabled.ANYCHANGE→ prepends"--require-approval" is set to 'any-change'.aws-cdk/lib/cli/cdk-toolkit.ts: emits the same bare-fact motivation astoolkit-lib;CliIoHost.augmentDeployApprovalMessageadds the framing. Single source of truth for the flag vocabulary.@aws-cdk/toolkit-lib/test/actions/deploy.test.ts,aws-cdk/test/cli/cdk-toolkit.test.ts: motivation assertions updated for the new bare-fact text.Example
Same stack, before vs after this PR:
Before
After (
cdk deploy, default--require-approval=broadening, broadening change)After (
cdk deploy --require-approval=any-change, broadening change)After (
await toolkit.deploy()programmatic, defaultNonInteractiveIoHost, security-sensitive change)After (
await toolkit.deploy()programmatic, defaultNonInteractiveIoHost, non-security change)Integrators who want to suppress the message entirely can implement their own
IIoHostand drop theCDK_TOOLKIT_I5060request:Test plan
test/actions/deploy.test.ts(27/27 pass; motivation assertions updated).test/cli/io-host/cli-io-host.test.tsandtest/cli/cdk-toolkit.test.tspass with updated assertion.cdk deploy --yesandawait toolkit.deploy()against broadening (IAM role) and non-security (S3 bucket) stacks under--require-approval=broadeningand--require-approval=any-change. Each produces a single, correctly-prefixed prompt.NonInteractiveIoHostannotation paths (true,false, string, number) via a probe script: each produces the expected annotation and returns the correct value.Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license