Refactor op/script/component loading and spec validation
Signed-off-by: Solomon Hykes <sh.github.6811@hykes.org>
This commit is contained in:
parent
bbe16283ab
commit
e10025d688
@ -114,7 +114,7 @@ func (cfg *ClientConfig) Finalize(ctx context.Context) (map[string]string, error
|
|||||||
return nil, errors.Wrap(err, "invalid client config")
|
return nil, errors.Wrap(err, "invalid client config")
|
||||||
}
|
}
|
||||||
// Finalize boot script
|
// Finalize boot script
|
||||||
boot, err := v.Get("boot").Script()
|
boot, err := NewScript(v.Get("boot"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "invalid env boot script")
|
return nil, errors.Wrap(err, "invalid env boot script")
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func (cc *Compiler) Spec() *Spec {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
cc.spec, err = v.Spec()
|
cc.spec, err = newSpec(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -56,12 +56,15 @@ func (cc *Compiler) Compile(name string, src interface{}) (*Value, error) {
|
|||||||
return cc.Wrap(inst.Value(), inst), nil
|
return cc.Wrap(inst.Value(), inst), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compile a cue configuration, and load it as a script.
|
||||||
|
// If the cue configuration is invalid, or does not match the script spec,
|
||||||
|
// return an error.
|
||||||
func (cc *Compiler) CompileScript(name string, src interface{}) (*Script, error) {
|
func (cc *Compiler) CompileScript(name string, src interface{}) (*Script, error) {
|
||||||
v, err := cc.Compile(name, src)
|
v, err := cc.Compile(name, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return v.Script()
|
return NewScript(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a cue configuration tree from the files in fs.
|
// Build a cue configuration tree from the files in fs.
|
||||||
|
@ -3,21 +3,44 @@ package dagger
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Component struct {
|
type Component struct {
|
||||||
|
// Source value for the component, without spec merged
|
||||||
|
// eg. `{ string, #dagger: compute: [{do:"fetch-container", ...}]}`
|
||||||
v *Value
|
v *Value
|
||||||
|
|
||||||
|
// Annotation value for the component , with spec merged.
|
||||||
|
// -> the contents of #dagger.compute
|
||||||
|
// eg. `compute: [{do:"fetch-container", ...}]`
|
||||||
|
//
|
||||||
|
// The spec is merged at this level because the Cue API
|
||||||
|
// does not support merging embedded scalar with nested definition.
|
||||||
|
config *Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewComponent(v *Value) (*Component, error) {
|
||||||
|
config := v.Get("#dagger")
|
||||||
|
if !config.Exists() {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
spec := v.cc.Spec()
|
||||||
|
config, err := spec.Get("#ComponentConfig").Merge(v.Get("#dagger"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "invalid component config")
|
||||||
|
}
|
||||||
|
return &Component{
|
||||||
|
v: v,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Component) Value() *Value {
|
func (c *Component) Value() *Value {
|
||||||
return c.v
|
return c.v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Component) Exists() bool {
|
|
||||||
// Does #dagger exist?
|
|
||||||
return c.Config().Exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the contents of the "#dagger" annotation.
|
// Return the contents of the "#dagger" annotation.
|
||||||
func (c *Component) Config() *Value {
|
func (c *Component) Config() *Value {
|
||||||
return c.Value().Get("#dagger")
|
return c.Value().Get("#dagger")
|
||||||
@ -38,11 +61,7 @@ func (c *Component) Validate() error {
|
|||||||
|
|
||||||
// Return this component's compute script.
|
// Return this component's compute script.
|
||||||
func (c *Component) ComputeScript() (*Script, error) {
|
func (c *Component) ComputeScript() (*Script, error) {
|
||||||
v := c.Value().Get("#dagger.compute")
|
return newScript(c.Config().Get("compute"))
|
||||||
if !v.Exists() {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
return v.Script()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the configuration for this component.
|
// Compute the configuration for this component.
|
||||||
|
@ -5,6 +5,38 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestComponentNotExist(t *testing.T) {
|
||||||
|
cc := &Compiler{}
|
||||||
|
root, err := cc.Compile("root.cue", `
|
||||||
|
foo: hello: "world"
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = NewComponent(root.Get("bar")) // non-existent key
|
||||||
|
if err != ErrNotExist {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = NewComponent(root.Get("foo")) // non-existent #dagger
|
||||||
|
if err != ErrNotExist {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadEmptyComponent(t *testing.T) {
|
||||||
|
cc := &Compiler{}
|
||||||
|
root, err := cc.Compile("root.cue", `
|
||||||
|
foo: #dagger: {}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, err = NewComponent(root.Get("foo"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test that default values in spec are applied at the component level
|
// Test that default values in spec are applied at the component level
|
||||||
// See issue #19
|
// See issue #19
|
||||||
func TestComponentDefaults(t *testing.T) {
|
func TestComponentDefaults(t *testing.T) {
|
||||||
@ -28,7 +60,7 @@ func TestComponentDefaults(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
c, err := v.Component()
|
c, err := NewComponent(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -53,7 +85,7 @@ func TestValidateEmptyComponent(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_, err = v.Component()
|
_, err = NewComponent(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -65,7 +97,7 @@ func TestValidateSimpleComponent(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
c, err := v.Component()
|
c, err := NewComponent(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -187,8 +187,9 @@ func (env *Env) Walk(ctx context.Context, fn EnvWalkFunc) (*Value, error) {
|
|||||||
ctx := lg.WithContext(ctx)
|
ctx := lg.WithContext(ctx)
|
||||||
|
|
||||||
lg.Debug().Msg("Env.Walk: processing")
|
lg.Debug().Msg("Env.Walk: processing")
|
||||||
|
// FIXME: get directly from state Value ? Locking issue?
|
||||||
val := env.cc.Wrap(v, flowInst)
|
val := env.cc.Wrap(v, flowInst)
|
||||||
c, err := val.Component()
|
c, err := NewComponent(val)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// Not a component: skip
|
// Not a component: skip
|
||||||
return nil, nil
|
return nil, nil
|
||||||
@ -207,3 +208,9 @@ func (env *Env) Walk(ctx context.Context, fn EnvWalkFunc) (*Value, error) {
|
|||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the component at the specified path in the config, eg. `www`
|
||||||
|
// If the component does not exist, os.ErrNotExist is returned.
|
||||||
|
func (env *Env) Component(target string) (*Component, error) {
|
||||||
|
return NewComponent(env.state.Get(target))
|
||||||
|
}
|
||||||
|
@ -117,9 +117,4 @@ package dagger
|
|||||||
src: string | *"/"
|
src: string | *"/"
|
||||||
dest: string | *"/"
|
dest: string | *"/"
|
||||||
}
|
}
|
||||||
|
|
||||||
#TestScript: #Script & [
|
|
||||||
{do: "fetch-container", ref: "alpine:latest"},
|
|
||||||
{do: "exec", args: ["echo", "hello", "world"]},
|
|
||||||
]
|
|
||||||
`
|
`
|
||||||
|
@ -13,6 +13,16 @@ type Mount struct {
|
|||||||
v *Value
|
v *Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newMount(v *Value, dest string) (*Mount, error) {
|
||||||
|
if !v.Exists() {
|
||||||
|
return nil, ErrNotExist
|
||||||
|
}
|
||||||
|
return &Mount{
|
||||||
|
v: v,
|
||||||
|
dest: dest,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (mnt *Mount) Validate(defs ...string) error {
|
func (mnt *Mount) Validate(defs ...string) error {
|
||||||
return mnt.v.Validate(append(defs, "#Mount")...)
|
return mnt.v.Validate(append(defs, "#Mount")...)
|
||||||
}
|
}
|
||||||
@ -26,7 +36,7 @@ func (mnt *Mount) LLB(ctx context.Context, s Solver) (llb.RunOption, error) {
|
|||||||
return nil, fmt.Errorf("FIXME: cache mount not yet implemented")
|
return nil, fmt.Errorf("FIXME: cache mount not yet implemented")
|
||||||
}
|
}
|
||||||
// Compute source component or script, discarding fs writes & output value
|
// Compute source component or script, discarding fs writes & output value
|
||||||
from, err := mnt.v.Lookup("from").Executable()
|
from, err := newExecutable(mnt.v.Lookup("from"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "from")
|
return nil, errors.Wrap(err, "from")
|
||||||
}
|
}
|
||||||
|
29
dagger/op.go
29
dagger/op.go
@ -14,6 +14,25 @@ type Op struct {
|
|||||||
v *Value
|
v *Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewOp(v *Value) (*Op, error) {
|
||||||
|
spec := v.cc.Spec().Get("#Op")
|
||||||
|
final, err := spec.Merge(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "invalid op")
|
||||||
|
}
|
||||||
|
return newOp(final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as newOp, but without spec merge + validation.
|
||||||
|
func newOp(v *Value) (*Op, error) {
|
||||||
|
if !v.Exists() {
|
||||||
|
return nil, ErrNotExist
|
||||||
|
}
|
||||||
|
return &Op{
|
||||||
|
v: v,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (op *Op) Execute(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
func (op *Op) Execute(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
||||||
action, err := op.Action()
|
action, err := op.Action()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -29,7 +48,7 @@ func (op *Op) Walk(ctx context.Context, fn func(*Op) error) error {
|
|||||||
isCopy := (op.Validate("#Copy") == nil)
|
isCopy := (op.Validate("#Copy") == nil)
|
||||||
isLoad := (op.Validate("#Load") == nil)
|
isLoad := (op.Validate("#Load") == nil)
|
||||||
if isCopy || isLoad {
|
if isCopy || isLoad {
|
||||||
if from, err := op.Get("from").Executable(); err == nil {
|
if from, err := newExecutable(op.Get("from")); err == nil {
|
||||||
if err := from.Walk(ctx, fn); err != nil {
|
if err := from.Walk(ctx, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -38,7 +57,7 @@ func (op *Op) Walk(ctx context.Context, fn func(*Op) error) error {
|
|||||||
}
|
}
|
||||||
if err := op.Validate("#Exec"); err == nil {
|
if err := op.Validate("#Exec"); err == nil {
|
||||||
return op.Get("mount").RangeStruct(func(k string, v *Value) error {
|
return op.Get("mount").RangeStruct(func(k string, v *Value) error {
|
||||||
if from, err := op.Get("from").Executable(); err == nil {
|
if from, err := newExecutable(op.Get("from")); err == nil {
|
||||||
if err := from.Walk(ctx, fn); err != nil {
|
if err := from.Walk(ctx, fn); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -98,7 +117,7 @@ func (op *Op) Copy(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fs, err
|
return fs, err
|
||||||
}
|
}
|
||||||
from, err := op.Get("from").Executable()
|
from, err := newExecutable(op.Get("from"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fs, errors.Wrap(err, "from")
|
return fs, errors.Wrap(err, "from")
|
||||||
}
|
}
|
||||||
@ -167,7 +186,7 @@ func (op *Op) Exec(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
|||||||
// mounts
|
// mounts
|
||||||
if mounts := op.v.Lookup("mount"); mounts.Exists() {
|
if mounts := op.v.Lookup("mount"); mounts.Exists() {
|
||||||
if err := mounts.RangeStruct(func(k string, v *Value) error {
|
if err := mounts.RangeStruct(func(k string, v *Value) error {
|
||||||
mnt, err := v.Mount(k)
|
mnt, err := newMount(v, k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -233,7 +252,7 @@ func (op *Op) Export(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (op *Op) Load(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
func (op *Op) Load(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
||||||
from, err := op.Get("from").Executable()
|
from, err := newExecutable(op.Get("from"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fs, errors.Wrap(err, "load")
|
return fs, errors.Wrap(err, "load")
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ func TestLocalMatch(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
op, err := v.Op()
|
op, err := newOp(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ func TestCopyMatch(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
op, err := v.Op()
|
op, err := newOp(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package dagger
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"cuelang.org/go/cue"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
@ -15,15 +16,46 @@ type Script struct {
|
|||||||
v *Value
|
v *Value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Script) Validate() error {
|
func NewScript(v *Value) (*Script, error) {
|
||||||
// FIXME this crashes when a script is incomplete or empty
|
spec := v.cc.Spec().Get("#Script")
|
||||||
return s.Value().Validate("#Script")
|
final, err := spec.Merge(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "invalid script")
|
||||||
|
}
|
||||||
|
return newScript(final)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same as newScript, but without spec merge + validation.
|
||||||
|
func newScript(v *Value) (*Script, error) {
|
||||||
|
if !v.Exists() {
|
||||||
|
return nil, ErrNotExist
|
||||||
|
}
|
||||||
|
// Assume script is valid.
|
||||||
|
// Spec validation is already done at component creation.
|
||||||
|
return &Script{
|
||||||
|
v: v,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Script) Value() *Value {
|
func (s *Script) Value() *Value {
|
||||||
return s.v
|
return s.v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the operation at index idx
|
||||||
|
func (s *Script) Op(idx int) (*Op, error) {
|
||||||
|
v := s.v.LookupPath(cue.MakePath(cue.Index(idx)))
|
||||||
|
if !v.Exists() {
|
||||||
|
return nil, ErrNotExist
|
||||||
|
}
|
||||||
|
return newOp(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the number of operations in the script
|
||||||
|
func (s *Script) Len() uint64 {
|
||||||
|
l, _ := s.v.Len().Uint64()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
// Run a dagger script
|
// Run a dagger script
|
||||||
func (s *Script) Execute(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
func (s *Script) Execute(ctx context.Context, fs FS, out *Fillable) (FS, error) {
|
||||||
err := s.v.RangeList(func(idx int, v *Value) error {
|
err := s.v.RangeList(func(idx int, v *Value) error {
|
||||||
@ -38,7 +70,7 @@ func (s *Script) Execute(ctx context.Context, fs FS, out *Fillable) (FS, error)
|
|||||||
Msg("script is unspecified, aborting execution")
|
Msg("script is unspecified, aborting execution")
|
||||||
return ErrAbortExecution
|
return ErrAbortExecution
|
||||||
}
|
}
|
||||||
op, err := v.Op()
|
op, err := newOp(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "validate op %d/%d", idx+1, s.v.Len())
|
return errors.Wrapf(err, "validate op %d/%d", idx+1, s.v.Len())
|
||||||
}
|
}
|
||||||
@ -58,7 +90,7 @@ func (s *Script) Execute(ctx context.Context, fs FS, out *Fillable) (FS, error)
|
|||||||
|
|
||||||
func (s *Script) Walk(ctx context.Context, fn func(op *Op) error) error {
|
func (s *Script) Walk(ctx context.Context, fn func(op *Op) error) error {
|
||||||
return s.v.RangeList(func(idx int, v *Value) error {
|
return s.v.RangeList(func(idx int, v *Value) error {
|
||||||
op, err := v.Op()
|
op, err := newOp(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "validate op %d/%d", idx+1, s.v.Len())
|
return errors.Wrapf(err, "validate op %d/%d", idx+1, s.v.Len())
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,47 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Test a script which loads a nested script
|
||||||
|
func TestScriptLoadScript(t *testing.T) {
|
||||||
|
mkScript(t, 2, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
do: "load"
|
||||||
|
from: [
|
||||||
|
{
|
||||||
|
do: "fetch-container"
|
||||||
|
ref: "alpine:latest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test a script which loads a nested component
|
||||||
|
func TestScriptLoadComponent(t *testing.T) {
|
||||||
|
mkScript(t, 2, `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
do: "load"
|
||||||
|
from: {
|
||||||
|
#dagger: compute: [
|
||||||
|
{
|
||||||
|
do: "fetch-container"
|
||||||
|
ref: "alpine:latest"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
// Test that default values in spec are applied
|
// Test that default values in spec are applied
|
||||||
func TestScriptDefaults(t *testing.T) {
|
func TestScriptDefaults(t *testing.T) {
|
||||||
cc := &Compiler{}
|
cc := &Compiler{}
|
||||||
v, err := cc.Compile("", `
|
v, err := cc.Compile("", `
|
||||||
|
[
|
||||||
{
|
{
|
||||||
do: "exec"
|
do: "exec"
|
||||||
args: ["sh", "-c", """
|
args: ["sh", "-c", """
|
||||||
@ -17,15 +54,17 @@ func TestScriptDefaults(t *testing.T) {
|
|||||||
"""]
|
"""]
|
||||||
// dir: "/"
|
// dir: "/"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
op, err := v.Op()
|
script, err := NewScript(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := op.Validate(); err != nil {
|
op, err := script.Op(0)
|
||||||
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
dir, err := op.Get("dir").String()
|
dir, err := op.Get("dir").String()
|
||||||
@ -64,7 +103,7 @@ func TestLocalScript(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
s, err := v.Script()
|
s, err := NewScript(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -84,12 +123,14 @@ func TestLocalScript(t *testing.T) {
|
|||||||
func TestWalkBootScript(t *testing.T) {
|
func TestWalkBootScript(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
cc := &Compiler{}
|
cfg := &ClientConfig{}
|
||||||
cfg, err := cc.Compile("clientconfig.cue", baseClientConfig)
|
_, err := cfg.Finalize(context.TODO())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
script, err := cfg.Get("boot").Script()
|
|
||||||
|
cc := &Compiler{}
|
||||||
|
script, err := cc.CompileScript("boot.cue", cfg.Boot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -160,3 +201,28 @@ func TestWalkBiggerScript(t *testing.T) {
|
|||||||
t.Fatal(got)
|
t.Fatal(got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UTILITIES
|
||||||
|
|
||||||
|
// Compile a script and check that it has the correct
|
||||||
|
// number of operations.
|
||||||
|
func mkScript(t *testing.T, nOps int, src string) *Script {
|
||||||
|
cc := &Compiler{}
|
||||||
|
s, err := cc.CompileScript("test.cue", src)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Count ops (including nested `from`)
|
||||||
|
n := 0
|
||||||
|
err = s.Walk(context.TODO(), func(op *Op) error {
|
||||||
|
n++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if n != nOps {
|
||||||
|
t.Fatal(n)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
@ -112,8 +112,3 @@ package dagger
|
|||||||
src: string | *"/"
|
src: string | *"/"
|
||||||
dest: string | *"/"
|
dest: string | *"/"
|
||||||
}
|
}
|
||||||
|
|
||||||
#TestScript: #Script & [
|
|
||||||
{do: "fetch-container", ref: "alpine:latest"},
|
|
||||||
{do: "exec", args: ["echo", "hello", "world"]},
|
|
||||||
]
|
|
||||||
|
@ -10,6 +10,16 @@ type Spec struct {
|
|||||||
root *Value
|
root *Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newSpec(v *Value) (*Spec, error) {
|
||||||
|
// Spec contents must be a struct
|
||||||
|
if _, err := v.Struct(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Spec{
|
||||||
|
root: v,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// eg. Validate(op, "#Op")
|
// eg. Validate(op, "#Op")
|
||||||
func (s Spec) Validate(v *Value, defpath string) error {
|
func (s Spec) Validate(v *Value, defpath string) error {
|
||||||
// Lookup def by name, eg. "#Script" or "#Copy"
|
// Lookup def by name, eg. "#Script" or "#Copy"
|
||||||
|
@ -2,16 +2,35 @@ package dagger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
cueflow "cuelang.org/go/tools/flow"
|
cueflow "cuelang.org/go/tools/flow"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNotExist = os.ErrNotExist
|
||||||
|
|
||||||
// Implemented by Component, Script, Op
|
// Implemented by Component, Script, Op
|
||||||
type Executable interface {
|
type Executable interface {
|
||||||
Execute(context.Context, FS, *Fillable) (FS, error)
|
Execute(context.Context, FS, *Fillable) (FS, error)
|
||||||
Walk(context.Context, func(*Op) error) error
|
Walk(context.Context, func(*Op) error) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newExecutable(v *Value) (Executable, error) {
|
||||||
|
// NOTE: here we need full spec validation,
|
||||||
|
// so we call NewScript, NewComponent, NewOp.
|
||||||
|
if script, err := NewScript(v); err == nil {
|
||||||
|
return script, nil
|
||||||
|
}
|
||||||
|
if component, err := NewComponent(v); err == nil {
|
||||||
|
return component, nil
|
||||||
|
}
|
||||||
|
if op, err := NewOp(v); err == nil {
|
||||||
|
return op, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("value is not executable")
|
||||||
|
}
|
||||||
|
|
||||||
// Something which can be filled in-place with a cue value
|
// Something which can be filled in-place with a cue value
|
||||||
type Fillable struct {
|
type Fillable struct {
|
||||||
t *cueflow.Task
|
t *cueflow.Task
|
||||||
|
@ -2,7 +2,6 @@ package dagger
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
"cuelang.org/go/cue"
|
"cuelang.org/go/cue"
|
||||||
cueformat "cuelang.org/go/cue/format"
|
cueformat "cuelang.org/go/cue/format"
|
||||||
@ -210,6 +209,8 @@ func (v *Value) Save(fs FS, filename string) (FS, error) {
|
|||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check that a value is valid. Optionally check that it matches
|
||||||
|
// all the specified spec definitions..
|
||||||
func (v *Value) Validate(defs ...string) error {
|
func (v *Value) Validate(defs ...string) error {
|
||||||
if err := v.val.Validate(); err != nil {
|
if err := v.val.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -238,90 +239,3 @@ func (v *Value) IsEmptyStruct() bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component returns the component value if v is a valid dagger component or an error otherwise.
|
|
||||||
// If no '#dagger' annotation is present, os.ErrNotExist
|
|
||||||
// is returned.
|
|
||||||
func (v *Value) Component() (*Component, error) {
|
|
||||||
c := &Component{
|
|
||||||
v: v,
|
|
||||||
}
|
|
||||||
if !c.Exists() {
|
|
||||||
return c, os.ErrNotExist
|
|
||||||
}
|
|
||||||
if err := c.Validate(); err != nil {
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Value) Script() (*Script, error) {
|
|
||||||
s := &Script{
|
|
||||||
v: v,
|
|
||||||
}
|
|
||||||
if err := s.Validate(); err != nil {
|
|
||||||
return s, err
|
|
||||||
}
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Value) Executable() (Executable, error) {
|
|
||||||
if script, err := v.Script(); err == nil {
|
|
||||||
return script, nil
|
|
||||||
}
|
|
||||||
if component, err := v.Component(); err == nil {
|
|
||||||
return component, nil
|
|
||||||
}
|
|
||||||
if op, err := v.Op(); err == nil {
|
|
||||||
return op, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("value is not executable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScriptOrComponent returns one of:
|
|
||||||
// (1) the component value if v is a valid component (type *Component)
|
|
||||||
// (2) the script value if v is a valid script (type *Script)
|
|
||||||
// (3) an error otherwise
|
|
||||||
func (v *Value) ScriptOrComponent() (interface{}, error) {
|
|
||||||
s, err := v.Script()
|
|
||||||
if err == nil {
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
c, err := v.Component()
|
|
||||||
if err == nil {
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("not a script or component")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Value) Op() (*Op, error) {
|
|
||||||
// Merge #Op definition from spec to get default values
|
|
||||||
spec := v.cc.Spec()
|
|
||||||
v, err := spec.Get("#Op").Merge(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
op := &Op{
|
|
||||||
v: v,
|
|
||||||
}
|
|
||||||
return op, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Value) Mount(dest string) (*Mount, error) {
|
|
||||||
mnt := &Mount{
|
|
||||||
v: v,
|
|
||||||
dest: dest,
|
|
||||||
}
|
|
||||||
return mnt, mnt.Validate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpret this value as a spec
|
|
||||||
func (v *Value) Spec() (*Spec, error) {
|
|
||||||
// Spec must be a struct
|
|
||||||
if _, err := v.Struct(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Spec{
|
|
||||||
root: v,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
@ -29,28 +29,10 @@ func TestJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompileBootScript(t *testing.T) {
|
|
||||||
cc := &Compiler{}
|
|
||||||
cfg, err := cc.Compile("boot.cue", baseClientConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s, err := cfg.Get("bootscript").Script()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := s.Validate(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCompileSimpleScript(t *testing.T) {
|
func TestCompileSimpleScript(t *testing.T) {
|
||||||
cc := &Compiler{}
|
cc := &Compiler{}
|
||||||
s, err := cc.CompileScript("simple.cue", `[{do: "local", dir: "."}]`)
|
_, err := cc.CompileScript("simple.cue", `[{do: "local", dir: "."}]`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if err := s.Validate(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,3 @@ realempty: {
|
|||||||
empty: {
|
empty: {
|
||||||
#dagger: compute: []
|
#dagger: compute: []
|
||||||
}
|
}
|
||||||
|
|
||||||
// additional prop, should not error
|
|
||||||
withprops: {
|
|
||||||
#dagger: {
|
|
||||||
compute: []
|
|
||||||
foo: bar: "foo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -4,17 +4,14 @@ hello: "world"
|
|||||||
|
|
||||||
bar: string
|
bar: string
|
||||||
|
|
||||||
#dagger: {
|
#dagger: compute: [
|
||||||
compute: [
|
{
|
||||||
{
|
do: "fetch-container"
|
||||||
do: "fetch-container"
|
ref: "alpine"
|
||||||
ref: "alpine"
|
},
|
||||||
},
|
{
|
||||||
{
|
do: "exec"
|
||||||
do: "exec"
|
dir: "/"
|
||||||
dir: "/"
|
args: ["sh", "-c", "echo \(bar)"]
|
||||||
args: ["sh", "-c", "echo \(foo.bar)"]
|
},
|
||||||
},
|
]
|
||||||
]
|
|
||||||
foo: bar: bar
|
|
||||||
}
|
|
||||||
|
@ -25,11 +25,11 @@ test::compute(){
|
|||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/invalid/int
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/invalid/int
|
||||||
test::one "Compute: invalid struct should fail" --exit=1 --stdout= \
|
test::one "Compute: invalid struct should fail" --exit=1 --stdout= \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/invalid/struct
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/invalid/struct
|
||||||
test::one "Compute: noop should succeed" --exit=0 --stdout='{"empty":{},"realempty":{},"withprops":{}}' \
|
test::one "Compute: noop should succeed" --exit=0 --stdout='{"empty":{},"realempty":{}}' \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/noop
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/noop
|
||||||
test::one "Compute: simple should succeed" --exit=0 --stdout="{}" \
|
test::one "Compute: simple should succeed" --exit=0 --stdout="{}" \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/simple
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/simple
|
||||||
test::one "Compute: unresolved should be ignored" --exit=0 --stdout='{"hello":"world"}' \
|
test::one "Compute: script with undefined values should not fail" --exit=0 --stdout='{"hello":"world"}' \
|
||||||
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/undefined_prop
|
"$dagger" "${DAGGER_BINARY_ARGS[@]}" compute "$d"/compute/undefined_prop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user