2021-05-26 01:30:49 +02:00
|
|
|
package solver
|
2021-01-05 09:37:29 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2021-02-06 00:05:41 +01:00
|
|
|
"encoding/json"
|
2021-05-26 03:56:16 +02:00
|
|
|
"errors"
|
2021-02-17 05:13:51 +01:00
|
|
|
"fmt"
|
2021-05-26 03:56:16 +02:00
|
|
|
"strings"
|
2021-01-05 09:37:29 +01:00
|
|
|
|
2021-03-12 01:41:19 +01:00
|
|
|
bk "github.com/moby/buildkit/client"
|
2021-01-05 09:37:29 +01:00
|
|
|
"github.com/moby/buildkit/client/llb"
|
2021-04-13 02:45:38 +02:00
|
|
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
2021-02-25 23:17:01 +01:00
|
|
|
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
2021-01-05 09:37:29 +01:00
|
|
|
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
2021-03-12 01:41:19 +01:00
|
|
|
"github.com/moby/buildkit/session"
|
2021-02-19 09:09:53 +01:00
|
|
|
bkpb "github.com/moby/buildkit/solver/pb"
|
2021-02-06 00:05:41 +01:00
|
|
|
"github.com/opencontainers/go-digest"
|
|
|
|
"github.com/rs/zerolog/log"
|
2021-01-05 09:37:29 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Solver struct {
|
2021-05-26 01:30:49 +02:00
|
|
|
opts Opts
|
2021-01-05 09:37:29 +01:00
|
|
|
}
|
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
type Opts struct {
|
|
|
|
Control *bk.Client
|
|
|
|
Gateway bkgw.Client
|
|
|
|
Events chan *bk.SolveStatus
|
|
|
|
Auth *RegistryAuthProvider
|
2021-05-26 03:56:16 +02:00
|
|
|
Secrets session.Attachable
|
2021-05-26 01:30:49 +02:00
|
|
|
NoCache bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(opts Opts) Solver {
|
2021-01-05 09:37:29 +01:00
|
|
|
return Solver{
|
2021-05-26 01:30:49 +02:00
|
|
|
opts: opts,
|
2021-01-05 09:37:29 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-15 01:21:18 +02:00
|
|
|
func invalidateCache(def *llb.Definition) error {
|
|
|
|
for _, dt := range def.Def {
|
2021-04-15 01:27:15 +02:00
|
|
|
var op bkpb.Op
|
2021-04-15 01:21:18 +02:00
|
|
|
if err := (&op).Unmarshal(dt); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
dgst := digest.FromBytes(dt)
|
|
|
|
opMetadata, ok := def.Metadata[dgst]
|
|
|
|
if !ok {
|
2021-04-15 01:27:15 +02:00
|
|
|
opMetadata = bkpb.OpMetadata{}
|
2021-04-15 01:21:18 +02:00
|
|
|
}
|
|
|
|
c := llb.Constraints{Metadata: opMetadata}
|
|
|
|
llb.IgnoreCache(&c)
|
|
|
|
def.Metadata[dgst] = c.Metadata
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
func (s Solver) NoCache() bool {
|
|
|
|
return s.opts.NoCache
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Solver) AddCredentials(target, username, secret string) {
|
|
|
|
s.opts.Auth.AddCredentials(target, username, secret)
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (s Solver) Marshal(ctx context.Context, st llb.State) (*bkpb.Definition, error) {
|
|
|
|
// FIXME: do not hardcode the platform
|
|
|
|
def, err := st.Marshal(ctx, llb.LinuxAmd64)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-01-05 09:37:29 +01:00
|
|
|
}
|
2021-04-15 01:21:18 +02:00
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
if s.opts.NoCache {
|
2021-04-15 01:21:18 +02:00
|
|
|
if err := invalidateCache(def); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
return def.ToPB(), nil
|
2021-01-05 09:37:29 +01:00
|
|
|
}
|
|
|
|
|
2021-02-25 00:38:03 +01:00
|
|
|
func (s Solver) SessionID() string {
|
2021-05-26 01:30:49 +02:00
|
|
|
return s.opts.Gateway.BuildOpts().SessionID
|
2021-02-25 00:38:03 +01:00
|
|
|
}
|
|
|
|
|
2021-02-25 23:17:01 +01:00
|
|
|
func (s Solver) ResolveImageConfig(ctx context.Context, ref string, opts llb.ResolveImageConfigOpt) (dockerfile2llb.Image, error) {
|
|
|
|
var image dockerfile2llb.Image
|
|
|
|
|
|
|
|
// Load image metadata and convert to to LLB.
|
|
|
|
// Inspired by https://github.com/moby/buildkit/blob/master/frontend/dockerfile/dockerfile2llb/convert.go
|
|
|
|
// FIXME: this needs to handle platform
|
2021-05-26 01:30:49 +02:00
|
|
|
_, meta, err := s.opts.Gateway.ResolveImageConfig(ctx, ref, opts)
|
2021-02-25 23:17:01 +01:00
|
|
|
if err != nil {
|
|
|
|
return image, err
|
|
|
|
}
|
|
|
|
if err := json.Unmarshal(meta, &image); err != nil {
|
|
|
|
return image, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return image, nil
|
|
|
|
}
|
|
|
|
|
2021-02-19 09:09:53 +01:00
|
|
|
// Solve will block until the state is solved and returns a Reference.
|
2021-04-13 02:45:38 +02:00
|
|
|
func (s Solver) SolveRequest(ctx context.Context, req bkgw.SolveRequest) (*bkgw.Result, error) {
|
2021-05-26 03:56:16 +02:00
|
|
|
res, err := s.opts.Gateway.Solve(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, CleanError(err)
|
|
|
|
}
|
|
|
|
return res, nil
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
|
2021-01-15 23:17:33 +01:00
|
|
|
// Solve will block until the state is solved and returns a Reference.
|
2021-01-05 09:37:29 +01:00
|
|
|
func (s Solver) Solve(ctx context.Context, st llb.State) (bkgw.Reference, error) {
|
|
|
|
// marshal llb
|
2021-03-12 22:00:11 +01:00
|
|
|
def, err := s.Marshal(ctx, st)
|
2021-01-05 09:37:29 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-02-06 00:05:41 +01:00
|
|
|
|
2021-04-12 01:30:48 +02:00
|
|
|
jsonLLB, err := dumpLLB(def)
|
2021-02-06 00:05:41 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Trace().
|
2021-04-12 01:30:48 +02:00
|
|
|
RawJSON("llb", jsonLLB).
|
2021-02-06 00:05:41 +01:00
|
|
|
Msg("solving")
|
|
|
|
|
2021-01-05 09:37:29 +01:00
|
|
|
// call solve
|
2021-04-13 02:45:38 +02:00
|
|
|
res, err := s.SolveRequest(ctx, bkgw.SolveRequest{
|
2021-03-12 22:00:11 +01:00
|
|
|
Definition: def,
|
2021-01-15 23:17:33 +01:00
|
|
|
|
|
|
|
// makes Solve() to block until LLB graph is solved. otherwise it will
|
|
|
|
// return result (that you can for example use for next build) that
|
|
|
|
// will be evaluated on export or if you access files on it.
|
|
|
|
Evaluate: true,
|
|
|
|
})
|
2021-04-13 02:45:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return res.SingleRef()
|
2021-01-05 09:37:29 +01:00
|
|
|
}
|
2021-02-06 00:05:41 +01:00
|
|
|
|
2021-03-12 01:41:19 +01:00
|
|
|
// Export will export `st` to `output`
|
|
|
|
// FIXME: this is currently impleneted as a hack, starting a new Build session
|
|
|
|
// within buildkit from the Control API. Ideally the Gateway API should allow to
|
|
|
|
// Export directly.
|
2021-04-13 02:45:38 +02:00
|
|
|
func (s Solver) Export(ctx context.Context, st llb.State, img *dockerfile2llb.Image, output bk.ExportEntry) (*bk.SolveResponse, error) {
|
2021-03-12 22:00:11 +01:00
|
|
|
def, err := s.Marshal(ctx, st)
|
2021-03-12 01:41:19 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
opts := bk.SolveOpt{
|
|
|
|
Exports: []bk.ExportEntry{output},
|
2021-06-05 00:56:59 +02:00
|
|
|
Session: []session.Attachable{
|
|
|
|
s.opts.Auth,
|
|
|
|
s.opts.Secrets,
|
|
|
|
NewDockerSocketProvider(),
|
|
|
|
},
|
2021-03-12 01:41:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ch := make(chan *bk.SolveStatus)
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
// Forward this build session events to the main events channel, for logging
|
|
|
|
// purposes.
|
2021-03-12 01:41:19 +01:00
|
|
|
go func() {
|
|
|
|
for event := range ch {
|
2021-05-26 01:30:49 +02:00
|
|
|
s.opts.Events <- event
|
2021-03-12 01:41:19 +01:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
return s.opts.Control.Build(ctx, opts, "", func(ctx context.Context, c bkgw.Client) (*bkgw.Result, error) {
|
2021-04-13 02:45:38 +02:00
|
|
|
res, err := c.Solve(ctx, bkgw.SolveRequest{
|
2021-03-12 22:00:11 +01:00
|
|
|
Definition: def,
|
2021-03-12 01:41:19 +01:00
|
|
|
})
|
2021-04-13 02:45:38 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Attach the image config if provided
|
|
|
|
if img != nil {
|
|
|
|
config, err := json.Marshal(img)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to marshal image config: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
res.AddMeta(exptypes.ExporterImageConfigKey, config)
|
|
|
|
}
|
|
|
|
|
|
|
|
return res, nil
|
2021-03-12 01:41:19 +01:00
|
|
|
}, ch)
|
|
|
|
}
|
|
|
|
|
2021-02-06 00:05:41 +01:00
|
|
|
type llbOp struct {
|
2021-02-19 09:09:53 +01:00
|
|
|
Op bkpb.Op
|
2021-02-06 00:05:41 +01:00
|
|
|
Digest digest.Digest
|
2021-02-19 09:09:53 +01:00
|
|
|
OpMetadata bkpb.OpMetadata
|
2021-02-06 00:05:41 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func dumpLLB(def *bkpb.Definition) ([]byte, error) {
|
2021-02-06 00:05:41 +01:00
|
|
|
ops := make([]llbOp, 0, len(def.Def))
|
|
|
|
for _, dt := range def.Def {
|
2021-02-19 09:09:53 +01:00
|
|
|
var op bkpb.Op
|
2021-02-06 00:05:41 +01:00
|
|
|
if err := (&op).Unmarshal(dt); err != nil {
|
2021-02-17 05:13:51 +01:00
|
|
|
return nil, fmt.Errorf("failed to parse op: %w", err)
|
2021-02-06 00:05:41 +01:00
|
|
|
}
|
|
|
|
dgst := digest.FromBytes(dt)
|
|
|
|
ent := llbOp{Op: op, Digest: dgst, OpMetadata: def.Metadata[dgst]}
|
|
|
|
ops = append(ops, ent)
|
|
|
|
}
|
|
|
|
return json.Marshal(ops)
|
|
|
|
}
|
2021-05-26 03:56:16 +02:00
|
|
|
|
|
|
|
// A helper to remove noise from buildkit error messages.
|
|
|
|
// FIXME: Obviously a cleaner solution would be nice.
|
|
|
|
func CleanError(err error) error {
|
|
|
|
noise := []string{
|
|
|
|
"executor failed running ",
|
|
|
|
"buildkit-runc did not terminate successfully",
|
|
|
|
"rpc error: code = Unknown desc = ",
|
|
|
|
"failed to solve: ",
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := err.Error()
|
|
|
|
|
|
|
|
for _, s := range noise {
|
|
|
|
msg = strings.ReplaceAll(msg, s, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.New(msg)
|
|
|
|
}
|