This repository has no description
1package dummy
2
3import (
4 "context"
5 "fmt"
6 "log/slog"
7 "time"
8
9 "gopkg.in/yaml.v3"
10 "tangled.org/core/api/tangled"
11 "tangled.org/core/spindle/engine"
12 "tangled.org/core/spindle/models"
13 "tangled.org/core/spindle/secrets"
14)
15
16// DummyEngine is a no-op engine that logs all lifecycle events via slog and writes
17// step output to the workflow logger. Useful for testing pipeline plumbing
18// without a real execution backend.
19type DummyEngine struct {
20 l *slog.Logger
21 StepDelay time.Duration
22}
23
24func New(l *slog.Logger) *DummyEngine {
25 return &DummyEngine{l: l.With("engine", "dummy")}
26}
27
28type Step struct {
29 name string
30 kind models.StepKind
31 command string
32}
33
34func (s Step) Name() string { return s.name }
35func (s Step) Command() string { return s.command }
36func (s Step) Kind() models.StepKind { return s.kind }
37
38func (e *DummyEngine) InitWorkflow(twf tangled.Pipeline_Workflow, _ tangled.Pipeline) (*models.Workflow, error) {
39 dwf := &struct {
40 Steps []struct {
41 Name string `yaml:"name"`
42 Command string `yaml:"command"`
43 } `yaml:"steps"`
44 Environment map[string]string `yaml:"environment"`
45 }{}
46
47 if err := yaml.Unmarshal([]byte(twf.Raw), dwf); err != nil {
48 return nil, err
49 }
50
51 wf := &models.Workflow{
52 Name: twf.Name,
53 Environment: dwf.Environment,
54 }
55 for _, ds := range dwf.Steps {
56 wf.Steps = append(wf.Steps, Step{
57 name: ds.Name,
58 kind: models.StepKindUser,
59 command: ds.Command,
60 })
61 }
62
63 e.l.Info("workflow initialised", "name", twf.Name, "steps", len(wf.Steps))
64 return wf, nil
65}
66
67func (e *DummyEngine) SetupWorkflow(_ context.Context, wid models.WorkflowId, wf *models.Workflow, wfLogger models.WorkflowLogger) error {
68 e.l.Info("setting up workflow", "wid", wid)
69
70 setupStep := Step{name: "dummy setup", kind: models.StepKindSystem}
71 const setupIdx = -1
72
73 wfLogger.ControlWriter(setupIdx, setupStep, models.StepStatusStart).Write([]byte{0})
74 defer wfLogger.ControlWriter(setupIdx, setupStep, models.StepStatusEnd).Write([]byte{0})
75
76 fmt.Fprintf(wfLogger.DataWriter(setupIdx, "stdout"), "dummy engine: workflow %q ready", wf.Name)
77 return nil
78}
79
80func (e *DummyEngine) WorkflowTimeout() time.Duration {
81 return 5 * time.Minute
82}
83
84// no capacity limit, so always a no-op slot regardless of mode
85func (e *DummyEngine) AcquireWorkflowSlot(_ context.Context, _ models.WorkflowId, _ *models.Workflow, _ engine.AcquireMode) (engine.WorkflowSlot, error) {
86 return engine.NoopSlot{}, nil
87}
88
89func (e *DummyEngine) DestroyWorkflow(_ context.Context, wid models.WorkflowId) error {
90 e.l.Info("destroying workflow", "wid", wid)
91 return nil
92}
93
94func (e *DummyEngine) RunStep(ctx context.Context, wid models.WorkflowId, w *models.Workflow, idx int, _ []secrets.UnlockedSecret, wfLogger models.WorkflowLogger) error {
95 step := w.Steps[idx]
96 e.l.Info("running step", "wid", wid, "step", step.Name(), "command", step.Command())
97 fmt.Fprintf(wfLogger.DataWriter(idx, "stdout"), "$ %s", step.Command())
98 if e.StepDelay > 0 {
99 select {
100 case <-ctx.Done():
101 return ctx.Err()
102 case <-time.After(e.StepDelay):
103 }
104 }
105 return nil
106}