-
Notifications
You must be signed in to change notification settings - Fork 2.1k
mount: add bind-recursive=<bool|string> and deprecate bind-nonrecursive=<bool>
#4316
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
Conversation
Codecov Report
Additional details and impacted files@@ Coverage Diff @@
## master #4316 +/- ##
=======================================
Coverage 59.70% 59.71%
=======================================
Files 288 288
Lines 24816 24858 +42
=======================================
+ Hits 14817 14843 +26
- Misses 9113 9128 +15
- Partials 886 887 +1 |
|
@thaJeztah PTAL 🙏 |
|
Oh! Thanks for the ping, had this one on my list, started looking, and then forgot 😅 |
| // ValidateMountWithAPIVersion validates a mount with the server API version. | ||
| func ValidateMountWithAPIVersion(m mounttypes.Mount, serverAPIVersion string) error { | ||
| if m.BindOptions != nil { | ||
| if m.BindOptions.NonRecursive && versions.LessThan(serverAPIVersion, "1.40") { |
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 guess the overhead is neglectable, so probably not worth doing; we could consider having a if versions.GreaterThanOrEqualTo(serverAPIVersion, "1.44") somewhere to skip the checks.
| By default, submounts are recursively bind-mounted as well. However, this behavior can be confusing when a | ||
| bind mount is configured with <tt>readonly</tt> option, because submounts are not mounted as read-only. | ||
| bind mount is configured with <tt>readonly</tt> option, because submounts are not mounted as read-only | ||
| if Docker Engine is older than v25, or Linux kernel is older than v5.12. |
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.
We should probably use a full version for these v25.0.0, but probably we should consider rewriting this section to assume "latest" (it now works).
| </td> | ||
| </tr> | ||
| <tr> | ||
| <td><b>bind-readonly-nonrecursive</b> or <b>bind-ro-nonrecursive</b></td> |
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'm considering if we should (soft) deprecated bind-nonrecursive, and instead have a "recursive mounts" option that takes a (non-boolean) value, e.g. something like;
recursive-mounts=enabled(default)recursive-mounts=disabledrecursive-mounts=readonly(should error ifbind-propagationis set by user (to anything other thanrprivate), but perhaps handled by API)recursive-mounts=force-readonly(should error ifbind-propagationis set by user (to anything other thanrprivate), but perhaps handled by API)
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 about this?
case "bind-recursive":
valS := val
// Allow boolean as an alias to "enabled" or "disabled"
if b, err := strconv.ParseBool(valS); err == nil {
if b {
valS = "enabled"
} else {
valS = "disabled"
}
}
switch valS {
case "enabled": // read-only mounts are recursively read-only if Engine >= v25 && kernel >= v5.
12
// NOP
case "disabled": // alias of bind-nonrecursive=true
bindOptions().NonRecursive = true
case "readonly-classic": // conforms to Docker v24; read-only mounts are recursively mounted bu
t not recursively read-only
bindOptions().ReadOnlyNonRecursive = true
mount.ReadOnly = true
case "readonly-force": // force recursively read-only, or raise an error
bindOptions().ReadOnlyForceRecursive = true
bindOptions().Propagation = mounttypes.PropagationRPrivate
mount.ReadOnly = true
default:
return fmt.Errorf("invalid value for %s: %s (must be \"enabled\", \"disabled\", \"reado
nly-classic\", or \"readonly-force\")",
key, val)
}bind-recursive=<bool|string> and deprecate bind-nonrecursive=<bool>
|
@thaJeztah Could you take a look? |
|
Sorry, should've posted some comments here. I've been starting to review this one multiple times, and trying to construct a table with possible combinations (and what their result would be), and my head exploded each time 😂 However, the other day it occurred to me what I think was the part that I had trouble with;
So the goal we're aiming for here is;
However I think (and that's the part I need to look at again, as I was considering this away from my computer), to achieve the same result as While it's good to allow the user to have more fine-grained control, my ideal would be to make that "optional"; even with the advanced So my thinking is; this: --mount type=bind,source=host-path,target=container-path,readonlyTo be the equivalent of: -v host-path:container-path:ro(i.e.; try recursive-readonly, and fallback to non-recursive) But if the user needs to have more fine-grained control, then allow them to set the e.g.: force recursive read-only: --mount type=bind,source=host-path,target=container-path,readonly,bind-recursive=readonly-forceMaybe we don't even need the e.g.: read-only, but no recursive mounts: --mount type=bind,source=host-path,target=container-path,readonly,bind-recursive=disabled |
|
Thanks, renamed |
opts/mount.go
Outdated
| case "readonly-classic": // conforms to Docker v24; read-only mounts are recursively mounted but not recursively read-only | ||
| bindOptions().ReadOnlyNonRecursive = true | ||
| mount.ReadOnly = true | ||
| case "readonly": // force recursively read-only, or raise an error | ||
| bindOptions().ReadOnlyForceRecursive = true | ||
| bindOptions().Propagation = mounttypes.PropagationRPrivate | ||
| mount.ReadOnly = 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.
I think we're getting close (sorry had to jump out earlier as I had some calls); if we consider the bind-recursive option to be controlling options for recursive mounts (and not for the "main" mount), should we update mount.Readonly at all here?
My thinking (please reply if I'm completely off the mark ❤️);
readonlyis the "overall" option; when enabled, by default it makes both the "main" mount, and any nested mounts read-only (i.e. "best-effort recursive-readonly"bind-recursive=disableddisables nested mountsbind-recursive=readonlyforces nested mounts to be read-only- 👆 this can also be done separate from
readonly, so--mount type=bind,source=host-path,target=container-path,bind-recursive=readonly(without "global"readonly) would then mean: the main mount is read-write, but nested mounts (if any) would be mounted read-only - 👆 ❓ is that combination technically possible?
- 👆 this can also be done separate from
- which leaves
readonly-classicif we don't setmount.ReadOnlythere, would that make it the same asenabled? (i.e.; nested mounts are mounted, but "read-write") ? If that's the case, then maybe we don't need that option 🤔
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.
mount.ReadOnly = true is set here, as mount.ReadOnly == false && bindOptions().ReadOnlyXXX = true doesn't seem a valid configuration.
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.
That's the part I was wondering; is it (technically) possible to make the "main" mount (so target) read-write, but making nested mounts read-only?
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.
Basically; my thinking here is to make nested mounts "inherit" the readonly option as a default, with the option to differentiate (through the bind-recursive options). In that case it's (I think) easier to comprehend (by default, they'll all be attempted the same, but if I need them to be different, I can control that through this option)
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.
That's the part I was wondering; is it (technically) possible to make the "main" mount (so
target) read-write, but making nested mounts read-only?
Theoretically possible by scanning procfs, but I don't think we want to implement it
neersighted
left a comment
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 think this is a lot better UX-wise, with the recursion divorced from the readonly option. Nonetheless, I think it's not quite there, mostly because confusing combinations are possible and should be caught in validation instead of silently overriding each other.
That's hard, since we don't have validation yet/the way this code is structured makes it rather obtuse. Still, we could e.g. use a fixed source order with a slice that defines a strong ordering, and check that way, or we could think of another method.
Let me know if my comments make sense and I can help with figuring out how to implement them.
opts/mount.go
Outdated
| // NOP | ||
| case "disabled": // alias of bind-nonrecursive=true | ||
| bindOptions().NonRecursive = true | ||
| case "readonly-classic": // conforms to Docker v24; read-only mounts are recursively mounted but not recursively read-only |
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.
readonly-classic doesn't feel right to me, as we're trying to split the "recursion" from the "readonlyness" with this.
How about writable, which makes it clear that recursive mounts will be writable/that was the legacy behavior?
I also think setting ReadOnly here is a smell -- we should instead have some check that errors when ReadOnly has not been set by the user (e.g. readonly=false,bind-recursive=readonly should be an error in validation).
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.
Updated to "writable", but what about calling this "classic" or "legacy"?
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 still think classic or legacy don't really describe what it does, and when documenting this option and the default for old engine versions, it really hammers home the old behavior. I don't object to an alias under those names, but I would prefer avoiding it as I think they're meaningless unless you have an excellent understanding of the kernel/engine history around this.
opts/mount.go
Outdated
| mount.ReadOnly = true | ||
| case "readonly": // force recursively read-only, or raise an error | ||
| bindOptions().ReadOnlyForceRecursive = true | ||
| bindOptions().Propagation = mounttypes.PropagationRPrivate |
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 don't think we should overwrite the propagation here unconditionally. I think it's fine to set it implicitly from a UX perspective, but if the user specifies any different propagation it should be a validation error.
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.
Updated
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.
Hmm, I do like the trivial validation overall, but I'm not sure about this case. I don't think that requiring the user to set the propagation explicitly is good UX; I'd prefer this to be set implicitly, but to error if the user explicitly sets a different propagation. Unfortunately that doesn't work with the simple validation you've added, but maybe you have some ideas to that end?
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 default implicit "rprivate" propagation sometimes flips to "rslave":
So I guess we should demand explicit "rprivate" here
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.
Yeah, I had that in mind. What I meant to say is that we want to be explicit at the API level, but I'm not sure that we want to require the user to explicitly specify a propagation mode to use bind-recursive=readonly. However with the current validation, this is messy.
I am okay with what you have; it makes the feature usable, and bind-recursive=readonly is definitely a power-user feature/we're "upgrading" users by default to the new enabled behavior anyway.
How about a // TODO: to implicitly set propagation and error if the user specifies a propagation in a future refactor/UX polish pass?
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.
Added
opts/mount.go
Outdated
| return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type) | ||
| } | ||
|
|
||
| if mount.BindOptions != nil && mount.BindOptions.ReadOnlyForceRecursive { |
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 probably needs a check for ReadOnlyNonRecursive as well, as readonly=false,bind-recursive=writable makes no sense.
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.
as
readonly=false,bind-recursive=readonly
Ah, yes, that was the case I mentioned above (and was hoping it would work, as I could see use-cases for a "writable" bind-mount with nested mounts read-only), but currently not a combination that was possible; #4316 (comment)
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.
Just to be sure; in the current implementation, does --mount type=bind,readonly,..... do the same as -v source:target:ro (so best-effort recursive), or do we need to make changes to the readonly option to make that happen (I lost track again on which combination did that, and what parts we did on the daemon side)
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 probably needs a check for
ReadOnlyNonRecursiveas well, asreadonly=false,bind-recursive=readonlymakes no sense.
Done (I assume you meant readonly=false,bind-recursive=writable)
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.
Just to be sure; in the current implementation, does
--mount type=bind,readonly,.....do the same as-v source:target:ro(so best-effort recursive), or do we need to make changes to thereadonlyoption to make that happen (I lost track again on which combination did that, and what parts we did on the daemon side)
The former one.
|
FWIW; we discussed this PR Yesterday, and w.r.t. validation it's possible that we need to make some follow-up changes, because of the way these options are parsed (i.e., order can matter if multiple options interact with the same "API" options behind the scenes). We are looking at doing a first beta for v25.0 soon (to get the release train warmed up), and I think for that the main goal is to have the UX "sorted" (as good as possible), but after that we can still tweak / fix validation corner cases etc. |
neersighted
left a comment
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.
Overall LGTM with reservations (about the need for the user to explicitly set the propagation mode); as per my review comment, I think a TODO would be fine here, and we should get this in.
…rsive=<bool>` See `opts/mount_test.go:TestMountOptSetBindRecursive()` for the behavior. Documentation will be added separately after reaching consensus on the design. Signed-off-by: Akihiro Suda <[email protected]>
thaJeztah
left a comment
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.
LGTM
added a docs/revisit label, as we will have to do some docs follow-ups, as well as looking at completion scripts (not sure if we provide completion for these currently)
| valS := val | ||
| // Allow boolean as an alias to "enabled" or "disabled" | ||
| if b, err := strconv.ParseBool(valS); err == nil { | ||
| if b { | ||
| valS = "enabled" | ||
| } else { | ||
| valS = "disabled" | ||
| } | ||
| } | ||
| switch valS { |
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.
Opened a PR for discussion to remove the boolean variants (motivation outlined in that PR);
The `bind-nonrecursive` option was replaced with the [`bind-recursive`]
option (see [cli-4316], [cli-4671]). The option was still accepted, but
printed a deprecation warning:
bind-nonrecursive is deprecated, use bind-recursive=disabled instead
In the v29.0 release, this warning is removed, and returned as an error.
Users should use the equivalent `bind-recursive=disabled` option instead.
[`bind-recursive`]: https://docs.docker.com/engine/storage/bind-mounts/#recursive-mounts
[cli-4316]: docker#4316
[cli-4671]: docker#4671
Signed-off-by: Sebastiaan van Stijn <[email protected]>
The `bind-nonrecursive` option was replaced with the [`bind-recursive`] option (see [cli-4316], [cli-4671]), but the deprecated docs was not updated to mention. Based on abfe4d4 in master [`bind-recursive`]: https://docs.docker.com/engine/storage/bind-mounts/#recursive-mounts [cli-4316]: docker#4316 [cli-4671]: docker#4671 Signed-off-by: Sebastiaan van Stijn <[email protected]> (cherry picked from commit abfe4d4) Signed-off-by: Sebastiaan van Stijn <[email protected]>
The `bind-nonrecursive` option was replaced with the [`bind-recursive`] option (see [cli-4316], [cli-4671]), but the deprecated docs was not updated to mention. Based on abfe4d4 in master [`bind-recursive`]: https://docs.docker.com/engine/storage/bind-mounts/#recursive-mounts [cli-4316]: docker#4316 [cli-4671]: docker#4671 Signed-off-by: Sebastiaan van Stijn <[email protected]> (cherry picked from commit abfe4d4) Signed-off-by: Sebastiaan van Stijn <[email protected]>
For:
See
opts/mount_test.go:TestMountOptSetBindRecursive()for the behavior:bind-recursive=true,bind-recursive=enabled: read-only mounts are recursively read-only if Engine >= v25 && kernel >= v5.12bind-recursive=false,bind-recursive=disabled: alias ofbind-nonrecursive=true. Now this form is preferred overbind-nonrecursive=true.bind-recursive=writable: conforms to Docker v24; read-only mounts are recursively mounted but not recursively read-onlybind-recursive=readonly: force recursively read-only, or raise an errorDocumentation will be added separately after reaching consensus on the design.
Backup (2023-05-29)
AkihiroSuda@6ebf6ba
- What I did
Added the CLI and the docs for:
- How I did it
The
-voptions are implemented on the daemon side.ro-non-recursivero-force-recursive(alias:rro)The following "CSV" options for
--mountare newly added in this CLI-side PR:bind-readonly-nonrecursive(alias:bind-ro-nonrecursive) (Do not confuse this with the exiswtingbind-nonrecursive)bind-readonly-forcerecursive(alias:bind-ro-forcerecursive)- How to verify it
Run
docker run --mount type=bind,source=/mnt,target=/mnt,bind-ro-forcerecursive,bind-propagation=rprivate, and make sure the mount is made recursively read-only.- Description for the changelog
Add CLI and docs for recursively read-only mounts
- A picture of a cute animal (not mandatory but encouraged)
🐧