2021-11-25 01:22:33 +01:00
|
|
|
package task
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2022-03-07 14:12:39 +01:00
|
|
|
"strings"
|
2021-11-25 01:22:33 +01:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"cuelang.org/go/cue"
|
|
|
|
"go.dagger.io/dagger/compiler"
|
2022-01-11 21:40:02 +01:00
|
|
|
"go.dagger.io/dagger/pkg"
|
2021-11-25 01:22:33 +01:00
|
|
|
"go.dagger.io/dagger/plancontext"
|
|
|
|
"go.dagger.io/dagger/solver"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrNotTask = errors.New("not a task")
|
|
|
|
tasks sync.Map
|
2021-12-20 19:00:43 +01:00
|
|
|
typePath = cue.MakePath(
|
|
|
|
cue.Str("$dagger"),
|
|
|
|
cue.Str("task"),
|
2022-02-16 19:37:16 +01:00
|
|
|
cue.Hid("_name", pkg.DaggerPackage))
|
2022-03-07 14:12:39 +01:00
|
|
|
lookups = []LookupFunc{
|
|
|
|
defaultLookup,
|
|
|
|
pathLookup,
|
|
|
|
}
|
2021-11-25 01:22:33 +01:00
|
|
|
)
|
|
|
|
|
2022-02-19 00:00:38 +01:00
|
|
|
// State is the state of the task.
|
|
|
|
type State string
|
|
|
|
|
|
|
|
const (
|
|
|
|
StateComputing = State("computing")
|
|
|
|
StateCanceled = State("canceled")
|
|
|
|
StateFailed = State("failed")
|
|
|
|
StateCompleted = State("completed")
|
|
|
|
)
|
|
|
|
|
2021-11-25 01:22:33 +01:00
|
|
|
type NewFunc func() Task
|
2022-03-07 14:12:39 +01:00
|
|
|
type LookupFunc func(*compiler.Value) (Task, error)
|
2021-11-25 01:22:33 +01:00
|
|
|
|
|
|
|
type Task interface {
|
|
|
|
Run(ctx context.Context, pctx *plancontext.Context, s solver.Solver, v *compiler.Value) (*compiler.Value, error)
|
|
|
|
}
|
|
|
|
|
2021-12-23 20:23:52 +01:00
|
|
|
type PreRunner interface {
|
|
|
|
Task
|
|
|
|
|
|
|
|
PreRun(ctx context.Context, pctx *plancontext.Context, v *compiler.Value) error
|
|
|
|
}
|
|
|
|
|
2021-11-25 01:22:33 +01:00
|
|
|
// Register a task type and initializer
|
|
|
|
func Register(typ string, f NewFunc) {
|
|
|
|
tasks.Store(typ, f)
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new Task of the given type.
|
|
|
|
func New(typ string) Task {
|
|
|
|
v, ok := tasks.Load(typ)
|
|
|
|
if !ok {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
fn := v.(NewFunc)
|
|
|
|
return fn()
|
|
|
|
}
|
|
|
|
|
|
|
|
func Lookup(v *compiler.Value) (Task, error) {
|
2022-03-07 14:12:39 +01:00
|
|
|
for _, lookup := range lookups {
|
|
|
|
t, err := lookup(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if t != nil {
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, ErrNotTask
|
|
|
|
}
|
|
|
|
|
|
|
|
func defaultLookup(v *compiler.Value) (Task, error) {
|
2021-11-25 01:22:33 +01:00
|
|
|
if v.Kind() != cue.StructKind {
|
2022-03-07 14:12:39 +01:00
|
|
|
return nil, nil
|
2021-11-25 01:22:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
typ := v.LookupPath(typePath)
|
|
|
|
if !typ.Exists() {
|
2022-03-07 14:12:39 +01:00
|
|
|
return nil, nil
|
2021-11-25 01:22:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
typeString, err := typ.String()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
t := New(typeString)
|
|
|
|
if t == nil {
|
|
|
|
return nil, fmt.Errorf("unknown type %q", typeString)
|
|
|
|
}
|
2022-03-07 14:12:39 +01:00
|
|
|
|
2021-11-25 01:22:33 +01:00
|
|
|
return t, nil
|
|
|
|
}
|
2022-03-07 14:12:39 +01:00
|
|
|
|
|
|
|
func pathLookup(v *compiler.Value) (Task, error) {
|
|
|
|
selectors := v.Path().Selectors()
|
|
|
|
|
|
|
|
// The `actions` field won't have any path based tasks since it's in user land
|
|
|
|
if len(selectors) == 0 || selectors[0].String() == "actions" {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Try an exact match first
|
|
|
|
if t := New(v.Path().String()); t != nil {
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: is there a way to avoid having to loop here?
|
|
|
|
var t Task
|
|
|
|
tasks.Range(func(key, value interface{}) bool {
|
|
|
|
if matchPathMask(selectors, key.(string)) {
|
|
|
|
fn := value.(NewFunc)
|
|
|
|
t = fn()
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return t, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func matchPathMask(sels []cue.Selector, mask string) bool {
|
|
|
|
parts := strings.Split(mask, ".")
|
|
|
|
if len(sels) != len(parts) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i, sel := range sels {
|
|
|
|
// use a '*' in a path mask part to match any selector
|
|
|
|
if parts[i] == "*" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if sel.String() != parts[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|