Skip to content

Conversation

@Benehiko
Copy link
Member

@Benehiko Benehiko commented Feb 21, 2024

Fixes #4850

> ~/g/s/g/d/cli on fix-prompt-termination ⨯ ./build/docker volume prune      16:35:17
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] ^C
⋊> ~/g/s/g/d/cli on fix-prompt-termination ⨯ echo $status                     16:35:21
0
⋊> ~/g/s/g/d/cli on fix-prompt-termination ⨯ bash                             16:35:23
[benehiko@benehiko cli]$ ./build/docker volume prune
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] ^C
[benehiko@benehiko cli]$ echo $?
0
⋊> ~/g/s/g/d/cli on fix-prompt-termination ⨯ zsh                              16:35:32
benehiko% ./build/docker volume prune
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N] ^C
benehiko% echo $?
0

- What I did

The docker command/utils.go provides a function for prompting the user for a confirmation [Y/n]. To fix the termination of the CLI while the prompt is active, the reader is blocked inside a gouroutine instead allowing for checks on context cancellation from the parent or from a SIGINT/SIGTERM. An error is then propagated up the stack when the prompt is terminated instead of an immediate program termination. The caller should respect the error and either close the reader or exit the CLI to prevent goroutine leaks and unexpected behavior on re-prompting.

- How I did it

Catch the termination signal on prompt.

- How to verify it

Manually and with tests.

- Description for the changelog

CLI confirmation prompts will now safely cancel the operation on termination (SIGINT/SIGTERM) producing an exit code of 0.

These are the affected CLI commands:

  • docker system prune
  • docker volume prune
  • docker plugin upgrade
  • docker image prune
  • docker network prune
  • docker container prune
  • docker network rm
  • docker builder prune
  • docker trust revoke

Some commands above are replaced by plugins, which will not be affected.

- A picture of a cute animal (not mandatory but encouraged)
image
(https://www.rawpixel.com/image/6041428/photo-image-background-public-domain-cat)

@Benehiko Benehiko force-pushed the fix-prompt-termination branch 2 times, most recently from 0bffacc to c532931 Compare February 21, 2024 16:57
@Benehiko Benehiko marked this pull request as ready for review February 21, 2024 16:59
@Benehiko Benehiko requested a review from a team February 22, 2024 10:44
@codecov-commenter
Copy link

codecov-commenter commented Feb 22, 2024

Codecov Report

Attention: Patch coverage is 84.72222% with 11 lines in your changes missing coverage. Please review.

Project coverage is 61.45%. Comparing base (310daf2) to head (10bf91a).
Report is 636 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4888      +/-   ##
==========================================
+ Coverage   61.13%   61.45%   +0.31%     
==========================================
  Files         287      289       +2     
  Lines       20136    20229      +93     
==========================================
+ Hits        12310    12431     +121     
+ Misses       6933     6896      -37     
- Partials      893      902       +9     

@Benehiko Benehiko force-pushed the fix-prompt-termination branch 6 times, most recently from 7ddd3b8 to e67927c Compare February 23, 2024 09:18
@@ -1,36 +1,45 @@
package command
package command_test
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

had cyclical import between the command package and test.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine if we do this, but I wonder if the cyclical import was a smell that we should move something.

@Benehiko Benehiko force-pushed the fix-prompt-termination branch 5 times, most recently from de7081a to e662a1e Compare February 27, 2024 10:00
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
}
return command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?"), nil
ctx := context.TODO()
Copy link
Member Author

@Benehiko Benehiko Feb 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here the context isn't apparent. Either we use the top level dockerCli context or create a new context within the function. The moby project does not support passing context on this function call https://github.com/moby/moby/blob/d19d98b136f6a7e80029b65c5ae8886eacdf43d7/api/types/client.go#L292

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW; if we would benefit from passing contexts, we should consider updating the moby code. For a lot of code in moby, context didn't exist yet when it was written, and we started passing through contexts more, but there's still many areas where we didn't.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll take a look at also adding context in the moby api client. It will help greatly with otel and just task cleanup in general.

