Allow Dockerfile from outside build-context

Historically, the Dockerfile had to be insde the build-context, because it was
sent as part of the build-context.

3f6dc81e10
added support for passing the Dockerfile through stdin, in which case the
contents of the Dockerfile is injected into the build-context.

This patch uses the same mechanism for situations where the location of the
Dockerfile is passed, and its path is outside of the build-context.

Before this change:

    $ mkdir -p myproject/context myproject/dockerfiles && cd myproject
    $ echo "hello" > context/hello
    $ echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > dockerfiles/Dockerfile
    $ docker build --no-cache -f $PWD/dockerfiles/Dockerfile $PWD/context

    unable to prepare context: the Dockerfile (/Users/sebastiaan/projects/test/dockerfile-outside/myproject/dockerfiles/Dockerfile) must be within the build context

After this change:

    $ mkdir -p myproject/context myproject/dockerfiles && cd myproject
    $ echo "hello" > context/hello
    $ echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > dockerfiles/Dockerfile
    $ docker build --no-cache -f $PWD/dockerfiles/Dockerfile $PWD/context

    Sending build context to Docker daemon  2.607kB
    Step 1/3 : FROM busybox
     ---> 6ad733544a63
    Step 2/3 : COPY /hello /
     ---> 9a5ae1c7be9e
    Step 3/3 : RUN cat /hello
     ---> Running in 20dfef2d180f
    hello
    Removing intermediate container 20dfef2d180f
     ---> ce1748f91bb2
    Successfully built ce1748f91bb2

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2018-02-18 01:40:55 +01:00
parent a0044ba3a7
commit a1048523d2
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
3 changed files with 66 additions and 6 deletions

View File

@ -9,8 +9,10 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"regexp" "regexp"
"runtime" "runtime"
"strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
@ -206,6 +208,14 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName) buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
case isLocalDir(specifiedContext): case isLocalDir(specifiedContext):
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName) contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
if err == nil && 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 {
return errors.Errorf("unable to open Dockerfile: %v", err)
}
defer dockerfileCtx.Close()
}
case urlutil.IsGitURL(specifiedContext): case urlutil.IsGitURL(specifiedContext):
tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName) tempDir, relDockerfile, err = build.GetContextFromGitURL(specifiedContext, options.dockerfileName)
case urlutil.IsURL(specifiedContext): case urlutil.IsURL(specifiedContext):
@ -253,7 +263,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
} }
} }
// replace Dockerfile if it was added from stdin and there is archive context // replace Dockerfile if it was added from stdin or a file outside the build-context, and there is archive context
if dockerfileCtx != nil && buildCtx != nil { if dockerfileCtx != nil && buildCtx != nil {
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx) buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
if err != nil { if err != nil {
@ -261,7 +271,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
} }
} }
// if streaming and dockerfile was not from stdin then read from file // if streaming and Dockerfile was not from stdin then read from file
// to the same reader that is usually stdin // to the same reader that is usually stdin
if options.stream && dockerfileCtx == nil { if options.stream && dockerfileCtx == nil {
dockerfileCtx, err = os.Open(relDockerfile) dockerfileCtx, err = os.Open(relDockerfile)

View File

@ -167,6 +167,10 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
return "", "", err return "", "", err
} }
relDockerfile, err := getDockerfileRelPath(absContextDir, dockerfileName) relDockerfile, err := getDockerfileRelPath(absContextDir, dockerfileName)
if err == nil && strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
return "", "", errors.Errorf("the Dockerfile (%s) must be within the build context", dockerfileName)
}
return absContextDir, relDockerfile, err return absContextDir, relDockerfile, err
} }
@ -318,10 +322,6 @@ func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error)
return "", errors.Errorf("unable to get relative Dockerfile path: %v", err) return "", errors.Errorf("unable to get relative Dockerfile path: %v", err)
} }
if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
return "", errors.Errorf("the Dockerfile (%s) must be within the build context", givenDockerfile)
}
return relDockerfile, nil return relDockerfile, nil
} }

View File

@ -108,6 +108,56 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
assert.Equal(t, []string{dockerfileName, ".dockerignore", "foo"}, actual) assert.Equal(t, []string{dockerfileName, ".dockerignore", "foo"}, actual)
} }
func TestRunBuildDockerfileOutsideContext(t *testing.T) {
dir := fs.NewDir(t, t.Name(),
fs.WithFile("data", "data file"),
)
defer dir.Remove()
// Dockerfile outside of build-context
df := fs.NewFile(t, t.Name(),
fs.WithContent(`
FROM FOOBAR
COPY data /data
`),
)
defer df.Remove()
dest, err := ioutil.TempDir("", t.Name())
require.NoError(t, err)
defer os.RemoveAll(dest)
var dockerfileName string
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
buffer := new(bytes.Buffer)
tee := io.TeeReader(context, buffer)
assert.NoError(t, archive.Untar(tee, dest, nil))
dockerfileName = options.Dockerfile
body := new(bytes.Buffer)
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
options := newBuildOptions()
options.context = dir.Path()
options.dockerfileName = df.Path()
err = runBuild(cli, options)
require.NoError(t, err)
files, err := ioutil.ReadDir(dest)
require.NoError(t, err)
var actual []string
for _, fileInfo := range files {
actual = append(actual, fileInfo.Name())
}
sort.Strings(actual)
assert.Equal(t, []string{dockerfileName, ".dockerignore", "data"}, actual)
}
// TestRunBuildFromLocalGitHubDirNonExistingRepo tests that build contexts // TestRunBuildFromLocalGitHubDirNonExistingRepo tests that build contexts
// starting with `github.com/` are special-cased, and the build command attempts // starting with `github.com/` are special-cased, and the build command attempts
// to clone the remote repo. // to clone the remote repo.