1package llbsolver 2 3import ( 4 "context" 5 "strings" 6 "time" 7 8 "github.com/moby/buildkit/cache" 9 "github.com/moby/buildkit/cache/remotecache" 10 "github.com/moby/buildkit/client" 11 controlgateway "github.com/moby/buildkit/control/gateway" 12 "github.com/moby/buildkit/exporter" 13 "github.com/moby/buildkit/frontend" 14 "github.com/moby/buildkit/frontend/gateway" 15 "github.com/moby/buildkit/identity" 16 "github.com/moby/buildkit/session" 17 "github.com/moby/buildkit/solver" 18 "github.com/moby/buildkit/util/entitlements" 19 "github.com/moby/buildkit/util/progress" 20 "github.com/moby/buildkit/worker" 21 digest "github.com/opencontainers/go-digest" 22 specs "github.com/opencontainers/image-spec/specs-go/v1" 23 "github.com/pkg/errors" 24) 25 26const keyEntitlements = "llb.entitlements" 27 28type ExporterRequest struct { 29 Exporter exporter.ExporterInstance 30 CacheExporter remotecache.Exporter 31 CacheExportMode solver.CacheExportMode 32} 33 34// ResolveWorkerFunc returns default worker for the temporary default non-distributed use cases 35type ResolveWorkerFunc func() (worker.Worker, error) 36 37type Solver struct { 38 workerController *worker.Controller 39 solver *solver.Solver 40 resolveWorker ResolveWorkerFunc 41 frontends map[string]frontend.Frontend 42 resolveCacheImporter remotecache.ResolveCacheImporterFunc 43 platforms []specs.Platform 44 gatewayForwarder *controlgateway.GatewayForwarder 45} 46 47func New(wc *worker.Controller, f map[string]frontend.Frontend, cache solver.CacheManager, resolveCI remotecache.ResolveCacheImporterFunc, gatewayForwarder *controlgateway.GatewayForwarder) (*Solver, error) { 48 s := &Solver{ 49 workerController: wc, 50 resolveWorker: defaultResolver(wc), 51 frontends: f, 52 resolveCacheImporter: resolveCI, 53 gatewayForwarder: gatewayForwarder, 54 } 55 56 // executing is currently only allowed on default worker 57 w, err := wc.GetDefault() 58 if err != nil { 59 return nil, err 60 } 61 s.platforms = w.Platforms() 62 63 s.solver = solver.NewSolver(solver.SolverOpt{ 64 ResolveOpFunc: s.resolver(), 65 DefaultCache: cache, 66 }) 67 return s, nil 68} 69 70func (s *Solver) resolver() solver.ResolveOpFunc { 71 return func(v solver.Vertex, b solver.Builder) (solver.Op, error) { 72 w, err := s.resolveWorker() 73 if err != nil { 74 return nil, err 75 } 76 return w.ResolveOp(v, s.Bridge(b)) 77 } 78} 79 80func (s *Solver) Bridge(b solver.Builder) frontend.FrontendLLBBridge { 81 return &llbBridge{ 82 builder: b, 83 frontends: s.frontends, 84 resolveWorker: s.resolveWorker, 85 resolveCacheImporter: s.resolveCacheImporter, 86 cms: map[string]solver.CacheManager{}, 87 platforms: s.platforms, 88 } 89} 90 91func (s *Solver) Solve(ctx context.Context, id string, req frontend.SolveRequest, exp ExporterRequest, ent []entitlements.Entitlement) (*client.SolveResponse, error) { 92 j, err := s.solver.NewJob(id) 93 if err != nil { 94 return nil, err 95 } 96 97 defer j.Discard() 98 99 set, err := entitlements.WhiteList(ent, supportedEntitlements()) 100 if err != nil { 101 return nil, err 102 } 103 j.SetValue(keyEntitlements, set) 104 105 j.SessionID = session.FromContext(ctx) 106 107 var res *frontend.Result 108 if s.gatewayForwarder != nil && req.Definition == nil && req.Frontend == "" { 109 fwd := gateway.NewBridgeForwarder(ctx, s.Bridge(j), s.workerController) 110 defer fwd.Discard() 111 if err := s.gatewayForwarder.RegisterBuild(ctx, id, fwd); err != nil { 112 return nil, err 113 } 114 defer s.gatewayForwarder.UnregisterBuild(ctx, id) 115 116 var err error 117 select { 118 case <-fwd.Done(): 119 res, err = fwd.Result() 120 case <-ctx.Done(): 121 err = ctx.Err() 122 } 123 if err != nil { 124 return nil, err 125 } 126 } else { 127 res, err = s.Bridge(j).Solve(ctx, req) 128 if err != nil { 129 return nil, err 130 } 131 } 132 133 defer func() { 134 res.EachRef(func(ref solver.CachedResult) error { 135 go ref.Release(context.TODO()) 136 return nil 137 }) 138 }() 139 140 var exporterResponse map[string]string 141 if exp := exp.Exporter; exp != nil { 142 inp := exporter.Source{ 143 Metadata: res.Metadata, 144 } 145 if inp.Metadata == nil { 146 inp.Metadata = make(map[string][]byte) 147 } 148 if res := res.Ref; res != nil { 149 workerRef, ok := res.Sys().(*worker.WorkerRef) 150 if !ok { 151 return nil, errors.Errorf("invalid reference: %T", res.Sys()) 152 } 153 inp.Ref = workerRef.ImmutableRef 154 } 155 if res.Refs != nil { 156 m := make(map[string]cache.ImmutableRef, len(res.Refs)) 157 for k, res := range res.Refs { 158 if res == nil { 159 m[k] = nil 160 } else { 161 workerRef, ok := res.Sys().(*worker.WorkerRef) 162 if !ok { 163 return nil, errors.Errorf("invalid reference: %T", res.Sys()) 164 } 165 m[k] = workerRef.ImmutableRef 166 } 167 } 168 inp.Refs = m 169 } 170 171 if err := inVertexContext(j.Context(ctx), exp.Name(), "", func(ctx context.Context) error { 172 exporterResponse, err = exp.Export(ctx, inp) 173 return err 174 }); err != nil { 175 return nil, err 176 } 177 } 178 179 if e := exp.CacheExporter; e != nil { 180 if err := inVertexContext(j.Context(ctx), "exporting cache", "", func(ctx context.Context) error { 181 prepareDone := oneOffProgress(ctx, "preparing build cache for export") 182 if err := res.EachRef(func(res solver.CachedResult) error { 183 // all keys have same export chain so exporting others is not needed 184 _, err := res.CacheKeys()[0].Exporter.ExportTo(ctx, e, solver.CacheExportOpt{ 185 Convert: workerRefConverter, 186 Mode: exp.CacheExportMode, 187 }) 188 return err 189 }); err != nil { 190 return prepareDone(err) 191 } 192 prepareDone(nil) 193 return e.Finalize(ctx) 194 }); err != nil { 195 return nil, err 196 } 197 } 198 199 if exporterResponse == nil { 200 exporterResponse = make(map[string]string) 201 } 202 203 for k, v := range res.Metadata { 204 if strings.HasPrefix(k, "frontend.") { 205 exporterResponse[k] = string(v) 206 } 207 } 208 209 return &client.SolveResponse{ 210 ExporterResponse: exporterResponse, 211 }, nil 212} 213 214func (s *Solver) Status(ctx context.Context, id string, statusChan chan *client.SolveStatus) error { 215 j, err := s.solver.Get(id) 216 if err != nil { 217 close(statusChan) 218 return err 219 } 220 return j.Status(ctx, statusChan) 221} 222 223func defaultResolver(wc *worker.Controller) ResolveWorkerFunc { 224 return func() (worker.Worker, error) { 225 return wc.GetDefault() 226 } 227} 228 229func oneOffProgress(ctx context.Context, id string) func(err error) error { 230 pw, _, _ := progress.FromContext(ctx) 231 now := time.Now() 232 st := progress.Status{ 233 Started: &now, 234 } 235 pw.Write(id, st) 236 return func(err error) error { 237 // TODO: set error on status 238 now := time.Now() 239 st.Completed = &now 240 pw.Write(id, st) 241 pw.Close() 242 return err 243 } 244} 245 246func inVertexContext(ctx context.Context, name, id string, f func(ctx context.Context) error) error { 247 if id == "" { 248 id = identity.NewID() 249 } 250 v := client.Vertex{ 251 Digest: digest.FromBytes([]byte(id)), 252 Name: name, 253 } 254 pw, _, ctx := progress.FromContext(ctx, progress.WithMetadata("vertex", v.Digest)) 255 notifyStarted(ctx, &v, false) 256 defer pw.Close() 257 err := f(ctx) 258 notifyCompleted(ctx, &v, err, false) 259 return err 260} 261 262func notifyStarted(ctx context.Context, v *client.Vertex, cached bool) { 263 pw, _, _ := progress.FromContext(ctx) 264 defer pw.Close() 265 now := time.Now() 266 v.Started = &now 267 v.Completed = nil 268 v.Cached = cached 269 pw.Write(v.Digest.String(), *v) 270} 271 272func notifyCompleted(ctx context.Context, v *client.Vertex, err error, cached bool) { 273 pw, _, _ := progress.FromContext(ctx) 274 defer pw.Close() 275 now := time.Now() 276 if v.Started == nil { 277 v.Started = &now 278 } 279 v.Completed = &now 280 v.Cached = cached 281 if err != nil { 282 v.Error = err.Error() 283 } 284 pw.Write(v.Digest.String(), *v) 285} 286 287var AllowNetworkHostUnstable = false // TODO: enable in constructor 288 289func supportedEntitlements() []entitlements.Entitlement { 290 out := []entitlements.Entitlement{} // nil means no filter 291 if AllowNetworkHostUnstable { 292 out = append(out, entitlements.EntitlementNetworkHost) 293 } 294 return out 295} 296 297func loadEntitlements(b solver.Builder) (entitlements.Set, error) { 298 var ent entitlements.Set = map[entitlements.Entitlement]struct{}{} 299 err := b.EachValue(context.TODO(), keyEntitlements, func(v interface{}) error { 300 set, ok := v.(entitlements.Set) 301 if !ok { 302 return errors.Errorf("invalid entitlements %T", v) 303 } 304 for k := range set { 305 ent[k] = struct{}{} 306 } 307 return nil 308 }) 309 if err != nil { 310 return nil, err 311 } 312 return ent, nil 313} 314