-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Before only raw buildkitd unix socket or docker socket was supported. This adds support for: - Docker with custom socket path - Buildx instances (defaults to the currently selected buildx builder instance) - Connect over a docker container which works similarly to buildx but does not require a buildx config. For the buildx support, currently only buildx instances over docker-containers are supported. Support other buildx drivers is possible (e.g. the kubernetes driver), but just needs some working through what's there. Also adds buildkit's connection helpers so any of the "standard" buildkit ones will just work. Signed-off-by: Brian Goff <[email protected]>
- Loading branch information
Showing
8 changed files
with
309 additions
and
62 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package connhelpers | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"math/rand" | ||
"net" | ||
"net/url" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
|
||
"github.com/cpuguy83/dockercfg" | ||
"github.com/cpuguy83/go-docker" | ||
"github.com/cpuguy83/go-docker/container" | ||
"github.com/cpuguy83/go-docker/errdefs" | ||
"github.com/moby/buildkit/client/connhelper" | ||
log "github.com/sirupsen/logrus" | ||
) | ||
|
||
func init() { | ||
connhelper.Register("buildx", Buildx) | ||
} | ||
|
||
type buildxConfig struct { | ||
Driver string | ||
Nodes []struct { | ||
Name string | ||
Endpoint string | ||
} | ||
} | ||
|
||
// Buildx returns a buildkit connection helper for connecting to a buildx instance. | ||
// Only "docker-container" buildkit instances are currently supported. | ||
// If there are multiple nodes configured, one will be chosen at random. | ||
func Buildx(u *url.URL) (*connhelper.ConnectionHelper, error) { | ||
if u.Path != "" { | ||
return nil, fmt.Errorf("buildx driver does not support path elements: %s", u.Path) | ||
} | ||
return &connhelper.ConnectionHelper{ | ||
ContextDialer: buildxContextDialer(u.Host), | ||
}, nil | ||
} | ||
|
||
func buildxContextDialer(builder string) func(context.Context, string) (net.Conn, error) { | ||
return func(ctx context.Context, _ string) (net.Conn, error) { | ||
configPath, err := dockercfg.ConfigPath() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if builder == "" { | ||
// Standard env for setting a buildx builder name to use | ||
// This is used by buildx so we should use it too. | ||
builder = os.Getenv("BUILDX_BUILDER") | ||
} | ||
|
||
base := filepath.Join(filepath.Dir(configPath), "buildx") | ||
if builder == "" { | ||
dt, err := os.ReadFile(filepath.Join(base, "current")) | ||
if err != nil { | ||
return nil, err | ||
} | ||
type ref struct { | ||
Name string `json:"name"` | ||
} | ||
var r ref | ||
if err := json.Unmarshal(dt, &r); err != nil { | ||
return nil, fmt.Errorf("could not unmarshal buildx config: %w", err) | ||
} | ||
builder = r.Name | ||
} | ||
|
||
// Note: buildx inspect does not return json here, so we can't use the output directly | ||
cmd := exec.CommandContext(ctx, "docker", "buildx", "inspect", "--bootstrap", builder) | ||
errBuf := bytes.NewBuffer(nil) | ||
cmd.Stderr = errBuf | ||
if err := cmd.Run(); err != nil { | ||
return nil, fmt.Errorf("could not inspect buildx instance: %w: %s", err, errBuf.String()) | ||
} | ||
|
||
// Read the config from the buildx instance | ||
dt, err := os.ReadFile(filepath.Join(base, "instances", builder)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var cfg buildxConfig | ||
if err := json.Unmarshal(dt, &cfg); err != nil { | ||
return nil, fmt.Errorf("could not unmarshal buildx instance config: %w", err) | ||
} | ||
|
||
if cfg.Driver != "docker-container" { | ||
return nil, fmt.Errorf("unsupported buildx driver: %s", cfg.Driver) | ||
} | ||
|
||
if len(cfg.Nodes) == 0 { | ||
return nil, errors.New("no nodes configured for buildx instance") | ||
} | ||
|
||
log.WithFields(log.Fields{ | ||
"driver": cfg.Driver, | ||
"endpoint": cfg.Nodes[0].Endpoint, | ||
"name": cfg.Nodes[0].Name, | ||
}).Debug("Connect to buildx instance") | ||
|
||
nodes := cfg.Nodes | ||
if len(nodes) > 1 { | ||
rand.Shuffle(len(nodes), func(i, j int) { | ||
nodes[i], nodes[j] = nodes[j], nodes[i] | ||
}) | ||
} | ||
return containerContextDialer(ctx, nodes[0].Endpoint, "buildx_buildkit_"+nodes[0].Name) | ||
} | ||
} | ||
|
||
func containerContextDialer(ctx context.Context, host, name string) (net.Conn, error) { | ||
tr, err := getDockerTransport(host) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
cli := docker.NewClient(docker.WithTransport(tr)) | ||
c := cli.ContainerService().NewContainer(ctx, name) | ||
|
||
conn1, conn2 := net.Pipe() | ||
ep, err := c.Exec(ctx, container.WithExecCmd("buildctl", "dial-stdio"), func(cfg *container.ExecConfig) { | ||
cfg.Stdin = conn1 | ||
cfg.Stdout = conn1 | ||
cfg.Stderr = conn1 | ||
}) | ||
if err != nil { | ||
if errdefs.IsNotFound(err) { | ||
return nil, fmt.Errorf("could not find container %s: %w", name, err) | ||
} | ||
if err2 := c.Start(ctx); err2 != nil { | ||
return nil, err | ||
} | ||
|
||
ep, err = c.Exec(ctx, container.WithExecCmd("buildctl", "dial-stdio"), func(cfg *container.ExecConfig) { | ||
cfg.Stdin = conn1 | ||
cfg.Stdout = conn1 | ||
cfg.Stderr = conn1 | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
|
||
if err := ep.Start(ctx); err != nil { | ||
return nil, fmt.Errorf("could not start exec proxy: %w", err) | ||
} | ||
|
||
return conn2, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package connhelpers | ||
|
||
import ( | ||
"context" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"path" | ||
|
||
"github.com/cpuguy83/go-docker/transport" | ||
"github.com/cpuguy83/go-docker/version" | ||
"github.com/moby/buildkit/client/connhelper" | ||
) | ||
|
||
func init() { | ||
connhelper.Register("docker", Docker) | ||
} | ||
|
||
// Docker returns a buildkit connection helper for connecting to a docker daemon. | ||
func Docker(u *url.URL) (*connhelper.ConnectionHelper, error) { | ||
tr, err := getDockerTransport(path.Join(u.Host, u.Path)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &connhelper.ConnectionHelper{ | ||
ContextDialer: func(ctx context.Context, addr string) (net.Conn, error) { | ||
return tr.DoRaw(ctx, http.MethodPost, version.Join(ctx, "/grpc"), transport.WithUpgrade("h2c")) | ||
}, | ||
}, nil | ||
} | ||
|
||
func getDockerTransport(addr string) (transport.Doer, error) { | ||
if addr == "" { | ||
addr = os.Getenv("DOCKER_HOST") | ||
} | ||
if addr == "" { | ||
return transport.DefaultTransport() | ||
} | ||
return transport.FromConnectionString(addr) | ||
} |
Oops, something went wrong.