-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Fix Windows args and ArgsEscaped
handling
#4723
base: master
Are you sure you want to change the base?
Conversation
Oh, and testing! There should probably be some tests for this, but I wasn't sure where to look or start. This has taken me days to work through already. 😅 |
Thanks for this, will take a look at this, this weekend. |
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.
Here are comments from my first pass. I'm also testing locally and will drop comments here.
Also just a general observation, first thanks for noting the TODOs! Any way we could just close on them as part of this PR? If the scope is a little wide, I'm open to splitting the work and helping.
d.image.Config.Cmd = args | ||
d.image.Config.ArgsEscaped = true //nolint:staticcheck // ignore SA1019: field is deprecated in OCI Image spec, but used for backward-compatibility with Docker image spec. |
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.
nit: does moving this into the windows
if-block have any effect on the Linux case?
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.
Yes, it absolutely does -- that was the original intent of my looking into this in the first place. In short, ArgsEscaped
should never be set on a non-Windows image, and certainly never to true
, and any case where it is gets (correctly) ignored by the runtimes anyhow. It's a 100% Windows-specific field.
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 ArgsEscaped
field was added in containerd a while ago and it essentially converts the behavior of the Cmd
field into what CmdLine
does (roughly). If ArgsEscaped
is set to true
, the shim expects that ENTRYPOINT
or CMD
is a single element array, with the command, along with any other arguments, already escaped.
For example, if we have ArgsEscaped
set to true
, by the time the CMD
reaches the shim, it should look like this:
CMD ["\"c:\\Path to\\Some\\application.exe\" "some space delimited arg" someOtherArg"]
And that gets passed along to HcsCreateProcess
. I remember having to deal with double escaping of args when I first implemented the windows executor. Back then we were sending spec.Cmd
along with args. And the args were getting escaped again once they got to the shim. Details are fuzzy, but the consensus when I discussed this issue with the MSFT folks was to just send the one string as CmdLine
instead of Cmd
and Args
.
I think this change is okay, as long as we just log the inconsistency in place of that TODO, similarly to what happens in moby now.
@@ -1398,6 +1407,23 @@ func dispatchEntrypoint(d *dispatchState, c *instructions.EntrypointCommand) err | |||
if c.PrependShell { | |||
args = withShell(d.image, args) | |||
} | |||
if d.image.OS == "windows" { |
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.
nit: this block is repeated twice, L1390 and 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.
Correct -- we need separate, similar (but slightly different) handling for ENTRYPOINT
vs CMD
(see the linked original Moby PR/implementation).
if d.image.Config.ArgsEscaped != argsEscaped && | ||
(len(d.image.Config.Cmd) > 1 || | ||
(len(d.image.Config.Cmd) == 1 && | ||
strings.ToLower(d.image.Config.Cmd[0]) != `c:\windows\system32\cmd.exe` && |
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.
nit: how about if the user just had cmd.exe
instead of the absolute path?
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.
See the comment above (and the linked original PR) -- this is just to handle the special case of the default Cmd
set in the published base images.
Some notes from testing=> Is there anything you are doing to build FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
RUN mkdir foo
RUN mkdir "foo bar"
# need to check how to excape spaces for COPY
COPY ./ ./
# COPY ./args.exe ./foo/args.exe
CMD [ "C:\\foo bar\\args.exe", "foo bar", "baz buzz" ] build gives this error:
However, it's successful for PS > docker build -t buildkit-args .
Sending build context to Docker daemon 3.827MB
Step 1/5 : FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
---> 6ad91fb31728
Step 2/5 : RUN mkdir foo
---> Using cache
---> 522ec323b2ef
Step 3/5 : RUN mkdir "foo bar"
---> Using cache
---> 0df2959b2f98
Step 4/5 : COPY ./ ./
---> Using cache
---> 4c0a8b317934
Step 5/5 : CMD [ "C:\\foo bar\\args.exe", "foo bar", "baz buzz" ]
---> Using cache
---> 8c5e638d0180
Successfully built 8c5e638d0180
Successfully tagged buildkit-args:latest
PS > docker run --rm buildkit-args
[]string{"C:\\foo bar\\args.exe", "foo bar", "baz buzz"} |
This looks related to fsutil: https://github.com/tonistiigi/fsutil/blob/7525a1af2bb545e89dc9bced785bff7a3b7f08c2/validator.go#L31 |
Yes, absolutely! Sorry for not being more clear -- I do not intend for this to land with the |
Here's the FROM --platform=$BUILDPLATFORM golang AS build
COPY args.go ./
RUN GOOS=windows GOARCH=amd64 go build -o '/args.exe' ./args.go
FROM mcr.microsoft.com/windows/servercore:ltsc2022
COPY --from=build ["/args.exe","/foo bar/"]
COPY --from=build ["/args.exe","/foo.exe"]
CMD ["C:\\foo bar\\args.exe", "foo bar", "baz buzz"] // args.go
package main
import (
"fmt"
"os"
)
func main() {
fmt.Printf("%#v\n", os.Args)
} You can find a built copy of the image at |
@gabriel-samfira @profnandaa What's the state of this? Is it something we can include in v0.14? |
Will look at this in a couple of hours. Sorry for completely missing the notifications from this. |
I created a Dockerfile with the following contents: FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
RUN mkdir foo
RUN mkdir "foo bar"
COPY ["/args.exe","/foo bar/"]
COPY ["/args.exe","/foo.exe"]
COPY ["/args.exe","/"]
# Notice that when the command you're running has a space in it, you need to quote
# the entire line. The RUN stanza does not get escaped, so you need to take care of escaping
# yourself.
# Make sure that:
#
# cmd.exe /S /C <everything that comes after RUN>
#
# is correct.
RUN ""c:\\foo bar\\args.exe" "foo bar" "baz buzz""
# This should run fine.
RUN C:\\args.exe "foo bar" "baz buzz" hello
CMD ["c:\\foo bar\\args.exe", "foo bar", "baz buzz"] I then built the dockerfile: PS C:\Users\Administrator\args> buildctl build --frontend=dockerfile.v0 --progress plain --local context=. --local dockerfile=. --output type=image,name=docker.io/gsamfira/buildkit-args,push=true
#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 371B done
#1 DONE 0.1s
#2 [internal] load metadata for mcr.microsoft.com/windows/nanoserver:ltsc2022
#2 DONE 0.9s
#3 [internal] load .dockerignore
#3 transferring context: 2B done
#3 DONE 0.1s
#4 [1/8] FROM mcr.microsoft.com/windows/nanoserver:ltsc2022@sha256:ac6a7571d5a404398e2f734d92f9b8f580a4fe1e6ae1820a61c5f138b1bdeff3
#4 resolve mcr.microsoft.com/windows/nanoserver:ltsc2022@sha256:ac6a7571d5a404398e2f734d92f9b8f580a4fe1e6ae1820a61c5f138b1bdeff3 0.1s done
#4 DONE 0.1s
#5 [internal] load build context
#5 transferring context: 2.00MB 0.0s done
#5 DONE 0.1s
#4 [1/8] FROM mcr.microsoft.com/windows/nanoserver:ltsc2022@sha256:ac6a7571d5a404398e2f734d92f9b8f580a4fe1e6ae1820a61c5f138b1bdeff3
#4 extracting sha256:6ef672c2d22f72854c3c475bef3811e110f08f4b049cb0023435b993651ea048
#4 extracting sha256:6ef672c2d22f72854c3c475bef3811e110f08f4b049cb0023435b993651ea048 8.8s done
#4 DONE 8.9s
#6 [2/8] RUN mkdir foo
#6 DONE 1.7s
#7 [3/8] RUN mkdir "foo bar"
#7 DONE 1.9s
#8 [4/8] COPY [/args.exe,/foo bar/]
#8 DONE 0.9s
#9 [5/8] COPY [/args.exe,/foo.exe]
#9 DONE 0.3s
#10 [6/8] COPY [/args.exe,/]
#10 DONE 0.3s
#11 [7/8] RUN ""c:\foo bar\args.exe" "foo bar" "baz buzz""
#11 1.138 []string{"c:\\\\foo bar\\\\args.exe", "foo bar", "baz buzz"}
#11 DONE 1.5s
#12 [8/8] RUN C:\args.exe "foo bar" "baz buzz" hello
#12 1.796 []string{"C:\\\\args.exe", "foo bar", "baz buzz", "hello"}
#12 DONE 2.1s
#13 exporting to image
#13 exporting layers
#13 exporting layers 1.8s done
#13 exporting manifest sha256:2ce4ddc37f834278b51294103cb772b9ecde9e4dc3bb80b3b5410b21d18e92f2 0.0s done
#13 exporting config sha256:9d785e5c076ff553a7b01f57acc72a70f0cf4b13b986d8288933ac888f94640a 0.0s done
#13 naming to docker.io/gsamfira/buildkit-args done
#13 pushing layers
#13 ...
#14 [auth] gsamfira/buildkit-args:pull,push token for registry-1.docker.io
#14 DONE 0.0s
#13 exporting to image
#13 pushing layers 3.9s done
#13 pushing manifest for docker.io/gsamfira/buildkit-args:latest@sha256:2ce4ddc37f834278b51294103cb772b9ecde9e4dc3bb80b3b5410b21d18e92f2
#13 pushing manifest for docker.io/gsamfira/buildkit-args:latest@sha256:2ce4ddc37f834278b51294103cb772b9ecde9e4dc3bb80b3b5410b21d18e92f2 0.4s done
#13 DONE 6.2s If I inspect the image: PS C:\Users\Administrator> docker inspect docker.io/gsamfira/buildkit-args
[
{
"Id": "sha256:9d785e5c076ff553a7b01f57acc72a70f0cf4b13b986d8288933ac888f94640a",
"RepoTags": [
"gsamfira/buildkit-args:latest"
],
"RepoDigests": [
"gsamfira/buildkit-args@sha256:2ce4ddc37f834278b51294103cb772b9ecde9e4dc3bb80b3b5410b21d18e92f2"
],
"Parent": "",
"Comment": "buildkit.dockerfile.v0",
"Created": "2024-05-29T00:38:14.803361-07:00",
"DockerVersion": "",
"Author": "",
"Config": {
"Hostname": "",
"Domainname": "",
"User": "ContainerUser",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=c:\\Windows\\System32;c:\\Windows"
],
"Cmd": [
"c:\\foo bar\\args.exe",
"foo bar",
"baz buzz"
],
"ArgsEscaped": true,
"Image": "",
"Volumes": null,
"WorkingDir": "",
"Entrypoint": null,
"OnBuild": null,
"Labels": null
},
"Architecture": "amd64",
"Os": "windows",
"OsVersion": "10.0.20348.2461",
"Size": 304141881,
"GraphDriver": {
"Data": {
"dir": "C:\\ProgramData\\docker\\windowsfilter\\7fa65018234eac621261aa25e94452e9ee3ada5d614835069ffef166303416ec"
},
"Name": "windowsfilter"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:84f6522b491493a414e7db5cca32dfaff6cfbf9b3b3fa1565699b84b7d0ce71a",
"sha256:f61517395295ff1a667dc9f085c8353d994b7044b5c2d0fa98ec14bfe7590940",
"sha256:ea07b12033b8110232b7a14bf9e918ffedb6d3bafff3cfe122cba3e36bf013e4",
"sha256:32e6c5b9f7837e88508509fd7a08659b052df4ed376c22981e37619b890ae106",
"sha256:c078682e46f376efeda987f86d9433f2d06b4de6bda78007bc3cf6a1c115c8fd",
"sha256:2635bb703e549b57eee78570dab40edc9f58c134eb0ef72113d9608222d43e06",
"sha256:5c2d2ae98ff2199772ffe2eaeabfe6f4cca0667893d7ba30c27aab56b2fa80ee",
"sha256:18746cee89bb1daa0bce117c0f3ad1dd8b032620dc97e5f26719cbd421042880"
]
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
] And when I run it: PS C:\Users\Administrator> docker run --rm docker.io/gsamfira/buildkit-args:latest
[]string{"c:\\foo bar\\args.exe", "foo bar", "baz buzz"}
PS C:\Users\Administrator> The behavior of The I will add some comments regarding the |
For reference, this is my current environment: PS C:\Users\Administrator> containerd.exe -version
containerd github.com/containerd/containerd v1.7.13 7c3aca7a610df76212171d200ca3811ff6096eb8
PS C:\Users\Administrator> buildkitd.exe -version
buildkitd github.com/moby/buildkit v0.13.0-rc2 596ef8f01e11e15889576a88ffa4c7f92fa44518
PS C:\Users\Administrator> docker version
Client:
Version: 26.1.3
API version: 1.45
Go version: go1.21.10
Git commit: b72abbb
Built: Thu May 16 08:34:37 2024
OS/Arch: windows/amd64
Context: default
Server: Docker Engine - Community
Engine:
Version: 26.1.3
API version: 1.45 (minimum version 1.24)
Go version: go1.21.10
Git commit: 8e96db1
Built: Thu May 16 08:33:14 2024
OS/Arch: windows/amd64
Experimental: false
PS C:\Users\Administrator> gcim win32_operatingsystem | select Caption,BuildNumber,Version
Caption BuildNumber Version
------- ----------- -------
Microsoft Windows Server 2022 Datacenter Evaluation 20348 10.0.20348 |
Ahh, I also had a CMD ["\"c:\\foo bar\\args.exe\"", "foo bar", "baz buzz"] Which I admit is atrocious user experience. This was an edge case that I sadly did not think about 😞. But for this case to be solved, I believe it's enough to escape just the first element of |
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 executor changes are not really needed as far as I can tell. But the ones in dockerfile2llb
seem okay.
@@ -102,5 +103,12 @@ func (d *containerState) getTaskOpts() ([]containerd.NewTaskOpts, error) { | |||
} | |||
|
|||
func setArgs(spec *specs.Process, args []string) { | |||
spec.CommandLine = strings.Join(args, " ") | |||
// TODO handle ArgsEscaped correctly here somehow (ie, avoid re-escaping args[0] if it's 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.
On my system, with the default shell that buildkitd uses (cmd /S /C
) this change will make the following RUN
stanza:
RUN C:\\args.exe "foo bar" "baz buzz" hello
result in the following:
#9 [5/5] RUN C:\args.exe "foo bar" "baz buzz" hello
#9 1.021 []string{"C:\\\\args.exe", "\"foo", "bar\"", "\"baz", "buzz\"", "hello"}
#9 DONE 1.4s
And if we use a space in the command:
RUN "c:\\foo bar\\args.exe" "foo bar" "baz buzz" hello
results in the following error:
#9 [5/6] RUN "c:\foo bar\args.exe" "foo bar" "baz buzz" hello
#9 1.054 '\"c:\\foo bar\\args.exe\"' is not recognized as an internal or external command,
#9 1.054 operable program or batch file.
#9 ERROR: process "cmd /S /C \"c:\\\\foo bar\\\\args.exe\" \"foo bar\" \"baz buzz\" hello" did not complete successfully: exit code: 1
time="2024-05-29T06:14:56-07:00" level=debug msg="stopping session" spanID=44e1bed51aee5c31 traceID=76af8ec835d4f23d69fb3cbf608e24e5
------
> [5/6] RUN "c:\foo bar\args.exe" "foo bar" "baz buzz" hello:
1.054 '\"c:\\foo bar\\args.exe\"' is not recognized as an internal or external command,
1.054 operable program or batch file.
------
Dockerfile:18
--------------------
16 | #
17 | # is correct.
18 | >>> RUN "c:\\foo bar\\args.exe" "foo bar" "baz buzz" hello
19 |
20 | # This should run fine.
--------------------
error: failed to solve: process "cmd /S /C \"c:\\\\foo bar\\\\args.exe\" \"foo bar\" \"baz buzz\" hello" did not complete successfully: exit code: 1
So escaping the args before sending them as part of the CmdLine
, will break the default shell which is undesirable. The string in the RUN
stanza gets passed to the default shell as is, as one string. As long as the command is already escaped in the Dockerfile, it should work. See bellow.
Without the change:
# Notice that the whole line is quoted in this case
RUN ""c:\\foo bar\\args.exe" "foo bar" "baz buzz" hello"
results in:
#9 [5/6] RUN ""c:\foo bar\args.exe" "foo bar" "baz buzz" hello"
#9 1.188 []string{"c:\\\\foo bar\\\\args.exe", "foo bar", "baz buzz", "hello"}
#9 DONE 1.5s
And:
# No quotes needed for the entire line
RUN C:\\args.exe "foo bar" "baz buzz" hello
results in:
#10 [6/6] RUN C:\args.exe "foo bar" "baz buzz" hello
#10 1.914 []string{"C:\\\\args.exe", "foo bar", "baz buzz", "hello"}
#10 DONE 2.3s
If we replace the default shell with something more forgiving, escaping every arg does not make a difference:
FROM mcr.microsoft.com/powershell:lts-7.2-nanoserver-ltsc2022
RUN mkdir "foo bar"
SHELL ["C:\\Program Files\\PowerShell\\pwsh.exe", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
COPY ["/args.exe","/foo bar/"]
COPY ["/args.exe","/foo.exe"]
COPY ["/args.exe","/"]
RUN (Get-Command pwsh.exe).Source
RUN & 'c:\\foo bar\\args.exe' 'foo bar' 'baz buzz'
# Just like the above, but with linux style path separators
RUN & 'c:/foo bar/args.exe' 'foo bar' 'baz buzz'
# Another example that showcases why escaping is difficult on Windows
# A combination of both windows and linux path separators
RUN & 'c:/foo bar\\args.exe' 'foo bar' 'baz buzz'
# And another one that omits the drive letter
RUN & '/foo bar\\args.exe' 'foo bar' 'baz buzz'
RUN C:/args.exe 'foo bar' 'baz buzz'
# RUN ls
CMD ["c:\\foo bar\\args.exe", "foo bar", "baz buzz"]
d.image.Config.Cmd = args | ||
d.image.Config.ArgsEscaped = true //nolint:staticcheck // ignore SA1019: field is deprecated in OCI Image spec, but used for backward-compatibility with Docker image spec. |
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 ArgsEscaped
field was added in containerd a while ago and it essentially converts the behavior of the Cmd
field into what CmdLine
does (roughly). If ArgsEscaped
is set to true
, the shim expects that ENTRYPOINT
or CMD
is a single element array, with the command, along with any other arguments, already escaped.
For example, if we have ArgsEscaped
set to true
, by the time the CMD
reaches the shim, it should look like this:
CMD ["\"c:\\Path to\\Some\\application.exe\" "some space delimited arg" someOtherArg"]
And that gets passed along to HcsCreateProcess
. I remember having to deal with double escaping of args when I first implemented the windows executor. Back then we were sending spec.Cmd
along with args. And the args were getting escaped again once they got to the shim. Details are fuzzy, but the consensus when I discussed this issue with the MSFT folks was to just send the one string as CmdLine
instead of Cmd
and Args
.
I think this change is okay, as long as we just log the inconsistency in place of that TODO, similarly to what happens in moby now.
The whole point of the JSON syntax in the first place is to ask the runtime to properly escape the arguments for you. 😅 (I realize that's a bit at odds with how Windows works, but it is the existing convention, also honored by the "classic" builder correctly) |
Yes, Of course. Ignore that comment. There is no way that is a desirable resolution. I need to use my inner voice for some things. FWIW, the changes you made to handle I think we can do a better job when parsing the Edit: Currently the default shell on Windows is defined as: []string{"cmd", "/S", "/C"} The RUN ""c:\\foo bar\\args.exe" "foo bar" "baz buzz" hello" works, although it looks ugly. With the |
@gabriel-samfira did we still want the requested changes made or is the PR good as is? |
This PR doesn't really fix the issue. A proper fix will need to touch When we get an imperative/JSON form of the Otherwise we fix one case but break the other. @profnandaa Would you mind having a look at this? I am currently on PTO. |
Sorry for my delay, scheduled to look into this, this week. |
I was surprised to see `ArgsEscaped` being set on Linux images -- it's Windows specific and should never be set on Linux images. In a case of perfect timing, we got our first official `buildkitd.exe` builds and I realized the handling of this goes deeper now (involving the runtime/executors now too). Previously to this, we were not properly escaping Windows command/arguments while constructing `CommandLine`, which has unexpected behavior. To illustrate, I created a simple Go program that does nothing but `fmt.Printf("%#v\n", os.Args)`. I installed it at `C:\foo bar\args.exe` and set `CMD ["C:\\foo bar\\args.exe", "foo bar", "baz buzz"]`. With just that, we get the expected `[]string{"C:\\foo bar\\args.exe", "foo bar", "baz buzz"}` output from our program. However, when we *also* install `args.exe` as `C:\\foo.exe`, `C:\\foo bar\\args.exe` being unescaped at the start of `CommandLine` (thanks to `ArgsEscaped: true`) becomes ambiguous, and Windows chooses the more conservative path, and our output becomes `[]string{"C:\\foo", "bar\\args.exe", "foo bar", "baz buzz"}` instead (even though we used the imperative/JSON form of `CMD` which should've avoided this!). In the case of the new `RUN` support inside the builder, things were actually even worse! Our output (from `RUN ["C:\\foo bar\\args.exe", "foo bar", "baz buzz"]`) was instead `[]string{"C:\\foo", "bar\\args.exe", "foo", "bar", "baz", "buzz"}` because the code was effectively just `CommandLine = strings.Join(args, " ")`, which is definitely not enough. 😅 See the PR for several references to related discussions/code in Moby. 🚀 Signed-off-by: Tianon Gravi <[email protected]>
Rebased (especially to fix the conflict caused by 89ce746 / #4913) I guess now the "warning" could be added to the linter apparatus? I believe that's all that's missing here, but I don't know (and I need someone else to carry it over the line or at the very least tell me how to surface the warning in an acceptable way). |
@tianon -- currently on it, sorry took long to TAL. BTW, curious, why didn't you do this PR from your |
Just reporting that I can repro the issue. I have these two images one built with FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
ENV DEST="foo bar"
RUN mkdir "foo bar"
COPY ./main.exe ./foo.exe
COPY ./main.exe ./$DEST/args.exe
RUN dir
CMD ["C:\\foo bar\\args.exe", "foo bar", "baz buzz"] Build with
However, with your changes, I still get the regression:
Am I missing something? |
I was surprised to see
ArgsEscaped
being set on Linux images -- it's Windows specific and should never be set on Linux images. In a case of perfect timing, we got our first officialbuildkitd.exe
builds and I realized the handling of this goes deeper now (involving the runtime/executors now too).Previously to this, we were not properly escaping Windows command/arguments while constructing
CommandLine
, which has unexpected behavior.To illustrate, I created a simple Go program that does nothing but
fmt.Printf("%#v\n", os.Args)
. I installed it atC:\foo bar\args.exe
and setCMD ["C:\\foo bar\\args.exe", "foo bar", "baz buzz"]
.With just that, we get the expected
[]string{"C:\\foo bar\\args.exe", "foo bar", "baz buzz"}
output from our program. However, when we also installargs.exe
asC:\\foo.exe
,C:\\foo bar\\args.exe
being unescaped at the start ofCommandLine
(thanks toArgsEscaped: true
) becomes ambiguous, and Windows chooses the more conservative path, and our output becomes[]string{"C:\\foo", "bar\\args.exe", "foo bar", "baz buzz"}
instead (even though we used the imperative/JSON form ofCMD
which should've avoided this!).In the case of the new
RUN
support inside the builder, things were actually even worse! Our output (fromRUN ["C:\\foo bar\\args.exe", "foo bar", "baz buzz"]
) was instead[]string{"C:\\foo", "bar\\args.exe", "foo", "bar", "baz", "buzz"}
because the code was effectively justCommandLine = strings.Join(args, " ")
, which is definitely not enough. 😅Several references to related discussions/code in Moby: 🚀
There are several
TODO
s in the code that I'm not actually sure how to resolve -- how to accessArgsEscaped
fromsetArgs
andwithProcessArgs
(which I'm not actually sure whyRun
andExec
are so separate in thatcontainerdexecutor
package 😅), and either how to emit a warning indispatchCmd
/dispatchEntrypoint
or whether an error seems more appropriate (so I can actually test whether I've copied that conditional logic correctly 😂 😭 ❤️).