This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
dagger/plan/plan.go
Marcos Lilljedahl 34c7a2ff12 Automatically set target platform based on client architecture
Set the default platform based on the client's OS and architecture. This
function is the same one that buildkit uses (https://github.com/moby/buildkit/blob/master/frontend/dockerfile/builder/build.go#L100-L102) to set the default build target platform

Signed-off-by: Marcos Lilljedahl <marcosnils@gmail.com>
2022-04-05 15:30:11 -03:00

228 lines
5.0 KiB
Go

package plan
import (
"context"
"errors"
"fmt"
"strings"
"cuelang.org/go/cue"
cueflow "cuelang.org/go/tools/flow"
"github.com/rs/zerolog/log"
"go.dagger.io/dagger/compiler"
"go.dagger.io/dagger/pkg"
"go.dagger.io/dagger/plan/task"
"go.dagger.io/dagger/plancontext"
"go.dagger.io/dagger/solver"
"go.opentelemetry.io/otel"
)
var (
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")
)
type Plan struct {
config Config
context *plancontext.Context
source *compiler.Value
action *Action
}
type Config struct {
Args []string
With []string
Target string
}
func Load(ctx context.Context, cfg Config) (*Plan, error) {
log.Ctx(ctx).Debug().Interface("args", cfg.Args).Msg("loading plan")
_, cueModExists := pkg.GetCueModParent()
if !cueModExists {
return nil, fmt.Errorf("dagger project not found. Run `dagger project init`")
}
v, err := compiler.Build("", nil, cfg.Args...)
if err != nil {
errstring := err.Error()
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)
}
}
return nil, err
}
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")
fillErr := v.FillPath(cue.MakePath(), paramV)
if fillErr != nil {
return nil, fillErr
}
}
p := &Plan{
config: cfg,
context: plancontext.New(),
source: v,
}
p.fillAction()
if err := p.configPlatform(); err != nil {
return nil, err
}
if err := p.prepare(ctx); err != nil {
return nil, err
}
return p, nil
}
func (p *Plan) Context() *plancontext.Context {
return p.context
}
func (p *Plan) Source() *compiler.Value {
return p.source
}
func (p *Plan) Action() *Action {
return p.action
}
// 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
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
}
// prepare executes the pre-run hooks of tasks
func (p *Plan) prepare(ctx context.Context) error {
flow := cueflow.New(
&cueflow.Config{
FindHiddenTasks: true,
},
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
}
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)
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)
}
// Do executes an action in the plan
func (p *Plan) Do(ctx context.Context, path cue.Path, s *solver.Solver) error {
ctx, span := otel.Tracer("dagger").Start(ctx, "plan.Up")
defer span.End()
r := NewRunner(p.context, path, s)
return r.Run(ctx, p.source)
}
func (p *Plan) fillAction() {
cfg := &cueflow.Config{
FindHiddenTasks: true,
Root: cue.MakePath(ActionSelector),
}
flow := cueflow.New(
cfg,
p.source.Cue(),
noOpRunner,
)
p.action = &Action{
Name: ActionSelector.String(),
Hidden: false,
Path: cue.MakePath(ActionSelector),
Children: []*Action{},
}
actions := p.source.LookupPath(cue.MakePath(ActionSelector))
if !actions.Exists() {
return
}
p.action.Documentation = actions.DocSummary()
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{
Name: s.String(),
Hidden: s.PkgPath() != "",
Path: path,
Documentation: v.DocSummary(),
Children: []*Action{},
}
prevAction.AddChild(a)
}
prevAction = a
}
}
}