diff --git a/cli/command/image/build.go b/cli/command/image/build.go index 66beeee2bb..6c6db76675 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -28,7 +28,6 @@ import ( buildtypes "github.com/docker/docker/api/types/build" "github.com/docker/docker/api/types/container" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/builder/remotecontext/urlutil" "github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/streamformatter" "github.com/moby/go-archive" @@ -76,12 +75,6 @@ func (o buildOptions) dockerfileFromStdin() bool { return o.dockerfileName == "-" } -// contextFromStdin returns true when the user specified that the build context -// should be read from stdin -func (o buildOptions) contextFromStdin() bool { - return o.context == "-" -} - func newBuildOptions() buildOptions { ulimits := make(map[string]*container.Ulimit) return buildOptions{ @@ -189,21 +182,24 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) buildCtx io.ReadCloser dockerfileCtx io.ReadCloser contextDir string - tempDir string relDockerfile string progBuff io.Writer buildBuff io.Writer remote string ) + contextType, err := build.DetectContextType(options.context) + if err != nil { + return err + } + if options.dockerfileFromStdin() { - if options.contextFromStdin() { + if contextType == build.ContextTypeStdin { return errors.New("invalid argument: can't use stdin for both build context and dockerfile") } dockerfileCtx = dockerCli.In() } - specifiedContext := options.context progBuff = dockerCli.Out() buildBuff = dockerCli.Out() if options.quiet { @@ -217,13 +213,19 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) } } - switch { - case options.contextFromStdin(): + switch contextType { + case build.ContextTypeStdin: // buildCtx is tar archive. if stdin was dockerfile then it is wrapped buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName) - case isLocalDir(specifiedContext): - contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName) - if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) { + if err != nil { + return fmt.Errorf("unable to prepare context from STDIN: %w", err) + } + case build.ContextTypeLocal: + contextDir, relDockerfile, err = build.GetContextFromLocalDir(options.context, options.dockerfileName) + if err != nil { + return errors.Errorf("unable to prepare context: %s", err) + } + if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) { // Dockerfile is outside of build-context; read the Dockerfile and pass it as dockerfileCtx dockerfileCtx, err = os.Open(options.dockerfileName) if err != nil { @@ -231,24 +233,23 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) } defer dockerfileCtx.Close() } - case urlutil.IsGitURL(specifiedContext): - tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName) - case urlutil.IsURL(specifiedContext): - buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, specifiedContext, options.dockerfileName) - default: - return errors.Errorf("unable to prepare context: path %q not found", specifiedContext) - } - - if err != nil { - if options.quiet && urlutil.IsURL(specifiedContext) { + case build.ContextTypeGit: + var tempDir string + tempDir, relDockerfile, err = build.GetContextFromGitURL(options.context, options.dockerfileName) + if err != nil { + return errors.Errorf("unable to prepare context: %s", err) + } + defer func() { + _ = os.RemoveAll(tempDir) + }() + contextDir = tempDir + case build.ContextTypeRemote: + buildCtx, relDockerfile, err = build.GetContextFromURL(progBuff, options.context, options.dockerfileName) + if err != nil && options.quiet { _, _ = fmt.Fprintln(dockerCli.Err(), progBuff) } - return errors.Errorf("unable to prepare context: %s", err) - } - - if tempDir != "" { - defer os.RemoveAll(tempDir) - contextDir = tempDir + default: + return errors.Errorf("unable to prepare context: path %q not found", options.context) } // read from a directory into tar archive @@ -415,11 +416,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) return nil } -func isLocalDir(c string) bool { - _, err := os.Stat(c) - return err == nil -} - type translatorFunc func(context.Context, reference.NamedTagged) (reference.Canonical, error) // validateTag checks if the given image name can be resolved. diff --git a/cli/command/image/build/context_detect.go b/cli/command/image/build/context_detect.go new file mode 100644 index 0000000000..7e47addc80 --- /dev/null +++ b/cli/command/image/build/context_detect.go @@ -0,0 +1,39 @@ +package build + +import ( + "fmt" + "os" + + "github.com/docker/docker/builder/remotecontext/urlutil" +) + +// ContextType describes the type (source) of build-context specified. +type ContextType string + +const ( + ContextTypeStdin ContextType = "stdin" // ContextTypeStdin indicates that the build-context is a TAR archive passed through STDIN. + ContextTypeLocal ContextType = "local" // ContextTypeLocal indicates that the build-context is a local directory. + ContextTypeRemote ContextType = "remote" // ContextTypeRemote indicates that the build-context is a remote URL. + ContextTypeGit ContextType = "git" // ContextTypeGit indicates that the build-context is a GIT URL. +) + +// DetectContextType detects the type (source) of the build-context. +func DetectContextType(specifiedContext string) (ContextType, error) { + switch { + case specifiedContext == "-": + return ContextTypeStdin, nil + case isLocalDir(specifiedContext): + return ContextTypeLocal, nil + case urlutil.IsGitURL(specifiedContext): + return ContextTypeGit, nil + case urlutil.IsURL(specifiedContext): + return ContextTypeRemote, nil + default: + return "", fmt.Errorf("unable to prepare context: path %q not found", specifiedContext) + } +} + +func isLocalDir(c string) bool { + _, err := os.Stat(c) + return err == nil +}