buildkit/solver/llbsolver/solver.go

165 lines
3.9 KiB
Go

package llbsolver
import (
"context"
"time"
"github.com/moby/buildkit/cache"
"github.com/moby/buildkit/cache/remotecache"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/exporter"
"github.com/moby/buildkit/frontend"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/util/progress"
"github.com/moby/buildkit/worker"
"github.com/pkg/errors"
)
type ExporterRequest struct {
Exporter exporter.ExporterInstance
CacheExporter *remotecache.RegistryCacheExporter
CacheExportMode solver.CacheExportMode
}
// ResolveWorkerFunc returns default worker for the temporary default non-distributed use cases
type ResolveWorkerFunc func() (worker.Worker, error)
type Solver struct {
solver *solver.Solver
resolveWorker ResolveWorkerFunc
frontends map[string]frontend.Frontend
ci *remotecache.CacheImporter
}
func New(wc *worker.Controller, f map[string]frontend.Frontend, cacheStore solver.CacheKeyStorage, ci *remotecache.CacheImporter) *Solver {
s := &Solver{
resolveWorker: defaultResolver(wc),
frontends: f,
ci: ci,
}
results := newCacheResultStorage(wc)
cache := solver.NewCacheManager("local", cacheStore, results)
s.solver = solver.NewSolver(solver.SolverOpt{
ResolveOpFunc: s.resolver(),
DefaultCache: cache,
})
return s
}
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{
builder: b,
frontends: s.frontends,
resolveWorker: s.resolveWorker,
ci: s.ci,
cms: map[string]solver.CacheManager{},
}
}
func (s *Solver) Solve(ctx context.Context, id string, req frontend.SolveRequest, exp ExporterRequest) (*client.SolveResponse, error) {
j, err := s.solver.NewJob(id)
if err != nil {
return nil, err
}
defer j.Discard()
j.SessionID = session.FromContext(ctx)
res, exporterOpt, err := s.Bridge(j).Solve(ctx, req)
if err != nil {
return nil, err
}
defer func() {
if res != nil {
go res.Release(context.TODO())
}
}()
var exporterResponse map[string]string
if exp := exp.Exporter; exp != nil {
var immutable cache.ImmutableRef
if res != nil {
workerRef, ok := res.Sys().(*worker.WorkerRef)
if !ok {
return nil, errors.Errorf("invalid reference: %T", res.Sys())
}
immutable = workerRef.ImmutableRef
}
if err := j.Call(ctx, exp.Name(), func(ctx context.Context) error {
exporterResponse, err = exp.Export(ctx, immutable, exporterOpt)
return err
}); err != nil {
return nil, err
}
}
if e := exp.CacheExporter; e != nil {
if err := j.Call(ctx, "exporting cache", func(ctx context.Context) error {
prepareDone := oneOffProgress(ctx, "preparing build cache for export")
if _, err := res.CacheKey().Exporter.ExportTo(ctx, e, solver.CacheExportOpt{
Convert: workerRefConverter,
Mode: exp.CacheExportMode,
}); err != nil {
return prepareDone(err)
}
prepareDone(nil)
return e.Finalize(ctx)
}); err != nil {
return nil, err
}
}
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 {
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
}
}