Skip to content

Commit

Permalink
Merge pull request #2015 from crazy-max/fix-bake-cwd-dockerfile
Browse files Browse the repository at this point in the history
bake: local dockerfile support for remote definition
  • Loading branch information
tonistiigi authored Oct 19, 2023
2 parents ad674e2 + 7497e64 commit 31d021a
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 5 deletions.
35 changes: 35 additions & 0 deletions bake/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -1044,6 +1044,9 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
if t.Dockerfile != nil {
dockerfilePath = *t.Dockerfile
}
if !strings.HasPrefix(dockerfilePath, "cwd://") {
dockerfilePath = path.Clean(dockerfilePath)
}

bi := build.Inputs{
ContextPath: contextPath,
Expand All @@ -1054,6 +1057,38 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
bi.DockerfileInline = *t.DockerfileInline
}
updateContext(&bi, inp)
if strings.HasPrefix(bi.DockerfilePath, "cwd://") {
// If Dockerfile is local for a remote invocation, we first check if
// it's not outside the working directory and then resolve it to an
// absolute path.
bi.DockerfilePath = path.Clean(strings.TrimPrefix(bi.DockerfilePath, "cwd://"))
if err := checkPath(bi.DockerfilePath); err != nil {
return nil, err
}
var err error
bi.DockerfilePath, err = filepath.Abs(bi.DockerfilePath)
if err != nil {
return nil, err
}
} else if !build.IsRemoteURL(bi.DockerfilePath) && strings.HasPrefix(bi.ContextPath, "cwd://") && (inp != nil && build.IsRemoteURL(inp.URL)) {
// We don't currently support reading a remote Dockerfile with a local
// context when doing a remote invocation because we automatically
// derive the dockerfile from the context atm:
//
// target "default" {
// context = BAKE_CMD_CONTEXT
// dockerfile = "Dockerfile.app"
// }
//
// > docker buildx bake https://github.com/foo/bar.git
// failed to solve: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount3004544897/Dockerfile.app: no such file or directory
//
// To avoid mistakenly reading a local Dockerfile, we check if the
// Dockerfile exists locally and if so, we error out.
if _, err := os.Stat(filepath.Join(path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://")), bi.DockerfilePath)); err == nil {
return nil, errors.Errorf("reading a dockerfile for a remote build invocation is currently not supported")
}
}
if strings.HasPrefix(bi.ContextPath, "cwd://") {
bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://"))
}
Expand Down
42 changes: 37 additions & 5 deletions bake/bake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package bake
import (
"context"
"os"
"path/filepath"
"sort"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -379,7 +381,7 @@ services:
require.Equal(t, []string{"web_app"}, g["default"].Targets)
}

func TestHCLCwdPrefix(t *testing.T) {
func TestHCLContextCwdPrefix(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
Expand All @@ -400,11 +402,41 @@ func TestHCLCwdPrefix(t *testing.T) {

require.Equal(t, 1, len(m))
require.Contains(t, m, "app")
require.Equal(t, "test", *m["app"].Dockerfile)
require.Equal(t, "foo", *m["app"].Context)
assert.Equal(t, "test", *m["app"].Dockerfile)
assert.Equal(t, "foo", *m["app"].Context)
assert.Equal(t, "foo/test", bo["app"].Inputs.DockerfilePath)
assert.Equal(t, "foo", bo["app"].Inputs.ContextPath)
}

func TestHCLDockerfileCwdPrefix(t *testing.T) {
fp := File{
Name: "docker-bake.hcl",
Data: []byte(
`target "app" {
context = "."
dockerfile = "cwd://Dockerfile.app"
}`),
}
ctx := context.TODO()

cwd, err := os.Getwd()
require.NoError(t, err)

require.Equal(t, "foo/test", bo["app"].Inputs.DockerfilePath)
require.Equal(t, "foo", bo["app"].Inputs.ContextPath)
m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
require.NoError(t, err)

bo, err := TargetsToBuildOpt(m, &Input{})
require.NoError(t, err)

require.Equal(t, 1, len(g))
require.Equal(t, []string{"app"}, g["default"].Targets)

require.Equal(t, 1, len(m))
require.Contains(t, m, "app")
assert.Equal(t, "cwd://Dockerfile.app", *m["app"].Dockerfile)
assert.Equal(t, ".", *m["app"].Context)
assert.Equal(t, filepath.Join(cwd, "Dockerfile.app"), bo["app"].Inputs.DockerfilePath)
assert.Equal(t, ".", bo["app"].Inputs.ContextPath)
}

func TestOverrideMerge(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,10 @@ func LoadInputs(ctx context.Context, d *driver.DriverHandle, inp Inputs, pw prog
case IsRemoteURL(inp.ContextPath):
if inp.DockerfilePath == "-" {
dockerfileReader = inp.InStream
} else if filepath.IsAbs(inp.DockerfilePath) {
dockerfileDir = filepath.Dir(inp.DockerfilePath)
dockerfileName = filepath.Base(inp.DockerfilePath)
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
}
target.FrontendAttrs["context"] = inp.ContextPath
default:
Expand Down
101 changes: 101 additions & 0 deletions tests/bake.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tests

import (
"os"
"path/filepath"
"testing"

Expand All @@ -25,6 +26,8 @@ var bakeTests = []func(t *testing.T, sb integration.Sandbox){
testBakeRemoteContextSubdir,
testBakeRemoteCmdContextEscapeRoot,
testBakeRemoteCmdContextEscapeRelative,
testBakeRemoteDockerfileCwd,
testBakeRemoteLocalContextRemoteDockerfile,
}

func testBakeLocal(t *testing.T, sb integration.Sandbox) {
Expand Down Expand Up @@ -287,3 +290,101 @@ EOT
require.NoError(t, err, out)
require.FileExists(t, filepath.Join(dirDest, "foo"))
}

func testBakeRemoteDockerfileCwd(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(`
target "default" {
context = "."
dockerfile = "cwd://Dockerfile.app"
}
`)
dockerfile := []byte(`
FROM scratch
COPY bar /bar
`)
dockerfileApp := []byte(`
FROM scratch
COPY foo /foo
`)

dirSpec := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
fstest.CreateFile("Dockerfile", dockerfile, 0600),
fstest.CreateFile("foo", []byte("foo"), 0600),
fstest.CreateFile("bar", []byte("bar"), 0600),
)
dirSrc := tmpdir(
t,
fstest.CreateFile("Dockerfile.app", dockerfileApp, 0600),
)
dirDest := t.TempDir()

git, err := gitutil.New(gitutil.WithWorkingDir(dirSpec))
require.NoError(t, err)

gitutil.GitInit(git, t)
gitutil.GitAdd(git, t, "docker-bake.hcl")
gitutil.GitAdd(git, t, "Dockerfile")
gitutil.GitAdd(git, t, "foo")
gitutil.GitAdd(git, t, "bar")
gitutil.GitCommit(git, t, "initial commit")
addr := gitutil.GitServeHTTP(git, t)

out, err := bakeCmd(
sb,
withDir(dirSrc),
withArgs(addr, "--set", "*.output=type=local,dest="+dirDest),
)
require.NoError(t, err, out)
require.FileExists(t, filepath.Join(dirDest, "foo"))

err = os.Remove(filepath.Join(dirSrc, "Dockerfile.app"))
require.NoError(t, err)

out, err = bakeCmd(
sb,
withDir(dirSrc),
withArgs(addr, "--set", "*.output=type=cacheonly"),
)
require.Error(t, err, out)
}

func testBakeRemoteLocalContextRemoteDockerfile(t *testing.T, sb integration.Sandbox) {
bakefile := []byte(`
target "default" {
context = BAKE_CMD_CONTEXT
dockerfile = "Dockerfile.app"
}
`)
dockerfileApp := []byte(`
FROM scratch
COPY foo /foo
`)

dirSpec := tmpdir(
t,
fstest.CreateFile("docker-bake.hcl", bakefile, 0600),
)
dirSrc := tmpdir(
t,
fstest.CreateFile("Dockerfile.app", dockerfileApp, 0600),
fstest.CreateFile("foo", []byte("foo"), 0600),
)

git, err := gitutil.New(gitutil.WithWorkingDir(dirSpec))
require.NoError(t, err)

gitutil.GitInit(git, t)
gitutil.GitAdd(git, t, "docker-bake.hcl")
gitutil.GitCommit(git, t, "initial commit")
addr := gitutil.GitServeHTTP(git, t)

out, err := bakeCmd(
sb,
withDir(dirSrc),
withArgs(addr, "--set", "*.output=type=cacheonly"),
)
require.Error(t, err, out)
require.Contains(t, out, "reading a dockerfile for a remote build invocation is currently not supported")
}

0 comments on commit 31d021a

Please sign in to comment.