monitor: resolve paths arguments in client

Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
This commit is contained in:
Kohei Tokunaga 2023-02-01 10:27:43 +09:00
parent f3a4cd5176
commit 17105bfc50
2 changed files with 407 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
@ -28,6 +29,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
dockeropts "github.com/docker/cli/opts"
"github.com/docker/docker/builder/remotecontext/urlutil"
"github.com/docker/docker/pkg/ioutils"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
@ -514,6 +516,13 @@ func launchControllerAndRunBuild(dockerCli command.Cli, options buildOptions) er
}
// Start build
// NOTE: buildx server has the current working directory different from the client
// so we need to resolve paths to abosolute ones in the client.
optsP, err := resolvePaths(&opts)
if err != nil {
return err
}
opts = *optsP
ref, resp, err := c.Build(ctx, opts, pr, os.Stdout, os.Stderr, progress)
if err != nil {
return errors.Wrapf(err, "failed to build") // TODO: allow invoke even on error
@ -649,3 +658,150 @@ func dockerUlimitToControllerUlimit(u *dockeropts.UlimitOpt) *controllerapi.Ulim
}
return &controllerapi.UlimitOpt{Values: values}
}
// resolvePaths resolves all paths contained in controllerapi.BuildOptions
// and replaces them to absolute paths.
func resolvePaths(options *controllerapi.BuildOptions) (_ *controllerapi.BuildOptions, err error) {
if options.ContextPath != "" && options.ContextPath != "-" {
options.ContextPath, err = filepath.Abs(options.ContextPath)
if err != nil {
return nil, err
}
}
if options.DockerfileName != "" && options.DockerfileName != "-" {
options.DockerfileName, err = filepath.Abs(options.DockerfileName)
if err != nil {
return nil, err
}
}
var contexts map[string]string
for k, v := range options.NamedContexts {
p := v
if !urlutil.IsGitURL(p) && !urlutil.IsURL(p) && !strings.HasPrefix(p, "docker-image://") && !strings.HasPrefix(p, "oci-layout://") {
// named context can specify non-path value
// https://github.com/docker/buildx/blob/v0.10.3/docs/reference/buildx_build.md#-additional-build-contexts---build-context
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
if contexts == nil {
contexts = make(map[string]string)
}
contexts[k] = p
}
options.NamedContexts = contexts
var cacheFrom []*controllerapi.CacheOptionsEntry
for _, co := range options.CacheFrom {
switch co.Type {
case "local":
var attrs map[string]string
for k, v := range co.Attrs {
if attrs == nil {
attrs = make(map[string]string)
}
switch k {
case "src":
p := v
if p != "" {
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
attrs[k] = p
default:
attrs[k] = v
}
}
co.Attrs = attrs
cacheFrom = append(cacheFrom, co)
default:
cacheFrom = append(cacheFrom, co)
}
}
options.CacheFrom = cacheFrom
var cacheTo []*controllerapi.CacheOptionsEntry
for _, co := range options.CacheTo {
switch co.Type {
case "local":
var attrs map[string]string
for k, v := range co.Attrs {
if attrs == nil {
attrs = make(map[string]string)
}
switch k {
case "dest":
p := v
if p != "" {
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
attrs[k] = p
default:
attrs[k] = v
}
}
co.Attrs = attrs
cacheTo = append(cacheTo, co)
default:
cacheTo = append(cacheTo, co)
}
}
options.CacheTo = cacheTo
var exports []*controllerapi.ExportEntry
for _, e := range options.Exports {
if e.Destination != "" && e.Destination != "-" {
e.Destination, err = filepath.Abs(e.Destination)
if err != nil {
return nil, err
}
}
exports = append(exports, e)
}
options.Exports = exports
var secrets []*controllerapi.Secret
for _, s := range options.Secrets {
if s.FilePath != "" {
s.FilePath, err = filepath.Abs(s.FilePath)
if err != nil {
return nil, err
}
}
secrets = append(secrets, s)
}
options.Secrets = secrets
var ssh []*controllerapi.SSH
for _, s := range options.SSH {
var ps []string
for _, pt := range s.Paths {
p := pt
if p != "" {
p, err = filepath.Abs(p)
if err != nil {
return nil, err
}
}
ps = append(ps, p)
}
s.Paths = ps
ssh = append(ssh, s)
}
options.SSH = ssh
if options.Opts != nil && options.Opts.MetadataFile != "" {
options.Opts.MetadataFile, err = filepath.Abs(options.Opts.MetadataFile)
if err != nil {
return nil, err
}
}
return options, nil
}

251
commands/build_test.go Normal file
View File

@ -0,0 +1,251 @@
package commands
import (
"os"
"path/filepath"
"reflect"
"testing"
controllerapi "github.com/docker/buildx/controller/pb"
"github.com/stretchr/testify/require"
)
func TestResolvePaths(t *testing.T) {
tmpwd, err := os.MkdirTemp("", "testresolvepaths")
require.NoError(t, err)
defer os.Remove(tmpwd)
require.NoError(t, os.Chdir(tmpwd))
tests := []struct {
name string
options controllerapi.BuildOptions
want controllerapi.BuildOptions
}{
{
name: "contextpath",
options: controllerapi.BuildOptions{ContextPath: "test"},
want: controllerapi.BuildOptions{ContextPath: filepath.Join(tmpwd, "test")},
},
{
name: "contextpath-cwd",
options: controllerapi.BuildOptions{ContextPath: "."},
want: controllerapi.BuildOptions{ContextPath: tmpwd},
},
{
name: "contextpath-dash",
options: controllerapi.BuildOptions{ContextPath: "-"},
want: controllerapi.BuildOptions{ContextPath: "-"},
},
{
name: "dockerfilename",
options: controllerapi.BuildOptions{DockerfileName: "test"},
want: controllerapi.BuildOptions{DockerfileName: filepath.Join(tmpwd, "test")},
},
{
name: "dockerfilename-dash",
options: controllerapi.BuildOptions{DockerfileName: "-"},
want: controllerapi.BuildOptions{DockerfileName: "-"},
},
{
name: "contexts",
options: controllerapi.BuildOptions{NamedContexts: map[string]string{"a": "test1", "b": "test2",
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git"}},
want: controllerapi.BuildOptions{NamedContexts: map[string]string{"a": filepath.Join(tmpwd, "test1"), "b": filepath.Join(tmpwd, "test2"),
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git"}},
},
{
name: "cache-from",
options: controllerapi.BuildOptions{
CacheFrom: []*controllerapi.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"src": "test"},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
want: controllerapi.BuildOptions{
CacheFrom: []*controllerapi.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"src": filepath.Join(tmpwd, "test")},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
},
{
name: "cache-to",
options: controllerapi.BuildOptions{
CacheTo: []*controllerapi.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"dest": "test"},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
want: controllerapi.BuildOptions{
CacheTo: []*controllerapi.CacheOptionsEntry{
{
Type: "local",
Attrs: map[string]string{"dest": filepath.Join(tmpwd, "test")},
},
{
Type: "registry",
Attrs: map[string]string{"ref": "user/app"},
},
},
},
},
{
name: "exports",
options: controllerapi.BuildOptions{
Exports: []*controllerapi.ExportEntry{
{
Type: "local",
Destination: "-",
},
{
Type: "local",
Destination: "test1",
},
{
Type: "tar",
Destination: "test3",
},
{
Type: "oci",
Destination: "-",
},
{
Type: "docker",
Destination: "test4",
},
{
Type: "image",
Attrs: map[string]string{"push": "true"},
},
},
},
want: controllerapi.BuildOptions{
Exports: []*controllerapi.ExportEntry{
{
Type: "local",
Destination: "-",
},
{
Type: "local",
Destination: filepath.Join(tmpwd, "test1"),
},
{
Type: "tar",
Destination: filepath.Join(tmpwd, "test3"),
},
{
Type: "oci",
Destination: "-",
},
{
Type: "docker",
Destination: filepath.Join(tmpwd, "test4"),
},
{
Type: "image",
Attrs: map[string]string{"push": "true"},
},
},
},
},
{
name: "secrets",
options: controllerapi.BuildOptions{
Secrets: []*controllerapi.Secret{
{
FilePath: "test1",
},
{
ID: "val",
Env: "a",
},
{
ID: "test",
FilePath: "test3",
},
},
},
want: controllerapi.BuildOptions{
Secrets: []*controllerapi.Secret{
{
FilePath: filepath.Join(tmpwd, "test1"),
},
{
ID: "val",
Env: "a",
},
{
ID: "test",
FilePath: filepath.Join(tmpwd, "test3"),
},
},
},
},
{
name: "ssh",
options: controllerapi.BuildOptions{
SSH: []*controllerapi.SSH{
{
ID: "default",
Paths: []string{"test1", "test2"},
},
{
ID: "a",
Paths: []string{"test3"},
},
},
},
want: controllerapi.BuildOptions{
SSH: []*controllerapi.SSH{
{
ID: "default",
Paths: []string{filepath.Join(tmpwd, "test1"), filepath.Join(tmpwd, "test2")},
},
{
ID: "a",
Paths: []string{filepath.Join(tmpwd, "test3")},
},
},
},
},
{
name: "metadatafile",
options: controllerapi.BuildOptions{
Opts: &controllerapi.CommonOptions{
MetadataFile: "test1",
},
},
want: controllerapi.BuildOptions{
Opts: &controllerapi.CommonOptions{
MetadataFile: filepath.Join(tmpwd, "test1"),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := resolvePaths(&tt.options)
require.NoError(t, err)
if !reflect.DeepEqual(tt.want, *got) {
t.Fatalf("expected %#v, got %#v", tt.want, *got)
}
})
}
}