@@ -0,0 +1,2 @@
Please confirm you would like to delete all signature data for example/trust-demo? [y/N]
Aborting action.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aah i guess this could be another confirmation prompt to "fix" by standardizing the output (aka not printing "Aborting action" and making it behave more like everything else). For another moment of course

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah true

@Benehiko Benehiko force-pushed the fix-prompt-termination branch 3 times, most recently from f4e617d to 77ad0c2 Compare February 28, 2024 12:14
@Benehiko Benehiko force-pushed the fix-prompt-termination branch from 77ad0c2 to d4d2552 Compare February 28, 2024 12:16
@Benehiko Benehiko force-pushed the fix-prompt-termination branch 2 times, most recently from ee32a21 to ef1624c Compare February 28, 2024 14:13
@Benehiko Benehiko requested a review from krissetto February 28, 2024 14:19
@@ -0,0 +1,2 @@
Upgrading plugin foo/bar from localhost:5000/foo/bar:v0.1.0 to localhost:5000/foo/bar:v1.0.0
Plugin images do not match, are you sure? [y/N] No newline at end of file
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing a newline at the end here? (The other new golden files have them 🤔)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Benehiko could we fix this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, i'm not sure why this is. The file is generated by the -update command.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this is likely due to us missing a newline when printing things somewhere

Copy link
Collaborator

@laurazard laurazard left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice, thank you! Left a few comments (mostly nits), but overall a good change.

Not sure if I missed it, but are we explicitly testing the exit code anywhere?

@Benehiko Benehiko force-pushed the fix-prompt-termination branch 2 times, most recently from 4824d80 to 385e049 Compare March 4, 2024 11:49
@Benehiko
Copy link
Member Author

Benehiko commented Mar 4, 2024

Really nice, thank you! Left a few comments (mostly nits), but overall a good change.

Not sure if I missed it, but are we explicitly testing the exit code anywhere?

Hmm, I suppose we could check the status code, but the status code seems to be added to a specific error type. It seems the main package handles some exit codes based on the error type here https://github.com/docker/cli/blob/master/cmd/docker/docker.go#L35-L50

Since we gracefully propagate the error up we check for it inside the test, which I think should be enough. Wdyt?

@laurazard
Copy link
Collaborator

imo those tests are useful, but if we want to preserve the "exit code is 0 when user CTRL-C's a confirmation prompt" in the future, we could add this check in particular for the e2e tests for these commands. (But fine if you'd prefer to do it in another PR).

@Benehiko
Copy link
Member Author

Benehiko commented Mar 4, 2024

Seems like this test is quite flaky

56.34 === FAIL: cli-plugins/socket TestConnectAndWait/connect_goroutine_exits_after_EOF 

@Benehiko Benehiko force-pushed the fix-prompt-termination branch from f21679e to 10bf91a Compare March 4, 2024 14:28
@neersighted neersighted merged commit 181575b into docker:master Mar 4, 2024
@Benehiko Benehiko deleted the fix-prompt-termination branch March 4, 2024 14:57
Comment on lines +25 to +28
// NewWriterWithHook returns a new WriterWithHook that still writes to the actualWriter
// but also calls the hook function after every write.
// The hook function is useful for testing, or waiting for a write to complete.
func NewWriterWithHook(actualWriter io.Writer, hook func([]byte)) *WriterWithHook {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed we're only using this in internal/test and one other usage, I wonder if this needs to be public at all (thinking about this because otherwise we can relax a bit with the docstrings.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, can address this (and e2e tests) in a follow-up (cc @Benehiko)


// Catch the termination signal and exit the prompt gracefully.
// The caller is responsible for properly handling the termination.
notifyCtx, notifyCancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a utility function should set up signal handlers for the process as a side effect. I think this should just use the passed in context for cancellation, and if that context is tied to the os signals is up to the caller. Normally, a process already has a signal handler and this will conflict with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

canceling "docker volume prune" does not consistently print a newline, and exits with non-zero status

7 participants