You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
244 lines
7.2 KiB
Go
244 lines
7.2 KiB
Go
package control
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/containerd/console"
|
|
"github.com/docker/buildx/build"
|
|
cbuild "github.com/docker/buildx/controller/build"
|
|
controllerapi "github.com/docker/buildx/controller/pb"
|
|
"github.com/docker/buildx/store"
|
|
"github.com/docker/buildx/util/progress"
|
|
controlapi "github.com/moby/buildkit/api/services/control"
|
|
"github.com/moby/buildkit/client"
|
|
"github.com/moby/buildkit/util/progress/progressui"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/sync/errgroup"
|
|
)
|
|
|
|
type BuildxController interface {
|
|
Build(ctx context.Context, options controllerapi.BuildOptions, in io.ReadCloser, statusChan chan *controllerapi.StatusResponse) (ref string, resp *client.SolveResponse, err error)
|
|
// Invoke starts an IO session into the specified process.
|
|
// If pid doesn't matche to any running processes, it starts a new process with the specified config.
|
|
// If there is no container running or InvokeConfig.Rollback is speicfied, 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).
|
|
Invoke(ctx context.Context, ref, pid string, options controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
|
|
Kill(ctx context.Context) error
|
|
Close() error
|
|
List(ctx context.Context) (refs []string, _ error)
|
|
Disconnect(ctx context.Context, ref string) error
|
|
ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error)
|
|
DisconnectProcess(ctx context.Context, ref, pid string) error
|
|
}
|
|
|
|
type ControlOptions struct {
|
|
ServerConfig string
|
|
Root string
|
|
Detach bool
|
|
}
|
|
|
|
// Build is a helper function that builds the build and prints the status using the controller.
|
|
func Build(ctx context.Context, c BuildxController, options controllerapi.BuildOptions, in io.ReadCloser, w io.Writer, out console.File, progressMode string) (ref string, resp *client.SolveResponse, err error) {
|
|
pwCh := make(chan *progress.Printer)
|
|
statusChan := make(chan *controllerapi.StatusResponse)
|
|
statusDone := make(chan struct{})
|
|
eg, egCtx := errgroup.WithContext(ctx)
|
|
eg.Go(func() error {
|
|
defer close(statusChan)
|
|
var err error
|
|
ref, resp, err = c.Build(egCtx, options, in, statusChan)
|
|
return err
|
|
})
|
|
var resultInfo []*controllerapi.ResultInfoMessage
|
|
eg.Go(func() error {
|
|
defer close(statusDone)
|
|
var pw *progress.Printer
|
|
for s := range statusChan {
|
|
st := s
|
|
switch m := st.GetStatus().(type) {
|
|
case *controllerapi.StatusResponse_NodeInfo:
|
|
if pw != nil {
|
|
return errors.Errorf("node info is already registered")
|
|
}
|
|
ng := m.NodeInfo
|
|
pw, err = progress.NewPrinter(context.TODO(), w, out, progressMode, progressui.WithDesc(
|
|
fmt.Sprintf("building with %q instance using %s driver", ng.Name, ng.Driver),
|
|
fmt.Sprintf("%s:%s", ng.Driver, ng.Name),
|
|
))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pwCh <- pw
|
|
case *controllerapi.StatusResponse_SolveStatus:
|
|
if pw != nil {
|
|
pw.Write(toSolveStatus(m.SolveStatus))
|
|
}
|
|
case *controllerapi.StatusResponse_ResultInfo:
|
|
resultInfo = append(resultInfo, m.ResultInfo)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
eg.Go(func() error {
|
|
pw := <-pwCh
|
|
<-statusDone
|
|
if err := pw.Wait(); err != nil {
|
|
return err
|
|
}
|
|
cbuild.PrintWarnings(w, pw.Warnings(), progressMode)
|
|
return nil
|
|
})
|
|
if err := eg.Wait(); err != nil {
|
|
return ref, resp, err
|
|
}
|
|
for _, ri := range resultInfo {
|
|
cbuild.PrintResult(w, &build.PrintFunc{Name: ri.Name, Format: ri.Format}, ri.Result)
|
|
}
|
|
return ref, resp, nil
|
|
}
|
|
|
|
// ForwardProgress creates a progress printer backed by Status API.
|
|
func ForwardProgress(statusChan chan *controllerapi.StatusResponse) cbuild.ProgressConfig {
|
|
return cbuild.ProgressConfig{
|
|
Printer: func(ctx context.Context, ng *store.NodeGroup) (*progress.Printer, error) {
|
|
statusChan <- &controllerapi.StatusResponse{
|
|
Status: &controllerapi.StatusResponse_NodeInfo{
|
|
NodeInfo: &controllerapi.NodeInfoMessage{Name: ng.Name, Driver: ng.Driver},
|
|
},
|
|
}
|
|
pw, err := progress.NewPrinter(ctx, io.Discard, os.Stderr, "quiet")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return progress.Tee(pw, forwardStatus(statusChan)), nil
|
|
},
|
|
PrintResultFunc: func(f *build.PrintFunc, res map[string]string) error {
|
|
statusChan <- &controllerapi.StatusResponse{
|
|
Status: &controllerapi.StatusResponse_ResultInfo{
|
|
ResultInfo: &controllerapi.ResultInfoMessage{
|
|
Result: res,
|
|
Name: f.Name,
|
|
Format: f.Format,
|
|
},
|
|
},
|
|
}
|
|
return nil
|
|
},
|
|
// PrintWarningsFunc: printed on the client side
|
|
}
|
|
}
|
|
|
|
func forwardStatus(statusChan chan *controllerapi.StatusResponse) chan *client.SolveStatus {
|
|
ch := make(chan *client.SolveStatus)
|
|
go func() {
|
|
for st := range ch {
|
|
st2 := toControlStatus(st)
|
|
statusChan <- &controllerapi.StatusResponse{
|
|
Status: &controllerapi.StatusResponse_SolveStatus{
|
|
SolveStatus: st2,
|
|
},
|
|
}
|
|
}
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
func toControlStatus(s *client.SolveStatus) *controllerapi.SolveStatusMessage {
|
|
resp := controllerapi.SolveStatusMessage{}
|
|
for _, v := range s.Vertexes {
|
|
resp.Vertexes = append(resp.Vertexes, &controlapi.Vertex{
|
|
Digest: v.Digest,
|
|
Inputs: v.Inputs,
|
|
Name: v.Name,
|
|
Started: v.Started,
|
|
Completed: v.Completed,
|
|
Error: v.Error,
|
|
Cached: v.Cached,
|
|
ProgressGroup: v.ProgressGroup,
|
|
})
|
|
}
|
|
for _, v := range s.Statuses {
|
|
resp.Statuses = append(resp.Statuses, &controlapi.VertexStatus{
|
|
ID: v.ID,
|
|
Vertex: v.Vertex,
|
|
Name: v.Name,
|
|
Total: v.Total,
|
|
Current: v.Current,
|
|
Timestamp: v.Timestamp,
|
|
Started: v.Started,
|
|
Completed: v.Completed,
|
|
})
|
|
}
|
|
for _, v := range s.Logs {
|
|
resp.Logs = append(resp.Logs, &controlapi.VertexLog{
|
|
Vertex: v.Vertex,
|
|
Stream: int64(v.Stream),
|
|
Msg: v.Data,
|
|
Timestamp: v.Timestamp,
|
|
})
|
|
}
|
|
for _, v := range s.Warnings {
|
|
resp.Warnings = append(resp.Warnings, &controlapi.VertexWarning{
|
|
Vertex: v.Vertex,
|
|
Level: int64(v.Level),
|
|
Short: v.Short,
|
|
Detail: v.Detail,
|
|
Url: v.URL,
|
|
Info: v.SourceInfo,
|
|
Ranges: v.Range,
|
|
})
|
|
}
|
|
return &resp
|
|
}
|
|
|
|
func toSolveStatus(resp *controllerapi.SolveStatusMessage) *client.SolveStatus {
|
|
s := client.SolveStatus{}
|
|
for _, v := range resp.Vertexes {
|
|
s.Vertexes = append(s.Vertexes, &client.Vertex{
|
|
Digest: v.Digest,
|
|
Inputs: v.Inputs,
|
|
Name: v.Name,
|
|
Started: v.Started,
|
|
Completed: 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: v.Vertex,
|
|
Name: v.Name,
|
|
Total: v.Total,
|
|
Current: v.Current,
|
|
Timestamp: v.Timestamp,
|
|
Started: v.Started,
|
|
Completed: v.Completed,
|
|
})
|
|
}
|
|
for _, v := range resp.Logs {
|
|
s.Logs = append(s.Logs, &client.VertexLog{
|
|
Vertex: v.Vertex,
|
|
Stream: int(v.Stream),
|
|
Data: v.Msg,
|
|
Timestamp: v.Timestamp,
|
|
})
|
|
}
|
|
for _, v := range resp.Warnings {
|
|
s.Warnings = append(s.Warnings, &client.VertexWarning{
|
|
Vertex: v.Vertex,
|
|
Level: int(v.Level),
|
|
Short: v.Short,
|
|
Detail: v.Detail,
|
|
URL: v.Url,
|
|
SourceInfo: v.Info,
|
|
Range: v.Ranges,
|
|
})
|
|
}
|
|
return &s
|
|
}
|