cache fix: stable ordering of maps (Env, Mount, ...)
Maps were causing the same Pipeline to randomly produce slightly different LLB on each run (because they are represented as an array in LLB, wheras they're a map in CueLLB). This forces every Cue field iteration (env, mount, etc) to be predictable by using stable sorting. Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
This commit is contained in:
parent
1904d3a918
commit
b8de4e5049
@ -1,6 +1,8 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
"cuelang.org/go/cue"
|
"cuelang.org/go/cue"
|
||||||
cueformat "cuelang.org/go/cue/format"
|
cueformat "cuelang.org/go/cue/format"
|
||||||
)
|
)
|
||||||
@ -72,9 +74,33 @@ func (v *Value) Kind() cue.Kind {
|
|||||||
return v.val.Kind()
|
return v.val.Kind()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Field represents a struct field
|
||||||
|
type Field struct {
|
||||||
|
Label string
|
||||||
|
Value *Value
|
||||||
|
}
|
||||||
|
|
||||||
// Proxy function to the underlying cue.Value
|
// Proxy function to the underlying cue.Value
|
||||||
func (v *Value) Fields() (*cue.Iterator, error) {
|
// Field ordering is guaranteed to be stable.
|
||||||
return v.val.Fields()
|
func (v *Value) Fields() ([]Field, error) {
|
||||||
|
it, err := v.val.Fields()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := []Field{}
|
||||||
|
for it.Next() {
|
||||||
|
fields = append(fields, Field{
|
||||||
|
Label: it.Label(),
|
||||||
|
Value: v.Wrap(it.Value()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(fields, func(i, j int) bool {
|
||||||
|
return fields[i].Label < fields[j].Label
|
||||||
|
})
|
||||||
|
|
||||||
|
return fields, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy function to the underlying cue.Value
|
// Proxy function to the underlying cue.Value
|
||||||
@ -121,38 +147,10 @@ func (v *Value) List() ([]*Value, error) {
|
|||||||
for it.Next() {
|
for it.Next() {
|
||||||
l = append(l, v.Wrap(it.Value()))
|
l = append(l, v.Wrap(it.Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
return l, nil
|
return l, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: deprecate to simplify
|
|
||||||
func (v *Value) RangeList(fn func(int, *Value) error) error {
|
|
||||||
it, err := v.val.List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i := 0
|
|
||||||
for it.Next() {
|
|
||||||
if err := fn(i, v.Wrap(it.Value())); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Value) RangeStruct(fn func(string, *Value) error) error {
|
|
||||||
it, err := v.Fields()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for it.Next() {
|
|
||||||
if err := fn(it.Label(), v.Wrap(it.Value())); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: receive string path?
|
// FIXME: receive string path?
|
||||||
func (v *Value) Merge(x interface{}, path ...string) (*Value, error) {
|
func (v *Value) Merge(x interface{}, path ...string) (*Value, error) {
|
||||||
if xval, ok := x.(*Value); ok {
|
if xval, ok := x.(*Value); ok {
|
||||||
|
@ -112,12 +112,15 @@ func analyzeOp(fn func(*compiler.Value) error, op *compiler.Value) error {
|
|||||||
case "load", "copy":
|
case "load", "copy":
|
||||||
return Analyze(fn, op.Get("from"))
|
return Analyze(fn, op.Get("from"))
|
||||||
case "exec":
|
case "exec":
|
||||||
return op.Get("mount").RangeStruct(func(dest string, mnt *compiler.Value) error {
|
fields, err := op.Get("mount").Fields()
|
||||||
if from := mnt.Get("from"); from.Exists() {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, mnt := range fields {
|
||||||
|
if from := mnt.Value.Get("from"); from.Exists() {
|
||||||
return Analyze(fn, from)
|
return Analyze(fn, from)
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -337,7 +340,6 @@ func (p *Pipeline) Exec(ctx context.Context, op *compiler.Value, st llb.State) (
|
|||||||
opts := []llb.RunOption{}
|
opts := []llb.RunOption{}
|
||||||
var cmd struct {
|
var cmd struct {
|
||||||
Args []string
|
Args []string
|
||||||
Env map[string]string
|
|
||||||
Dir string
|
Dir string
|
||||||
Always bool
|
Always bool
|
||||||
}
|
}
|
||||||
@ -349,10 +351,22 @@ func (p *Pipeline) Exec(ctx context.Context, op *compiler.Value, st llb.State) (
|
|||||||
opts = append(opts, llb.Args(cmd.Args))
|
opts = append(opts, llb.Args(cmd.Args))
|
||||||
// dir
|
// dir
|
||||||
opts = append(opts, llb.Dir(cmd.Dir))
|
opts = append(opts, llb.Dir(cmd.Dir))
|
||||||
|
|
||||||
// env
|
// env
|
||||||
for k, v := range cmd.Env {
|
if env := op.Get("env"); env.Exists() {
|
||||||
opts = append(opts, llb.AddEnv(k, v))
|
envs, err := op.Get("env").Fields()
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
for _, env := range envs {
|
||||||
|
v, err := env.Value.String()
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
opts = append(opts, llb.AddEnv(env.Label, v))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// always?
|
// always?
|
||||||
// FIXME: initialize once for an entire compute job, to avoid cache misses
|
// FIXME: initialize once for an entire compute job, to avoid cache misses
|
||||||
if cmd.Always {
|
if cmd.Always {
|
||||||
@ -385,14 +399,17 @@ func (p *Pipeline) Exec(ctx context.Context, op *compiler.Value, st llb.State) (
|
|||||||
|
|
||||||
func (p *Pipeline) mountAll(ctx context.Context, mounts *compiler.Value) ([]llb.RunOption, error) {
|
func (p *Pipeline) mountAll(ctx context.Context, mounts *compiler.Value) ([]llb.RunOption, error) {
|
||||||
opts := []llb.RunOption{}
|
opts := []llb.RunOption{}
|
||||||
err := mounts.RangeStruct(func(dest string, mnt *compiler.Value) error {
|
fields, err := mounts.Fields()
|
||||||
o, err := p.mount(ctx, dest, mnt)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, mnt := range fields {
|
||||||
|
o, err := p.mount(ctx, mnt.Label, mnt.Value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
opts = append(opts, o)
|
opts = append(opts, o)
|
||||||
return nil
|
}
|
||||||
})
|
|
||||||
return opts, err
|
return opts, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,31 +726,31 @@ func (p *Pipeline) DockerBuild(ctx context.Context, op *compiler.Value, st llb.S
|
|||||||
}
|
}
|
||||||
|
|
||||||
if buildArgs := op.Lookup("buildArg"); buildArgs.Exists() {
|
if buildArgs := op.Lookup("buildArg"); buildArgs.Exists() {
|
||||||
err := buildArgs.RangeStruct(func(key string, value *compiler.Value) error {
|
fields, err := buildArgs.Fields()
|
||||||
v, err := value.String()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.FrontendOpt["build-arg:"+key] = v
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
|
for _, buildArg := range fields {
|
||||||
|
v, err := buildArg.Value.String()
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
req.FrontendOpt["build-arg:"+buildArg.Label] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if labels := op.Lookup("label"); labels.Exists() {
|
if labels := op.Lookup("label"); labels.Exists() {
|
||||||
err := labels.RangeStruct(func(key string, value *compiler.Value) error {
|
fields, err := labels.Fields()
|
||||||
s, err := value.String()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
req.FrontendOpt["label:"+key] = s
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return st, err
|
return st, err
|
||||||
}
|
}
|
||||||
|
for _, label := range fields {
|
||||||
|
s, err := label.Value.String()
|
||||||
|
if err != nil {
|
||||||
|
return st, err
|
||||||
|
}
|
||||||
|
req.FrontendOpt["label:"+label.Label] = s
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if platforms := op.Lookup("platforms"); platforms.Exists() {
|
if platforms := op.Lookup("platforms"); platforms.Exists() {
|
||||||
|
Reference in New Issue
Block a user