Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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://") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow-up: this should work with remote URLs as well

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean similar to what has been said in #2015 (comment) so we support reading a remote Dockerfile with a local context?

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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to return the absolute representation of the dockerfile path after checking its a valid one like we do for context so we can set the right dockerfile dir if context is a remote URL during build.

I was also thinking reading the file in bi.DockerfileInline and then set bi.DockerfilePath = "-" but that's hacky and would be confusing.

if err != nil {
return nil, err
}
} else if !build.IsRemoteURL(bi.DockerfilePath) && strings.HasPrefix(bi.ContextPath, "cwd://") && (inp != nil && build.IsRemoteURL(inp.URL)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this condition:

!build.IsRemoteURL(bi.DockerfilePath) && strings.HasPrefix(bi.ContextPath, "cwd://")

If Dockerfile is not remote and context is based on local dir then why would that be invalid config?

Maybe a comment would help.

// 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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this for security?

Copy link
Member Author

@crazy-max crazy-max Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, just not supported atm, see #2015 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment to better explain the issue.

}
}
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")
}