2021-05-26 01:30:49 +02:00
|
|
|
package environment
|
2021-02-08 20:47:07 +01:00
|
|
|
|
|
|
|
import (
|
2021-03-18 00:55:28 +01:00
|
|
|
"bytes"
|
2021-02-08 20:47:07 +01:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2021-02-19 09:09:53 +01:00
|
|
|
"errors"
|
2021-02-08 20:47:07 +01:00
|
|
|
"fmt"
|
2021-03-04 02:24:21 +01:00
|
|
|
"io/fs"
|
2021-05-11 23:36:57 +02:00
|
|
|
"net"
|
2021-05-13 19:33:15 +02:00
|
|
|
"net/url"
|
2021-03-18 17:38:50 +01:00
|
|
|
"path"
|
2021-02-19 09:09:53 +01:00
|
|
|
"strings"
|
2021-02-08 20:47:07 +01:00
|
|
|
|
2021-04-08 03:41:44 +02:00
|
|
|
"cuelang.org/go/cue"
|
2021-02-25 23:17:01 +01:00
|
|
|
"github.com/docker/distribution/reference"
|
2021-03-12 01:41:19 +01:00
|
|
|
bk "github.com/moby/buildkit/client"
|
2021-02-08 20:47:07 +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-19 09:09:53 +01:00
|
|
|
dockerfilebuilder "github.com/moby/buildkit/frontend/dockerfile/builder"
|
2021-04-13 02:45:38 +02:00
|
|
|
"github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb"
|
2021-03-18 17:38:50 +01:00
|
|
|
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
|
2021-02-19 09:09:53 +01:00
|
|
|
bkgw "github.com/moby/buildkit/frontend/gateway/client"
|
|
|
|
bkpb "github.com/moby/buildkit/solver/pb"
|
2021-02-08 20:47:07 +01:00
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
|
2021-05-26 01:53:26 +02:00
|
|
|
"go.dagger.io/dagger/compiler"
|
|
|
|
"go.dagger.io/dagger/solver"
|
2021-02-08 20:47:07 +01:00
|
|
|
)
|
|
|
|
|
2021-03-18 00:55:28 +01:00
|
|
|
const (
|
|
|
|
daggerignoreFilename = ".daggerignore"
|
|
|
|
)
|
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
// An execution pipeline
|
|
|
|
type Pipeline struct {
|
2021-05-05 01:26:56 +02:00
|
|
|
code *compiler.Value
|
2021-04-08 03:41:44 +02:00
|
|
|
name string
|
2021-05-26 01:30:49 +02:00
|
|
|
s solver.Solver
|
2021-04-08 03:41:44 +02:00
|
|
|
state llb.State
|
|
|
|
result bkgw.Reference
|
2021-04-13 02:45:38 +02:00
|
|
|
image dockerfile2llb.Image
|
2021-04-08 03:41:44 +02:00
|
|
|
computed *compiler.Value
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
func NewPipeline(code *compiler.Value, s solver.Solver) *Pipeline {
|
2021-02-08 20:47:07 +01:00
|
|
|
return &Pipeline{
|
2021-05-05 01:26:56 +02:00
|
|
|
code: code,
|
|
|
|
name: code.Path().String(),
|
2021-04-08 03:41:44 +02:00
|
|
|
s: s,
|
|
|
|
state: llb.Scratch(),
|
|
|
|
computed: compiler.NewValue(),
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-05 01:26:56 +02:00
|
|
|
func (p *Pipeline) WithCustomName(name string) *Pipeline {
|
|
|
|
p.name = name
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) State() llb.State {
|
|
|
|
return p.state
|
|
|
|
}
|
|
|
|
|
2021-03-23 00:30:17 +01:00
|
|
|
func (p *Pipeline) Result() (llb.State, error) {
|
|
|
|
if p.result == nil {
|
|
|
|
return llb.Scratch(), nil
|
|
|
|
}
|
|
|
|
return p.result.ToState()
|
2021-03-12 22:00:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (p *Pipeline) FS() fs.FS {
|
2021-05-26 01:30:49 +02:00
|
|
|
return solver.NewBuildkitFS(p.result)
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 02:45:38 +02:00
|
|
|
func (p *Pipeline) ImageConfig() dockerfile2llb.Image {
|
|
|
|
return p.image
|
|
|
|
}
|
|
|
|
|
2021-04-08 03:41:44 +02:00
|
|
|
func (p *Pipeline) Computed() *compiler.Value {
|
|
|
|
return p.computed
|
|
|
|
}
|
|
|
|
|
2021-02-19 23:04:40 +01:00
|
|
|
func isComponent(v *compiler.Value) bool {
|
2021-04-02 23:58:12 +02:00
|
|
|
return v.Lookup("#up").Exists()
|
2021-02-19 23:04:40 +01:00
|
|
|
}
|
|
|
|
|
2021-05-05 01:26:56 +02:00
|
|
|
func ops(code *compiler.Value) ([]*compiler.Value, error) {
|
2021-02-17 03:31:03 +01:00
|
|
|
ops := []*compiler.Value{}
|
2021-05-05 01:26:56 +02:00
|
|
|
// 1. attachment array
|
|
|
|
if isComponent(code) {
|
|
|
|
xops, err := code.Lookup("#up").List()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-05-05 01:26:56 +02:00
|
|
|
// 'from' has an executable attached
|
|
|
|
ops = append(ops, xops...)
|
|
|
|
// 2. individual op
|
|
|
|
} else if _, err := code.Lookup("do").String(); err == nil {
|
|
|
|
ops = append(ops, code)
|
|
|
|
// 3. op array
|
|
|
|
} else if xops, err := code.List(); err == nil {
|
|
|
|
ops = append(ops, xops...)
|
|
|
|
} else {
|
|
|
|
// 4. error
|
|
|
|
source, err := code.Source()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("not executable: %s", source)
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
return ops, nil
|
|
|
|
}
|
|
|
|
|
2021-05-05 01:26:56 +02:00
|
|
|
func Analyze(fn func(*compiler.Value) error, code *compiler.Value) error {
|
|
|
|
ops, err := ops(code)
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, op := range ops {
|
|
|
|
if err := analyzeOp(fn, op); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-17 03:31:03 +01:00
|
|
|
func analyzeOp(fn func(*compiler.Value) error, op *compiler.Value) error {
|
2021-02-08 20:47:07 +01:00
|
|
|
if err := fn(op); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-03-31 22:40:46 +02:00
|
|
|
do, err := op.Lookup("do").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
switch do {
|
|
|
|
case "load", "copy":
|
2021-03-31 22:40:46 +02:00
|
|
|
return Analyze(fn, op.Lookup("from"))
|
2021-02-08 20:47:07 +01:00
|
|
|
case "exec":
|
2021-03-31 22:40:46 +02:00
|
|
|
fields, err := op.Lookup("mount").Fields()
|
2021-03-19 23:07:01 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, mnt := range fields {
|
2021-03-31 22:40:46 +02:00
|
|
|
if from := mnt.Value.Lookup("from"); from.Exists() {
|
2021-02-08 20:47:07 +01:00
|
|
|
return Analyze(fn, from)
|
|
|
|
}
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-05 01:26:56 +02:00
|
|
|
func (p *Pipeline) Run(ctx context.Context) error {
|
|
|
|
ops, err := ops(p.code)
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-03-23 00:30:17 +01:00
|
|
|
|
|
|
|
// Execute each operation in sequence
|
2021-02-08 20:47:07 +01:00
|
|
|
for idx, op := range ops {
|
|
|
|
// If op not concrete, interrupt without error.
|
|
|
|
// This allows gradual resolution:
|
|
|
|
// compute what you can compute.. leave the rest incomplete.
|
|
|
|
if err := op.IsConcreteR(); err != nil {
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
2021-02-17 02:54:43 +01:00
|
|
|
Warn().
|
2021-02-08 20:47:07 +01:00
|
|
|
Str("original_cue_error", err.Error()).
|
|
|
|
Int("op", idx).
|
2021-02-17 02:54:43 +01:00
|
|
|
Msg("pipeline was partially executed because of missing inputs")
|
|
|
|
return nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-12 22:00:11 +01:00
|
|
|
p.state, err = p.doOp(ctx, op, p.state)
|
|
|
|
if err != nil {
|
2021-02-08 20:47:07 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Force a buildkit solve request at each operation,
|
|
|
|
// so that errors map to the correct cue path.
|
|
|
|
// FIXME: might as well change FS to make every operation
|
|
|
|
// synchronous.
|
2021-03-12 22:00:11 +01:00
|
|
|
p.result, err = p.s.Solve(ctx, p.state)
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2021-03-23 00:30:17 +01:00
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) doOp(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-03-31 22:40:46 +02:00
|
|
|
do, err := op.Lookup("do").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-05-04 19:55:42 +02:00
|
|
|
// FIXME: make this more readable then promote to INFO
|
|
|
|
// we need a readable trace of what operations are executed.
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Debug().
|
|
|
|
Str("pipeline", p.name).
|
|
|
|
Str("do", do).
|
|
|
|
Msg("executing operation")
|
2021-02-08 20:47:07 +01:00
|
|
|
switch do {
|
|
|
|
case "copy":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Copy(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "exec":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Exec(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "export":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Export(ctx, op, st)
|
2021-04-27 02:39:19 +02:00
|
|
|
case "docker-login":
|
|
|
|
return p.DockerLogin(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "fetch-container":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.FetchContainer(ctx, op, st)
|
2021-03-12 01:41:19 +01:00
|
|
|
case "push-container":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.PushContainer(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "fetch-git":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.FetchGit(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "local":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Local(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "load":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Load(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
case "subdir":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Subdir(ctx, op, st)
|
2021-02-19 09:09:53 +01:00
|
|
|
case "docker-build":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.DockerBuild(ctx, op, st)
|
2021-03-04 02:24:21 +01:00
|
|
|
case "write-file":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.WriteFile(ctx, op, st)
|
2021-03-04 02:24:21 +01:00
|
|
|
case "mkdir":
|
2021-03-12 22:00:11 +01:00
|
|
|
return p.Mkdir(ctx, op, st)
|
2021-02-08 20:47:07 +01:00
|
|
|
default:
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, fmt.Errorf("invalid operation: %s", op.JSON())
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-24 00:37:32 +01:00
|
|
|
func (p *Pipeline) vertexNamef(format string, a ...interface{}) string {
|
|
|
|
prefix := fmt.Sprintf("@%s@", p.name)
|
|
|
|
name := fmt.Sprintf(format, a...)
|
|
|
|
return prefix + " " + name
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Subdir(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-02-08 20:47:07 +01:00
|
|
|
// FIXME: this could be more optimized by carrying subdir path as metadata,
|
|
|
|
// and using it in copy, load or mount.
|
|
|
|
|
2021-03-31 22:40:46 +02:00
|
|
|
dir, err := op.Lookup("dir").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
|
|
|
}
|
2021-04-24 19:31:36 +02:00
|
|
|
return llb.Scratch().File(
|
2021-03-12 22:00:11 +01:00
|
|
|
llb.Copy(
|
|
|
|
st,
|
|
|
|
dir,
|
|
|
|
"/",
|
|
|
|
&llb.CopyInfo{
|
|
|
|
CopyDirContentsOnly: true,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Subdir %s", dir)),
|
|
|
|
), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Copy(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-02-08 20:47:07 +01:00
|
|
|
// Decode copy options
|
2021-03-31 22:40:46 +02:00
|
|
|
src, err := op.Lookup("src").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-31 22:40:46 +02:00
|
|
|
dest, err := op.Lookup("dest").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
// Execute 'from' in a tmp pipeline, and use the resulting fs
|
2021-05-05 01:26:56 +02:00
|
|
|
from := NewPipeline(op.Lookup("from"), p.s)
|
|
|
|
if err := from.Run(ctx); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
return st.File(
|
|
|
|
llb.Copy(
|
2021-04-13 02:45:38 +02:00
|
|
|
from.State(),
|
2021-03-12 22:00:11 +01:00
|
|
|
src,
|
|
|
|
dest,
|
|
|
|
// FIXME: allow more configurable llb options
|
|
|
|
// For now we define the following convenience presets:
|
|
|
|
&llb.CopyInfo{
|
|
|
|
CopyDirContentsOnly: true,
|
|
|
|
CreateDestPath: true,
|
|
|
|
AllowWildcard: true,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Copy %s %s", src, dest)),
|
|
|
|
), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Local(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-03-31 22:40:46 +02:00
|
|
|
dir, err := op.Lookup("dir").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-18 00:55:28 +01:00
|
|
|
|
|
|
|
// daggerignore processing
|
|
|
|
// buildkit related setup
|
|
|
|
daggerignoreState := llb.Local(
|
|
|
|
dir,
|
|
|
|
llb.SessionID(p.s.SessionID()),
|
|
|
|
llb.FollowPaths([]string{daggerignoreFilename}),
|
|
|
|
llb.SharedKeyHint(dir+"-"+daggerignoreFilename),
|
2021-03-18 17:38:50 +01:00
|
|
|
llb.WithCustomName(p.vertexNamef("Try loading %s", path.Join(dir, daggerignoreFilename))),
|
2021-03-18 00:55:28 +01:00
|
|
|
)
|
2021-03-18 17:38:50 +01:00
|
|
|
ref, err := p.s.Solve(ctx, daggerignoreState)
|
2021-03-18 00:55:28 +01:00
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to read file
|
2021-03-18 17:38:50 +01:00
|
|
|
var daggerignore []byte
|
|
|
|
// bool in case file is empty
|
|
|
|
ignorefound := true
|
2021-03-18 00:55:28 +01:00
|
|
|
daggerignore, err = ref.ReadFile(ctx, bkgw.ReadRequest{
|
|
|
|
Filename: daggerignoreFilename,
|
|
|
|
})
|
|
|
|
// hack for string introspection because !errors.Is(err, os.ErrNotExist) does not work, same for fs
|
2021-03-18 17:38:50 +01:00
|
|
|
if err != nil {
|
|
|
|
if !strings.Contains(err.Error(), ".daggerignore: no such file or directory") {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
ignorefound = false
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-18 00:55:28 +01:00
|
|
|
|
|
|
|
// parse out excludes, works even if file does not exist
|
|
|
|
var excludes []string
|
|
|
|
excludes, err = dockerignore.ReadAll(bytes.NewBuffer(daggerignore))
|
|
|
|
if err != nil {
|
|
|
|
return st, fmt.Errorf("%w failed to parse daggerignore", err)
|
|
|
|
}
|
|
|
|
|
2021-03-18 17:38:50 +01:00
|
|
|
// log out patterns if file exists
|
|
|
|
if ignorefound {
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Debug().
|
|
|
|
Str("patterns", fmt.Sprint(excludes)).
|
|
|
|
Msg("daggerignore exclude patterns")
|
|
|
|
}
|
2021-02-25 00:38:03 +01:00
|
|
|
// FIXME: Remove the `Copy` and use `Local` directly.
|
|
|
|
//
|
|
|
|
// Copy'ing is a costly operation which should be unnecessary.
|
2021-03-18 19:18:06 +01:00
|
|
|
// However, using llb.Local directly breaks caching sometimes for unknown reasons.
|
2021-03-12 22:00:11 +01:00
|
|
|
return st.File(
|
|
|
|
llb.Copy(
|
|
|
|
llb.Local(
|
|
|
|
dir,
|
2021-03-18 00:55:28 +01:00
|
|
|
// llb.FollowPaths(include),
|
|
|
|
llb.ExcludePatterns(excludes),
|
2021-03-12 22:00:11 +01:00
|
|
|
llb.WithCustomName(p.vertexNamef("Local %s [transfer]", dir)),
|
2021-02-18 02:19:49 +01:00
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
// Without hint, multiple `llb.Local` operations on the
|
|
|
|
// same path get a different digest.
|
|
|
|
llb.SessionID(p.s.SessionID()),
|
|
|
|
llb.SharedKeyHint(dir),
|
|
|
|
),
|
|
|
|
"/",
|
|
|
|
"/",
|
|
|
|
),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Local %s [copy]", dir)),
|
|
|
|
), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Exec(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-02-08 20:47:07 +01:00
|
|
|
opts := []llb.RunOption{}
|
|
|
|
var cmd struct {
|
|
|
|
Args []string
|
|
|
|
Dir string
|
|
|
|
Always bool
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := op.Decode(&cmd); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
// args
|
|
|
|
opts = append(opts, llb.Args(cmd.Args))
|
|
|
|
// dir
|
|
|
|
opts = append(opts, llb.Dir(cmd.Dir))
|
2021-03-19 23:07:01 +01:00
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
// env
|
2021-03-31 22:40:46 +02:00
|
|
|
if env := op.Lookup("env"); env.Exists() {
|
|
|
|
envs, err := op.Lookup("env").Fields()
|
2021-03-19 23:07:01 +01:00
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
for _, env := range envs {
|
|
|
|
v, err := env.Value.String()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
2021-05-01 03:05:37 +02:00
|
|
|
opts = append(opts, llb.AddEnv(env.Label(), v))
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-19 23:07:01 +01:00
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
// always?
|
|
|
|
if cmd.Always {
|
2021-04-15 21:34:15 +02:00
|
|
|
// FIXME: also disables persistent cache directories
|
|
|
|
// There's an ongoing proposal that would fix this: https://github.com/moby/buildkit/issues/1213
|
2021-04-15 21:09:01 +02:00
|
|
|
opts = append(opts, llb.IgnoreCache)
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-05-11 23:36:57 +02:00
|
|
|
|
|
|
|
if hosts := op.Lookup("hosts"); hosts.Exists() {
|
|
|
|
fields, err := hosts.Fields()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
for _, host := range fields {
|
|
|
|
s, err := host.Value.String()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
opts = append(opts, llb.AddExtraHost(host.Label(), net.ParseIP(s)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if user := op.Lookup("user"); user.Exists() {
|
|
|
|
u, err := user.String()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
opts = append(opts, llb.User(u))
|
|
|
|
}
|
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
// mounts
|
|
|
|
if mounts := op.Lookup("mount"); mounts.Exists() {
|
|
|
|
mntOpts, err := p.mountAll(ctx, mounts)
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
opts = append(opts, mntOpts...)
|
|
|
|
}
|
2021-02-24 00:37:32 +01:00
|
|
|
|
|
|
|
// marker for status events
|
|
|
|
// FIXME
|
2021-02-24 01:37:45 +01:00
|
|
|
args := make([]string, 0, len(cmd.Args))
|
|
|
|
for _, a := range cmd.Args {
|
|
|
|
args = append(args, fmt.Sprintf("%q", a))
|
|
|
|
}
|
|
|
|
opts = append(opts, llb.WithCustomName(p.vertexNamef("Exec [%s]", strings.Join(args, ", "))))
|
2021-02-24 00:37:32 +01:00
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
// --> Execute
|
2021-03-12 22:00:11 +01:00
|
|
|
return st.Run(opts...).Root(), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-02-17 03:31:03 +01:00
|
|
|
func (p *Pipeline) mountAll(ctx context.Context, mounts *compiler.Value) ([]llb.RunOption, error) {
|
2021-02-08 20:47:07 +01:00
|
|
|
opts := []llb.RunOption{}
|
2021-03-19 23:07:01 +01:00
|
|
|
fields, err := mounts.Fields()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, mnt := range fields {
|
2021-05-01 03:05:37 +02:00
|
|
|
o, err := p.mount(ctx, mnt.Label(), mnt.Value)
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-19 23:07:01 +01:00
|
|
|
return nil, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
opts = append(opts, o)
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-02-08 20:47:07 +01:00
|
|
|
return opts, err
|
|
|
|
}
|
|
|
|
|
2021-02-17 03:31:03 +01:00
|
|
|
func (p *Pipeline) mount(ctx context.Context, dest string, mnt *compiler.Value) (llb.RunOption, error) {
|
2021-02-08 20:47:07 +01:00
|
|
|
if s, err := mnt.String(); err == nil {
|
|
|
|
// eg. mount: "/foo": "cache"
|
|
|
|
switch s {
|
|
|
|
case "cache":
|
|
|
|
return llb.AddMount(
|
|
|
|
dest,
|
|
|
|
llb.Scratch(),
|
|
|
|
llb.AsPersistentCacheDir(
|
2021-05-05 01:26:56 +02:00
|
|
|
p.canonicalPath(mnt),
|
2021-02-08 20:47:07 +01:00
|
|
|
llb.CacheMountShared,
|
|
|
|
),
|
|
|
|
), nil
|
2021-03-04 23:03:50 +01:00
|
|
|
case "tmpfs":
|
2021-02-08 20:47:07 +01:00
|
|
|
return llb.AddMount(
|
|
|
|
dest,
|
|
|
|
llb.Scratch(),
|
|
|
|
llb.Tmpfs(),
|
|
|
|
), nil
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("invalid mount source: %q", s)
|
|
|
|
}
|
|
|
|
}
|
2021-05-26 03:56:16 +02:00
|
|
|
// eg. mount: "/foo": secret: mysecret
|
|
|
|
if secret := mnt.Lookup("secret"); secret.Exists() {
|
|
|
|
if !secret.HasAttr("secret") {
|
|
|
|
return nil, fmt.Errorf("invalid secret %q: not a secret", secret.Path().String())
|
|
|
|
}
|
|
|
|
idValue := secret.Lookup("id")
|
|
|
|
if !idValue.Exists() {
|
|
|
|
return nil, fmt.Errorf("invalid secret %q: no id field", secret.Path().String())
|
|
|
|
}
|
|
|
|
id, err := idValue.String()
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid secret id: %w", err)
|
|
|
|
}
|
|
|
|
return llb.AddSecret(dest,
|
|
|
|
llb.SecretID(id),
|
|
|
|
llb.SecretFileOpt(0, 0, 0400), // uid, gid, mask)
|
|
|
|
), nil
|
|
|
|
}
|
|
|
|
|
2021-02-08 20:47:07 +01:00
|
|
|
// eg. mount: "/foo": { from: www.source }
|
2021-05-05 01:26:56 +02:00
|
|
|
from := NewPipeline(mnt.Lookup("from"), p.s)
|
|
|
|
if err := from.Run(ctx); err != nil {
|
2021-02-08 20:47:07 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// possibly construct mount options for LLB from
|
|
|
|
var mo []llb.MountOption
|
|
|
|
// handle "path" option
|
|
|
|
if mp := mnt.Lookup("path"); mp.Exists() {
|
|
|
|
mps, err := mp.String()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
mo = append(mo, llb.SourcePath(mps))
|
|
|
|
}
|
2021-04-13 02:45:38 +02:00
|
|
|
return llb.AddMount(dest, from.State(), mo...), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-05-05 01:26:56 +02:00
|
|
|
// canonicalPath returns the canonical path of `v`
|
|
|
|
// If the pipeline is a reference to another pipeline, `canonicalPath()` will
|
|
|
|
// return the path of the reference of `v`.
|
|
|
|
// FIXME: this doesn't work with references of references.
|
|
|
|
func (p *Pipeline) canonicalPath(v *compiler.Value) string {
|
|
|
|
// value path
|
|
|
|
vPath := v.Path().Selectors()
|
|
|
|
|
|
|
|
// pipeline path
|
|
|
|
pipelinePath := p.code.Path().Selectors()
|
|
|
|
|
|
|
|
// check if the pipeline is a reference
|
|
|
|
_, ref := p.code.ReferencePath()
|
|
|
|
if len(ref.Selectors()) == 0 {
|
|
|
|
return v.Path().String()
|
|
|
|
}
|
|
|
|
canonicalPipelinePath := ref.Selectors()
|
|
|
|
|
|
|
|
// replace the pipeline path with the canonical pipeline path
|
|
|
|
// 1. strip the pipeline path from the value path
|
|
|
|
vPath = vPath[len(pipelinePath):]
|
|
|
|
// 2. inject the canonical pipeline path
|
|
|
|
vPath = append(canonicalPipelinePath, vPath...)
|
|
|
|
|
|
|
|
return cue.MakePath(vPath...).String()
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Export(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-03-31 22:40:46 +02:00
|
|
|
source, err := op.Lookup("source").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-31 22:40:46 +02:00
|
|
|
format, err := op.Lookup("format").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-12 22:00:11 +01:00
|
|
|
contents, err := fs.ReadFile(p.FS(), source)
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, fmt.Errorf("export %s: %w", source, err)
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
switch format {
|
|
|
|
case "string":
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Debug().
|
|
|
|
Bytes("contents", contents).
|
|
|
|
Msg("exporting string")
|
|
|
|
|
2021-04-08 03:41:44 +02:00
|
|
|
if err := p.computed.FillPath(cue.MakePath(), string(contents)); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
case "json":
|
|
|
|
var o interface{}
|
|
|
|
o, err := unmarshalAnything(contents, json.Unmarshal)
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Debug().
|
|
|
|
Interface("contents", o).
|
|
|
|
Msg("exporting json")
|
|
|
|
|
2021-04-08 03:41:44 +02:00
|
|
|
if err := p.computed.FillPath(cue.MakePath(), o); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
case "yaml":
|
|
|
|
var o interface{}
|
|
|
|
o, err := unmarshalAnything(contents, yaml.Unmarshal)
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Debug().
|
|
|
|
Interface("contents", o).
|
|
|
|
Msg("exporting yaml")
|
|
|
|
|
2021-04-08 03:41:44 +02:00
|
|
|
if err := p.computed.FillPath(cue.MakePath(), o); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
default:
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, fmt.Errorf("unsupported export format: %q", format)
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type unmarshaller func([]byte, interface{}) error
|
|
|
|
|
|
|
|
func unmarshalAnything(data []byte, fn unmarshaller) (interface{}, error) {
|
|
|
|
// unmarshalling a map into interface{} yields an error:
|
|
|
|
// "unsupported Go type for map key (interface {})"
|
|
|
|
// we want to attempt to unmarshal to a map[string]interface{} first
|
|
|
|
var oMap map[string]interface{}
|
|
|
|
if err := fn(data, &oMap); err == nil {
|
|
|
|
return oMap, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the previous attempt didn't work, we might be facing a scalar (e.g.
|
|
|
|
// bool).
|
|
|
|
// Try to unmarshal to interface{} directly.
|
|
|
|
var o interface{}
|
|
|
|
err := fn(data, &o)
|
|
|
|
return o, err
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Load(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-02-08 20:47:07 +01:00
|
|
|
// Execute 'from' in a tmp pipeline, and use the resulting fs
|
2021-05-05 01:26:56 +02:00
|
|
|
from := NewPipeline(op.Lookup("from"), p.s)
|
|
|
|
if err := from.Run(ctx); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-04-13 02:45:38 +02:00
|
|
|
p.image = from.ImageConfig()
|
|
|
|
return from.State(), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-04-27 02:39:19 +02:00
|
|
|
func (p *Pipeline) DockerLogin(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
|
|
|
username, err := op.Lookup("username").String()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
|
|
|
secret, err := op.Lookup("secret").String()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
|
|
|
target, err := op.Lookup("target").String()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
p.s.AddCredentials(target, username, secret)
|
2021-04-27 02:39:19 +02:00
|
|
|
log.
|
|
|
|
Ctx(ctx).
|
|
|
|
Debug().
|
|
|
|
Str("target", target).
|
|
|
|
Msg("docker login to registry")
|
|
|
|
|
|
|
|
return st, nil
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) FetchContainer(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-03-31 22:40:46 +02:00
|
|
|
rawRef, err := op.Lookup("ref").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-02-25 23:17:01 +01:00
|
|
|
|
|
|
|
ref, err := reference.ParseNormalizedNamed(rawRef)
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, fmt.Errorf("failed to parse ref %s: %w", rawRef, err)
|
2021-02-25 23:17:01 +01:00
|
|
|
}
|
|
|
|
// Add the default tag "latest" to a reference if it only has a repo name.
|
|
|
|
ref = reference.TagNameOnly(ref)
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
st = llb.Image(
|
2021-02-25 23:17:01 +01:00
|
|
|
ref.String(),
|
|
|
|
llb.WithCustomName(p.vertexNamef("FetchContainer %s", rawRef)),
|
2021-02-24 00:37:32 +01:00
|
|
|
)
|
2021-02-25 23:17:01 +01:00
|
|
|
|
|
|
|
// Load image metadata and convert to to LLB.
|
2021-04-13 02:45:38 +02:00
|
|
|
p.image, err = p.s.ResolveImageConfig(ctx, ref.String(), llb.ResolveImageConfigOpt{
|
2021-02-25 23:17:01 +01:00
|
|
|
LogName: p.vertexNamef("load metadata for %s", ref.String()),
|
|
|
|
})
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-25 23:17:01 +01:00
|
|
|
}
|
|
|
|
|
2021-04-13 02:45:38 +02:00
|
|
|
return applyImageToState(p.image, st), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// applyImageToState converts an image config into LLB instructions
|
|
|
|
func applyImageToState(image dockerfile2llb.Image, st llb.State) llb.State {
|
|
|
|
// FIXME: there are unhandled sections of the image config
|
2021-02-25 23:17:01 +01:00
|
|
|
for _, env := range image.Config.Env {
|
|
|
|
k, v := parseKeyValue(env)
|
2021-03-12 22:00:11 +01:00
|
|
|
st = st.AddEnv(k, v)
|
2021-02-25 23:17:01 +01:00
|
|
|
}
|
|
|
|
if image.Config.WorkingDir != "" {
|
2021-03-12 22:00:11 +01:00
|
|
|
st = st.Dir(image.Config.WorkingDir)
|
2021-02-25 23:17:01 +01:00
|
|
|
}
|
|
|
|
if image.Config.User != "" {
|
2021-03-12 22:00:11 +01:00
|
|
|
st = st.User(image.Config.User)
|
2021-02-25 23:17:01 +01:00
|
|
|
}
|
2021-04-13 02:45:38 +02:00
|
|
|
return st
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
|
|
|
|
2021-02-25 23:17:01 +01:00
|
|
|
func parseKeyValue(env string) (string, string) {
|
|
|
|
parts := strings.SplitN(env, "=", 2)
|
|
|
|
v := ""
|
|
|
|
if len(parts) > 1 {
|
|
|
|
v = parts[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
return parts[0], v
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) PushContainer(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-03-31 22:40:46 +02:00
|
|
|
rawRef, err := op.Lookup("ref").String()
|
2021-03-12 01:41:19 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-12 01:41:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ref, err := reference.ParseNormalizedNamed(rawRef)
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, fmt.Errorf("failed to parse ref %s: %w", rawRef, err)
|
2021-03-12 01:41:19 +01:00
|
|
|
}
|
|
|
|
// Add the default tag "latest" to a reference if it only has a repo name.
|
|
|
|
ref = reference.TagNameOnly(ref)
|
|
|
|
|
2021-04-17 01:18:33 +02:00
|
|
|
resp, err := p.s.Export(ctx, p.State(), &p.image, bk.ExportEntry{
|
2021-03-12 01:41:19 +01:00
|
|
|
Type: bk.ExporterImage,
|
|
|
|
Attrs: map[string]string{
|
|
|
|
"name": ref.String(),
|
|
|
|
"push": "true",
|
|
|
|
},
|
|
|
|
})
|
2021-03-12 22:00:11 +01:00
|
|
|
|
2021-04-17 01:18:33 +02:00
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if digest, ok := resp.ExporterResponse["containerimage.digest"]; ok {
|
|
|
|
imageRef := fmt.Sprintf(
|
|
|
|
"%s@%s",
|
|
|
|
resp.ExporterResponse["image.name"],
|
|
|
|
digest,
|
|
|
|
)
|
|
|
|
|
|
|
|
return st.File(
|
|
|
|
llb.Mkdir("/dagger", fs.FileMode(0755)),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Mkdir /dagger")),
|
|
|
|
).File(
|
|
|
|
llb.Mkfile("/dagger/image_digest", fs.FileMode(0644), []byte(digest)),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Storing image digest to /dagger/image_digest")),
|
|
|
|
).File(
|
|
|
|
llb.Mkfile("/dagger/image_ref", fs.FileMode(0644), []byte(imageRef)),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Storing image ref to /dagger/image_ref")),
|
|
|
|
), nil
|
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-12 01:41:19 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) FetchGit(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-03-31 22:40:46 +02:00
|
|
|
remote, err := op.Lookup("remote").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-31 22:40:46 +02:00
|
|
|
ref, err := op.Lookup("ref").String()
|
2021-02-08 20:47:07 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-03-18 19:18:06 +01:00
|
|
|
|
2021-05-13 19:33:15 +02:00
|
|
|
remoteRedacted := remote
|
|
|
|
if u, err := url.Parse(remote); err == nil {
|
|
|
|
remoteRedacted = u.Redacted()
|
|
|
|
}
|
|
|
|
|
|
|
|
gitOpts := []llb.GitOption{}
|
|
|
|
var opts struct {
|
|
|
|
AuthTokenSecret string
|
|
|
|
AuthHeaderSecret string
|
|
|
|
KeepGitDir bool
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := op.Decode(&opts); err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.KeepGitDir {
|
|
|
|
gitOpts = append(gitOpts, llb.KeepGitDir())
|
|
|
|
}
|
|
|
|
if opts.AuthTokenSecret != "" {
|
|
|
|
gitOpts = append(gitOpts, llb.AuthTokenSecret(opts.AuthTokenSecret))
|
|
|
|
}
|
|
|
|
if opts.AuthHeaderSecret != "" {
|
|
|
|
gitOpts = append(gitOpts, llb.AuthTokenSecret(opts.AuthHeaderSecret))
|
|
|
|
}
|
|
|
|
|
|
|
|
gitOpts = append(gitOpts, llb.WithCustomName(p.vertexNamef("FetchGit %s@%s", remoteRedacted, ref)))
|
|
|
|
|
2021-03-18 19:18:06 +01:00
|
|
|
// FIXME: Remove the `Copy` and use `Git` directly.
|
|
|
|
//
|
|
|
|
// Copy'ing is a costly operation which should be unnecessary.
|
|
|
|
// However, using llb.Git directly breaks caching sometimes for unknown reasons.
|
|
|
|
return st.File(
|
|
|
|
llb.Copy(
|
|
|
|
llb.Git(
|
|
|
|
remote,
|
|
|
|
ref,
|
2021-05-13 19:33:15 +02:00
|
|
|
gitOpts...,
|
2021-03-18 19:18:06 +01:00
|
|
|
),
|
|
|
|
"/",
|
|
|
|
"/",
|
|
|
|
),
|
2021-05-13 19:33:15 +02:00
|
|
|
llb.WithCustomName(p.vertexNamef("FetchGit %s@%s [copy]", remoteRedacted, ref)),
|
2021-03-12 22:00:11 +01:00
|
|
|
), nil
|
2021-02-08 20:47:07 +01:00
|
|
|
}
|
2021-02-19 09:09:53 +01:00
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) DockerBuild(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-02-19 09:09:53 +01:00
|
|
|
var (
|
2021-04-12 01:30:48 +02:00
|
|
|
dockerContext = op.Lookup("context")
|
|
|
|
dockerfile = op.Lookup("dockerfile")
|
2021-02-19 09:09:53 +01:00
|
|
|
|
|
|
|
contextDef *bkpb.Definition
|
|
|
|
dockerfileDef *bkpb.Definition
|
|
|
|
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
2021-04-12 01:30:48 +02:00
|
|
|
if !dockerContext.Exists() && !dockerfile.Exists() {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, errors.New("context or dockerfile required")
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// docker build context. This can come from another component, so we need to
|
|
|
|
// compute it first.
|
2021-04-12 01:30:48 +02:00
|
|
|
if dockerContext.Exists() {
|
2021-05-05 01:26:56 +02:00
|
|
|
from := NewPipeline(op.Lookup("context"), p.s)
|
|
|
|
if err := from.Run(ctx); err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
2021-04-13 02:45:38 +02:00
|
|
|
contextDef, err = p.s.Marshal(ctx, from.State())
|
2021-02-19 09:09:53 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
dockerfileDef = contextDef
|
|
|
|
}
|
|
|
|
|
|
|
|
// Inlined dockerfile: need to be converted to LLB
|
|
|
|
if dockerfile.Exists() {
|
|
|
|
content, err := dockerfile.String()
|
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
2021-03-12 22:00:11 +01:00
|
|
|
dockerfileDef, err = p.s.Marshal(ctx,
|
2021-02-19 09:09:53 +01:00
|
|
|
llb.Scratch().File(
|
|
|
|
llb.Mkfile("/Dockerfile", 0644, []byte(content)),
|
|
|
|
),
|
2021-03-12 22:00:11 +01:00
|
|
|
)
|
2021-02-19 09:09:53 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
if contextDef == nil {
|
|
|
|
contextDef = dockerfileDef
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 02:45:38 +02:00
|
|
|
opts, err := dockerBuildOpts(op)
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
|
2021-05-26 01:30:49 +02:00
|
|
|
if p.s.NoCache() {
|
2021-04-15 20:26:01 +02:00
|
|
|
opts["no-cache"] = ""
|
|
|
|
}
|
|
|
|
|
2021-02-19 09:09:53 +01:00
|
|
|
req := bkgw.SolveRequest{
|
|
|
|
Frontend: "dockerfile.v0",
|
2021-04-13 02:45:38 +02:00
|
|
|
FrontendOpt: opts,
|
2021-02-19 09:09:53 +01:00
|
|
|
FrontendInputs: map[string]*bkpb.Definition{
|
|
|
|
dockerfilebuilder.DefaultLocalNameContext: contextDef,
|
|
|
|
dockerfilebuilder.DefaultLocalNameDockerfile: dockerfileDef,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2021-04-13 02:45:38 +02:00
|
|
|
res, err := p.s.SolveRequest(ctx, req)
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
if meta, ok := res.Metadata[exptypes.ExporterImageConfigKey]; ok {
|
|
|
|
if err := json.Unmarshal(meta, &p.image); err != nil {
|
|
|
|
return st, fmt.Errorf("failed to unmarshal image config: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ref, err := res.SingleRef()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
st, err = ref.ToState()
|
|
|
|
if err != nil {
|
|
|
|
return st, err
|
|
|
|
}
|
|
|
|
return applyImageToState(p.image, st), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func dockerBuildOpts(op *compiler.Value) (map[string]string, error) {
|
|
|
|
opts := map[string]string{}
|
|
|
|
|
2021-02-19 09:09:53 +01:00
|
|
|
if dockerfilePath := op.Lookup("dockerfilePath"); dockerfilePath.Exists() {
|
|
|
|
filename, err := dockerfilePath.String()
|
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
2021-04-13 02:45:38 +02:00
|
|
|
opts["filename"] = filename
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
|
2021-05-10 06:09:13 +02:00
|
|
|
if target := op.Lookup("target"); target.Exists() {
|
|
|
|
tgr, err := target.String()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
opts["target"] = tgr
|
|
|
|
}
|
2021-05-10 06:09:56 +02:00
|
|
|
|
|
|
|
if hosts := op.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, ",")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-19 09:09:53 +01:00
|
|
|
if buildArgs := op.Lookup("buildArg"); buildArgs.Exists() {
|
2021-03-19 23:07:01 +01:00
|
|
|
fields, err := buildArgs.Fields()
|
2021-02-19 09:09:53 +01:00
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
2021-03-19 23:07:01 +01:00
|
|
|
for _, buildArg := range fields {
|
|
|
|
v, err := buildArg.Value.String()
|
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-05-01 03:05:37 +02:00
|
|
|
opts["build-arg:"+buildArg.Label()] = v
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if labels := op.Lookup("label"); labels.Exists() {
|
2021-03-19 23:07:01 +01:00
|
|
|
fields, err := labels.Fields()
|
2021-02-19 09:09:53 +01:00
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
2021-03-19 23:07:01 +01:00
|
|
|
for _, label := range fields {
|
|
|
|
s, err := label.Value.String()
|
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-05-01 03:05:37 +02:00
|
|
|
opts["label:"+label.Label()] = s
|
2021-03-19 23:07:01 +01:00
|
|
|
}
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if platforms := op.Lookup("platforms"); platforms.Exists() {
|
|
|
|
p := []string{}
|
|
|
|
list, err := platforms.List()
|
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, platform := range list {
|
|
|
|
s, err := platform.String()
|
|
|
|
if err != nil {
|
2021-04-13 02:45:38 +02:00
|
|
|
return nil, err
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
p = append(p, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(p) > 0 {
|
2021-04-13 02:45:38 +02:00
|
|
|
opts["platform"] = strings.Join(p, ",")
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
if len(p) > 1 {
|
2021-04-13 02:45:38 +02:00
|
|
|
opts["multi-platform"] = "true"
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 02:45:38 +02:00
|
|
|
return opts, nil
|
2021-02-19 09:09:53 +01:00
|
|
|
}
|
2021-03-04 02:24:21 +01:00
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) WriteFile(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-04-09 08:52:17 +02:00
|
|
|
var content []byte
|
|
|
|
var err error
|
|
|
|
|
|
|
|
switch kind := op.Lookup("content").Kind(); kind {
|
|
|
|
case cue.BytesKind:
|
|
|
|
content, err = op.Lookup("content").Bytes()
|
|
|
|
case cue.StringKind:
|
|
|
|
var str string
|
|
|
|
str, err = op.Lookup("content").String()
|
|
|
|
if err == nil {
|
|
|
|
content = []byte(str)
|
|
|
|
}
|
2021-04-22 18:43:45 +02:00
|
|
|
case cue.BottomKind:
|
2021-05-05 01:26:56 +02:00
|
|
|
err = fmt.Errorf("%s: WriteFile content is not set", p.canonicalPath(op))
|
2021-04-09 08:52:17 +02:00
|
|
|
default:
|
2021-05-05 01:26:56 +02:00
|
|
|
err = fmt.Errorf("%s: unhandled data type in WriteFile: %s", p.canonicalPath(op), kind)
|
2021-04-09 08:52:17 +02:00
|
|
|
}
|
2021-03-04 02:24:21 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-31 22:40:46 +02:00
|
|
|
dest, err := op.Lookup("dest").String()
|
2021-03-04 02:24:21 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-31 22:40:46 +02:00
|
|
|
mode, err := op.Lookup("mode").Int64()
|
2021-03-04 02:24:21 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
return st.File(
|
2021-04-09 08:52:17 +02:00
|
|
|
llb.Mkfile(dest, fs.FileMode(mode), content),
|
2021-03-12 22:00:11 +01:00
|
|
|
llb.WithCustomName(p.vertexNamef("WriteFile %s", dest)),
|
|
|
|
), nil
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
func (p *Pipeline) Mkdir(ctx context.Context, op *compiler.Value, st llb.State) (llb.State, error) {
|
2021-04-12 01:30:48 +02:00
|
|
|
pathString, err := op.Lookup("path").String()
|
2021-03-04 02:24:21 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-31 22:40:46 +02:00
|
|
|
dir, err := op.Lookup("dir").String()
|
2021-03-04 02:24:21 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-31 22:40:46 +02:00
|
|
|
mode, err := op.Lookup("mode").Int64()
|
2021-03-04 02:24:21 +01:00
|
|
|
if err != nil {
|
2021-03-12 22:00:11 +01:00
|
|
|
return st, err
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|
|
|
|
|
2021-03-12 22:00:11 +01:00
|
|
|
return st.Dir(dir).File(
|
2021-04-12 01:30:48 +02:00
|
|
|
llb.Mkdir(pathString, fs.FileMode(mode)),
|
|
|
|
llb.WithCustomName(p.vertexNamef("Mkdir %s", pathString)),
|
2021-03-12 22:00:11 +01:00
|
|
|
), nil
|
2021-03-04 02:24:21 +01:00
|
|
|
}
|