7873c27cfb
strings.ReplaceAll(s, old, new) is a wrapper function for strings.Replace(s, old, new, -1). But strings.ReplaceAll is more readable and removes the hardcoded -1. Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
141 lines
5.8 KiB
Go
141 lines
5.8 KiB
Go
package dockerfile // import "github.com/docker/docker/builder/dockerfile"
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/pkg/system"
|
|
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
|
)
|
|
|
|
var pattern = regexp.MustCompile(`^[a-zA-Z]:\.$`)
|
|
|
|
// normalizeWorkdir normalizes a user requested working directory in a
|
|
// platform semantically consistent way.
|
|
func normalizeWorkdir(platform string, current string, requested string) (string, error) {
|
|
if platform == "" {
|
|
platform = "windows"
|
|
}
|
|
if platform == "windows" {
|
|
return normalizeWorkdirWindows(current, requested)
|
|
}
|
|
return normalizeWorkdirUnix(current, requested)
|
|
}
|
|
|
|
// normalizeWorkdirUnix normalizes a user requested working directory in a
|
|
// platform semantically consistent way.
|
|
func normalizeWorkdirUnix(current string, requested string) (string, error) {
|
|
if requested == "" {
|
|
return "", errors.New("cannot normalize nothing")
|
|
}
|
|
current = strings.ReplaceAll(current, string(os.PathSeparator), "/")
|
|
requested = strings.ReplaceAll(requested, string(os.PathSeparator), "/")
|
|
if !path.IsAbs(requested) {
|
|
return path.Join(`/`, current, requested), nil
|
|
}
|
|
return requested, nil
|
|
}
|
|
|
|
// normalizeWorkdirWindows normalizes a user requested working directory in a
|
|
// platform semantically consistent way.
|
|
func normalizeWorkdirWindows(current string, requested string) (string, error) {
|
|
if requested == "" {
|
|
return "", errors.New("cannot normalize nothing")
|
|
}
|
|
|
|
// `filepath.Clean` will replace "" with "." so skip in that case
|
|
if current != "" {
|
|
current = filepath.Clean(current)
|
|
}
|
|
if requested != "" {
|
|
requested = filepath.Clean(requested)
|
|
}
|
|
|
|
// If either current or requested in Windows is:
|
|
// C:
|
|
// C:.
|
|
// then an error will be thrown as the definition for the above
|
|
// refers to `current directory on drive C:`
|
|
// Since filepath.Clean() will automatically normalize the above
|
|
// to `C:.`, we only need to check the last format
|
|
if pattern.MatchString(current) {
|
|
return "", fmt.Errorf("%s is not a directory. If you are specifying a drive letter, please add a trailing '\\'", current)
|
|
}
|
|
if pattern.MatchString(requested) {
|
|
return "", fmt.Errorf("%s is not a directory. If you are specifying a drive letter, please add a trailing '\\'", requested)
|
|
}
|
|
|
|
// Target semantics is C:\somefolder, specifically in the format:
|
|
// UPPERCASEDriveLetter-Colon-Backslash-FolderName. We are already
|
|
// guaranteed that `current`, if set, is consistent. This allows us to
|
|
// cope correctly with any of the following in a Dockerfile:
|
|
// WORKDIR a --> C:\a
|
|
// WORKDIR c:\\foo --> C:\foo
|
|
// WORKDIR \\foo --> C:\foo
|
|
// WORKDIR /foo --> C:\foo
|
|
// WORKDIR c:\\foo \ WORKDIR bar --> C:\foo --> C:\foo\bar
|
|
// WORKDIR C:/foo \ WORKDIR bar --> C:\foo --> C:\foo\bar
|
|
// WORKDIR C:/foo \ WORKDIR \\bar --> C:\foo --> C:\bar
|
|
// WORKDIR /foo \ WORKDIR c:/bar --> C:\foo --> C:\bar
|
|
if len(current) == 0 || system.IsAbs(requested) {
|
|
if (requested[0] == os.PathSeparator) ||
|
|
(len(requested) > 1 && string(requested[1]) != ":") ||
|
|
(len(requested) == 1) {
|
|
requested = filepath.Join(`C:\`, requested)
|
|
}
|
|
} else {
|
|
requested = filepath.Join(current, requested)
|
|
}
|
|
// Upper-case drive letter
|
|
return (strings.ToUpper(string(requested[0])) + requested[1:]), nil
|
|
}
|
|
|
|
// resolveCmdLine takes a command line arg set and optionally prepends a platform-specific
|
|
// shell in front of it. It returns either an array of arguments and an indication that
|
|
// the arguments are not yet escaped; Or, an array containing a single command line element
|
|
// along with an indication that the arguments are escaped so the runtime shouldn't escape.
|
|
//
|
|
// A better solution could be made, but it would be exceptionally invasive throughout
|
|
// many parts of the daemon which are coded assuming Linux args array only only, not taking
|
|
// account of Windows-natural command line semantics and it's argv handling. Put another way,
|
|
// while what is here is good-enough, it could be improved, but would be highly invasive.
|
|
//
|
|
// The commands when this function is called are RUN, ENTRYPOINT and CMD.
|
|
func resolveCmdLine(cmd instructions.ShellDependantCmdLine, runConfig *container.Config, os, command, original string) ([]string, bool) {
|
|
|
|
// Make sure we return an empty array if there is no cmd.CmdLine
|
|
if len(cmd.CmdLine) == 0 {
|
|
return []string{}, runConfig.ArgsEscaped
|
|
}
|
|
|
|
if os == "windows" { // ie WCOW
|
|
if cmd.PrependShell {
|
|
// WCOW shell-form. Return a single-element array containing the original command line prepended with the shell.
|
|
// Also indicate that it has not been escaped (so will be passed through directly to HCS). Note that
|
|
// we go back to the original un-parsed command line in the dockerfile line, strip off both the command part of
|
|
// it (RUN/ENTRYPOINT/CMD), and also strip any leading white space. IOW, we deliberately ignore any prior parsing
|
|
// so as to ensure it is treated exactly as a command line. For those interested, `RUN mkdir "c:/foo"` is a particularly
|
|
// good example of why this is necessary if you fancy debugging how cmd.exe and its builtin mkdir works. (Windows
|
|
// doesn't have a mkdir.exe, and I'm guessing cmd.exe has some very long unavoidable and unchangeable historical
|
|
// design decisions over how both its built-in echo and mkdir are coded. Probably more too.)
|
|
original = original[len(command):] // Strip off the command
|
|
original = strings.TrimLeft(original, " \t\v\n") // Strip of leading whitespace
|
|
return []string{strings.Join(getShell(runConfig, os), " ") + " " + original}, true
|
|
}
|
|
|
|
// WCOW JSON/"exec" form.
|
|
return cmd.CmdLine, false
|
|
}
|
|
|
|
// LCOW - use args as an array, same as LCOL.
|
|
if cmd.PrependShell && cmd.CmdLine != nil {
|
|
return append(getShell(runConfig, os), cmd.CmdLine...), false
|
|
}
|
|
return cmd.CmdLine, false
|
|
}
|