package task import ( "context" "encoding/json" "fmt" "strings" "cuelang.org/go/cue" 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" "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) { // FIXME: support auth source, err := pctx.FS.FromValue(v.Lookup("source")) if err != nil { return nil, err } sourceSt, err := source.Result().ToState() 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 } out := compiler.NewValue() if err := out.FillPath(cue.ParsePath("output"), pctx.FS.New(ref).MarshalCUE()); err != nil { return nil, err } // Load image metadata if meta, ok := res.Metadata[exptypes.ExporterImageConfigKey]; ok { var image dockerfile2llb.Image if err := json.Unmarshal(meta, &image); err != nil { return nil, fmt.Errorf("failed to unmarshal image config: %w", err) } if err := out.FillPath(cue.ParsePath("config"), image.Config); err != nil { return nil, err } } return out, nil } 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 }