2018-04-13 21:05:29 +00:00
|
|
|
package llbsolver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2018-08-16 10:44:25 +00:00
|
|
|
"strings"
|
2018-04-13 21:05:29 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/moby/buildkit/cache"
|
2018-05-07 21:24:28 +00:00
|
|
|
"github.com/moby/buildkit/cache/remotecache"
|
2018-04-13 21:05:29 +00:00
|
|
|
"github.com/moby/buildkit/client"
|
2018-07-19 16:34:44 +00:00
|
|
|
controlgateway "github.com/moby/buildkit/control/gateway"
|
2018-04-13 21:05:29 +00:00
|
|
|
"github.com/moby/buildkit/exporter"
|
|
|
|
"github.com/moby/buildkit/frontend"
|
2018-07-19 16:34:44 +00:00
|
|
|
"github.com/moby/buildkit/frontend/gateway"
|
2018-07-27 22:39:14 +00:00
|
|
|
"github.com/moby/buildkit/identity"
|
2018-04-13 21:05:29 +00:00
|
|
|
"github.com/moby/buildkit/session"
|
2018-05-11 05:58:41 +00:00
|
|
|
"github.com/moby/buildkit/solver"
|
2018-08-04 19:42:01 +00:00
|
|
|
"github.com/moby/buildkit/util/entitlements"
|
2018-04-13 21:05:29 +00:00
|
|
|
"github.com/moby/buildkit/util/progress"
|
|
|
|
"github.com/moby/buildkit/worker"
|
2018-07-27 22:39:14 +00:00
|
|
|
digest "github.com/opencontainers/go-digest"
|
2018-06-23 00:31:55 +00:00
|
|
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
2018-04-13 21:05:29 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2018-08-04 19:42:01 +00:00
|
|
|
const keyEntitlements = "llb.entitlements"
|
|
|
|
|
2018-04-13 21:05:29 +00:00
|
|
|
type ExporterRequest struct {
|
2018-05-07 21:51:44 +00:00
|
|
|
Exporter exporter.ExporterInstance
|
2018-07-03 09:59:33 +00:00
|
|
|
CacheExporter remotecache.Exporter
|
2018-05-07 21:51:44 +00:00
|
|
|
CacheExportMode solver.CacheExportMode
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ResolveWorkerFunc returns default worker for the temporary default non-distributed use cases
|
|
|
|
type ResolveWorkerFunc func() (worker.Worker, error)
|
|
|
|
|
|
|
|
type Solver struct {
|
2018-09-11 08:02:46 +00:00
|
|
|
workerController *worker.Controller
|
|
|
|
solver *solver.Solver
|
|
|
|
resolveWorker ResolveWorkerFunc
|
|
|
|
frontends map[string]frontend.Frontend
|
|
|
|
resolveCacheImporterFuncs map[string]remotecache.ResolveCacheImporterFunc
|
|
|
|
platforms []specs.Platform
|
|
|
|
gatewayForwarder *controlgateway.GatewayForwarder
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
|
2018-09-11 08:02:46 +00:00
|
|
|
func New(wc *worker.Controller, f map[string]frontend.Frontend, cache solver.CacheManager, resolveCI map[string]remotecache.ResolveCacheImporterFunc, gatewayForwarder *controlgateway.GatewayForwarder) (*Solver, error) {
|
2018-04-13 21:05:29 +00:00
|
|
|
s := &Solver{
|
2018-09-11 08:02:46 +00:00
|
|
|
workerController: wc,
|
|
|
|
resolveWorker: defaultResolver(wc),
|
|
|
|
frontends: f,
|
|
|
|
resolveCacheImporterFuncs: resolveCI,
|
|
|
|
gatewayForwarder: gatewayForwarder,
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
|
2018-06-23 00:31:55 +00:00
|
|
|
// executing is currently only allowed on default worker
|
|
|
|
w, err := wc.GetDefault()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
s.platforms = w.Platforms()
|
|
|
|
|
2018-04-13 21:05:29 +00:00
|
|
|
s.solver = solver.NewSolver(solver.SolverOpt{
|
|
|
|
ResolveOpFunc: s.resolver(),
|
|
|
|
DefaultCache: cache,
|
|
|
|
})
|
2018-06-23 00:31:55 +00:00
|
|
|
return s, nil
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Solver) resolver() solver.ResolveOpFunc {
|
|
|
|
return func(v solver.Vertex, b solver.Builder) (solver.Op, error) {
|
|
|
|
w, err := s.resolveWorker()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return w.ResolveOp(v, s.Bridge(b))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Solver) Bridge(b solver.Builder) frontend.FrontendLLBBridge {
|
|
|
|
return &llbBridge{
|
2018-09-11 08:02:46 +00:00
|
|
|
builder: b,
|
|
|
|
frontends: s.frontends,
|
|
|
|
resolveWorker: s.resolveWorker,
|
|
|
|
resolveCacheImporterFuncs: s.resolveCacheImporterFuncs,
|
|
|
|
cms: map[string]solver.CacheManager{},
|
|
|
|
platforms: s.platforms,
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:42:01 +00:00
|
|
|
func (s *Solver) Solve(ctx context.Context, id string, req frontend.SolveRequest, exp ExporterRequest, ent []entitlements.Entitlement) (*client.SolveResponse, error) {
|
2018-08-07 01:49:30 +00:00
|
|
|
j, err := s.solver.NewJob(id)
|
2018-08-04 19:42:01 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-08-07 01:49:30 +00:00
|
|
|
defer j.Discard()
|
|
|
|
|
|
|
|
set, err := entitlements.WhiteList(ent, supportedEntitlements())
|
2018-04-13 21:05:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-08-04 19:42:01 +00:00
|
|
|
j.SetValue(keyEntitlements, set)
|
|
|
|
|
2018-04-13 21:05:29 +00:00
|
|
|
j.SessionID = session.FromContext(ctx)
|
|
|
|
|
2018-07-19 16:34:44 +00:00
|
|
|
var res *frontend.Result
|
|
|
|
if s.gatewayForwarder != nil && req.Definition == nil && req.Frontend == "" {
|
|
|
|
fwd := gateway.NewBridgeForwarder(ctx, s.Bridge(j), s.workerController)
|
2018-08-20 13:09:28 +00:00
|
|
|
defer fwd.Discard()
|
2018-07-19 16:34:44 +00:00
|
|
|
if err := s.gatewayForwarder.RegisterBuild(ctx, id, fwd); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer s.gatewayForwarder.UnregisterBuild(ctx, id)
|
|
|
|
|
|
|
|
var err error
|
|
|
|
select {
|
|
|
|
case <-fwd.Done():
|
|
|
|
res, err = fwd.Result()
|
|
|
|
case <-ctx.Done():
|
|
|
|
err = ctx.Err()
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
res, err = s.Bridge(j).Solve(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
defer func() {
|
2018-07-11 23:51:41 +00:00
|
|
|
res.EachRef(func(ref solver.CachedResult) error {
|
|
|
|
go ref.Release(context.TODO())
|
|
|
|
return nil
|
|
|
|
})
|
2018-04-13 21:05:29 +00:00
|
|
|
}()
|
|
|
|
|
|
|
|
var exporterResponse map[string]string
|
|
|
|
if exp := exp.Exporter; exp != nil {
|
2018-07-10 18:05:22 +00:00
|
|
|
inp := exporter.Source{
|
|
|
|
Metadata: res.Metadata,
|
|
|
|
}
|
solver: avoid dereferencing nil res.Metadata
I saw this:
panic: assignment to entry in nil map
goroutine 3173 [running]:
github.com/moby/buildkit/exporter/oci.(*imageExporterInstance).Export(0xc42094ac40, 0xecac60, 0xc4211ca690, 0xed12c0, 0xc42000fca8, 0x0, 0x0, 0x0, 0x0, 0x0)
/go/src/github.com/moby/buildkit/exporter/oci/export.go:113 +0x156
github.com/moby/buildkit/solver/llbsolver.(*Solver).Solve.func2(0xecac60, 0xc4211ca690, 0xc4202207c0, 0x0)
/go/src/github.com/moby/buildkit/solver/llbsolver/solver.go:132 +0x7d
github.com/moby/buildkit/solver/llbsolver.inVertexContext(0xecac60, 0xc4211ca690, 0xe2ec38, 0x1d, 0xc420d43400, 0x0, 0x0)
/go/src/github.com/moby/buildkit/solver/llbsolver/solver.go:203 +0x1f6
github.com/moby/buildkit/solver/llbsolver.(*Solver).Solve(0xc4203f7dc0, 0xecac60, 0xc420deb830, 0xc4203ed200, 0x19, 0xc4202ff840, 0x0, 0x0, 0x0, 0x0, ...)
/go/src/github.com/moby/buildkit/solver/llbsolver/solver.go:131 +0x761
github.com/moby/buildkit/control.(*Controller).Solve(0xc42017e870, 0xecac60, 0xc420deb830, 0xc4201edd40, 0xc42017e870, 0x1, 0x1)
/go/src/github.com/moby/buildkit/control/control.go:207 +0x4b8
github.com/moby/buildkit/api/services/control._Control_Solve_Handler.func1(0xecac60, 0xc420deb800, 0xde2180, 0xc4201edd40, 0xecac60, 0xc420deb800, 0xed67a0, 0x1595288)
/go/src/github.com/moby/buildkit/api/services/control/control.pb.go:810 +0x86
github.com/moby/buildkit/vendor/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc.OpenTracingServerInterceptor.func1(0xecac60, 0xc420deb800, 0xde2180, 0xc4201edd40, 0xc4202ff9e0, 0xc4202ffa00, 0x0, 0x0, 0xebbea0, 0xc420188310)
/go/src/github.com/moby/buildkit/vendor/github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc/server.go:57 +0x2ba
main.unaryInterceptor.func1(0xecaba0, 0xc4206afe40, 0xde2180, 0xc4201edd40, 0xc4202ff9e0, 0xc4202ffa00, 0x0, 0x0, 0x0, 0x0)
/go/src/github.com/moby/buildkit/cmd/buildkitd/main.go:330 +0x15f
github.com/moby/buildkit/api/services/control._Control_Solve_Handler(0xd76a00, 0xc42017e870, 0xecac60, 0xc420deb530, 0xc42028d030, 0xc420450760, 0x0, 0x0, 0x34, 0x3)
/go/src/github.com/moby/buildkit/api/services/control/control.pb.go:812 +0x167
github.com/moby/buildkit/vendor/google.golang.org/grpc.(*Server).processUnaryRPC(0xc420244700, 0xed39c0, 0xc420694000, 0xc420671680, 0xc420497650, 0x152cef8, 0x0, 0x0, 0x0)
/go/src/github.com/moby/buildkit/vendor/google.golang.org/grpc/server.go:1011 +0x4fc
github.com/moby/buildkit/vendor/google.golang.org/grpc.(*Server).handleStream(0xc420244700, 0xed39c0, 0xc420694000, 0xc420671680, 0x0)
/go/src/github.com/moby/buildkit/vendor/google.golang.org/grpc/server.go:1249 +0x1318
github.com/moby/buildkit/vendor/google.golang.org/grpc.(*Server).serveStreams.func1.1(0xc42063e0f0, 0xc420244700, 0xed39c0, 0xc420694000, 0xc420671680)
/go/src/github.com/moby/buildkit/vendor/google.golang.org/grpc/server.go:680 +0x9f
created by github.com/moby/buildkit/vendor/google.golang.org/grpc.(*Server).serveStreams.func1
/go/src/github.com/moby/buildkit/vendor/google.golang.org/grpc/server.go:678 +0xa1
Which was due to `res, err := s.Bridge(j).Solve(ctx, req)` having `res.Metadata
== nil`. There are several paths in `llbBridge.Solve()` where this can be the
case, plus a case where this comes from a frontend which should not be allowed
to crash the daemon.
Likely introduced by d70d816dee6d or 6be1257f5d3e.
Signed-off-by: Ian Campbell <ijc@docker.com>
2018-08-03 15:02:26 +00:00
|
|
|
if inp.Metadata == nil {
|
|
|
|
inp.Metadata = make(map[string][]byte)
|
|
|
|
}
|
2018-07-10 18:05:22 +00:00
|
|
|
if res := res.Ref; res != nil {
|
2018-07-11 23:51:41 +00:00
|
|
|
workerRef, ok := res.Sys().(*worker.WorkerRef)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("invalid reference: %T", res.Sys())
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
2018-07-10 18:05:22 +00:00
|
|
|
inp.Ref = workerRef.ImmutableRef
|
|
|
|
}
|
|
|
|
if res.Refs != nil {
|
|
|
|
m := make(map[string]cache.ImmutableRef, len(res.Refs))
|
|
|
|
for k, res := range res.Refs {
|
|
|
|
if res == nil {
|
|
|
|
m[k] = nil
|
|
|
|
} else {
|
|
|
|
workerRef, ok := res.Sys().(*worker.WorkerRef)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.Errorf("invalid reference: %T", res.Sys())
|
|
|
|
}
|
|
|
|
m[k] = workerRef.ImmutableRef
|
|
|
|
}
|
|
|
|
}
|
|
|
|
inp.Refs = m
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
|
2018-09-17 20:08:58 +00:00
|
|
|
if err := inVertexContext(j.Context(ctx), exp.Name(), "", func(ctx context.Context) error {
|
2018-07-10 18:05:22 +00:00
|
|
|
exporterResponse, err = exp.Export(ctx, inp)
|
2018-04-13 21:05:29 +00:00
|
|
|
return err
|
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-11 08:02:46 +00:00
|
|
|
var cacheExporterResponse map[string]string
|
2018-05-07 21:51:44 +00:00
|
|
|
if e := exp.CacheExporter; e != nil {
|
2018-09-17 20:08:58 +00:00
|
|
|
if err := inVertexContext(j.Context(ctx), "exporting cache", "", func(ctx context.Context) error {
|
2018-04-13 21:05:29 +00:00
|
|
|
prepareDone := oneOffProgress(ctx, "preparing build cache for export")
|
2018-07-11 23:51:41 +00:00
|
|
|
if err := res.EachRef(func(res solver.CachedResult) error {
|
2018-07-23 21:27:35 +00:00
|
|
|
// all keys have same export chain so exporting others is not needed
|
|
|
|
_, err := res.CacheKeys()[0].Exporter.ExportTo(ctx, e, solver.CacheExportOpt{
|
2018-07-05 23:32:01 +00:00
|
|
|
Convert: workerRefConverter,
|
|
|
|
Mode: exp.CacheExportMode,
|
2018-07-11 23:51:41 +00:00
|
|
|
})
|
|
|
|
return err
|
|
|
|
}); err != nil {
|
|
|
|
return prepareDone(err)
|
2018-04-13 21:05:29 +00:00
|
|
|
}
|
|
|
|
prepareDone(nil)
|
2018-09-11 08:02:46 +00:00
|
|
|
cacheExporterResponse, err = e.Finalize(ctx)
|
|
|
|
return err
|
2018-04-13 21:05:29 +00:00
|
|
|
}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-16 10:44:25 +00:00
|
|
|
if exporterResponse == nil {
|
|
|
|
exporterResponse = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range res.Metadata {
|
|
|
|
if strings.HasPrefix(k, "frontend.") {
|
|
|
|
exporterResponse[k] = string(v)
|
|
|
|
}
|
|
|
|
}
|
2018-09-11 08:02:46 +00:00
|
|
|
for k, v := range cacheExporterResponse {
|
|
|
|
if strings.HasPrefix(k, "cache.") {
|
|
|
|
exporterResponse[k] = v
|
|
|
|
}
|
|
|
|
}
|
2018-08-16 10:44:25 +00:00
|
|
|
|
2018-04-13 21:05:29 +00:00
|
|
|
return &client.SolveResponse{
|
|
|
|
ExporterResponse: exporterResponse,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Solver) Status(ctx context.Context, id string, statusChan chan *client.SolveStatus) error {
|
|
|
|
j, err := s.solver.Get(id)
|
|
|
|
if err != nil {
|
2018-08-07 01:49:30 +00:00
|
|
|
close(statusChan)
|
2018-04-13 21:05:29 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return j.Status(ctx, statusChan)
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultResolver(wc *worker.Controller) ResolveWorkerFunc {
|
|
|
|
return func() (worker.Worker, error) {
|
|
|
|
return wc.GetDefault()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
|
|
|
pw, _, _ := progress.FromContext(ctx)
|
|
|
|
now := time.Now()
|
|
|
|
st := progress.Status{
|
|
|
|
Started: &now,
|
|
|
|
}
|
|
|
|
pw.Write(id, st)
|
|
|
|
return func(err error) error {
|
|
|
|
// TODO: set error on status
|
|
|
|
now := time.Now()
|
|
|
|
st.Completed = &now
|
|
|
|
pw.Write(id, st)
|
|
|
|
pw.Close()
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2018-07-27 22:39:14 +00:00
|
|
|
|
2018-09-17 20:08:58 +00:00
|
|
|
func inVertexContext(ctx context.Context, name, id string, f func(ctx context.Context) error) error {
|
|
|
|
if id == "" {
|
|
|
|
id = identity.NewID()
|
|
|
|
}
|
2018-07-27 22:39:14 +00:00
|
|
|
v := client.Vertex{
|
2018-09-17 20:08:58 +00:00
|
|
|
Digest: digest.FromBytes([]byte(id)),
|
2018-07-27 22:39:14 +00:00
|
|
|
Name: name,
|
|
|
|
}
|
|
|
|
pw, _, ctx := progress.FromContext(ctx, progress.WithMetadata("vertex", v.Digest))
|
|
|
|
notifyStarted(ctx, &v, false)
|
|
|
|
defer pw.Close()
|
|
|
|
err := f(ctx)
|
|
|
|
notifyCompleted(ctx, &v, err, false)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func notifyStarted(ctx context.Context, v *client.Vertex, cached bool) {
|
|
|
|
pw, _, _ := progress.FromContext(ctx)
|
|
|
|
defer pw.Close()
|
|
|
|
now := time.Now()
|
|
|
|
v.Started = &now
|
|
|
|
v.Completed = nil
|
|
|
|
v.Cached = cached
|
|
|
|
pw.Write(v.Digest.String(), *v)
|
|
|
|
}
|
|
|
|
|
|
|
|
func notifyCompleted(ctx context.Context, v *client.Vertex, err error, cached bool) {
|
|
|
|
pw, _, _ := progress.FromContext(ctx)
|
|
|
|
defer pw.Close()
|
|
|
|
now := time.Now()
|
|
|
|
if v.Started == nil {
|
|
|
|
v.Started = &now
|
|
|
|
}
|
|
|
|
v.Completed = &now
|
|
|
|
v.Cached = cached
|
|
|
|
if err != nil {
|
|
|
|
v.Error = err.Error()
|
|
|
|
}
|
|
|
|
pw.Write(v.Digest.String(), *v)
|
|
|
|
}
|
2018-08-04 19:42:01 +00:00
|
|
|
|
|
|
|
var AllowNetworkHostUnstable = false // TODO: enable in constructor
|
|
|
|
|
|
|
|
func supportedEntitlements() []entitlements.Entitlement {
|
|
|
|
out := []entitlements.Entitlement{} // nil means no filter
|
|
|
|
if AllowNetworkHostUnstable {
|
|
|
|
out = append(out, entitlements.EntitlementNetworkHost)
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadEntitlements(b solver.Builder) (entitlements.Set, error) {
|
|
|
|
var ent entitlements.Set = map[entitlements.Entitlement]struct{}{}
|
|
|
|
err := b.EachValue(context.TODO(), keyEntitlements, func(v interface{}) error {
|
|
|
|
set, ok := v.(entitlements.Set)
|
|
|
|
if !ok {
|
|
|
|
return errors.Errorf("invalid entitlements %T", v)
|
|
|
|
}
|
|
|
|
for k := range set {
|
|
|
|
ent[k] = struct{}{}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ent, nil
|
|
|
|
}
|