mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-17 16:37:46 +08:00
controller: remove controller grpc service
Remove the controller grpc service along with associated code related to sessions or remote controllers. Data types that are still used with complicated dependency chains have been kept in the same package for a future refactor. Signed-off-by: Jonathan A. Sternberg <jonathan.sternberg@docker.com>
This commit is contained in:
parent
2eaea647d8
commit
2f1be25b8f
@ -104,12 +104,10 @@ type buildOptions struct {
|
|||||||
exportPush bool
|
exportPush bool
|
||||||
exportLoad bool
|
exportLoad bool
|
||||||
|
|
||||||
control.ControlOptions
|
|
||||||
|
|
||||||
invokeConfig *invokeConfig
|
invokeConfig *invokeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error) {
|
func (o *buildOptions) toControllerOptions() (*cbuild.Options, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
buildArgs, err := listToMap(o.buildArgs, true)
|
buildArgs, err := listToMap(o.buildArgs, true)
|
||||||
@ -122,7 +120,7 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := controllerapi.BuildOptions{
|
opts := cbuild.Options{
|
||||||
Allow: o.allow,
|
Allow: o.allow,
|
||||||
Annotations: o.annotations,
|
Annotations: o.annotations,
|
||||||
BuildArgs: buildArgs,
|
BuildArgs: buildArgs,
|
||||||
@ -420,7 +418,7 @@ func getImageID(resp map[string]string) string {
|
|||||||
return dgst
|
return dgst
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
|
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild.Options, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
|
||||||
resp, res, dfmap, err := cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, false)
|
resp, res, dfmap, err := cbuild.RunBuild(ctx, dockerCli, opts, dockerCli.In(), printer, false)
|
||||||
if res != nil {
|
if res != nil {
|
||||||
res.Done()
|
res.Done()
|
||||||
@ -428,15 +426,12 @@ func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllera
|
|||||||
return resp, dfmap, err
|
return resp, dfmap, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
|
func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *cbuild.Options, options buildOptions, printer *progress.Printer) (*client.SolveResponse, *build.Inputs, error) {
|
||||||
if options.invokeConfig != nil && (options.dockerfileName == "-" || options.contextPath == "-") {
|
if options.invokeConfig != nil && (options.dockerfileName == "-" || options.contextPath == "-") {
|
||||||
// stdin must be usable for monitor
|
// stdin must be usable for monitor
|
||||||
return nil, nil, errors.Errorf("Dockerfile or context from stdin is not supported with invoke")
|
return nil, nil, errors.Errorf("Dockerfile or context from stdin is not supported with invoke")
|
||||||
}
|
}
|
||||||
c, err := controller.NewController(ctx, options.ControlOptions, dockerCli, printer)
|
c := controller.NewController(ctx, dockerCli)
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
logrus.Warnf("failed to close server connection %v", err)
|
logrus.Warnf("failed to close server connection %v", err)
|
||||||
@ -445,7 +440,7 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
|||||||
|
|
||||||
// NOTE: buildx server has the current working directory different from the client
|
// NOTE: buildx server has the current working directory different from the client
|
||||||
// so we need to resolve paths to abosolute ones in the client.
|
// so we need to resolve paths to abosolute ones in the client.
|
||||||
opts, err = controllerapi.ResolveOptionPaths(opts)
|
opts, err := cbuild.ResolveOptionPaths(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -471,7 +466,7 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ref, resp, inputs, err = c.Build(ctx, opts, pr, printer)
|
resp, inputs, err = c.Build(ctx, opts, pr, printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var be *controllererrors.BuildError
|
var be *controllererrors.BuildError
|
||||||
if errors.As(err, &be) {
|
if errors.As(err, &be) {
|
||||||
@ -515,8 +510,8 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
|||||||
resp, retErr = monitorBuildResult.Resp, monitorBuildResult.Err
|
resp, retErr = monitorBuildResult.Resp, monitorBuildResult.Err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := c.Disconnect(ctx, ref); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
logrus.Warnf("disconnect error: %v", err)
|
logrus.Warnf("close error: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -653,14 +648,6 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.D
|
|||||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
|
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
|
||||||
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`)
|
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`)
|
||||||
|
|
||||||
if confutil.IsExperimental() {
|
|
||||||
// TODO: move this to debug command if needed
|
|
||||||
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect")
|
|
||||||
flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)")
|
|
||||||
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server)")
|
|
||||||
cobrautil.MarkFlagsExperimental(flags, "root", "detach", "server-config")
|
|
||||||
}
|
|
||||||
|
|
||||||
flags.StringVar(&options.callFunc, "call", "build", `Set method for evaluating build ("check", "outline", "targets")`)
|
flags.StringVar(&options.callFunc, "call", "build", `Set method for evaluating build ("check", "outline", "targets")`)
|
||||||
flags.VarPF(callAlias(&options.callFunc, "check"), "check", "", `Shorthand for "--call=check"`)
|
flags.VarPF(callAlias(&options.callFunc, "check"), "check", "", `Shorthand for "--call=check"`)
|
||||||
flags.Lookup("check").NoOptDefVal = "true"
|
flags.Lookup("check").NoOptDefVal = "true"
|
||||||
@ -1017,12 +1004,12 @@ func (cfg *invokeConfig) needsDebug(retErr error) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *invokeConfig) runDebug(ctx context.Context, ref string, options *controllerapi.BuildOptions, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*monitor.MonitorBuildResult, error) {
|
func (cfg *invokeConfig) runDebug(ctx context.Context, ref string, options *cbuild.Options, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*monitor.MonitorBuildResult, error) {
|
||||||
con := console.Current()
|
con := console.Current()
|
||||||
if err := con.SetRaw(); err != nil {
|
if err := con.SetRaw(); err != nil {
|
||||||
// TODO: run disconnect in build command (on error case)
|
// TODO: run disconnect in build command (on error case)
|
||||||
if err := c.Disconnect(ctx, ref); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
logrus.Warnf("disconnect error: %v", err)
|
logrus.Warnf("close error: %v", err)
|
||||||
}
|
}
|
||||||
return nil, errors.Errorf("failed to configure terminal: %v", err)
|
return nil, errors.Errorf("failed to configure terminal: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,9 @@ package debug
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/controller"
|
"github.com/docker/buildx/controller"
|
||||||
"github.com/docker/buildx/controller/control"
|
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
"github.com/docker/buildx/monitor"
|
"github.com/docker/buildx/monitor"
|
||||||
"github.com/docker/buildx/util/cobrautil"
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
@ -35,7 +33,6 @@ type DebuggableCmd interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
||||||
var controlOptions control.ControlOptions
|
|
||||||
var progressMode string
|
var progressMode string
|
||||||
var options DebugConfig
|
var options DebugConfig
|
||||||
|
|
||||||
@ -50,10 +47,7 @@ func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
c, err := controller.NewController(ctx, controlOptions, dockerCli, printer)
|
c := controller.NewController(ctx, dockerCli)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := c.Close(); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
logrus.Warnf("failed to close server connection %v", err)
|
logrus.Warnf("failed to close server connection %v", err)
|
||||||
@ -76,13 +70,9 @@ func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
|||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
||||||
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
||||||
|
|
||||||
flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor")
|
|
||||||
flags.BoolVar(&controlOptions.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server for the monitor (supported only on linux)")
|
|
||||||
flags.StringVar(&controlOptions.ServerConfig, "server-config", "", "Specify buildx server config file for the monitor (used only when launching new server)")
|
|
||||||
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty", "rawjson") for the monitor. Use plain to show container output`)
|
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty", "rawjson") for the monitor. Use plain to show container output`)
|
||||||
|
|
||||||
cobrautil.MarkFlagsExperimental(flags, "invoke", "on", "root", "detach", "server-config")
|
cobrautil.MarkFlagsExperimental(flags, "invoke", "on")
|
||||||
|
|
||||||
for _, c := range children {
|
for _, c := range children {
|
||||||
cmd.AddCommand(c.NewDebugger(&options))
|
cmd.AddCommand(c.NewDebugger(&options))
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
debugcmd "github.com/docker/buildx/commands/debug"
|
debugcmd "github.com/docker/buildx/commands/debug"
|
||||||
historycmd "github.com/docker/buildx/commands/history"
|
historycmd "github.com/docker/buildx/commands/history"
|
||||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||||
"github.com/docker/buildx/controller/remote"
|
|
||||||
"github.com/docker/buildx/util/cobrautil/completion"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/util/confutil"
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/logutil"
|
"github.com/docker/buildx/util/logutil"
|
||||||
@ -124,7 +123,6 @@ func addCommands(cmd *cobra.Command, opts *rootOptions, dockerCli command.Cli) {
|
|||||||
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
||||||
newDebuggableBuild(dockerCli, opts),
|
newDebuggableBuild(dockerCli, opts),
|
||||||
))
|
))
|
||||||
remote.AddControllerCommands(cmd, dockerCli)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.RegisterFlagCompletionFunc( //nolint:errcheck
|
cmd.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||||
|
@ -34,7 +34,7 @@ const defaultTargetName = "default"
|
|||||||
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
|
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
|
||||||
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
|
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
|
||||||
// inspect the result and debug the cause of that error.
|
// inspect the result and debug the cause of that error.
|
||||||
func RunBuild(ctx context.Context, dockerCli command.Cli, in *controllerapi.BuildOptions, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, *build.Inputs, error) {
|
func RunBuild(ctx context.Context, dockerCli command.Cli, in *Options, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, *build.Inputs, error) {
|
||||||
if in.NoCache && len(in.NoCacheFilter) > 0 {
|
if in.NoCache && len(in.NoCacheFilter) > 0 {
|
||||||
return nil, nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
return nil, nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,52 @@
|
|||||||
package pb
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/controller/pb"
|
||||||
|
sourcepolicy "github.com/moby/buildkit/sourcepolicy/pb"
|
||||||
"github.com/moby/buildkit/util/gitutil"
|
"github.com/moby/buildkit/util/gitutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
ContextPath string
|
||||||
|
DockerfileName string
|
||||||
|
CallFunc *pb.CallFunc
|
||||||
|
NamedContexts map[string]string
|
||||||
|
Allow []string
|
||||||
|
Attests []*pb.Attest
|
||||||
|
BuildArgs map[string]string
|
||||||
|
CacheFrom []*pb.CacheOptionsEntry
|
||||||
|
CacheTo []*pb.CacheOptionsEntry
|
||||||
|
CgroupParent string
|
||||||
|
Exports []*pb.ExportEntry
|
||||||
|
ExtraHosts []string
|
||||||
|
Labels map[string]string
|
||||||
|
NetworkMode string
|
||||||
|
NoCacheFilter []string
|
||||||
|
Platforms []string
|
||||||
|
Secrets []*pb.Secret
|
||||||
|
ShmSize int64
|
||||||
|
SSH []*pb.SSH
|
||||||
|
Tags []string
|
||||||
|
Target string
|
||||||
|
Ulimits *pb.UlimitOpt
|
||||||
|
Builder string
|
||||||
|
NoCache bool
|
||||||
|
Pull bool
|
||||||
|
ExportPush bool
|
||||||
|
ExportLoad bool
|
||||||
|
SourcePolicy *sourcepolicy.Policy
|
||||||
|
Ref string
|
||||||
|
GroupRef string
|
||||||
|
Annotations []string
|
||||||
|
ProvenanceResponseMode string
|
||||||
|
}
|
||||||
|
|
||||||
// ResolveOptionPaths resolves all paths contained in BuildOptions
|
// ResolveOptionPaths resolves all paths contained in BuildOptions
|
||||||
// and replaces them to absolute paths.
|
// and replaces them to absolute paths.
|
||||||
func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
func ResolveOptionPaths(options *Options) (_ *Options, err error) {
|
||||||
localContext := false
|
localContext := false
|
||||||
if options.ContextPath != "" && options.ContextPath != "-" {
|
if options.ContextPath != "" && options.ContextPath != "-" {
|
||||||
if !isRemoteURL(options.ContextPath) {
|
if !isRemoteURL(options.ContextPath) {
|
||||||
@ -56,7 +93,7 @@ func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
|||||||
}
|
}
|
||||||
options.NamedContexts = contexts
|
options.NamedContexts = contexts
|
||||||
|
|
||||||
var cacheFrom []*CacheOptionsEntry
|
var cacheFrom []*pb.CacheOptionsEntry
|
||||||
for _, co := range options.CacheFrom {
|
for _, co := range options.CacheFrom {
|
||||||
switch co.Type {
|
switch co.Type {
|
||||||
case "local":
|
case "local":
|
||||||
@ -87,7 +124,7 @@ func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
|||||||
}
|
}
|
||||||
options.CacheFrom = cacheFrom
|
options.CacheFrom = cacheFrom
|
||||||
|
|
||||||
var cacheTo []*CacheOptionsEntry
|
var cacheTo []*pb.CacheOptionsEntry
|
||||||
for _, co := range options.CacheTo {
|
for _, co := range options.CacheTo {
|
||||||
switch co.Type {
|
switch co.Type {
|
||||||
case "local":
|
case "local":
|
||||||
@ -117,7 +154,7 @@ func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
options.CacheTo = cacheTo
|
options.CacheTo = cacheTo
|
||||||
var exports []*ExportEntry
|
var exports []*pb.ExportEntry
|
||||||
for _, e := range options.Exports {
|
for _, e := range options.Exports {
|
||||||
if e.Destination != "" && e.Destination != "-" {
|
if e.Destination != "" && e.Destination != "-" {
|
||||||
e.Destination, err = filepath.Abs(e.Destination)
|
e.Destination, err = filepath.Abs(e.Destination)
|
||||||
@ -129,7 +166,7 @@ func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
|||||||
}
|
}
|
||||||
options.Exports = exports
|
options.Exports = exports
|
||||||
|
|
||||||
var secrets []*Secret
|
var secrets []*pb.Secret
|
||||||
for _, s := range options.Secrets {
|
for _, s := range options.Secrets {
|
||||||
if s.FilePath != "" {
|
if s.FilePath != "" {
|
||||||
s.FilePath, err = filepath.Abs(s.FilePath)
|
s.FilePath, err = filepath.Abs(s.FilePath)
|
||||||
@ -141,7 +178,7 @@ func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
|||||||
}
|
}
|
||||||
options.Secrets = secrets
|
options.Secrets = secrets
|
||||||
|
|
||||||
var ssh []*SSH
|
var ssh []*pb.SSH
|
||||||
for _, s := range options.SSH {
|
for _, s := range options.SSH {
|
||||||
var ps []string
|
var ps []string
|
||||||
for _, pt := range s.Paths {
|
for _, pt := range s.Paths {
|
@ -1,12 +1,12 @@
|
|||||||
package pb
|
package build
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/controller/pb"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolvePaths(t *testing.T) {
|
func TestResolvePaths(t *testing.T) {
|
||||||
@ -16,59 +16,59 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
require.NoError(t, os.Chdir(tmpwd))
|
require.NoError(t, os.Chdir(tmpwd))
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
options *BuildOptions
|
options *Options
|
||||||
want *BuildOptions
|
want *Options
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "contextpath",
|
name: "contextpath",
|
||||||
options: &BuildOptions{ContextPath: "test"},
|
options: &Options{ContextPath: "test"},
|
||||||
want: &BuildOptions{ContextPath: filepath.Join(tmpwd, "test")},
|
want: &Options{ContextPath: filepath.Join(tmpwd, "test")},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contextpath-cwd",
|
name: "contextpath-cwd",
|
||||||
options: &BuildOptions{ContextPath: "."},
|
options: &Options{ContextPath: "."},
|
||||||
want: &BuildOptions{ContextPath: tmpwd},
|
want: &Options{ContextPath: tmpwd},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contextpath-dash",
|
name: "contextpath-dash",
|
||||||
options: &BuildOptions{ContextPath: "-"},
|
options: &Options{ContextPath: "-"},
|
||||||
want: &BuildOptions{ContextPath: "-"},
|
want: &Options{ContextPath: "-"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contextpath-ssh",
|
name: "contextpath-ssh",
|
||||||
options: &BuildOptions{ContextPath: "git@github.com:docker/buildx.git"},
|
options: &Options{ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
want: &BuildOptions{ContextPath: "git@github.com:docker/buildx.git"},
|
want: &Options{ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dockerfilename",
|
name: "dockerfilename",
|
||||||
options: &BuildOptions{DockerfileName: "test", ContextPath: "."},
|
options: &Options{DockerfileName: "test", ContextPath: "."},
|
||||||
want: &BuildOptions{DockerfileName: filepath.Join(tmpwd, "test"), ContextPath: tmpwd},
|
want: &Options{DockerfileName: filepath.Join(tmpwd, "test"), ContextPath: tmpwd},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dockerfilename-dash",
|
name: "dockerfilename-dash",
|
||||||
options: &BuildOptions{DockerfileName: "-", ContextPath: "."},
|
options: &Options{DockerfileName: "-", ContextPath: "."},
|
||||||
want: &BuildOptions{DockerfileName: "-", ContextPath: tmpwd},
|
want: &Options{DockerfileName: "-", ContextPath: tmpwd},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dockerfilename-remote",
|
name: "dockerfilename-remote",
|
||||||
options: &BuildOptions{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
|
options: &Options{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
want: &BuildOptions{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
|
want: &Options{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contexts",
|
name: "contexts",
|
||||||
options: &BuildOptions{NamedContexts: map[string]string{
|
options: &Options{NamedContexts: map[string]string{
|
||||||
"a": "test1", "b": "test2",
|
"a": "test1", "b": "test2",
|
||||||
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git",
|
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git",
|
||||||
}},
|
}},
|
||||||
want: &BuildOptions{NamedContexts: map[string]string{
|
want: &Options{NamedContexts: map[string]string{
|
||||||
"a": filepath.Join(tmpwd, "test1"), "b": filepath.Join(tmpwd, "test2"),
|
"a": filepath.Join(tmpwd, "test1"), "b": filepath.Join(tmpwd, "test2"),
|
||||||
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git",
|
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git",
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cache-from",
|
name: "cache-from",
|
||||||
options: &BuildOptions{
|
options: &Options{
|
||||||
CacheFrom: []*CacheOptionsEntry{
|
CacheFrom: []*pb.CacheOptionsEntry{
|
||||||
{
|
{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Attrs: map[string]string{"src": "test"},
|
Attrs: map[string]string{"src": "test"},
|
||||||
@ -79,8 +79,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &BuildOptions{
|
want: &Options{
|
||||||
CacheFrom: []*CacheOptionsEntry{
|
CacheFrom: []*pb.CacheOptionsEntry{
|
||||||
{
|
{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Attrs: map[string]string{"src": filepath.Join(tmpwd, "test")},
|
Attrs: map[string]string{"src": filepath.Join(tmpwd, "test")},
|
||||||
@ -94,8 +94,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "cache-to",
|
name: "cache-to",
|
||||||
options: &BuildOptions{
|
options: &Options{
|
||||||
CacheTo: []*CacheOptionsEntry{
|
CacheTo: []*pb.CacheOptionsEntry{
|
||||||
{
|
{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Attrs: map[string]string{"dest": "test"},
|
Attrs: map[string]string{"dest": "test"},
|
||||||
@ -106,8 +106,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &BuildOptions{
|
want: &Options{
|
||||||
CacheTo: []*CacheOptionsEntry{
|
CacheTo: []*pb.CacheOptionsEntry{
|
||||||
{
|
{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Attrs: map[string]string{"dest": filepath.Join(tmpwd, "test")},
|
Attrs: map[string]string{"dest": filepath.Join(tmpwd, "test")},
|
||||||
@ -121,8 +121,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "exports",
|
name: "exports",
|
||||||
options: &BuildOptions{
|
options: &Options{
|
||||||
Exports: []*ExportEntry{
|
Exports: []*pb.ExportEntry{
|
||||||
{
|
{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Destination: "-",
|
Destination: "-",
|
||||||
@ -149,8 +149,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &BuildOptions{
|
want: &Options{
|
||||||
Exports: []*ExportEntry{
|
Exports: []*pb.ExportEntry{
|
||||||
{
|
{
|
||||||
Type: "local",
|
Type: "local",
|
||||||
Destination: "-",
|
Destination: "-",
|
||||||
@ -180,8 +180,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "secrets",
|
name: "secrets",
|
||||||
options: &BuildOptions{
|
options: &Options{
|
||||||
Secrets: []*Secret{
|
Secrets: []*pb.Secret{
|
||||||
{
|
{
|
||||||
FilePath: "test1",
|
FilePath: "test1",
|
||||||
},
|
},
|
||||||
@ -195,8 +195,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &BuildOptions{
|
want: &Options{
|
||||||
Secrets: []*Secret{
|
Secrets: []*pb.Secret{
|
||||||
{
|
{
|
||||||
FilePath: filepath.Join(tmpwd, "test1"),
|
FilePath: filepath.Join(tmpwd, "test1"),
|
||||||
},
|
},
|
||||||
@ -213,8 +213,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ssh",
|
name: "ssh",
|
||||||
options: &BuildOptions{
|
options: &Options{
|
||||||
SSH: []*SSH{
|
SSH: []*pb.SSH{
|
||||||
{
|
{
|
||||||
ID: "default",
|
ID: "default",
|
||||||
Paths: []string{"test1", "test2"},
|
Paths: []string{"test1", "test2"},
|
||||||
@ -225,8 +225,8 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &BuildOptions{
|
want: &Options{
|
||||||
SSH: []*SSH{
|
SSH: []*pb.SSH{
|
||||||
{
|
{
|
||||||
ID: "default",
|
ID: "default",
|
||||||
Paths: []string{filepath.Join(tmpwd, "test1"), filepath.Join(tmpwd, "test2")},
|
Paths: []string{filepath.Join(tmpwd, "test1"), filepath.Join(tmpwd, "test2")},
|
||||||
@ -244,9 +244,7 @@ func TestResolvePaths(t *testing.T) {
|
|||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := ResolveOptionPaths(tt.options)
|
got, err := ResolveOptionPaths(tt.options)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if !proto.Equal(tt.want, got) {
|
require.Equal(t, tt.want, got)
|
||||||
t.Fatalf("expected %#v, got %#v", tt.want, got)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,29 +5,22 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
|
cbuild "github.com/docker/buildx/controller/build"
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/controller/processes"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BuildxController interface {
|
type BuildxController interface {
|
||||||
Build(ctx context.Context, options *controllerapi.BuildOptions, in io.ReadCloser, progress progress.Writer) (ref string, resp *client.SolveResponse, inputs *build.Inputs, err error)
|
Build(ctx context.Context, options *cbuild.Options, in io.ReadCloser, progress progress.Writer) (resp *client.SolveResponse, inputs *build.Inputs, err error)
|
||||||
// Invoke starts an IO session into the specified process.
|
// Invoke starts an IO session into the specified process.
|
||||||
// If pid doesn't match to any running processes, it starts a new process with the specified config.
|
// If pid doesn't match to any running processes, it starts a new process with the specified config.
|
||||||
// If there is no container running or InvokeConfig.Rollback is specified, the process will start in a newly created container.
|
// If there is no container running or InvokeConfig.Rollback is specified, the process will start in a newly created container.
|
||||||
// NOTE: If needed, in the future, we can split this API into three APIs (NewContainer, NewProcess and Attach).
|
// NOTE: If needed, in the future, we can split this API into three APIs (NewContainer, NewProcess and Attach).
|
||||||
Invoke(ctx context.Context, ref, pid string, options *controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
|
Invoke(ctx context.Context, pid string, options *controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
|
||||||
Kill(ctx context.Context) error
|
|
||||||
Close() error
|
Close() error
|
||||||
List(ctx context.Context) (refs []string, _ error)
|
ListProcesses(ctx context.Context) (infos []*processes.ProcessInfo, retErr error)
|
||||||
Disconnect(ctx context.Context, ref string) error
|
DisconnectProcess(ctx context.Context, pid string) error
|
||||||
ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error)
|
Inspect(ctx context.Context) *cbuild.Options
|
||||||
DisconnectProcess(ctx context.Context, ref, pid string) error
|
|
||||||
Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ControlOptions struct {
|
|
||||||
ServerConfig string
|
|
||||||
Root string
|
|
||||||
Detach bool
|
|
||||||
}
|
}
|
||||||
|
@ -2,35 +2,12 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/controller/control"
|
"github.com/docker/buildx/controller/control"
|
||||||
"github.com/docker/buildx/controller/local"
|
"github.com/docker/buildx/controller/local"
|
||||||
"github.com/docker/buildx/controller/remote"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewController(ctx context.Context, opts control.ControlOptions, dockerCli command.Cli, pw progress.Writer) (control.BuildxController, error) {
|
func NewController(ctx context.Context, dockerCli command.Cli) control.BuildxController {
|
||||||
var name string
|
return local.NewLocalBuildxController(ctx, dockerCli)
|
||||||
if opts.Detach {
|
|
||||||
name = "remote"
|
|
||||||
} else {
|
|
||||||
name = "local"
|
|
||||||
}
|
|
||||||
|
|
||||||
var c control.BuildxController
|
|
||||||
err := progress.Wrap(fmt.Sprintf("[internal] connecting to %s controller", name), pw.Write, func(l progress.SubLogger) (err error) {
|
|
||||||
if opts.Detach {
|
|
||||||
c, err = remote.NewRemoteBuildxController(ctx, dockerCli, opts, l)
|
|
||||||
} else {
|
|
||||||
c = local.NewLocalBuildxController(ctx, dockerCli, l)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to start buildx controller")
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.34.1
|
// protoc-gen-go v1.35.2
|
||||||
// protoc v3.11.4
|
// protoc v3.11.4
|
||||||
// source: github.com/docker/buildx/controller/errdefs/errdefs.proto
|
// source: github.com/docker/buildx/controller/errdefs/errdefs.proto
|
||||||
|
|
||||||
@ -31,11 +31,9 @@ type Build struct {
|
|||||||
|
|
||||||
func (x *Build) Reset() {
|
func (x *Build) Reset() {
|
||||||
*x = Build{}
|
*x = Build{}
|
||||||
if protoimpl.UnsafeEnabled {
|
|
||||||
mi := &file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes[0]
|
mi := &file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes[0]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Build) String() string {
|
func (x *Build) String() string {
|
||||||
@ -46,7 +44,7 @@ func (*Build) ProtoMessage() {}
|
|||||||
|
|
||||||
func (x *Build) ProtoReflect() protoreflect.Message {
|
func (x *Build) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes[0]
|
mi := &file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes[0]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
@ -106,7 +104,7 @@ func file_github_com_docker_buildx_controller_errdefs_errdefs_proto_rawDescGZIP(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
var file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||||
var file_github_com_docker_buildx_controller_errdefs_errdefs_proto_goTypes = []interface{}{
|
var file_github_com_docker_buildx_controller_errdefs_errdefs_proto_goTypes = []any{
|
||||||
(*Build)(nil), // 0: docker.buildx.errdefs.Build
|
(*Build)(nil), // 0: docker.buildx.errdefs.Build
|
||||||
}
|
}
|
||||||
var file_github_com_docker_buildx_controller_errdefs_errdefs_proto_depIdxs = []int32{
|
var file_github_com_docker_buildx_controller_errdefs_errdefs_proto_depIdxs = []int32{
|
||||||
@ -122,20 +120,6 @@ func file_github_com_docker_buildx_controller_errdefs_errdefs_proto_init() {
|
|||||||
if File_github_com_docker_buildx_controller_errdefs_errdefs_proto != nil {
|
if File_github_com_docker_buildx_controller_errdefs_errdefs_proto != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !protoimpl.UnsafeEnabled {
|
|
||||||
file_github_com_docker_buildx_controller_errdefs_errdefs_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
|
||||||
switch v := v.(*Build); i {
|
|
||||||
case 0:
|
|
||||||
return &v.state
|
|
||||||
case 1:
|
|
||||||
return &v.sizeCache
|
|
||||||
case 2:
|
|
||||||
return &v.unknownFields
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type x struct{}
|
type x struct{}
|
||||||
out := protoimpl.TypeBuilder{
|
out := protoimpl.TypeBuilder{
|
||||||
File: protoimpl.DescBuilder{
|
File: protoimpl.DescBuilder{
|
||||||
|
@ -19,10 +19,9 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLocalBuildxController(ctx context.Context, dockerCli command.Cli, logger progress.SubLogger) control.BuildxController {
|
func NewLocalBuildxController(ctx context.Context, dockerCli command.Cli) control.BuildxController {
|
||||||
return &localController{
|
return &localController{
|
||||||
dockerCli: dockerCli,
|
dockerCli: dockerCli,
|
||||||
sessionID: "local",
|
|
||||||
processes: processes.NewManager(),
|
processes: processes.NewManager(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,21 +30,20 @@ type buildConfig struct {
|
|||||||
// TODO: these two structs should be merged
|
// TODO: these two structs should be merged
|
||||||
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113279719
|
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113279719
|
||||||
resultCtx *build.ResultHandle
|
resultCtx *build.ResultHandle
|
||||||
buildOptions *controllerapi.BuildOptions
|
buildOptions *cbuild.Options
|
||||||
}
|
}
|
||||||
|
|
||||||
type localController struct {
|
type localController struct {
|
||||||
dockerCli command.Cli
|
dockerCli command.Cli
|
||||||
sessionID string
|
|
||||||
buildConfig buildConfig
|
buildConfig buildConfig
|
||||||
processes *processes.Manager
|
processes *processes.Manager
|
||||||
|
|
||||||
buildOnGoing atomic.Bool
|
buildOnGoing atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localController) Build(ctx context.Context, options *controllerapi.BuildOptions, in io.ReadCloser, progress progress.Writer) (string, *client.SolveResponse, *build.Inputs, error) {
|
func (b *localController) Build(ctx context.Context, options *cbuild.Options, in io.ReadCloser, progress progress.Writer) (*client.SolveResponse, *build.Inputs, error) {
|
||||||
if !b.buildOnGoing.CompareAndSwap(false, true) {
|
if !b.buildOnGoing.CompareAndSwap(false, true) {
|
||||||
return "", nil, nil, errors.New("build ongoing")
|
return nil, nil, errors.New("build ongoing")
|
||||||
}
|
}
|
||||||
defer b.buildOnGoing.Store(false)
|
defer b.buildOnGoing.Store(false)
|
||||||
|
|
||||||
@ -62,26 +60,20 @@ func (b *localController) Build(ctx context.Context, options *controllerapi.Buil
|
|||||||
if errors.As(buildErr, &ebr) {
|
if errors.As(buildErr, &ebr) {
|
||||||
ref = ebr.Ref
|
ref = ebr.Ref
|
||||||
}
|
}
|
||||||
buildErr = controllererrors.WrapBuild(buildErr, b.sessionID, ref)
|
buildErr = controllererrors.WrapBuild(buildErr, "", ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if buildErr != nil {
|
if buildErr != nil {
|
||||||
return "", nil, nil, buildErr
|
return nil, nil, buildErr
|
||||||
}
|
}
|
||||||
return b.sessionID, resp, dockerfileMappings, nil
|
return resp, dockerfileMappings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localController) ListProcesses(ctx context.Context, sessionID string) (infos []*controllerapi.ProcessInfo, retErr error) {
|
func (b *localController) ListProcesses(ctx context.Context) (infos []*processes.ProcessInfo, retErr error) {
|
||||||
if sessionID != b.sessionID {
|
|
||||||
return nil, errors.Errorf("unknown session ID %q", sessionID)
|
|
||||||
}
|
|
||||||
return b.processes.ListProcesses(), nil
|
return b.processes.ListProcesses(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localController) DisconnectProcess(ctx context.Context, sessionID, pid string) error {
|
func (b *localController) DisconnectProcess(ctx context.Context, pid string) error {
|
||||||
if sessionID != b.sessionID {
|
|
||||||
return errors.Errorf("unknown session ID %q", sessionID)
|
|
||||||
}
|
|
||||||
return b.processes.DeleteProcess(pid)
|
return b.processes.DeleteProcess(pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,11 +81,7 @@ func (b *localController) cancelRunningProcesses() {
|
|||||||
b.processes.CancelRunningProcesses()
|
b.processes.CancelRunningProcesses()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localController) Invoke(ctx context.Context, sessionID string, pid string, cfg *controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error {
|
func (b *localController) Invoke(ctx context.Context, pid string, cfg *controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error {
|
||||||
if sessionID != b.sessionID {
|
|
||||||
return errors.Errorf("unknown session ID %q", sessionID)
|
|
||||||
}
|
|
||||||
|
|
||||||
proc, ok := b.processes.Get(pid)
|
proc, ok := b.processes.Get(pid)
|
||||||
if !ok {
|
if !ok {
|
||||||
// Start a new process.
|
// Start a new process.
|
||||||
@ -121,11 +109,6 @@ func (b *localController) Invoke(ctx context.Context, sessionID string, pid stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localController) Kill(context.Context) error {
|
|
||||||
b.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *localController) Close() error {
|
func (b *localController) Close() error {
|
||||||
b.cancelRunningProcesses()
|
b.cancelRunningProcesses()
|
||||||
if b.buildConfig.resultCtx != nil {
|
if b.buildConfig.resultCtx != nil {
|
||||||
@ -135,18 +118,6 @@ func (b *localController) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *localController) List(ctx context.Context) (res []string, _ error) {
|
func (b *localController) Inspect(ctx context.Context) *cbuild.Options {
|
||||||
return []string{b.sessionID}, nil
|
return b.buildConfig.buildOptions
|
||||||
}
|
|
||||||
|
|
||||||
func (b *localController) Disconnect(ctx context.Context, key string) error {
|
|
||||||
b.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *localController) Inspect(ctx context.Context, sessionID string) (*controllerapi.InspectResponse, error) {
|
|
||||||
if sessionID != b.sessionID {
|
|
||||||
return nil, errors.Errorf("unknown session ID %q", sessionID)
|
|
||||||
}
|
|
||||||
return &controllerapi.InspectResponse{Options: b.buildConfig.buildOptions}, nil
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
package pb
|
package pb
|
||||||
|
|
||||||
|
type Attest struct {
|
||||||
|
Type string
|
||||||
|
Disabled bool
|
||||||
|
Attrs string
|
||||||
|
}
|
||||||
|
|
||||||
func CreateAttestations(attests []*Attest) map[string]*string {
|
func CreateAttestations(attests []*Attest) map[string]*string {
|
||||||
result := map[string]*string{}
|
result := map[string]*string{}
|
||||||
for _, attest := range attests {
|
for _, attest := range attests {
|
||||||
|
@ -6,6 +6,11 @@ import (
|
|||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CacheOptionsEntry struct {
|
||||||
|
Type string
|
||||||
|
Attrs map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
func CreateCaches(entries []*CacheOptionsEntry) []client.CacheOptionsEntry {
|
func CreateCaches(entries []*CacheOptionsEntry) []client.CacheOptionsEntry {
|
||||||
var outs []client.CacheOptionsEntry
|
var outs []client.CacheOptionsEntry
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,250 +0,0 @@
|
|||||||
syntax = "proto3";
|
|
||||||
|
|
||||||
package buildx.controller.v1;
|
|
||||||
|
|
||||||
import "github.com/moby/buildkit/api/services/control/control.proto";
|
|
||||||
import "github.com/moby/buildkit/sourcepolicy/pb/policy.proto";
|
|
||||||
|
|
||||||
option go_package = "github.com/docker/buildx/controller/pb";
|
|
||||||
|
|
||||||
service Controller {
|
|
||||||
rpc Build(BuildRequest) returns (BuildResponse);
|
|
||||||
rpc Inspect(InspectRequest) returns (InspectResponse);
|
|
||||||
rpc Status(StatusRequest) returns (stream StatusResponse);
|
|
||||||
rpc Input(stream InputMessage) returns (InputResponse);
|
|
||||||
rpc Invoke(stream Message) returns (stream Message);
|
|
||||||
rpc List(ListRequest) returns (ListResponse);
|
|
||||||
rpc Disconnect(DisconnectRequest) returns (DisconnectResponse);
|
|
||||||
rpc Info(InfoRequest) returns (InfoResponse);
|
|
||||||
rpc ListProcesses(ListProcessesRequest) returns (ListProcessesResponse);
|
|
||||||
rpc DisconnectProcess(DisconnectProcessRequest) returns (DisconnectProcessResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListProcessesRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListProcessesResponse {
|
|
||||||
repeated ProcessInfo Infos = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ProcessInfo {
|
|
||||||
string ProcessID = 1;
|
|
||||||
InvokeConfig InvokeConfig = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DisconnectProcessRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
string ProcessID = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DisconnectProcessResponse {
|
|
||||||
}
|
|
||||||
|
|
||||||
message BuildRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
BuildOptions Options = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BuildOptions {
|
|
||||||
string ContextPath = 1;
|
|
||||||
string DockerfileName = 2;
|
|
||||||
CallFunc CallFunc = 3;
|
|
||||||
map<string, string> NamedContexts = 4;
|
|
||||||
|
|
||||||
repeated string Allow = 5;
|
|
||||||
repeated Attest Attests = 6;
|
|
||||||
map<string, string> BuildArgs = 7;
|
|
||||||
repeated CacheOptionsEntry CacheFrom = 8;
|
|
||||||
repeated CacheOptionsEntry CacheTo = 9;
|
|
||||||
string CgroupParent = 10;
|
|
||||||
repeated ExportEntry Exports = 11;
|
|
||||||
repeated string ExtraHosts = 12;
|
|
||||||
map<string, string> Labels = 13;
|
|
||||||
string NetworkMode = 14;
|
|
||||||
repeated string NoCacheFilter = 15;
|
|
||||||
repeated string Platforms = 16;
|
|
||||||
repeated Secret Secrets = 17;
|
|
||||||
int64 ShmSize = 18;
|
|
||||||
repeated SSH SSH = 19;
|
|
||||||
repeated string Tags = 20;
|
|
||||||
string Target = 21;
|
|
||||||
UlimitOpt Ulimits = 22;
|
|
||||||
|
|
||||||
string Builder = 23;
|
|
||||||
bool NoCache = 24;
|
|
||||||
bool Pull = 25;
|
|
||||||
bool ExportPush = 26;
|
|
||||||
bool ExportLoad = 27;
|
|
||||||
moby.buildkit.v1.sourcepolicy.Policy SourcePolicy = 28;
|
|
||||||
string Ref = 29;
|
|
||||||
string GroupRef = 30;
|
|
||||||
repeated string Annotations = 31;
|
|
||||||
string ProvenanceResponseMode = 32;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ExportEntry {
|
|
||||||
string Type = 1;
|
|
||||||
map<string, string> Attrs = 2;
|
|
||||||
string Destination = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CacheOptionsEntry {
|
|
||||||
string Type = 1;
|
|
||||||
map<string, string> Attrs = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Attest {
|
|
||||||
string Type = 1;
|
|
||||||
bool Disabled = 2;
|
|
||||||
string Attrs = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SSH {
|
|
||||||
string ID = 1;
|
|
||||||
repeated string Paths = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Secret {
|
|
||||||
string ID = 1;
|
|
||||||
string FilePath = 2;
|
|
||||||
string Env = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CallFunc {
|
|
||||||
string Name = 1;
|
|
||||||
string Format = 2;
|
|
||||||
bool IgnoreStatus = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InspectRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InspectResponse {
|
|
||||||
BuildOptions Options = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UlimitOpt {
|
|
||||||
map<string, Ulimit> values = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Ulimit {
|
|
||||||
string Name = 1;
|
|
||||||
int64 Hard = 2;
|
|
||||||
int64 Soft = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BuildResponse {
|
|
||||||
map<string, string> ExporterResponse = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DisconnectRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DisconnectResponse {}
|
|
||||||
|
|
||||||
message ListRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ListResponse {
|
|
||||||
repeated string keys = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InputMessage {
|
|
||||||
oneof Input {
|
|
||||||
InputInitMessage Init = 1;
|
|
||||||
DataMessage Data = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message InputInitMessage {
|
|
||||||
string SessionID = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message DataMessage {
|
|
||||||
bool EOF = 1; // true if eof was reached
|
|
||||||
bytes Data = 2; // should be chunked smaller than 4MB:
|
|
||||||
// https://pkg.go.dev/google.golang.org/grpc#MaxRecvMsgSize
|
|
||||||
}
|
|
||||||
|
|
||||||
message InputResponse {}
|
|
||||||
|
|
||||||
message Message {
|
|
||||||
oneof Input {
|
|
||||||
InitMessage Init = 1;
|
|
||||||
// FdMessage used from client to server for input (stdin) and
|
|
||||||
// from server to client for output (stdout, stderr)
|
|
||||||
FdMessage File = 2;
|
|
||||||
// ResizeMessage used from client to server for terminal resize events
|
|
||||||
ResizeMessage Resize = 3;
|
|
||||||
// SignalMessage is used from client to server to send signal events
|
|
||||||
SignalMessage Signal = 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message InitMessage {
|
|
||||||
string SessionID = 1;
|
|
||||||
|
|
||||||
// If ProcessID already exists in the server, it tries to connect to it
|
|
||||||
// instead of invoking the new one. In this case, InvokeConfig will be ignored.
|
|
||||||
string ProcessID = 2;
|
|
||||||
InvokeConfig InvokeConfig = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InvokeConfig {
|
|
||||||
repeated string Entrypoint = 1;
|
|
||||||
repeated string Cmd = 2;
|
|
||||||
bool NoCmd = 11; // Do not set cmd but use the image's default
|
|
||||||
repeated string Env = 3;
|
|
||||||
string User = 4;
|
|
||||||
bool NoUser = 5; // Do not set user but use the image's default
|
|
||||||
string Cwd = 6;
|
|
||||||
bool NoCwd = 7; // Do not set cwd but use the image's default
|
|
||||||
bool Tty = 8;
|
|
||||||
bool Rollback = 9; // Kill all process in the container and recreate it.
|
|
||||||
bool Initial = 10; // Run container from the initial state of that stage (supported only on the failed step)
|
|
||||||
}
|
|
||||||
|
|
||||||
message FdMessage {
|
|
||||||
uint32 Fd = 1; // what fd the data was from
|
|
||||||
bool EOF = 2; // true if eof was reached
|
|
||||||
bytes Data = 3; // should be chunked smaller than 4MB:
|
|
||||||
// https://pkg.go.dev/google.golang.org/grpc#MaxRecvMsgSize
|
|
||||||
}
|
|
||||||
|
|
||||||
message ResizeMessage {
|
|
||||||
uint32 Rows = 1;
|
|
||||||
uint32 Cols = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message SignalMessage {
|
|
||||||
// we only send name (ie HUP, INT) because the int values
|
|
||||||
// are platform dependent.
|
|
||||||
string Name = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StatusRequest {
|
|
||||||
string SessionID = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message StatusResponse {
|
|
||||||
repeated moby.buildkit.v1.Vertex vertexes = 1;
|
|
||||||
repeated moby.buildkit.v1.VertexStatus statuses = 2;
|
|
||||||
repeated moby.buildkit.v1.VertexLog logs = 3;
|
|
||||||
repeated moby.buildkit.v1.VertexWarning warnings = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InfoRequest {}
|
|
||||||
|
|
||||||
message InfoResponse {
|
|
||||||
BuildxVersion buildxVersion = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BuildxVersion {
|
|
||||||
string package = 1;
|
|
||||||
string version = 2;
|
|
||||||
string revision = 3;
|
|
||||||
}
|
|
@ -1,452 +0,0 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// - protoc-gen-go-grpc v1.5.1
|
|
||||||
// - protoc v3.11.4
|
|
||||||
// source: github.com/docker/buildx/controller/pb/controller.proto
|
|
||||||
|
|
||||||
package pb
|
|
||||||
|
|
||||||
import (
|
|
||||||
context "context"
|
|
||||||
grpc "google.golang.org/grpc"
|
|
||||||
codes "google.golang.org/grpc/codes"
|
|
||||||
status "google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This is a compile-time assertion to ensure that this generated file
|
|
||||||
// is compatible with the grpc package it is being compiled against.
|
|
||||||
// Requires gRPC-Go v1.64.0 or later.
|
|
||||||
const _ = grpc.SupportPackageIsVersion9
|
|
||||||
|
|
||||||
const (
|
|
||||||
Controller_Build_FullMethodName = "/buildx.controller.v1.Controller/Build"
|
|
||||||
Controller_Inspect_FullMethodName = "/buildx.controller.v1.Controller/Inspect"
|
|
||||||
Controller_Status_FullMethodName = "/buildx.controller.v1.Controller/Status"
|
|
||||||
Controller_Input_FullMethodName = "/buildx.controller.v1.Controller/Input"
|
|
||||||
Controller_Invoke_FullMethodName = "/buildx.controller.v1.Controller/Invoke"
|
|
||||||
Controller_List_FullMethodName = "/buildx.controller.v1.Controller/List"
|
|
||||||
Controller_Disconnect_FullMethodName = "/buildx.controller.v1.Controller/Disconnect"
|
|
||||||
Controller_Info_FullMethodName = "/buildx.controller.v1.Controller/Info"
|
|
||||||
Controller_ListProcesses_FullMethodName = "/buildx.controller.v1.Controller/ListProcesses"
|
|
||||||
Controller_DisconnectProcess_FullMethodName = "/buildx.controller.v1.Controller/DisconnectProcess"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ControllerClient is the client API for Controller service.
|
|
||||||
//
|
|
||||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
|
||||||
type ControllerClient interface {
|
|
||||||
Build(ctx context.Context, in *BuildRequest, opts ...grpc.CallOption) (*BuildResponse, error)
|
|
||||||
Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectResponse, error)
|
|
||||||
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StatusResponse], error)
|
|
||||||
Input(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[InputMessage, InputResponse], error)
|
|
||||||
Invoke(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Message, Message], error)
|
|
||||||
List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error)
|
|
||||||
Disconnect(ctx context.Context, in *DisconnectRequest, opts ...grpc.CallOption) (*DisconnectResponse, error)
|
|
||||||
Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*InfoResponse, error)
|
|
||||||
ListProcesses(ctx context.Context, in *ListProcessesRequest, opts ...grpc.CallOption) (*ListProcessesResponse, error)
|
|
||||||
DisconnectProcess(ctx context.Context, in *DisconnectProcessRequest, opts ...grpc.CallOption) (*DisconnectProcessResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type controllerClient struct {
|
|
||||||
cc grpc.ClientConnInterface
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewControllerClient(cc grpc.ClientConnInterface) ControllerClient {
|
|
||||||
return &controllerClient{cc}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) Build(ctx context.Context, in *BuildRequest, opts ...grpc.CallOption) (*BuildResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(BuildResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_Build_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) Inspect(ctx context.Context, in *InspectRequest, opts ...grpc.CallOption) (*InspectResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(InspectResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_Inspect_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[StatusResponse], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &Controller_ServiceDesc.Streams[0], Controller_Status_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[StatusRequest, StatusResponse]{ClientStream: stream}
|
|
||||||
if err := x.ClientStream.SendMsg(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := x.ClientStream.CloseSend(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type Controller_StatusClient = grpc.ServerStreamingClient[StatusResponse]
|
|
||||||
|
|
||||||
func (c *controllerClient) Input(ctx context.Context, opts ...grpc.CallOption) (grpc.ClientStreamingClient[InputMessage, InputResponse], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &Controller_ServiceDesc.Streams[1], Controller_Input_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[InputMessage, InputResponse]{ClientStream: stream}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type Controller_InputClient = grpc.ClientStreamingClient[InputMessage, InputResponse]
|
|
||||||
|
|
||||||
func (c *controllerClient) Invoke(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[Message, Message], error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
stream, err := c.cc.NewStream(ctx, &Controller_ServiceDesc.Streams[2], Controller_Invoke_FullMethodName, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
x := &grpc.GenericClientStream[Message, Message]{ClientStream: stream}
|
|
||||||
return x, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type Controller_InvokeClient = grpc.BidiStreamingClient[Message, Message]
|
|
||||||
|
|
||||||
func (c *controllerClient) List(ctx context.Context, in *ListRequest, opts ...grpc.CallOption) (*ListResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(ListResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_List_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) Disconnect(ctx context.Context, in *DisconnectRequest, opts ...grpc.CallOption) (*DisconnectResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(DisconnectResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_Disconnect_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) Info(ctx context.Context, in *InfoRequest, opts ...grpc.CallOption) (*InfoResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(InfoResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_Info_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) ListProcesses(ctx context.Context, in *ListProcessesRequest, opts ...grpc.CallOption) (*ListProcessesResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(ListProcessesResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_ListProcesses_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *controllerClient) DisconnectProcess(ctx context.Context, in *DisconnectProcessRequest, opts ...grpc.CallOption) (*DisconnectProcessResponse, error) {
|
|
||||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
|
||||||
out := new(DisconnectProcessResponse)
|
|
||||||
err := c.cc.Invoke(ctx, Controller_DisconnectProcess_FullMethodName, in, out, cOpts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ControllerServer is the server API for Controller service.
|
|
||||||
// All implementations should embed UnimplementedControllerServer
|
|
||||||
// for forward compatibility.
|
|
||||||
type ControllerServer interface {
|
|
||||||
Build(context.Context, *BuildRequest) (*BuildResponse, error)
|
|
||||||
Inspect(context.Context, *InspectRequest) (*InspectResponse, error)
|
|
||||||
Status(*StatusRequest, grpc.ServerStreamingServer[StatusResponse]) error
|
|
||||||
Input(grpc.ClientStreamingServer[InputMessage, InputResponse]) error
|
|
||||||
Invoke(grpc.BidiStreamingServer[Message, Message]) error
|
|
||||||
List(context.Context, *ListRequest) (*ListResponse, error)
|
|
||||||
Disconnect(context.Context, *DisconnectRequest) (*DisconnectResponse, error)
|
|
||||||
Info(context.Context, *InfoRequest) (*InfoResponse, error)
|
|
||||||
ListProcesses(context.Context, *ListProcessesRequest) (*ListProcessesResponse, error)
|
|
||||||
DisconnectProcess(context.Context, *DisconnectProcessRequest) (*DisconnectProcessResponse, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnimplementedControllerServer should be embedded to have
|
|
||||||
// forward compatible implementations.
|
|
||||||
//
|
|
||||||
// NOTE: this should be embedded by value instead of pointer to avoid a nil
|
|
||||||
// pointer dereference when methods are called.
|
|
||||||
type UnimplementedControllerServer struct{}
|
|
||||||
|
|
||||||
func (UnimplementedControllerServer) Build(context.Context, *BuildRequest) (*BuildResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Build not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) Inspect(context.Context, *InspectRequest) (*InspectResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Inspect not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) Status(*StatusRequest, grpc.ServerStreamingServer[StatusResponse]) error {
|
|
||||||
return status.Errorf(codes.Unimplemented, "method Status not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) Input(grpc.ClientStreamingServer[InputMessage, InputResponse]) error {
|
|
||||||
return status.Errorf(codes.Unimplemented, "method Input not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) Invoke(grpc.BidiStreamingServer[Message, Message]) error {
|
|
||||||
return status.Errorf(codes.Unimplemented, "method Invoke not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) List(context.Context, *ListRequest) (*ListResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) Disconnect(context.Context, *DisconnectRequest) (*DisconnectResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Disconnect not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) Info(context.Context, *InfoRequest) (*InfoResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Info not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) ListProcesses(context.Context, *ListProcessesRequest) (*ListProcessesResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ListProcesses not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) DisconnectProcess(context.Context, *DisconnectProcessRequest) (*DisconnectProcessResponse, error) {
|
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method DisconnectProcess not implemented")
|
|
||||||
}
|
|
||||||
func (UnimplementedControllerServer) testEmbeddedByValue() {}
|
|
||||||
|
|
||||||
// UnsafeControllerServer may be embedded to opt out of forward compatibility for this service.
|
|
||||||
// Use of this interface is not recommended, as added methods to ControllerServer will
|
|
||||||
// result in compilation errors.
|
|
||||||
type UnsafeControllerServer interface {
|
|
||||||
mustEmbedUnimplementedControllerServer()
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterControllerServer(s grpc.ServiceRegistrar, srv ControllerServer) {
|
|
||||||
// If the following call pancis, it indicates UnimplementedControllerServer was
|
|
||||||
// embedded by pointer and is nil. This will cause panics if an
|
|
||||||
// unimplemented method is ever invoked, so we test this at initialization
|
|
||||||
// time to prevent it from happening at runtime later due to I/O.
|
|
||||||
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
|
|
||||||
t.testEmbeddedByValue()
|
|
||||||
}
|
|
||||||
s.RegisterService(&Controller_ServiceDesc, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_Build_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(BuildRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).Build(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_Build_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).Build(ctx, req.(*BuildRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_Inspect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(InspectRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).Inspect(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_Inspect_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).Inspect(ctx, req.(*InspectRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_Status_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
m := new(StatusRequest)
|
|
||||||
if err := stream.RecvMsg(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return srv.(ControllerServer).Status(m, &grpc.GenericServerStream[StatusRequest, StatusResponse]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type Controller_StatusServer = grpc.ServerStreamingServer[StatusResponse]
|
|
||||||
|
|
||||||
func _Controller_Input_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
return srv.(ControllerServer).Input(&grpc.GenericServerStream[InputMessage, InputResponse]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type Controller_InputServer = grpc.ClientStreamingServer[InputMessage, InputResponse]
|
|
||||||
|
|
||||||
func _Controller_Invoke_Handler(srv interface{}, stream grpc.ServerStream) error {
|
|
||||||
return srv.(ControllerServer).Invoke(&grpc.GenericServerStream[Message, Message]{ServerStream: stream})
|
|
||||||
}
|
|
||||||
|
|
||||||
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
|
|
||||||
type Controller_InvokeServer = grpc.BidiStreamingServer[Message, Message]
|
|
||||||
|
|
||||||
func _Controller_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(ListRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).List(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_List_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).List(ctx, req.(*ListRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_Disconnect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(DisconnectRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).Disconnect(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_Disconnect_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).Disconnect(ctx, req.(*DisconnectRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_Info_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(InfoRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).Info(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_Info_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).Info(ctx, req.(*InfoRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_ListProcesses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(ListProcessesRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).ListProcesses(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_ListProcesses_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).ListProcesses(ctx, req.(*ListProcessesRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func _Controller_DisconnectProcess_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
|
||||||
in := new(DisconnectProcessRequest)
|
|
||||||
if err := dec(in); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if interceptor == nil {
|
|
||||||
return srv.(ControllerServer).DisconnectProcess(ctx, in)
|
|
||||||
}
|
|
||||||
info := &grpc.UnaryServerInfo{
|
|
||||||
Server: srv,
|
|
||||||
FullMethod: Controller_DisconnectProcess_FullMethodName,
|
|
||||||
}
|
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
|
||||||
return srv.(ControllerServer).DisconnectProcess(ctx, req.(*DisconnectProcessRequest))
|
|
||||||
}
|
|
||||||
return interceptor(ctx, in, info, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Controller_ServiceDesc is the grpc.ServiceDesc for Controller service.
|
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
|
||||||
// and not to be introspected or modified (even as a copy)
|
|
||||||
var Controller_ServiceDesc = grpc.ServiceDesc{
|
|
||||||
ServiceName: "buildx.controller.v1.Controller",
|
|
||||||
HandlerType: (*ControllerServer)(nil),
|
|
||||||
Methods: []grpc.MethodDesc{
|
|
||||||
{
|
|
||||||
MethodName: "Build",
|
|
||||||
Handler: _Controller_Build_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Inspect",
|
|
||||||
Handler: _Controller_Inspect_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "List",
|
|
||||||
Handler: _Controller_List_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Disconnect",
|
|
||||||
Handler: _Controller_Disconnect_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "Info",
|
|
||||||
Handler: _Controller_Info_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "ListProcesses",
|
|
||||||
Handler: _Controller_ListProcesses_Handler,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
MethodName: "DisconnectProcess",
|
|
||||||
Handler: _Controller_DisconnectProcess_Handler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Streams: []grpc.StreamDesc{
|
|
||||||
{
|
|
||||||
StreamName: "Status",
|
|
||||||
Handler: _Controller_Status_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "Input",
|
|
||||||
Handler: _Controller_Input_Handler,
|
|
||||||
ClientStreams: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
StreamName: "Invoke",
|
|
||||||
Handler: _Controller_Invoke_Handler,
|
|
||||||
ServerStreams: true,
|
|
||||||
ClientStreams: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Metadata: "github.com/docker/buildx/controller/pb/controller.proto",
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,12 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ExportEntry struct {
|
||||||
|
Type string
|
||||||
|
Attrs map[string]string
|
||||||
|
Destination string
|
||||||
|
}
|
||||||
|
|
||||||
func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, []string, error) {
|
func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, []string, error) {
|
||||||
var outs []client.ExportEntry
|
var outs []client.ExportEntry
|
||||||
var localPaths []string
|
var localPaths []string
|
||||||
|
40
controller/pb/invoke.go
Normal file
40
controller/pb/invoke.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CallFunc struct {
|
||||||
|
Name string
|
||||||
|
Format string
|
||||||
|
IgnoreStatus bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CallFunc) String() string {
|
||||||
|
var elems []string
|
||||||
|
if x.Name != "" {
|
||||||
|
elems = append(elems, fmt.Sprintf("Name:%q", x.Name))
|
||||||
|
}
|
||||||
|
if x.Format != "" {
|
||||||
|
elems = append(elems, fmt.Sprintf("Format:%q", x.Format))
|
||||||
|
}
|
||||||
|
if x.IgnoreStatus {
|
||||||
|
elems = append(elems, fmt.Sprintf("IgnoreStatus:%v", x.IgnoreStatus))
|
||||||
|
}
|
||||||
|
return strings.Join(elems, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvokeConfig struct {
|
||||||
|
Entrypoint []string
|
||||||
|
Cmd []string
|
||||||
|
NoCmd bool
|
||||||
|
Env []string
|
||||||
|
User string
|
||||||
|
NoUser bool
|
||||||
|
Cwd string
|
||||||
|
NoCwd bool
|
||||||
|
Tty bool
|
||||||
|
Rollback bool
|
||||||
|
Initial bool
|
||||||
|
}
|
@ -1,162 +0,0 @@
|
|||||||
package pb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
control "github.com/moby/buildkit/api/services/control"
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
"github.com/opencontainers/go-digest"
|
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
|
||||||
)
|
|
||||||
|
|
||||||
type writer struct {
|
|
||||||
ch chan<- *StatusResponse
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProgressWriter(ch chan<- *StatusResponse) progress.Writer {
|
|
||||||
return &writer{ch: ch}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *writer) Write(status *client.SolveStatus) {
|
|
||||||
w.ch <- ToControlStatus(status)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *writer) WriteBuildRef(target string, ref string) {}
|
|
||||||
|
|
||||||
func (w *writer) ValidateLogSource(digest.Digest, any) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *writer) ClearLogSource(any) {}
|
|
||||||
|
|
||||||
func ToControlStatus(s *client.SolveStatus) *StatusResponse {
|
|
||||||
resp := StatusResponse{}
|
|
||||||
for _, v := range s.Vertexes {
|
|
||||||
resp.Vertexes = append(resp.Vertexes, &control.Vertex{
|
|
||||||
Digest: string(v.Digest),
|
|
||||||
Inputs: digestSliceToPB(v.Inputs),
|
|
||||||
Name: v.Name,
|
|
||||||
Started: timestampToPB(v.Started),
|
|
||||||
Completed: timestampToPB(v.Completed),
|
|
||||||
Error: v.Error,
|
|
||||||
Cached: v.Cached,
|
|
||||||
ProgressGroup: v.ProgressGroup,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, v := range s.Statuses {
|
|
||||||
resp.Statuses = append(resp.Statuses, &control.VertexStatus{
|
|
||||||
ID: v.ID,
|
|
||||||
Vertex: string(v.Vertex),
|
|
||||||
Name: v.Name,
|
|
||||||
Total: v.Total,
|
|
||||||
Current: v.Current,
|
|
||||||
Timestamp: timestamppb.New(v.Timestamp),
|
|
||||||
Started: timestampToPB(v.Started),
|
|
||||||
Completed: timestampToPB(v.Completed),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, v := range s.Logs {
|
|
||||||
resp.Logs = append(resp.Logs, &control.VertexLog{
|
|
||||||
Vertex: string(v.Vertex),
|
|
||||||
Stream: int64(v.Stream),
|
|
||||||
Msg: v.Data,
|
|
||||||
Timestamp: timestamppb.New(v.Timestamp),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, v := range s.Warnings {
|
|
||||||
resp.Warnings = append(resp.Warnings, &control.VertexWarning{
|
|
||||||
Vertex: string(v.Vertex),
|
|
||||||
Level: int64(v.Level),
|
|
||||||
Short: v.Short,
|
|
||||||
Detail: v.Detail,
|
|
||||||
Url: v.URL,
|
|
||||||
Info: v.SourceInfo,
|
|
||||||
Ranges: v.Range,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return &resp
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromControlStatus(resp *StatusResponse) *client.SolveStatus {
|
|
||||||
s := client.SolveStatus{}
|
|
||||||
for _, v := range resp.Vertexes {
|
|
||||||
s.Vertexes = append(s.Vertexes, &client.Vertex{
|
|
||||||
Digest: digest.Digest(v.Digest),
|
|
||||||
Inputs: digestSliceFromPB(v.Inputs),
|
|
||||||
Name: v.Name,
|
|
||||||
Started: timestampFromPB(v.Started),
|
|
||||||
Completed: timestampFromPB(v.Completed),
|
|
||||||
Error: v.Error,
|
|
||||||
Cached: v.Cached,
|
|
||||||
ProgressGroup: v.ProgressGroup,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, v := range resp.Statuses {
|
|
||||||
s.Statuses = append(s.Statuses, &client.VertexStatus{
|
|
||||||
ID: v.ID,
|
|
||||||
Vertex: digest.Digest(v.Vertex),
|
|
||||||
Name: v.Name,
|
|
||||||
Total: v.Total,
|
|
||||||
Current: v.Current,
|
|
||||||
Timestamp: v.Timestamp.AsTime(),
|
|
||||||
Started: timestampFromPB(v.Started),
|
|
||||||
Completed: timestampFromPB(v.Completed),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, v := range resp.Logs {
|
|
||||||
s.Logs = append(s.Logs, &client.VertexLog{
|
|
||||||
Vertex: digest.Digest(v.Vertex),
|
|
||||||
Stream: int(v.Stream),
|
|
||||||
Data: v.Msg,
|
|
||||||
Timestamp: v.Timestamp.AsTime(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
for _, v := range resp.Warnings {
|
|
||||||
s.Warnings = append(s.Warnings, &client.VertexWarning{
|
|
||||||
Vertex: digest.Digest(v.Vertex),
|
|
||||||
Level: int(v.Level),
|
|
||||||
Short: v.Short,
|
|
||||||
Detail: v.Detail,
|
|
||||||
URL: v.Url,
|
|
||||||
SourceInfo: v.Info,
|
|
||||||
Range: v.Ranges,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
func timestampFromPB(ts *timestamppb.Timestamp) *time.Time {
|
|
||||||
if ts == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
t := ts.AsTime()
|
|
||||||
if t.IsZero() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &t
|
|
||||||
}
|
|
||||||
|
|
||||||
func timestampToPB(ts *time.Time) *timestamppb.Timestamp {
|
|
||||||
if ts == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return timestamppb.New(*ts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func digestSliceFromPB(elems []string) []digest.Digest {
|
|
||||||
clone := make([]digest.Digest, len(elems))
|
|
||||||
for i, e := range elems {
|
|
||||||
clone[i] = digest.Digest(e)
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
||||||
|
|
||||||
func digestSliceToPB(elems []digest.Digest) []string {
|
|
||||||
clone := make([]string, len(elems))
|
|
||||||
for i, e := range elems {
|
|
||||||
clone[i] = string(e)
|
|
||||||
}
|
|
||||||
return clone
|
|
||||||
}
|
|
@ -5,6 +5,12 @@ import (
|
|||||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Secret struct {
|
||||||
|
ID string
|
||||||
|
FilePath string
|
||||||
|
Env string
|
||||||
|
}
|
||||||
|
|
||||||
func CreateSecrets(secrets []*Secret) (session.Attachable, error) {
|
func CreateSecrets(secrets []*Secret) (session.Attachable, error) {
|
||||||
fs := make([]secretsprovider.Source, 0, len(secrets))
|
fs := make([]secretsprovider.Source, 0, len(secrets))
|
||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
|
@ -7,6 +7,11 @@ import (
|
|||||||
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SSH struct {
|
||||||
|
ID string
|
||||||
|
Paths []string
|
||||||
|
}
|
||||||
|
|
||||||
func CreateSSH(ssh []*SSH) (session.Attachable, error) {
|
func CreateSSH(ssh []*SSH) (session.Attachable, error) {
|
||||||
configs := make([]sshprovider.AgentConfig, 0, len(ssh))
|
configs := make([]sshprovider.AgentConfig, 0, len(ssh))
|
||||||
for _, ssh := range ssh {
|
for _, ssh := range ssh {
|
||||||
|
11
controller/pb/ulimit.go
Normal file
11
controller/pb/ulimit.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
type UlimitOpt struct {
|
||||||
|
Values map[string]*Ulimit
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ulimit struct {
|
||||||
|
Name string
|
||||||
|
Hard int64
|
||||||
|
Soft int64
|
||||||
|
}
|
@ -73,9 +73,9 @@ func (m *Manager) CancelRunningProcesses() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ListProcesses lists all running processes.
|
// ListProcesses lists all running processes.
|
||||||
func (m *Manager) ListProcesses() (res []*pb.ProcessInfo) {
|
func (m *Manager) ListProcesses() (res []*ProcessInfo) {
|
||||||
m.processes.Range(func(key, value any) bool {
|
m.processes.Range(func(key, value any) bool {
|
||||||
res = append(res, &pb.ProcessInfo{
|
res = append(res, &ProcessInfo{
|
||||||
ProcessID: key.(string),
|
ProcessID: key.(string),
|
||||||
InvokeConfig: value.(*Process).invokeConfig,
|
InvokeConfig: value.(*Process).invokeConfig,
|
||||||
})
|
})
|
||||||
@ -154,3 +154,8 @@ func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *p
|
|||||||
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProcessInfo struct {
|
||||||
|
ProcessID string
|
||||||
|
InvokeConfig *pb.InvokeConfig
|
||||||
|
}
|
||||||
|
@ -1,243 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/v2/defaults"
|
|
||||||
"github.com/containerd/containerd/v2/pkg/dialer"
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
"github.com/docker/buildx/controller/pb"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
"github.com/moby/buildkit/identity"
|
|
||||||
"github.com/moby/buildkit/util/grpcerrors"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/backoff"
|
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewClient(ctx context.Context, addr string) (*Client, error) {
|
|
||||||
backoffConfig := backoff.DefaultConfig
|
|
||||||
backoffConfig.MaxDelay = 3 * time.Second
|
|
||||||
connParams := grpc.ConnectParams{
|
|
||||||
Backoff: backoffConfig,
|
|
||||||
}
|
|
||||||
gopts := []grpc.DialOption{
|
|
||||||
//nolint:staticcheck // ignore SA1019: WithBlock is deprecated and does not work with NewClient.
|
|
||||||
grpc.WithBlock(),
|
|
||||||
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
||||||
grpc.WithConnectParams(connParams),
|
|
||||||
grpc.WithContextDialer(dialer.ContextDialer),
|
|
||||||
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
|
|
||||||
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
|
|
||||||
grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor),
|
|
||||||
grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor),
|
|
||||||
}
|
|
||||||
//nolint:staticcheck // ignore SA1019: Recommended NewClient has different behavior from DialContext.
|
|
||||||
conn, err := grpc.DialContext(ctx, dialer.DialAddress(addr), gopts...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Client{conn: conn}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
conn *grpc.ClientConn
|
|
||||||
closeOnce sync.Once
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Close() (err error) {
|
|
||||||
c.closeOnce.Do(func() {
|
|
||||||
err = c.conn.Close()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Version(ctx context.Context) (string, string, string, error) {
|
|
||||||
res, err := c.client().Info(ctx, &pb.InfoRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return "", "", "", err
|
|
||||||
}
|
|
||||||
v := res.BuildxVersion
|
|
||||||
return v.Package, v.Version, v.Revision, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) List(ctx context.Context) (keys []string, retErr error) {
|
|
||||||
res, err := c.client().List(ctx, &pb.ListRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res.Keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Disconnect(ctx context.Context, sessionID string) error {
|
|
||||||
if sessionID == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
_, err := c.client().Disconnect(ctx, &pb.DisconnectRequest{SessionID: sessionID})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) ListProcesses(ctx context.Context, sessionID string) (infos []*pb.ProcessInfo, retErr error) {
|
|
||||||
res, err := c.client().ListProcesses(ctx, &pb.ListProcessesRequest{SessionID: sessionID})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return res.Infos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) DisconnectProcess(ctx context.Context, sessionID, pid string) error {
|
|
||||||
_, err := c.client().DisconnectProcess(ctx, &pb.DisconnectProcessRequest{SessionID: sessionID, ProcessID: pid})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Invoke(ctx context.Context, sessionID string, pid string, invokeConfig *pb.InvokeConfig, in io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
|
|
||||||
if sessionID == "" || pid == "" {
|
|
||||||
return errors.New("build session ID must be specified")
|
|
||||||
}
|
|
||||||
stream, err := c.client().Invoke(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return attachIO(ctx, stream, &pb.InitMessage{SessionID: sessionID, ProcessID: pid, InvokeConfig: invokeConfig}, ioAttachConfig{
|
|
||||||
stdin: in,
|
|
||||||
stdout: stdout,
|
|
||||||
stderr: stderr,
|
|
||||||
// TODO: Signal, Resize
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Inspect(ctx context.Context, sessionID string) (*pb.InspectResponse, error) {
|
|
||||||
return c.client().Inspect(ctx, &pb.InspectRequest{SessionID: sessionID})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Build(ctx context.Context, options *pb.BuildOptions, in io.ReadCloser, progress progress.Writer) (string, *client.SolveResponse, *build.Inputs, error) {
|
|
||||||
ref := identity.NewID()
|
|
||||||
statusChan := make(chan *client.SolveStatus)
|
|
||||||
eg, egCtx := errgroup.WithContext(ctx)
|
|
||||||
var resp *client.SolveResponse
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer close(statusChan)
|
|
||||||
var err error
|
|
||||||
resp, err = c.build(egCtx, ref, options, in, statusChan)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
for s := range statusChan {
|
|
||||||
st := s
|
|
||||||
progress.Write(st)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return ref, resp, nil, eg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) build(ctx context.Context, sessionID string, options *pb.BuildOptions, in io.ReadCloser, statusChan chan *client.SolveStatus) (*client.SolveResponse, error) {
|
|
||||||
eg, egCtx := errgroup.WithContext(ctx)
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
var resp *client.SolveResponse
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer close(done)
|
|
||||||
pbResp, err := c.client().Build(egCtx, &pb.BuildRequest{
|
|
||||||
SessionID: sessionID,
|
|
||||||
Options: options,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resp = &client.SolveResponse{
|
|
||||||
ExporterResponse: pbResp.ExporterResponse,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
eg.Go(func() error {
|
|
||||||
stream, err := c.client().Status(egCtx, &pb.StatusRequest{
|
|
||||||
SessionID: sessionID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
resp, err := stream.Recv()
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.Wrap(err, "failed to receive status")
|
|
||||||
}
|
|
||||||
statusChan <- pb.FromControlStatus(resp)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if in != nil {
|
|
||||||
eg.Go(func() error {
|
|
||||||
stream, err := c.client().Input(egCtx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := stream.Send(&pb.InputMessage{
|
|
||||||
Input: &pb.InputMessage_Init{
|
|
||||||
Init: &pb.InputInitMessage{
|
|
||||||
SessionID: sessionID,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to init input")
|
|
||||||
}
|
|
||||||
|
|
||||||
inReader, inWriter := io.Pipe()
|
|
||||||
eg2, _ := errgroup.WithContext(ctx)
|
|
||||||
eg2.Go(func() error {
|
|
||||||
<-done
|
|
||||||
return inWriter.Close()
|
|
||||||
})
|
|
||||||
go func() {
|
|
||||||
// do not wait for read completion but return here and let the caller send EOF
|
|
||||||
// this allows us to return on ctx.Done() without being blocked by this reader.
|
|
||||||
io.Copy(inWriter, in)
|
|
||||||
inWriter.Close()
|
|
||||||
}()
|
|
||||||
eg2.Go(func() error {
|
|
||||||
for {
|
|
||||||
buf := make([]byte, 32*1024)
|
|
||||||
n, err := inReader.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break // break loop and send EOF
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
} else if n > 0 {
|
|
||||||
if err := stream.Send(&pb.InputMessage{
|
|
||||||
Input: &pb.InputMessage_Data{
|
|
||||||
Data: &pb.DataMessage{
|
|
||||||
Data: buf[:n],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return stream.Send(&pb.InputMessage{
|
|
||||||
Input: &pb.InputMessage_Data{
|
|
||||||
Data: &pb.DataMessage{
|
|
||||||
EOF: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return eg2.Wait()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return resp, eg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) client() pb.ControllerClient {
|
|
||||||
return pb.NewControllerClient(c.conn)
|
|
||||||
}
|
|
@ -1,335 +0,0 @@
|
|||||||
//go:build linux
|
|
||||||
|
|
||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/containerd/log"
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
cbuild "github.com/docker/buildx/controller/build"
|
|
||||||
"github.com/docker/buildx/controller/control"
|
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
|
||||||
"github.com/docker/buildx/util/confutil"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/buildx/version"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
"github.com/moby/buildkit/util/grpcerrors"
|
|
||||||
"github.com/pelletier/go-toml"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
serveCommandName = "_INTERNAL_SERVE"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultLogFilename = fmt.Sprintf("buildx.%s.log", version.Revision)
|
|
||||||
defaultSocketFilename = fmt.Sprintf("buildx.%s.sock", version.Revision)
|
|
||||||
defaultPIDFilename = fmt.Sprintf("buildx.%s.pid", version.Revision)
|
|
||||||
)
|
|
||||||
|
|
||||||
type serverConfig struct {
|
|
||||||
// Specify buildx server root
|
|
||||||
Root string `toml:"root"`
|
|
||||||
|
|
||||||
// LogLevel sets the logging level [trace, debug, info, warn, error, fatal, panic]
|
|
||||||
LogLevel string `toml:"log_level"`
|
|
||||||
|
|
||||||
// Specify file to output buildx server log
|
|
||||||
LogFile string `toml:"log_file"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRemoteBuildxController(ctx context.Context, dockerCli command.Cli, opts control.ControlOptions, logger progress.SubLogger) (control.BuildxController, error) {
|
|
||||||
rootDir := opts.Root
|
|
||||||
if rootDir == "" {
|
|
||||||
rootDir = rootDataDir(dockerCli)
|
|
||||||
}
|
|
||||||
serverRoot := filepath.Join(rootDir, "shared")
|
|
||||||
|
|
||||||
// connect to buildx server if it is already running
|
|
||||||
ctx2, cancel := context.WithCancelCause(ctx)
|
|
||||||
ctx2, _ = context.WithTimeoutCause(ctx2, 1*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent
|
|
||||||
c, err := newBuildxClientAndCheck(ctx2, filepath.Join(serverRoot, defaultSocketFilename))
|
|
||||||
cancel(errors.WithStack(context.Canceled))
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, context.DeadlineExceeded) {
|
|
||||||
return nil, errors.Wrap(err, "cannot connect to the buildx server")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return &buildxController{c, serverRoot}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// start buildx server via subcommand
|
|
||||||
err = logger.Wrap("no buildx server found; launching...", func() error {
|
|
||||||
launchFlags := []string{}
|
|
||||||
if opts.ServerConfig != "" {
|
|
||||||
launchFlags = append(launchFlags, "--config", opts.ServerConfig)
|
|
||||||
}
|
|
||||||
logFile, err := getLogFilePath(dockerCli, opts.ServerConfig)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
wait, err := launch(ctx, logFile, append([]string{serveCommandName}, launchFlags...)...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
go wait()
|
|
||||||
|
|
||||||
// wait for buildx server to be ready
|
|
||||||
ctx2, cancel = context.WithCancelCause(ctx)
|
|
||||||
ctx2, _ = context.WithTimeoutCause(ctx2, 10*time.Second, errors.WithStack(context.DeadlineExceeded)) //nolint:govet,lostcancel // no need to manually cancel this context as we already rely on parent
|
|
||||||
c, err = newBuildxClientAndCheck(ctx2, filepath.Join(serverRoot, defaultSocketFilename))
|
|
||||||
cancel(errors.WithStack(context.Canceled))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "cannot connect to the buildx server")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &buildxController{c, serverRoot}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddControllerCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|
||||||
cmd.AddCommand(
|
|
||||||
serveCmd(dockerCli),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveCmd(dockerCli command.Cli) *cobra.Command {
|
|
||||||
var serverConfigPath string
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: fmt.Sprintf("%s [OPTIONS]", serveCommandName),
|
|
||||||
Hidden: true,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
// Parse config
|
|
||||||
config, err := getConfig(dockerCli, serverConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if config.LogLevel == "" {
|
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
|
||||||
} else {
|
|
||||||
lvl, err := logrus.ParseLevel(config.LogLevel)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to prepare logger")
|
|
||||||
}
|
|
||||||
logrus.SetLevel(lvl)
|
|
||||||
}
|
|
||||||
logrus.SetFormatter(&logrus.JSONFormatter{
|
|
||||||
TimestampFormat: log.RFC3339NanoFixed,
|
|
||||||
})
|
|
||||||
root, err := prepareRootDir(dockerCli, config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pidF := filepath.Join(root, defaultPIDFilename)
|
|
||||||
if err := os.WriteFile(pidF, fmt.Appendf(nil, "%d", os.Getpid()), 0600); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := os.Remove(pidF); err != nil {
|
|
||||||
logrus.Errorf("failed to clean up info file %q: %v", pidF, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// prepare server
|
|
||||||
b := NewServer(func(ctx context.Context, options *controllerapi.BuildOptions, stdin io.Reader, progress progress.Writer) (*client.SolveResponse, *build.ResultHandle, *build.Inputs, error) {
|
|
||||||
return cbuild.RunBuild(ctx, dockerCli, options, stdin, progress, true)
|
|
||||||
})
|
|
||||||
defer b.Close()
|
|
||||||
|
|
||||||
// serve server
|
|
||||||
addr := filepath.Join(root, defaultSocketFilename)
|
|
||||||
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { // avoid EADDRINUSE
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := os.Remove(addr); err != nil {
|
|
||||||
logrus.Errorf("failed to clean up socket %q: %v", addr, err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
logrus.Infof("starting server at %q", addr)
|
|
||||||
l, err := net.Listen("unix", addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rpc := grpc.NewServer(
|
|
||||||
grpc.UnaryInterceptor(grpcerrors.UnaryServerInterceptor),
|
|
||||||
grpc.StreamInterceptor(grpcerrors.StreamServerInterceptor),
|
|
||||||
)
|
|
||||||
controllerapi.RegisterControllerServer(rpc, b)
|
|
||||||
doneCh := make(chan struct{})
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
defer close(doneCh)
|
|
||||||
if err := rpc.Serve(l); err != nil {
|
|
||||||
errCh <- errors.Wrapf(err, "error on serving via socket %q", addr)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
var s os.Signal
|
|
||||||
sigCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigCh, syscall.SIGINT)
|
|
||||||
signal.Notify(sigCh, syscall.SIGTERM)
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
logrus.Errorf("got error %s, exiting", err)
|
|
||||||
return err
|
|
||||||
case s = <-sigCh:
|
|
||||||
logrus.Infof("got signal %s, exiting", s)
|
|
||||||
return nil
|
|
||||||
case <-doneCh:
|
|
||||||
logrus.Infof("rpc server done, exiting")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&serverConfigPath, "config", "", "Specify buildx server config file")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLogFilePath(dockerCli command.Cli, configPath string) (string, error) {
|
|
||||||
config, err := getConfig(dockerCli, configPath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if config.LogFile == "" {
|
|
||||||
root, err := prepareRootDir(dockerCli, config)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return filepath.Join(root, defaultLogFilename), nil
|
|
||||||
}
|
|
||||||
return config.LogFile, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConfig(dockerCli command.Cli, configPath string) (*serverConfig, error) {
|
|
||||||
var defaultConfigPath bool
|
|
||||||
if configPath == "" {
|
|
||||||
defaultRoot := rootDataDir(dockerCli)
|
|
||||||
configPath = filepath.Join(defaultRoot, "config.toml")
|
|
||||||
defaultConfigPath = true
|
|
||||||
}
|
|
||||||
var config serverConfig
|
|
||||||
tree, err := toml.LoadFile(configPath)
|
|
||||||
if err != nil && !(os.IsNotExist(err) && defaultConfigPath) {
|
|
||||||
return nil, errors.Wrapf(err, "failed to read config %q", configPath)
|
|
||||||
} else if err == nil {
|
|
||||||
if err := tree.Unmarshal(&config); err != nil {
|
|
||||||
return nil, errors.Wrapf(err, "failed to unmarshal config %q", configPath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareRootDir(dockerCli command.Cli, config *serverConfig) (string, error) {
|
|
||||||
rootDir := config.Root
|
|
||||||
if rootDir == "" {
|
|
||||||
rootDir = rootDataDir(dockerCli)
|
|
||||||
}
|
|
||||||
if rootDir == "" {
|
|
||||||
return "", errors.New("buildx root dir must be determined")
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(rootDir, 0700); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
serverRoot := filepath.Join(rootDir, "shared")
|
|
||||||
if err := os.MkdirAll(serverRoot, 0700); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return serverRoot, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rootDataDir(dockerCli command.Cli) string {
|
|
||||||
return filepath.Join(confutil.NewConfig(dockerCli).Dir(), "controller")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newBuildxClientAndCheck(ctx context.Context, addr string) (*Client, error) {
|
|
||||||
c, err := NewClient(ctx, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, v, r, err := c.Version(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
logrus.Debugf("connected to server (\"%v %v %v\")", p, v, r)
|
|
||||||
if !(p == version.Package && v == version.Version && r == version.Revision) {
|
|
||||||
return nil, errors.Errorf("version mismatch (client: \"%v %v %v\", server: \"%v %v %v\")", version.Package, version.Version, version.Revision, p, v, r)
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type buildxController struct {
|
|
||||||
*Client
|
|
||||||
serverRoot string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *buildxController) Kill(ctx context.Context) error {
|
|
||||||
pidB, err := os.ReadFile(filepath.Join(c.serverRoot, defaultPIDFilename))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pid, err := strconv.ParseInt(string(pidB), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if pid <= 0 {
|
|
||||||
return errors.New("no PID is recorded for buildx server")
|
|
||||||
}
|
|
||||||
p, err := os.FindProcess(int(pid))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := p.Signal(syscall.SIGINT); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// TODO: Should we send SIGKILL if process doesn't finish?
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func launch(ctx context.Context, logFile string, args ...string) (func() error, error) {
|
|
||||||
// set absolute path of binary, since we set the working directory to the root
|
|
||||||
pathname, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bCmd := exec.CommandContext(ctx, pathname, args...)
|
|
||||||
if logFile != "" {
|
|
||||||
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
bCmd.Stdout = f
|
|
||||||
bCmd.Stderr = f
|
|
||||||
}
|
|
||||||
bCmd.Stdin = nil
|
|
||||||
bCmd.Dir = "/"
|
|
||||||
bCmd.SysProcAttr = &syscall.SysProcAttr{
|
|
||||||
Setsid: true,
|
|
||||||
}
|
|
||||||
if err := bCmd.Start(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return bCmd.Wait, nil
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/controller/control"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRemoteBuildxController(ctx context.Context, dockerCli command.Cli, opts control.ControlOptions, logger progress.SubLogger) (control.BuildxController, error) {
|
|
||||||
return nil, errors.New("remote buildx unsupported")
|
|
||||||
}
|
|
||||||
|
|
||||||
func AddControllerCommands(cmd *cobra.Command, dockerCli command.Cli) {}
|
|
@ -1,430 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/controller/pb"
|
|
||||||
"github.com/moby/sys/signal"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
type msgStream interface {
|
|
||||||
Send(*pb.Message) error
|
|
||||||
Recv() (*pb.Message, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ioServerConfig struct {
|
|
||||||
stdin io.WriteCloser
|
|
||||||
stdout, stderr io.ReadCloser
|
|
||||||
|
|
||||||
// signalFn is a callback function called when a signal is reached to the client.
|
|
||||||
signalFn func(context.Context, syscall.Signal) error
|
|
||||||
|
|
||||||
// resizeFn is a callback function called when a resize event is reached to the client.
|
|
||||||
resizeFn func(context.Context, winSize) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func serveIO(attachCtx context.Context, srv msgStream, initFn func(*pb.InitMessage) error, ioConfig *ioServerConfig) (err error) {
|
|
||||||
stdin, stdout, stderr := ioConfig.stdin, ioConfig.stdout, ioConfig.stderr
|
|
||||||
stream := &debugStream{srv, "server=" + time.Now().String()}
|
|
||||||
eg, ctx := errgroup.WithContext(attachCtx)
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
msg, err := receive(ctx, stream)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
init := msg.GetInit()
|
|
||||||
if init == nil {
|
|
||||||
return errors.Errorf("unexpected message: %T; wanted init", msg.GetInput())
|
|
||||||
}
|
|
||||||
sessionID := init.SessionID
|
|
||||||
if sessionID == "" {
|
|
||||||
return errors.New("no session ID is provided")
|
|
||||||
}
|
|
||||||
if err := initFn(init); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to initialize IO server")
|
|
||||||
}
|
|
||||||
|
|
||||||
if stdout != nil {
|
|
||||||
stdoutReader, stdoutWriter := io.Pipe()
|
|
||||||
eg.Go(func() error {
|
|
||||||
<-done
|
|
||||||
return stdoutWriter.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// do not wait for read completion but return here and let the caller send EOF
|
|
||||||
// this allows us to return on ctx.Done() without being blocked by this reader.
|
|
||||||
io.Copy(stdoutWriter, stdout)
|
|
||||||
stdoutWriter.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer stdoutReader.Close()
|
|
||||||
return copyToStream(1, stream, stdoutReader)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if stderr != nil {
|
|
||||||
stderrReader, stderrWriter := io.Pipe()
|
|
||||||
eg.Go(func() error {
|
|
||||||
<-done
|
|
||||||
return stderrWriter.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// do not wait for read completion but return here and let the caller send EOF
|
|
||||||
// this allows us to return on ctx.Done() without being blocked by this reader.
|
|
||||||
io.Copy(stderrWriter, stderr)
|
|
||||||
stderrWriter.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer stderrReader.Close()
|
|
||||||
return copyToStream(2, stream, stderrReader)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
msgCh := make(chan *pb.Message)
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer close(msgCh)
|
|
||||||
for {
|
|
||||||
msg, err := receive(ctx, stream)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case msgCh <- msg:
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer close(done)
|
|
||||||
for {
|
|
||||||
var msg *pb.Message
|
|
||||||
select {
|
|
||||||
case msg = <-msgCh:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if msg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if file := msg.GetFile(); file != nil {
|
|
||||||
if file.Fd != 0 {
|
|
||||||
return errors.Errorf("unexpected fd: %v", file.Fd)
|
|
||||||
}
|
|
||||||
if stdin == nil {
|
|
||||||
continue // no stdin destination is specified so ignore the data
|
|
||||||
}
|
|
||||||
if len(file.Data) > 0 {
|
|
||||||
_, err := stdin.Write(file.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if file.EOF {
|
|
||||||
stdin.Close()
|
|
||||||
}
|
|
||||||
} else if resize := msg.GetResize(); resize != nil {
|
|
||||||
if ioConfig.resizeFn != nil {
|
|
||||||
ioConfig.resizeFn(ctx, winSize{
|
|
||||||
cols: resize.Cols,
|
|
||||||
rows: resize.Rows,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if sig := msg.GetSignal(); sig != nil {
|
|
||||||
if ioConfig.signalFn != nil {
|
|
||||||
syscallSignal, ok := signal.SignalMap[sig.Name]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ioConfig.signalFn(ctx, syscallSignal)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.Errorf("unexpected message: %T", msg.GetInput())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return eg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ioAttachConfig struct {
|
|
||||||
stdin io.ReadCloser
|
|
||||||
stdout, stderr io.WriteCloser
|
|
||||||
signal <-chan syscall.Signal
|
|
||||||
resize <-chan winSize
|
|
||||||
}
|
|
||||||
|
|
||||||
type winSize struct {
|
|
||||||
rows uint32
|
|
||||||
cols uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func attachIO(ctx context.Context, stream msgStream, initMessage *pb.InitMessage, cfg ioAttachConfig) (retErr error) {
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
done := make(chan struct{})
|
|
||||||
|
|
||||||
if err := stream.Send(&pb.Message{
|
|
||||||
Input: &pb.Message_Init{
|
|
||||||
Init: initMessage,
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to init")
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.stdin != nil {
|
|
||||||
stdinReader, stdinWriter := io.Pipe()
|
|
||||||
eg.Go(func() error {
|
|
||||||
<-done
|
|
||||||
return stdinWriter.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
// do not wait for read completion but return here and let the caller send EOF
|
|
||||||
// this allows us to return on ctx.Done() without being blocked by this reader.
|
|
||||||
io.Copy(stdinWriter, cfg.stdin)
|
|
||||||
stdinWriter.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer stdinReader.Close()
|
|
||||||
return copyToStream(0, stream, stdinReader)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.signal != nil {
|
|
||||||
eg.Go(func() error {
|
|
||||||
names := signalNames()
|
|
||||||
for {
|
|
||||||
var sig syscall.Signal
|
|
||||||
select {
|
|
||||||
case sig = <-cfg.signal:
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name := names[sig]
|
|
||||||
if name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := stream.Send(&pb.Message{
|
|
||||||
Input: &pb.Message_Signal{
|
|
||||||
Signal: &pb.SignalMessage{
|
|
||||||
Name: name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to send signal")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.resize != nil {
|
|
||||||
eg.Go(func() error {
|
|
||||||
for {
|
|
||||||
var win winSize
|
|
||||||
select {
|
|
||||||
case win = <-cfg.resize:
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := stream.Send(&pb.Message{
|
|
||||||
Input: &pb.Message_Resize{
|
|
||||||
Resize: &pb.ResizeMessage{
|
|
||||||
Rows: win.rows,
|
|
||||||
Cols: win.cols,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to send resize")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
msgCh := make(chan *pb.Message)
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer close(msgCh)
|
|
||||||
for {
|
|
||||||
msg, err := receive(ctx, stream)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case msgCh <- msg:
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
eg.Go(func() error {
|
|
||||||
eofs := make(map[uint32]struct{})
|
|
||||||
defer close(done)
|
|
||||||
for {
|
|
||||||
var msg *pb.Message
|
|
||||||
select {
|
|
||||||
case msg = <-msgCh:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if msg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if file := msg.GetFile(); file != nil {
|
|
||||||
if _, ok := eofs[file.Fd]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var out io.WriteCloser
|
|
||||||
switch file.Fd {
|
|
||||||
case 1:
|
|
||||||
out = cfg.stdout
|
|
||||||
case 2:
|
|
||||||
out = cfg.stderr
|
|
||||||
default:
|
|
||||||
return errors.Errorf("unsupported fd %d", file.Fd)
|
|
||||||
}
|
|
||||||
if out == nil {
|
|
||||||
logrus.Warnf("attachIO: no writer for fd %d", file.Fd)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(file.Data) > 0 {
|
|
||||||
if _, err := out.Write(file.Data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if file.EOF {
|
|
||||||
eofs[file.Fd] = struct{}{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return errors.Errorf("unexpected message: %T", msg.GetInput())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return eg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func receive(ctx context.Context, stream msgStream) (*pb.Message, error) {
|
|
||||||
msgCh := make(chan *pb.Message)
|
|
||||||
errCh := make(chan error)
|
|
||||||
go func() {
|
|
||||||
msg, err := stream.Recv()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
msgCh <- msg
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case msg := <-msgCh:
|
|
||||||
return msg, nil
|
|
||||||
case err := <-errCh:
|
|
||||||
return nil, err
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil, context.Cause(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyToStream(fd uint32, snd msgStream, r io.Reader) error {
|
|
||||||
for {
|
|
||||||
buf := make([]byte, 32*1024)
|
|
||||||
n, err := r.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break // break loop and send EOF
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
} else if n > 0 {
|
|
||||||
if err := snd.Send(&pb.Message{
|
|
||||||
Input: &pb.Message_File{
|
|
||||||
File: &pb.FdMessage{
|
|
||||||
Fd: fd,
|
|
||||||
Data: buf[:n],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return snd.Send(&pb.Message{
|
|
||||||
Input: &pb.Message_File{
|
|
||||||
File: &pb.FdMessage{
|
|
||||||
Fd: fd,
|
|
||||||
EOF: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func signalNames() map[syscall.Signal]string {
|
|
||||||
m := make(map[syscall.Signal]string, len(signal.SignalMap))
|
|
||||||
for name, value := range signal.SignalMap {
|
|
||||||
m[value] = name
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type debugStream struct {
|
|
||||||
msgStream
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *debugStream) Send(msg *pb.Message) error {
|
|
||||||
switch m := msg.GetInput().(type) {
|
|
||||||
case *pb.Message_File:
|
|
||||||
if m.File.EOF {
|
|
||||||
logrus.Debugf("|---> File Message (sender:%v) fd=%d, EOF", s.prefix, m.File.Fd)
|
|
||||||
} else {
|
|
||||||
logrus.Debugf("|---> File Message (sender:%v) fd=%d, %d bytes", s.prefix, m.File.Fd, len(m.File.Data))
|
|
||||||
}
|
|
||||||
case *pb.Message_Resize:
|
|
||||||
logrus.Debugf("|---> Resize Message (sender:%v): %+v", s.prefix, m.Resize)
|
|
||||||
case *pb.Message_Signal:
|
|
||||||
logrus.Debugf("|---> Signal Message (sender:%v): %s", s.prefix, m.Signal.Name)
|
|
||||||
}
|
|
||||||
return s.msgStream.Send(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *debugStream) Recv() (*pb.Message, error) {
|
|
||||||
msg, err := s.msgStream.Recv()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch m := msg.GetInput().(type) {
|
|
||||||
case *pb.Message_File:
|
|
||||||
if m.File.EOF {
|
|
||||||
logrus.Debugf("|<--- File Message (receiver:%v) fd=%d, EOF", s.prefix, m.File.Fd)
|
|
||||||
} else {
|
|
||||||
logrus.Debugf("|<--- File Message (receiver:%v) fd=%d, %d bytes", s.prefix, m.File.Fd, len(m.File.Data))
|
|
||||||
}
|
|
||||||
case *pb.Message_Resize:
|
|
||||||
logrus.Debugf("|<--- Resize Message (receiver:%v): %+v", s.prefix, m.Resize)
|
|
||||||
case *pb.Message_Signal:
|
|
||||||
logrus.Debugf("|<--- Signal Message (receiver:%v): %s", s.prefix, m.Signal.Name)
|
|
||||||
}
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
@ -1,445 +0,0 @@
|
|||||||
package remote
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
controllererrors "github.com/docker/buildx/controller/errdefs"
|
|
||||||
"github.com/docker/buildx/controller/pb"
|
|
||||||
"github.com/docker/buildx/controller/processes"
|
|
||||||
"github.com/docker/buildx/util/desktop"
|
|
||||||
"github.com/docker/buildx/util/ioset"
|
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/buildx/version"
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
type BuildFunc func(ctx context.Context, options *pb.BuildOptions, stdin io.Reader, progress progress.Writer) (resp *client.SolveResponse, res *build.ResultHandle, inp *build.Inputs, err error)
|
|
||||||
|
|
||||||
func NewServer(buildFunc BuildFunc) *Server {
|
|
||||||
return &Server{
|
|
||||||
buildFunc: buildFunc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Server struct {
|
|
||||||
buildFunc BuildFunc
|
|
||||||
session map[string]*session
|
|
||||||
sessionMu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
type session struct {
|
|
||||||
buildOnGoing atomic.Bool
|
|
||||||
statusChan chan *pb.StatusResponse
|
|
||||||
cancelBuild func(error)
|
|
||||||
buildOptions *pb.BuildOptions
|
|
||||||
inputPipe *io.PipeWriter
|
|
||||||
|
|
||||||
result *build.ResultHandle
|
|
||||||
|
|
||||||
processes *processes.Manager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *session) cancelRunningProcesses() {
|
|
||||||
s.processes.CancelRunningProcesses()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) ListProcesses(ctx context.Context, req *pb.ListProcessesRequest) (res *pb.ListProcessesResponse, err error) {
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
defer m.sessionMu.Unlock()
|
|
||||||
s, ok := m.session[req.SessionID]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("unknown session ID %q", req.SessionID)
|
|
||||||
}
|
|
||||||
res = new(pb.ListProcessesResponse)
|
|
||||||
res.Infos = append(res.Infos, s.processes.ListProcesses()...)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) DisconnectProcess(ctx context.Context, req *pb.DisconnectProcessRequest) (res *pb.DisconnectProcessResponse, err error) {
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
defer m.sessionMu.Unlock()
|
|
||||||
s, ok := m.session[req.SessionID]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("unknown session ID %q", req.SessionID)
|
|
||||||
}
|
|
||||||
return res, s.processes.DeleteProcess(req.ProcessID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Info(ctx context.Context, req *pb.InfoRequest) (res *pb.InfoResponse, err error) {
|
|
||||||
return &pb.InfoResponse{
|
|
||||||
BuildxVersion: &pb.BuildxVersion{
|
|
||||||
Package: version.Package,
|
|
||||||
Version: version.Version,
|
|
||||||
Revision: version.Revision,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) List(ctx context.Context, req *pb.ListRequest) (res *pb.ListResponse, err error) {
|
|
||||||
keys := make(map[string]struct{})
|
|
||||||
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
for k := range m.session {
|
|
||||||
keys[k] = struct{}{}
|
|
||||||
}
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
|
|
||||||
var keysL []string
|
|
||||||
for k := range keys {
|
|
||||||
keysL = append(keysL, k)
|
|
||||||
}
|
|
||||||
return &pb.ListResponse{
|
|
||||||
Keys: keysL,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Disconnect(ctx context.Context, req *pb.DisconnectRequest) (res *pb.DisconnectResponse, err error) {
|
|
||||||
sessionID := req.SessionID
|
|
||||||
if sessionID == "" {
|
|
||||||
return nil, errors.New("disconnect: empty session ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
if s, ok := m.session[sessionID]; ok {
|
|
||||||
if s.cancelBuild != nil {
|
|
||||||
s.cancelBuild(errors.WithStack(context.Canceled))
|
|
||||||
}
|
|
||||||
s.cancelRunningProcesses()
|
|
||||||
if s.result != nil {
|
|
||||||
s.result.Done()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(m.session, sessionID)
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
|
|
||||||
return &pb.DisconnectResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Close() error {
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
for k := range m.session {
|
|
||||||
if s, ok := m.session[k]; ok {
|
|
||||||
if s.cancelBuild != nil {
|
|
||||||
s.cancelBuild(errors.WithStack(context.Canceled))
|
|
||||||
}
|
|
||||||
s.cancelRunningProcesses()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Inspect(ctx context.Context, req *pb.InspectRequest) (*pb.InspectResponse, error) {
|
|
||||||
sessionID := req.SessionID
|
|
||||||
if sessionID == "" {
|
|
||||||
return nil, errors.New("inspect: empty session ID")
|
|
||||||
}
|
|
||||||
var bo *pb.BuildOptions
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
if s, ok := m.session[sessionID]; ok {
|
|
||||||
bo = s.buildOptions
|
|
||||||
} else {
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
return nil, errors.Errorf("inspect: unknown key %v", sessionID)
|
|
||||||
}
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
return &pb.InspectResponse{Options: bo}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Build(ctx context.Context, req *pb.BuildRequest) (*pb.BuildResponse, error) {
|
|
||||||
sessionID := req.SessionID
|
|
||||||
if sessionID == "" {
|
|
||||||
return nil, errors.New("build: empty session ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare status channel and session
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
if m.session == nil {
|
|
||||||
m.session = make(map[string]*session)
|
|
||||||
}
|
|
||||||
s, ok := m.session[sessionID]
|
|
||||||
if ok {
|
|
||||||
if !s.buildOnGoing.CompareAndSwap(false, true) {
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
return &pb.BuildResponse{}, errors.New("build ongoing")
|
|
||||||
}
|
|
||||||
s.cancelRunningProcesses()
|
|
||||||
s.result = nil
|
|
||||||
} else {
|
|
||||||
s = &session{}
|
|
||||||
s.buildOnGoing.Store(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.processes = processes.NewManager()
|
|
||||||
statusChan := make(chan *pb.StatusResponse)
|
|
||||||
s.statusChan = statusChan
|
|
||||||
inR, inW := io.Pipe()
|
|
||||||
defer inR.Close()
|
|
||||||
s.inputPipe = inW
|
|
||||||
m.session[sessionID] = s
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
defer func() {
|
|
||||||
close(statusChan)
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
s, ok := m.session[sessionID]
|
|
||||||
if ok {
|
|
||||||
s.statusChan = nil
|
|
||||||
s.buildOnGoing.Store(false)
|
|
||||||
}
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
pw := pb.NewProgressWriter(statusChan)
|
|
||||||
|
|
||||||
// Build the specified request
|
|
||||||
ctx, cancel := context.WithCancelCause(ctx)
|
|
||||||
defer func() { cancel(errors.WithStack(context.Canceled)) }()
|
|
||||||
resp, res, _, buildErr := m.buildFunc(ctx, req.Options, inR, pw)
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
if s, ok := m.session[sessionID]; ok {
|
|
||||||
// NOTE: buildFunc can return *build.ResultHandle even on error (e.g. when it's implemented using (github.com/docker/buildx/controller/build).RunBuild).
|
|
||||||
if res != nil {
|
|
||||||
s.result = res
|
|
||||||
s.cancelBuild = cancel
|
|
||||||
s.buildOptions = req.Options
|
|
||||||
m.session[sessionID] = s
|
|
||||||
if buildErr != nil {
|
|
||||||
var ref string
|
|
||||||
var ebr *desktop.ErrorWithBuildRef
|
|
||||||
if errors.As(buildErr, &ebr) {
|
|
||||||
ref = ebr.Ref
|
|
||||||
}
|
|
||||||
buildErr = controllererrors.WrapBuild(buildErr, sessionID, ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
return nil, errors.Errorf("build: unknown session ID %v", sessionID)
|
|
||||||
}
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
|
|
||||||
if buildErr != nil {
|
|
||||||
return nil, buildErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if resp == nil {
|
|
||||||
resp = &client.SolveResponse{}
|
|
||||||
}
|
|
||||||
return &pb.BuildResponse{
|
|
||||||
ExporterResponse: resp.ExporterResponse,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Status(req *pb.StatusRequest, stream pb.Controller_StatusServer) error {
|
|
||||||
sessionID := req.SessionID
|
|
||||||
if sessionID == "" {
|
|
||||||
return errors.New("status: empty session ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait and get status channel prepared by Build()
|
|
||||||
var statusChan <-chan *pb.StatusResponse
|
|
||||||
for {
|
|
||||||
// TODO: timeout?
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
if _, ok := m.session[sessionID]; !ok || m.session[sessionID].statusChan == nil {
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
time.Sleep(time.Millisecond) // TODO: wait Build without busy loop and make it cancellable
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
statusChan = m.session[sessionID].statusChan
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward status
|
|
||||||
for ss := range statusChan {
|
|
||||||
if ss == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err := stream.Send(ss); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Input(stream pb.Controller_InputServer) (err error) {
|
|
||||||
// Get the target ref from init message
|
|
||||||
msg, err := stream.Recv()
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, io.EOF) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
init := msg.GetInit()
|
|
||||||
if init == nil {
|
|
||||||
return errors.Errorf("unexpected message: %T; wanted init", msg.GetInit())
|
|
||||||
}
|
|
||||||
sessionID := init.SessionID
|
|
||||||
if sessionID == "" {
|
|
||||||
return errors.New("input: no session ID is provided")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait and get input stream pipe prepared by Build()
|
|
||||||
var inputPipeW *io.PipeWriter
|
|
||||||
for {
|
|
||||||
// TODO: timeout?
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
if _, ok := m.session[sessionID]; !ok || m.session[sessionID].inputPipe == nil {
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
time.Sleep(time.Millisecond) // TODO: wait Build without busy loop and make it cancellable
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
inputPipeW = m.session[sessionID].inputPipe
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forward input stream
|
|
||||||
eg, ctx := errgroup.WithContext(context.TODO())
|
|
||||||
done := make(chan struct{})
|
|
||||||
msgCh := make(chan *pb.InputMessage)
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer close(msgCh)
|
|
||||||
for {
|
|
||||||
msg, err := stream.Recv()
|
|
||||||
if err != nil {
|
|
||||||
if !errors.Is(err, io.EOF) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case msgCh <- msg:
|
|
||||||
case <-done:
|
|
||||||
return nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
eg.Go(func() (retErr error) {
|
|
||||||
defer close(done)
|
|
||||||
defer func() {
|
|
||||||
if retErr != nil {
|
|
||||||
inputPipeW.CloseWithError(retErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
inputPipeW.Close()
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
var msg *pb.InputMessage
|
|
||||||
select {
|
|
||||||
case msg = <-msgCh:
|
|
||||||
case <-ctx.Done():
|
|
||||||
return context.Cause(ctx)
|
|
||||||
}
|
|
||||||
if msg == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if data := msg.GetData(); data != nil {
|
|
||||||
if len(data.Data) > 0 {
|
|
||||||
_, err := inputPipeW.Write(data.Data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if data.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return eg.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Server) Invoke(srv pb.Controller_InvokeServer) error {
|
|
||||||
containerIn, containerOut := ioset.Pipe()
|
|
||||||
defer func() { containerOut.Close(); containerIn.Close() }()
|
|
||||||
|
|
||||||
initDoneCh := make(chan *processes.Process)
|
|
||||||
initErrCh := make(chan error)
|
|
||||||
eg, egCtx := errgroup.WithContext(context.TODO())
|
|
||||||
srvIOCtx, srvIOCancel := context.WithCancelCause(egCtx)
|
|
||||||
eg.Go(func() error {
|
|
||||||
defer srvIOCancel(errors.WithStack(context.Canceled))
|
|
||||||
return serveIO(srvIOCtx, srv, func(initMessage *pb.InitMessage) (retErr error) {
|
|
||||||
defer func() {
|
|
||||||
if retErr != nil {
|
|
||||||
initErrCh <- retErr
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
sessionID := initMessage.SessionID
|
|
||||||
cfg := initMessage.InvokeConfig
|
|
||||||
|
|
||||||
m.sessionMu.Lock()
|
|
||||||
s, ok := m.session[sessionID]
|
|
||||||
if !ok {
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
return errors.Errorf("invoke: unknown session ID %v", sessionID)
|
|
||||||
}
|
|
||||||
m.sessionMu.Unlock()
|
|
||||||
|
|
||||||
pid := initMessage.ProcessID
|
|
||||||
if pid == "" {
|
|
||||||
return errors.Errorf("invoke: specify process ID")
|
|
||||||
}
|
|
||||||
proc, ok := s.processes.Get(pid)
|
|
||||||
if !ok {
|
|
||||||
// Start a new process.
|
|
||||||
if cfg == nil {
|
|
||||||
return errors.New("no container config is provided")
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
proc, err = s.processes.StartProcess(pid, s.result, cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Attach containerIn to this process
|
|
||||||
proc.ForwardIO(&containerIn, srvIOCancel)
|
|
||||||
initDoneCh <- proc
|
|
||||||
return nil
|
|
||||||
}, &ioServerConfig{
|
|
||||||
stdin: containerOut.Stdin,
|
|
||||||
stdout: containerOut.Stdout,
|
|
||||||
stderr: containerOut.Stderr,
|
|
||||||
// TODO: signal, resize
|
|
||||||
})
|
|
||||||
})
|
|
||||||
eg.Go(func() (rErr error) {
|
|
||||||
defer srvIOCancel(errors.WithStack(context.Canceled))
|
|
||||||
// Wait for init done
|
|
||||||
var proc *processes.Process
|
|
||||||
select {
|
|
||||||
case p := <-initDoneCh:
|
|
||||||
proc = p
|
|
||||||
case err := <-initErrCh:
|
|
||||||
return err
|
|
||||||
case <-egCtx.Done():
|
|
||||||
return egCtx.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for IO done
|
|
||||||
select {
|
|
||||||
case <-srvIOCtx.Done():
|
|
||||||
return srvIOCtx.Err()
|
|
||||||
case err := <-proc.Done():
|
|
||||||
return err
|
|
||||||
case <-egCtx.Done():
|
|
||||||
return egCtx.Err()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return eg.Wait()
|
|
||||||
}
|
|
@ -28,7 +28,6 @@ Start a build
|
|||||||
| [`--cgroup-parent`](#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| [`--cgroup-parent`](#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
| [`--check`](#check) | `bool` | | Shorthand for `--call=check` |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| `--detach` | `bool` | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
|
|
||||||
| [`-f`](#file), [`--file`](#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| [`-f`](#file), [`--file`](#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
@ -44,10 +43,8 @@ Start a build
|
|||||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||||
| [`--push`](#push) | `bool` | | Shorthand for `--output=type=registry` |
|
| [`--push`](#push) | `bool` | | Shorthand for `--output=type=registry` |
|
||||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||||
| `--root` | `string` | | Specify root directory of server to connect (EXPERIMENTAL) |
|
|
||||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) (EXPERIMENTAL) |
|
|
||||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||||
|
@ -13,15 +13,12 @@ Start debugger (EXPERIMENTAL)
|
|||||||
### Options
|
### Options
|
||||||
|
|
||||||
| Name | Type | Default | Description |
|
| Name | Type | Default | Description |
|
||||||
|:------------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------|
|
|:----------------|:---------|:--------|:--------------------------------------------------------------------------------------------------------------------|
|
||||||
| `--builder` | `string` | | Override the configured builder instance |
|
| `--builder` | `string` | | Override the configured builder instance |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| `--detach` | `bool` | `true` | Detach buildx server for the monitor (supported only on linux) (EXPERIMENTAL) |
|
|
||||||
| `--invoke` | `string` | | Launch a monitor with executing specified command (EXPERIMENTAL) |
|
| `--invoke` | `string` | | Launch a monitor with executing specified command (EXPERIMENTAL) |
|
||||||
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) (EXPERIMENTAL) |
|
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) (EXPERIMENTAL) |
|
||||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`, `rawjson`) for the monitor. Use plain to show container output |
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`, `rawjson`) for the monitor. Use plain to show container output |
|
||||||
| `--root` | `string` | | Specify root directory of server to connect for the monitor (EXPERIMENTAL) |
|
|
||||||
| `--server-config` | `string` | | Specify buildx server config file for the monitor (used only when launching new server) (EXPERIMENTAL) |
|
|
||||||
|
|
||||||
|
|
||||||
<!---MARKER_GEN_END-->
|
<!---MARKER_GEN_END-->
|
||||||
|
@ -24,7 +24,6 @@ Start a build
|
|||||||
| `--cgroup-parent` | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
| `--cgroup-parent` | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||||
| `--check` | `bool` | | Shorthand for `--call=check` |
|
| `--check` | `bool` | | Shorthand for `--call=check` |
|
||||||
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
| `-D`, `--debug` | `bool` | | Enable debug logging |
|
||||||
| `--detach` | `bool` | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
|
|
||||||
| `-f`, `--file` | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
| `-f`, `--file` | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||||
| `--label` | `stringArray` | | Set metadata for an image |
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
@ -40,10 +39,8 @@ Start a build
|
|||||||
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
| `--pull` | `bool` | | Always attempt to pull all referenced images |
|
||||||
| `--push` | `bool` | | Shorthand for `--output=type=registry` |
|
| `--push` | `bool` | | Shorthand for `--output=type=registry` |
|
||||||
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
| `-q`, `--quiet` | `bool` | | Suppress the build output and print image ID on success |
|
||||||
| `--root` | `string` | | Specify root directory of server to connect (EXPERIMENTAL) |
|
|
||||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) (EXPERIMENTAL) |
|
|
||||||
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||||
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
| `-t`, `--tag` | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
| `-t`, `--tag` | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||||
|
2
go.mod
2
go.mod
@ -33,7 +33,6 @@ require (
|
|||||||
github.com/moby/go-archive v0.1.0
|
github.com/moby/go-archive v0.1.0
|
||||||
github.com/moby/sys/atomicwriter v0.1.0
|
github.com/moby/sys/atomicwriter v0.1.0
|
||||||
github.com/moby/sys/mountinfo v0.7.2
|
github.com/moby/sys/mountinfo v0.7.2
|
||||||
github.com/moby/sys/signal v0.7.1
|
|
||||||
github.com/morikuni/aec v1.0.0
|
github.com/morikuni/aec v1.0.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
@ -130,6 +129,7 @@ require (
|
|||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
github.com/moby/spdystream v0.5.0 // indirect
|
github.com/moby/spdystream v0.5.0 // indirect
|
||||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
|
github.com/moby/sys/signal v0.7.1 // indirect
|
||||||
github.com/moby/sys/user v0.4.0 // indirect
|
github.com/moby/sys/user v0.4.0 // indirect
|
||||||
github.com/moby/sys/userns v0.1.0 // indirect
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
github.com/moby/term v0.5.2 // indirect
|
github.com/moby/term v0.5.2 // indirect
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
|
||||||
|
|
||||||
|
cerrdefs "github.com/containerd/errdefs"
|
||||||
"github.com/docker/buildx/monitor/types"
|
"github.com/docker/buildx/monitor/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -23,57 +23,33 @@ func NewAttachCmd(m types.Monitor, stdout io.WriteCloser) types.Command {
|
|||||||
func (cm *AttachCmd) Info() types.CommandInfo {
|
func (cm *AttachCmd) Info() types.CommandInfo {
|
||||||
return types.CommandInfo{
|
return types.CommandInfo{
|
||||||
Name: "attach",
|
Name: "attach",
|
||||||
HelpMessage: "attach to a buildx server or a process in the container",
|
HelpMessage: "attach to a process in the container",
|
||||||
HelpMessageLong: `
|
HelpMessageLong: `
|
||||||
Usage:
|
Usage:
|
||||||
attach ID
|
attach PID
|
||||||
|
|
||||||
ID is for a session (visible via list command) or a process (visible via ps command).
|
PID is for a process (visible via ps command).
|
||||||
If you attached to a process, use Ctrl-a-c for switching the monitor to that process's STDIO.
|
Use Ctrl-a-c for switching the monitor to that process's STDIO.
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cm *AttachCmd) Exec(ctx context.Context, args []string) error {
|
func (cm *AttachCmd) Exec(ctx context.Context, args []string) error {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return errors.Errorf("ID of session or process must be passed")
|
return errors.Errorf("PID of process must be passed")
|
||||||
}
|
}
|
||||||
ref := args[1]
|
pid := args[1]
|
||||||
var id string
|
|
||||||
|
|
||||||
isProcess, err := isProcessID(ctx, cm.m, ref)
|
infos, err := cm.m.ListProcesses(ctx)
|
||||||
if err == nil && isProcess {
|
|
||||||
cm.m.Attach(ctx, ref)
|
|
||||||
id = ref
|
|
||||||
}
|
|
||||||
if id == "" {
|
|
||||||
refs, err := cm.m.List(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("failed to get the list of sessions: %v", err)
|
return err
|
||||||
}
|
|
||||||
if !slices.Contains(refs, ref) {
|
|
||||||
return errors.Errorf("unknown ID: %q", ref)
|
|
||||||
}
|
|
||||||
cm.m.Detach() // Finish existing attach
|
|
||||||
cm.m.AttachSession(ref)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cm.stdout, "Attached to process %q. Press Ctrl-a-c to switch to the new container\n", id)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isProcessID(ctx context.Context, c types.Monitor, ref string) (bool, error) {
|
|
||||||
sid := c.AttachedSessionID()
|
|
||||||
if sid == "" {
|
|
||||||
return false, errors.Errorf("no attaching session")
|
|
||||||
}
|
|
||||||
infos, err := c.ListProcesses(ctx, sid)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
for _, p := range infos {
|
for _, p := range infos {
|
||||||
if p.ProcessID == ref {
|
if p.ProcessID == pid {
|
||||||
return true, nil
|
cm.m.Attach(ctx, pid)
|
||||||
|
fmt.Fprintf(cm.stdout, "Attached to process %q. Press Ctrl-a-c to switch to the new container\n", pid)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false, nil
|
return errors.Wrapf(cerrdefs.ErrNotFound, "pid %s", pid)
|
||||||
}
|
}
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/monitor/types"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DisconnectCmd struct {
|
|
||||||
m types.Monitor
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDisconnectCmd(m types.Monitor) types.Command {
|
|
||||||
return &DisconnectCmd{m}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *DisconnectCmd) Info() types.CommandInfo {
|
|
||||||
return types.CommandInfo{
|
|
||||||
Name: "disconnect",
|
|
||||||
HelpMessage: "disconnect a client from a buildx server. Specific session ID can be specified an arg",
|
|
||||||
HelpMessageLong: fmt.Sprintf(`
|
|
||||||
Usage:
|
|
||||||
disconnect [ID]
|
|
||||||
|
|
||||||
ID is for a session (visible via list command). Default is %q.
|
|
||||||
`, cm.m.AttachedSessionID()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *DisconnectCmd) Exec(ctx context.Context, args []string) error {
|
|
||||||
target := cm.m.AttachedSessionID()
|
|
||||||
if len(args) >= 2 {
|
|
||||||
target = args[1]
|
|
||||||
} else if target == "" {
|
|
||||||
return errors.Errorf("no attaching session")
|
|
||||||
}
|
|
||||||
isProcess, err := isProcessID(ctx, cm.m, target)
|
|
||||||
if err == nil && isProcess {
|
|
||||||
sid := cm.m.AttachedSessionID()
|
|
||||||
if sid == "" {
|
|
||||||
return errors.Errorf("no attaching session")
|
|
||||||
}
|
|
||||||
if err := cm.m.DisconnectProcess(ctx, sid, target); err != nil {
|
|
||||||
return errors.Errorf("disconnecting from process failed %v", target)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := cm.m.DisconnectSession(ctx, target); err != nil {
|
|
||||||
return errors.Errorf("disconnecting from session failed: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -35,9 +35,6 @@ COMMAND and ARG... will be executed in the container.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ExecCmd) Exec(ctx context.Context, args []string) error {
|
func (cm *ExecCmd) Exec(ctx context.Context, args []string) error {
|
||||||
if ref := cm.m.AttachedSessionID(); ref == "" {
|
|
||||||
return errors.Errorf("no attaching session")
|
|
||||||
}
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return errors.Errorf("command must be passed")
|
return errors.Errorf("command must be passed")
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/monitor/types"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type KillCmd struct {
|
|
||||||
m types.Monitor
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKillCmd(m types.Monitor) types.Command {
|
|
||||||
return &KillCmd{m}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *KillCmd) Info() types.CommandInfo {
|
|
||||||
return types.CommandInfo{
|
|
||||||
Name: "kill",
|
|
||||||
HelpMessage: "kill buildx server",
|
|
||||||
HelpMessageLong: `
|
|
||||||
Usage:
|
|
||||||
kill
|
|
||||||
|
|
||||||
Kills the currently connecting buildx server process.
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *KillCmd) Exec(ctx context.Context, args []string) error {
|
|
||||||
if err := cm.m.Kill(ctx); err != nil {
|
|
||||||
return errors.Errorf("failed to kill: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/monitor/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListCmd struct {
|
|
||||||
m types.Monitor
|
|
||||||
|
|
||||||
stdout io.WriteCloser
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewListCmd(m types.Monitor, stdout io.WriteCloser) types.Command {
|
|
||||||
return &ListCmd{m, stdout}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *ListCmd) Info() types.CommandInfo {
|
|
||||||
return types.CommandInfo{
|
|
||||||
Name: "list",
|
|
||||||
HelpMessage: "list buildx sessions",
|
|
||||||
HelpMessageLong: `
|
|
||||||
Usage:
|
|
||||||
list
|
|
||||||
`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cm *ListCmd) Exec(ctx context.Context, args []string) error {
|
|
||||||
refs, err := cm.m.List(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sort.Strings(refs)
|
|
||||||
tw := tabwriter.NewWriter(cm.stdout, 1, 8, 1, '\t', 0)
|
|
||||||
fmt.Fprintln(tw, "ID\tCURRENT_SESSION")
|
|
||||||
for _, k := range refs {
|
|
||||||
fmt.Fprintf(tw, "%-20s\t%v\n", k, k == cm.m.AttachedSessionID())
|
|
||||||
}
|
|
||||||
tw.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -7,7 +7,6 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/docker/buildx/monitor/types"
|
"github.com/docker/buildx/monitor/types"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PsCmd struct {
|
type PsCmd struct {
|
||||||
@ -31,11 +30,7 @@ Usage:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *PsCmd) Exec(ctx context.Context, args []string) error {
|
func (cm *PsCmd) Exec(ctx context.Context, args []string) error {
|
||||||
ref := cm.m.AttachedSessionID()
|
plist, err := cm.m.ListProcesses(ctx)
|
||||||
if ref == "" {
|
|
||||||
return errors.Errorf("no attaching session")
|
|
||||||
}
|
|
||||||
plist, err := cm.m.ListProcesses(ctx, ref)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
cbuild "github.com/docker/buildx/controller/build"
|
||||||
controllererrors "github.com/docker/buildx/controller/errdefs"
|
controllererrors "github.com/docker/buildx/controller/errdefs"
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
"github.com/docker/buildx/monitor/types"
|
"github.com/docker/buildx/monitor/types"
|
||||||
@ -19,11 +20,11 @@ type ReloadCmd struct {
|
|||||||
stdout io.WriteCloser
|
stdout io.WriteCloser
|
||||||
progress *progress.Printer
|
progress *progress.Printer
|
||||||
|
|
||||||
options *controllerapi.BuildOptions
|
options *cbuild.Options
|
||||||
invokeConfig *controllerapi.InvokeConfig
|
invokeConfig *controllerapi.InvokeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewReloadCmd(m types.Monitor, stdout io.WriteCloser, progress *progress.Printer, options *controllerapi.BuildOptions, invokeConfig *controllerapi.InvokeConfig) types.Command {
|
func NewReloadCmd(m types.Monitor, stdout io.WriteCloser, progress *progress.Printer, options *cbuild.Options, invokeConfig *controllerapi.InvokeConfig) types.Command {
|
||||||
return &ReloadCmd{m, stdout, progress, options, invokeConfig}
|
return &ReloadCmd{m, stdout, progress, options, invokeConfig}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,34 +40,15 @@ Usage:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *ReloadCmd) Exec(ctx context.Context, args []string) error {
|
func (cm *ReloadCmd) Exec(ctx context.Context, args []string) error {
|
||||||
var bo *controllerapi.BuildOptions
|
bo := cm.m.Inspect(ctx)
|
||||||
if ref := cm.m.AttachedSessionID(); ref != "" {
|
|
||||||
// Rebuilding an existing session; Restore the build option used for building this session.
|
|
||||||
res, err := cm.m.Inspect(ctx, ref)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("failed to inspect the current build session: %v\n", err)
|
|
||||||
} else {
|
|
||||||
bo = res.Options
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
bo = cm.options
|
|
||||||
}
|
|
||||||
if bo == nil {
|
|
||||||
return errors.Errorf("no build option is provided")
|
|
||||||
}
|
|
||||||
if ref := cm.m.AttachedSessionID(); ref != "" {
|
|
||||||
if err := cm.m.Disconnect(ctx, ref); err != nil {
|
|
||||||
fmt.Println("disconnect error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var resultUpdated bool
|
var resultUpdated bool
|
||||||
cm.progress.Unpause()
|
cm.progress.Unpause()
|
||||||
ref, _, _, err := cm.m.Build(ctx, bo, nil, cm.progress) // TODO: support stdin, hold build ref
|
_, _, err := cm.m.Build(ctx, bo, nil, cm.progress) // TODO: support stdin, hold build ref
|
||||||
cm.progress.Pause()
|
cm.progress.Pause()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var be *controllererrors.BuildError
|
var be *controllererrors.BuildError
|
||||||
if errors.As(err, &be) {
|
if errors.As(err, &be) {
|
||||||
ref = be.SessionID
|
|
||||||
resultUpdated = true
|
resultUpdated = true
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("failed to reload: %v\n", err)
|
fmt.Printf("failed to reload: %v\n", err)
|
||||||
@ -79,7 +61,6 @@ func (cm *ReloadCmd) Exec(ctx context.Context, args []string) error {
|
|||||||
} else {
|
} else {
|
||||||
resultUpdated = true
|
resultUpdated = true
|
||||||
}
|
}
|
||||||
cm.m.AttachSession(ref)
|
|
||||||
if resultUpdated {
|
if resultUpdated {
|
||||||
// rollback the running container with the new result
|
// rollback the running container with the new result
|
||||||
id := cm.m.Rollback(ctx, cm.invokeConfig)
|
id := cm.m.Rollback(ctx, cm.invokeConfig)
|
||||||
|
@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
"github.com/docker/buildx/monitor/types"
|
"github.com/docker/buildx/monitor/types"
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RollbackCmd struct {
|
type RollbackCmd struct {
|
||||||
@ -38,9 +37,6 @@ COMMAND and ARG... will be executed in the container.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cm *RollbackCmd) Exec(ctx context.Context, args []string) error {
|
func (cm *RollbackCmd) Exec(ctx context.Context, args []string) error {
|
||||||
if ref := cm.m.AttachedSessionID(); ref == "" {
|
|
||||||
return errors.Errorf("no attaching session")
|
|
||||||
}
|
|
||||||
cfg := cm.invokeConfig
|
cfg := cm.invokeConfig
|
||||||
if len(args) >= 2 {
|
if len(args) >= 2 {
|
||||||
cmds := args[1:]
|
cmds := args[1:]
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/console"
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
|
cbuild "github.com/docker/buildx/controller/build"
|
||||||
"github.com/docker/buildx/controller/control"
|
"github.com/docker/buildx/controller/control"
|
||||||
controllerapi "github.com/docker/buildx/controller/pb"
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
"github.com/docker/buildx/monitor/commands"
|
"github.com/docker/buildx/monitor/commands"
|
||||||
@ -31,10 +32,10 @@ type MonitorBuildResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunMonitor provides an interactive session for running and managing containers via specified IO.
|
// RunMonitor provides an interactive session for running and managing containers via specified IO.
|
||||||
func RunMonitor(ctx context.Context, curRef string, options *controllerapi.BuildOptions, invokeConfig *controllerapi.InvokeConfig, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*MonitorBuildResult, error) {
|
func RunMonitor(ctx context.Context, curRef string, options *cbuild.Options, invokeConfig *controllerapi.InvokeConfig, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*MonitorBuildResult, error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := c.Disconnect(ctx, curRef); err != nil {
|
if err := c.Close(); err != nil {
|
||||||
logrus.Warnf("disconnect error: %v", err)
|
logrus.Warnf("close error: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -95,9 +96,6 @@ func RunMonitor(ctx context.Context, curRef string, options *controllerapi.Build
|
|||||||
availableCommands := []types.Command{
|
availableCommands := []types.Command{
|
||||||
commands.NewReloadCmd(m, stdout, progress, options, invokeConfig),
|
commands.NewReloadCmd(m, stdout, progress, options, invokeConfig),
|
||||||
commands.NewRollbackCmd(m, invokeConfig, stdout),
|
commands.NewRollbackCmd(m, invokeConfig, stdout),
|
||||||
commands.NewListCmd(m, stdout),
|
|
||||||
commands.NewDisconnectCmd(m),
|
|
||||||
commands.NewKillCmd(m),
|
|
||||||
commands.NewAttachCmd(m, stdout),
|
commands.NewAttachCmd(m, stdout),
|
||||||
commands.NewExecCmd(m, invokeConfig, stdout),
|
commands.NewExecCmd(m, invokeConfig, stdout),
|
||||||
commands.NewPsCmd(m, stdout),
|
commands.NewPsCmd(m, stdout),
|
||||||
@ -244,24 +242,12 @@ type monitor struct {
|
|||||||
lastBuildResult *MonitorBuildResult
|
lastBuildResult *MonitorBuildResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *monitor) Build(ctx context.Context, options *controllerapi.BuildOptions, in io.ReadCloser, progress progress.Writer) (ref string, resp *client.SolveResponse, input *build.Inputs, err error) {
|
func (m *monitor) Build(ctx context.Context, options *cbuild.Options, in io.ReadCloser, progress progress.Writer) (resp *client.SolveResponse, input *build.Inputs, err error) {
|
||||||
ref, resp, _, err = m.BuildxController.Build(ctx, options, in, progress)
|
resp, _, err = m.BuildxController.Build(ctx, options, in, progress)
|
||||||
m.lastBuildResult = &MonitorBuildResult{Resp: resp, Err: err} // Record build result
|
m.lastBuildResult = &MonitorBuildResult{Resp: resp, Err: err} // Record build result
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *monitor) DisconnectSession(ctx context.Context, targetID string) error {
|
|
||||||
return m.Disconnect(ctx, targetID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *monitor) AttachSession(ref string) {
|
|
||||||
m.ref.Store(ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *monitor) AttachedSessionID() string {
|
|
||||||
return m.ref.Load().(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *monitor) Rollback(ctx context.Context, cfg *controllerapi.InvokeConfig) string {
|
func (m *monitor) Rollback(ctx context.Context, cfg *controllerapi.InvokeConfig) string {
|
||||||
pid := identity.NewID()
|
pid := identity.NewID()
|
||||||
cfg1 := cfg
|
cfg1 := cfg
|
||||||
@ -323,9 +309,6 @@ func (m *monitor) invoke(ctx context.Context, pid string, cfg *controllerapi.Inv
|
|||||||
if err := m.muxIO.SwitchTo(1); err != nil {
|
if err := m.muxIO.SwitchTo(1); err != nil {
|
||||||
return errors.Errorf("failed to switch to process IO: %v", err)
|
return errors.Errorf("failed to switch to process IO: %v", err)
|
||||||
}
|
}
|
||||||
if m.AttachedSessionID() == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
invokeCtx, invokeCancel := context.WithCancelCause(ctx)
|
invokeCtx, invokeCancel := context.WithCancelCause(ctx)
|
||||||
|
|
||||||
containerIn, containerOut := ioset.Pipe()
|
containerIn, containerOut := ioset.Pipe()
|
||||||
@ -343,7 +326,7 @@ func (m *monitor) invoke(ctx context.Context, pid string, cfg *controllerapi.Inv
|
|||||||
defer invokeCancelAndDetachFn()
|
defer invokeCancelAndDetachFn()
|
||||||
m.invokeCancel = invokeCancelAndDetachFn
|
m.invokeCancel = invokeCancelAndDetachFn
|
||||||
|
|
||||||
err := m.Invoke(invokeCtx, m.AttachedSessionID(), pid, cfg, containerIn.Stdin, containerIn.Stdout, containerIn.Stderr)
|
err := m.Invoke(invokeCtx, pid, cfg, containerIn.Stdin, containerIn.Stdout, containerIn.Stderr)
|
||||||
close(waitInvokeDoneCh)
|
close(waitInvokeDoneCh)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -25,15 +25,6 @@ type Monitor interface {
|
|||||||
|
|
||||||
// Detach detaches IO from the container.
|
// Detach detaches IO from the container.
|
||||||
Detach()
|
Detach()
|
||||||
|
|
||||||
// DisconnectSession finishes the specified session.
|
|
||||||
DisconnectSession(ctx context.Context, targetID string) error
|
|
||||||
|
|
||||||
// AttachSession attaches the monitor to the specified session.
|
|
||||||
AttachSession(ref string)
|
|
||||||
|
|
||||||
// AttachedSessionID returns the ID of the attached session.
|
|
||||||
AttachedSessionID() string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommandInfo is information about a command.
|
// CommandInfo is information about a command.
|
||||||
|
74
vendor/github.com/containerd/containerd/v2/pkg/dialer/dialer.go
generated
vendored
74
vendor/github.com/containerd/containerd/v2/pkg/dialer/dialer.go
generated
vendored
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright The containerd Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dialResult struct {
|
|
||||||
c net.Conn
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContextDialer returns a GRPC net.Conn connected to the provided address
|
|
||||||
func ContextDialer(ctx context.Context, address string) (net.Conn, error) {
|
|
||||||
if deadline, ok := ctx.Deadline(); ok {
|
|
||||||
return timeoutDialer(address, time.Until(deadline))
|
|
||||||
}
|
|
||||||
return timeoutDialer(address, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeoutDialer(address string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
var (
|
|
||||||
stopC = make(chan struct{})
|
|
||||||
synC = make(chan *dialResult)
|
|
||||||
)
|
|
||||||
go func() {
|
|
||||||
defer close(synC)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-stopC:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
c, err := dialer(address, timeout)
|
|
||||||
if isNoent(err) {
|
|
||||||
<-time.After(10 * time.Millisecond)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
synC <- &dialResult{c, err}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case dr := <-synC:
|
|
||||||
return dr.c, dr.err
|
|
||||||
case <-time.After(timeout):
|
|
||||||
close(stopC)
|
|
||||||
go func() {
|
|
||||||
dr := <-synC
|
|
||||||
if dr != nil && dr.c != nil {
|
|
||||||
dr.c.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return nil, fmt.Errorf("dial %s: timeout", address)
|
|
||||||
}
|
|
||||||
}
|
|
43
vendor/github.com/containerd/containerd/v2/pkg/dialer/dialer_unix.go
generated
vendored
43
vendor/github.com/containerd/containerd/v2/pkg/dialer/dialer_unix.go
generated
vendored
@ -1,43 +0,0 @@
|
|||||||
//go:build !windows
|
|
||||||
|
|
||||||
/*
|
|
||||||
Copyright The containerd Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DialAddress returns the address with unix:// prepended to the
|
|
||||||
// provided address
|
|
||||||
func DialAddress(address string) string {
|
|
||||||
return fmt.Sprintf("unix://%s", address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNoent(err error) bool {
|
|
||||||
return errors.Is(err, syscall.ENOENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialer(address string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
address = strings.TrimPrefix(address, "unix://")
|
|
||||||
return net.DialTimeout("unix", address, timeout)
|
|
||||||
}
|
|
47
vendor/github.com/containerd/containerd/v2/pkg/dialer/dialer_windows.go
generated
vendored
47
vendor/github.com/containerd/containerd/v2/pkg/dialer/dialer_windows.go
generated
vendored
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright The containerd Authors.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package dialer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
winio "github.com/Microsoft/go-winio"
|
|
||||||
)
|
|
||||||
|
|
||||||
func isNoent(err error) bool {
|
|
||||||
return os.IsNotExist(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func dialer(address string, timeout time.Duration) (net.Conn, error) {
|
|
||||||
address = strings.TrimPrefix(filepath.ToSlash(address), "npipe://")
|
|
||||||
return winio.DialPipe(address, &timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialAddress returns the dial address with npipe:// prepended to the
|
|
||||||
// provided address
|
|
||||||
func DialAddress(address string) string {
|
|
||||||
address = filepath.ToSlash(address)
|
|
||||||
if !strings.HasPrefix(address, "npipe://") {
|
|
||||||
address = fmt.Sprintf("npipe://%s", address)
|
|
||||||
}
|
|
||||||
return address
|
|
||||||
}
|
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -164,7 +164,6 @@ github.com/containerd/containerd/v2/internal/fsverity
|
|||||||
github.com/containerd/containerd/v2/internal/randutil
|
github.com/containerd/containerd/v2/internal/randutil
|
||||||
github.com/containerd/containerd/v2/pkg/archive/compression
|
github.com/containerd/containerd/v2/pkg/archive/compression
|
||||||
github.com/containerd/containerd/v2/pkg/deprecation
|
github.com/containerd/containerd/v2/pkg/deprecation
|
||||||
github.com/containerd/containerd/v2/pkg/dialer
|
|
||||||
github.com/containerd/containerd/v2/pkg/filters
|
github.com/containerd/containerd/v2/pkg/filters
|
||||||
github.com/containerd/containerd/v2/pkg/identifiers
|
github.com/containerd/containerd/v2/pkg/identifiers
|
||||||
github.com/containerd/containerd/v2/pkg/kernelversion
|
github.com/containerd/containerd/v2/pkg/kernelversion
|
||||||
|
Loading…
x
Reference in New Issue
Block a user