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

Handle relative source mounts #3469

Merged
merged 1 commit into from
Mar 14, 2022
Merged

Conversation

rumpl
Copy link
Member

@rumpl rumpl commented Mar 14, 2022

- What I did

With this change it is now possible to give a relative path to the --volume and
--mount flags.

$ docker run --mount type=bind,source=./,target=/test ...
$ docker run -v .:/test ...

Fixes #1203

- How I did it

Updated opts parsing for volumes and mounts

- How to verify it

Run docker run --rm -v .:/test busybox ls -l /test and you should see the listing of your current working dir

- Description for the changelog
The run command accepts relative source paths in -v/--volume and -m/--mount flags

- A picture of a cute animal (not mandatory but encouraged)
image

@@ -92,6 +93,11 @@ func (m *MountOpt) Set(value string) error {
mount.Type = mounttypes.Type(strings.ToLower(value))
case "source", "src":
mount.Source = value
if strings.HasPrefix(value, "."+string(filepath.Separator)) || value == "." {
if abs, err := filepath.Abs(value); err == nil {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to return the error if this fails? (Not entirely sure if needed; it seems like the only case this would fail is if it's failing to resolve os.GetCWD() on Linux (although the Windows code seems to have more possible error conditions)

func TestMountRelative(t *testing.T) {
var mount MountOpt

assert.NilError(t, mount.Set("type=bind,source=.,target=/target"))
Copy link
Member

Choose a reason for hiding this comment

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

Can you add test-cases for ./ as well? Might be worth creating an array/table with test-cases (and using sub-tests) if that helps making it less DRY.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@@ -92,6 +93,11 @@ func (m *MountOpt) Set(value string) error {
mount.Type = mounttypes.Type(strings.ToLower(value))
case "source", "src":
mount.Source = value
if strings.HasPrefix(value, "."+string(filepath.Separator)) || value == "." {
Copy link
Member

Choose a reason for hiding this comment

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

I guess as a follow-up, we should consider;

  • also allowing ../ (relative parent directories)
  • wondering if we should always do a filepath.Clean()

Copy link
Member Author

Choose a reason for hiding this comment

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

For the second point, filepath.Abs already calls Clean

Copy link
Member

Choose a reason for hiding this comment

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

Ah, sorry I should've been clearer; so I was thinking of the "non-relative path" case, so in a case where we don't hit this if

(but definitely for a follow-up)

if parsed.Source != "" {
toBind := bind
Copy link
Member

Choose a reason for hiding this comment

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

This code is a bit hairy (not your fault), and could use some cleaning up at least (we're parsing the bind into a structured type, but effectively only to do some mild "validation" (however, we're throwing away any error caused by loader.ParseVolume().

One thing we can do already (if I see it correctly), is to use the parsed.Type field to only run this code if it's a bind-mount.

Ideally (but that bit might be ok to be done separate), we should

  • possibly make have loader.ParseVolume() already do the resolving (I guess that won't work for the compose case, as it would have to resolve relative to the compose-file)
  • if the above is not an option because of compose-file, we could update the parsed.Source, and;
  • change types.ServiceVolumeConfig (which is returned by loader.ParseVolume()) to have a .String() (or a separate utility for that), which returns parsed in its canonical string representation.

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 added the check for the parsed.Type, I didn't really want to touch the loader code since it touches the compose/swarm things and figured it would surely break something else. I do agree we could have something somewhere that can return a canonical string representation, we should also try and think about how we can make volume and mounts options parsing be in the same place.

With this change it is now possible to give a relative path to the --volume and
--mount flags.

$ docker run --mount type=bind,source=./,target=/test ...

$ docker run -v .:/test ...

Fixes docker#1203

Signed-off-by: Djordje Lukic <[email protected]>
@codecov-commenter
Copy link

Codecov Report

Merging #3469 (ab35e3f) into master (2b52f62) will increase coverage by 0.00%.
The diff coverage is 75.00%.

@@           Coverage Diff           @@
##           master    #3469   +/-   ##
=======================================
  Coverage   58.99%   58.99%           
=======================================
  Files         284      284           
  Lines       23848    23860   +12     
=======================================
+ Hits        14068    14076    +8     
- Misses       8920     8923    +3     
- Partials      860      861    +1     

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

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

LGTM

@jdruizip
Copy link

jdruizip commented May 3, 2023

Although the fix seems to work with paths such as . or ./test, a longer path such as ./test/case seems to always trigger an error on Windows.

When I try
docker run --rm -v ./foo/bar:/test busybox ls -l /test
I always encounter the error:
docker: Error response from daemon: create foo/bar: "foo/bar" includes invalid characters for a local volume name, only "[a-zA-Z0-9][a-zA-Z0-9_.-]" are allowed. If you intended to pass a host directory, use absolute path.

I am on Windows, and a path such as:

docker run --rm -v .\foo\bar:/test busybox ls -l /test (using \ instead of /)

Will run successfully, but then it is no longer valid on unix. It was my understanding that this change was implemented to help cross-platform compatibility, and to sidestep the need for shell-specific pwd calls. docker-compose is able to handle this well, using / on Windows without issue. Even -v %cd%/foo/bar:/foo/bar works well on Windows, so its really not obvious why only backslashes would work in this context.

In any case, even this isn't fixed to be fully cross-platform, the errors should be updated to reflect that not only absolute paths are usable.

@thaJeztah
Copy link
Member

@jdruizip thanks for reporting; could you open a ticket for that? Things can easily get lost when commented on merged pull-requests 🙈

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

Successfully merging this pull request may close these issues.

Support relative paths with bind mounts (volumes)
5 participants