83d6f52e10
Signed-off-by: Joel Longtine <joel@dagger.io>
241 lines
5.3 KiB
Go
241 lines
5.3 KiB
Go
package task
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
bkplatforms "github.com/containerd/containerd/platforms"
|
|
"github.com/moby/buildkit/client/llb"
|
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
|
dockerfilebuilder "github.com/moby/buildkit/frontend/dockerfile/builder"
|
|
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
|
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
|
bkpb "github.com/moby/buildkit/solver/pb"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"go.dagger.io/dagger/compiler"
|
|
"go.dagger.io/dagger/plancontext"
|
|
"go.dagger.io/dagger/solver"
|
|
)
|
|
|
|
func init() {
|
|
Register("Build", func() Task { return &buildTask{} })
|
|
}
|
|
|
|
type buildTask struct {
|
|
}
|
|
|
|
func (t *buildTask) Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) {
|
|
frontend, err := v.Lookup("frontend").String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch frontend {
|
|
case "dockerfile":
|
|
return t.dockerfile(ctx, pctx, s, v)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported frontend %q", frontend)
|
|
}
|
|
}
|
|
|
|
func (t *buildTask) dockerfile(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error) {
|
|
lg := log.Ctx(ctx)
|
|
|
|
// Read auth info
|
|
auth, err := decodeAuthValue(pctx, v.Lookup("auth"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, a := range auth {
|
|
s.AddCredentials(a.Target, a.Username, a.Secret.PlainText())
|
|
lg.Debug().Str("target", a.Target).Msg("add target credentials")
|
|
}
|
|
|
|
source, err := pctx.FS.FromValue(v.Lookup("source"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sourceSt, err := source.State()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// docker build context
|
|
contextDef, err := s.Marshal(ctx, sourceSt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Dockerfile context, default to docker build context
|
|
dockerfileDef := contextDef
|
|
|
|
// Support inlined dockerfile
|
|
if dockerfile := v.Lookup("dockerfile.contents"); dockerfile.Exists() {
|
|
contents, err := dockerfile.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dockerfileDef, err = s.Marshal(ctx,
|
|
llb.Scratch().File(
|
|
llb.Mkfile("/Dockerfile", 0644, []byte(contents)),
|
|
),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
opts, err := t.dockerBuildOpts(v, pctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Handle --no-cache
|
|
if s.NoCache() {
|
|
opts["no-cache"] = ""
|
|
}
|
|
|
|
req := bkgw.SolveRequest{
|
|
Frontend: "dockerfile.v0",
|
|
FrontendOpt: opts,
|
|
FrontendInputs: map[string]*bkpb.Definition{
|
|
dockerfilebuilder.DefaultLocalNameContext: contextDef,
|
|
dockerfilebuilder.DefaultLocalNameDockerfile: dockerfileDef,
|
|
},
|
|
}
|
|
res, err := s.SolveRequest(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ref, err := res.SingleRef()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
solvedRef := ref
|
|
if ref != nil {
|
|
st, err := ref.ToState()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
solvedRef, err = s.Solve(ctx, st, pctx.Platform.Get())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Image metadata
|
|
meta, ok := res.Metadata[exptypes.ExporterImageConfigKey]
|
|
if !ok {
|
|
return nil, errors.New("build returned no image config")
|
|
}
|
|
var image dockerfile2llb.Image
|
|
if err := json.Unmarshal(meta, &image); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal image config: %w", err)
|
|
}
|
|
|
|
return compiler.NewValue().FillFields(map[string]interface{}{
|
|
"output": pctx.FS.New(solvedRef).MarshalCUE(),
|
|
"config": image.Config,
|
|
})
|
|
}
|
|
|
|
func (t *buildTask) dockerBuildOpts(v *compiler.Value, pctx *plancontext.Context) (map[string]string, error) {
|
|
opts := map[string]string{}
|
|
|
|
if dockerfilePath := v.Lookup("dockerfile.path"); dockerfilePath.Exists() {
|
|
filename, err := dockerfilePath.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts["filename"] = filename
|
|
}
|
|
|
|
if target := v.Lookup("target"); target.Exists() {
|
|
tgr, err := target.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts["target"] = tgr
|
|
}
|
|
|
|
if hosts := v.Lookup("hosts"); hosts.Exists() {
|
|
p := []string{}
|
|
fields, err := hosts.Fields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, host := range fields {
|
|
s, err := host.Value.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p = append(p, host.Label()+"="+s)
|
|
}
|
|
if len(p) > 0 {
|
|
opts["add-hosts"] = strings.Join(p, ",")
|
|
}
|
|
}
|
|
|
|
if buildArgs := v.Lookup("buildArg"); buildArgs.Exists() {
|
|
fields, err := buildArgs.Fields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, buildArg := range fields {
|
|
s, err := buildArg.Value.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts["build-arg:"+buildArg.Label()] = s
|
|
}
|
|
}
|
|
|
|
if labels := v.Lookup("label"); labels.Exists() {
|
|
fields, err := labels.Fields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, label := range fields {
|
|
s, err := label.Value.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts["label:"+label.Label()] = s
|
|
}
|
|
}
|
|
|
|
if platforms := v.Lookup("platforms"); platforms.Exists() {
|
|
p := []string{}
|
|
list, err := platforms.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, platform := range list {
|
|
s, err := platform.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p = append(p, s)
|
|
}
|
|
|
|
if len(p) > 0 {
|
|
opts["platform"] = strings.Join(p, ",")
|
|
}
|
|
if len(p) > 1 {
|
|
opts["multi-platform"] = "true"
|
|
}
|
|
}
|
|
// Set platform to configured one if no one is defined
|
|
if opts["platform"] == "" {
|
|
opts["platform"] = bkplatforms.Format(pctx.Platform.Get())
|
|
}
|
|
|
|
return opts, nil
|
|
}
|