2021-11-25 01:22:33 +01:00
package plan
import (
"context"
2022-03-11 00:11:55 +01:00
"errors"
2021-11-25 01:22:33 +01:00
"fmt"
2022-03-11 00:11:55 +01:00
"strings"
2021-11-25 01:22:33 +01:00
"cuelang.org/go/cue"
cueflow "cuelang.org/go/tools/flow"
"github.com/rs/zerolog/log"
"go.dagger.io/dagger/compiler"
2022-01-12 01:36:23 +01:00
"go.dagger.io/dagger/pkg"
2021-11-25 01:22:33 +01:00
"go.dagger.io/dagger/plan/task"
"go.dagger.io/dagger/plancontext"
"go.dagger.io/dagger/solver"
"go.opentelemetry.io/otel"
)
2022-03-03 03:33:15 +01:00
var (
2022-03-11 00:11:55 +01:00
ErrIncompatiblePlan = errors . New ( "attempting to load a dagger 0.1.0 project.\nPlease upgrade your config to be compatible with this version of dagger. Contact the Dagger team if you need help" )
ActionSelector = cue . Str ( "actions" )
ClientSelector = cue . Str ( "client" )
2022-02-22 22:04:54 +01:00
)
2021-11-25 01:22:33 +01:00
type Plan struct {
2022-01-04 19:54:50 +01:00
config Config
2021-11-25 01:22:33 +01:00
context * plancontext . Context
source * compiler . Value
2022-02-22 22:04:54 +01:00
action * Action
2021-11-25 01:22:33 +01:00
}
2022-01-04 19:54:50 +01:00
type Config struct {
2021-12-23 16:09:01 +01:00
Args [ ] string
With [ ] string
Target string
2022-01-04 19:54:50 +01:00
}
func Load ( ctx context . Context , cfg Config ) ( * Plan , error ) {
log . Ctx ( ctx ) . Debug ( ) . Interface ( "args" , cfg . Args ) . Msg ( "loading plan" )
2021-12-08 23:28:02 +01:00
2022-03-07 23:09:39 +01:00
_ , cueModExists := pkg . GetCueModParent ( )
if ! cueModExists {
return nil , fmt . Errorf ( "dagger project not found. Run `dagger project init`" )
2021-11-25 01:22:33 +01:00
}
2022-01-04 19:54:50 +01:00
v , err := compiler . Build ( "" , nil , cfg . Args ... )
2021-11-25 01:22:33 +01:00
if err != nil {
2022-03-11 00:11:55 +01:00
errstring := err . Error ( )
2022-03-11 01:09:21 +01:00
if strings . Contains ( errstring , "cannot find package" ) {
if strings . Contains ( errstring , "alpha.dagger.io" ) {
return nil , ErrIncompatiblePlan
} else if strings . Contains ( errstring , pkg . DaggerModule ) || strings . Contains ( errstring , pkg . UniverseModule ) {
return nil , fmt . Errorf ( "%w: running `dagger project update` may resolve this" , err )
}
2022-03-11 00:11:55 +01:00
}
2021-11-25 01:22:33 +01:00
return nil , err
}
2022-01-04 19:54:50 +01:00
for i , param := range cfg . With {
log . Ctx ( ctx ) . Debug ( ) . Interface ( "with" , param ) . Msg ( "compiling overlay" )
paramV , err := compiler . Compile ( fmt . Sprintf ( "with%v" , i ) , param )
if err != nil {
return nil , err
}
log . Ctx ( ctx ) . Debug ( ) . Interface ( "with" , param ) . Msg ( "filling overlay" )
2022-01-04 22:40:34 +01:00
fillErr := v . FillPath ( cue . MakePath ( ) , paramV )
2022-01-04 19:54:50 +01:00
if fillErr != nil {
return nil , fillErr
2022-01-04 18:23:21 +01:00
}
}
2021-11-30 21:48:09 +01:00
p := & Plan {
2022-01-04 19:54:50 +01:00
config : cfg ,
2021-11-25 01:22:33 +01:00
context : plancontext . New ( ) ,
source : v ,
2021-11-30 21:48:09 +01:00
}
2022-02-22 22:04:54 +01:00
p . fillAction ( )
2022-04-05 21:10:54 +02:00
// FIXME: `platform` field temporarily disabled
// if err := p.configPlatform(); err != nil {
// return nil, err
// }
2021-11-30 21:48:09 +01:00
2021-12-23 20:23:52 +01:00
if err := p . prepare ( ctx ) ; err != nil {
2021-12-23 15:27:03 +01:00
return nil , err
}
2021-11-30 21:48:09 +01:00
return p , nil
2021-11-25 01:22:33 +01:00
}
func ( p * Plan ) Context ( ) * plancontext . Context {
return p . context
}
func ( p * Plan ) Source ( ) * compiler . Value {
return p . source
}
2022-02-22 22:04:54 +01:00
func ( p * Plan ) Action ( ) * Action {
return p . action
}
2021-12-23 15:27:03 +01:00
// configPlatform load the platform specified in the context
// Buildkit will then run every operation using that platform
// If platform is not define, context keep default platform
2022-04-06 18:34:43 +02:00
// FIXME: `platform` field temporarily disabled until we decide the proper
// DX for multi-platform builds
2022-04-05 21:10:54 +02:00
// func (p *Plan) configPlatform() error {
// platformField := p.source.Lookup("platform")
// // Ignore if platform is not set in `#Plan`
// if !platformField.Exists() {
// return nil
// }
// // Convert platform to string
// platform, err := platformField.String()
// if err != nil {
// return err
// }
// // Set platform to context
// err = p.context.Platform.SetString(platform)
// if err != nil {
// return err
// }
// return nil
// }
2021-12-23 15:27:03 +01:00
2021-12-23 20:23:52 +01:00
// prepare executes the pre-run hooks of tasks
func ( p * Plan ) prepare ( ctx context . Context ) error {
flow := cueflow . New (
2022-01-20 02:19:37 +01:00
& cueflow . Config {
FindHiddenTasks : true ,
} ,
2021-12-23 20:23:52 +01:00
p . source . Cue ( ) ,
func ( flowVal cue . Value ) ( cueflow . Runner , error ) {
v := compiler . Wrap ( flowVal )
t , err := task . Lookup ( v )
if err != nil {
// Not a task
if err == task . ErrNotTask {
return nil , nil
}
return nil , err
}
r , ok := t . ( task . PreRunner )
if ! ok {
return nil , nil
}
2021-11-25 01:22:33 +01:00
2021-12-23 20:23:52 +01:00
return cueflow . RunnerFunc ( func ( t * cueflow . Task ) error {
ctx := t . Context ( )
lg := log . Ctx ( ctx ) . With ( ) . Str ( "task" , t . Path ( ) . String ( ) ) . Logger ( )
ctx = lg . WithContext ( ctx )
2021-11-25 01:22:33 +01:00
2021-12-23 20:23:52 +01:00
if err := r . PreRun ( ctx , p . context , compiler . Wrap ( t . Value ( ) ) ) ; err != nil {
return fmt . Errorf ( "%s: %w" , t . Path ( ) . String ( ) , err )
}
return nil
} ) , nil
} ,
)
return flow . Run ( ctx )
2021-11-25 01:22:33 +01:00
}
2022-03-03 03:33:15 +01:00
// Do executes an action in the plan
2022-03-23 23:02:17 +01:00
func ( p * Plan ) Do ( ctx context . Context , path cue . Path , s * solver . Solver ) error {
2021-11-25 01:22:33 +01:00
ctx , span := otel . Tracer ( "dagger" ) . Start ( ctx , "plan.Up" )
defer span . End ( )
2022-03-03 03:33:15 +01:00
r := NewRunner ( p . context , path , s )
return r . Run ( ctx , p . source )
2021-11-25 01:22:33 +01:00
}
2022-02-22 22:04:54 +01:00
func ( p * Plan ) fillAction ( ) {
cfg := & cueflow . Config {
FindHiddenTasks : true ,
2022-03-03 03:33:15 +01:00
Root : cue . MakePath ( ActionSelector ) ,
2022-02-22 22:04:54 +01:00
}
flow := cueflow . New (
cfg ,
p . source . Cue ( ) ,
noOpRunner ,
)
2022-03-09 03:34:47 +01:00
p . action = & Action {
Name : ActionSelector . String ( ) ,
Hidden : false ,
Path : cue . MakePath ( ActionSelector ) ,
Children : [ ] * Action { } ,
}
2022-03-03 03:33:15 +01:00
actions := p . source . LookupPath ( cue . MakePath ( ActionSelector ) )
2022-03-09 19:21:49 +01:00
if ! actions . Exists ( ) {
2022-03-09 03:34:47 +01:00
return
2022-02-22 22:04:54 +01:00
}
2022-03-11 20:32:29 +01:00
p . action . Documentation = actions . DocSummary ( )
2022-02-22 22:04:54 +01:00
tasks := flow . Tasks ( )
for _ , t := range tasks {
var q [ ] cue . Selector
prevAction := p . action
for _ , s := range t . Path ( ) . Selectors ( ) {
q = append ( q , s )
path := cue . MakePath ( q ... )
a := prevAction . FindByPath ( path )
if a == nil {
v := p . Source ( ) . LookupPath ( path )
a = & Action {
2022-03-11 20:32:29 +01:00
Name : s . String ( ) ,
Hidden : s . PkgPath ( ) != "" ,
Path : path ,
Documentation : v . DocSummary ( ) ,
Children : [ ] * Action { } ,
2022-02-22 22:04:54 +01:00
}
prevAction . AddChild ( a )
}
prevAction = a
}
}
}