feat(server): auto-derive outputs on save + resolve inline steps

This commit is contained in:
2026-07-21 10:20:12 +01:00
parent 813f9e6fef
commit 7342c46d99
3 changed files with 105 additions and 3 deletions
+37
View File
@@ -0,0 +1,37 @@
package services
import (
"testing"
"github.com/mrhid6/vantage/server/internal/models"
)
func TestResolveInlineStep(t *testing.T) {
ref := models.WorkflowStepRef{
Order: 2,
OnFailure: "",
Inline: &models.WorkflowStep{
Name: "adhoc",
Interpreter: "bash",
Script: "echo hi",
SecretRefs: []string{"TOKEN"},
DeclaredInputs: []models.InputParam{
{Name: "REGION", Default: "eu"},
},
},
Inputs: map[string]string{"REGION": "us"},
}
rs := resolveInlineStep(ref)
if rs.Name != "adhoc" || rs.Script != "echo hi" || rs.Order != 2 {
t.Fatalf("bad resolve: %+v", rs)
}
if rs.OnFailure != "stop" {
t.Fatalf("want default on_failure=stop, got %q", rs.OnFailure)
}
if rs.Inputs["REGION"] != "us" {
t.Fatalf("want input override us, got %q", rs.Inputs["REGION"])
}
if len(rs.SecretRefs) != 1 || rs.SecretRefs[0] != "TOKEN" {
t.Fatalf("bad secret refs: %v", rs.SecretRefs)
}
}
@@ -83,6 +83,10 @@ func resolveSteps(wf *models.Workflow) ([]models.ResolvedStep, error) {
defer cancel()
out := make([]models.ResolvedStep, 0, len(wf.Steps))
for _, ref := range wf.Steps {
if ref.Inline != nil {
out = append(out, resolveInlineStep(ref))
continue
}
lib, err := getStep(ctx, ref.StepID)
if err != nil {
return nil, err
@@ -123,6 +127,35 @@ func resolveSteps(wf *models.Workflow) ([]models.ResolvedStep, error) {
return out, nil
}
// resolveInlineStep freezes an ad-hoc (inline) step ref into a ResolvedStep.
func resolveInlineStep(ref models.WorkflowStepRef) models.ResolvedStep {
in := ref.Inline
inputs := map[string]string{}
for _, p := range in.DeclaredInputs {
if ref.Inputs != nil {
if v, ok := ref.Inputs[p.Name]; ok {
inputs[p.Name] = v
continue
}
}
inputs[p.Name] = p.Default
}
onFailure := ref.OnFailure
if onFailure == "" {
onFailure = "stop"
}
return models.ResolvedStep{
Order: ref.Order,
Name: in.Name,
Interpreter: in.Interpreter,
Script: in.Script,
SecretRefs: in.SecretRefs,
OnFailure: onFailure,
MaxRetries: ref.MaxRetries,
Inputs: inputs,
}
}
// executeRun fans out one goroutine per server run and waits for all to finish.
func executeRun(runID string) {
run, err := GetRun(runID)
+35 -3
View File
@@ -60,8 +60,9 @@ func CreateStep(s models.WorkflowStep) (*models.WorkflowStep, error) {
s.StepID = uuid.New().String()
s.CreatedAt = time.Now()
s.UpdatedAt = s.CreatedAt
if s.DeclaredOutputs == nil {
s.DeclaredOutputs = []string{}
s.DeclaredOutputs = DeriveOutputs(s.Script)
if s.Source == "" {
s.Source = "user"
}
if s.SecretRefs == nil {
s.SecretRefs = []string{}
@@ -83,7 +84,7 @@ func UpdateStep(stepID string, s models.WorkflowStep) error {
"description": s.Description,
"interpreter": s.Interpreter,
"script": s.Script,
"declared_outputs": s.DeclaredOutputs,
"declared_outputs": DeriveOutputs(s.Script),
"declared_inputs": s.DeclaredInputs,
"secret_refs": s.SecretRefs,
"updated_at": time.Now(),
@@ -178,6 +179,10 @@ func CreateWorkflow(w models.Workflow) (*models.Workflow, error) {
if w.Steps == nil {
w.Steps = []models.WorkflowStepRef{}
}
if err := ValidateWorkflow(w); err != nil {
return nil, err
}
normalizeInlineSteps(&w)
if _, err := db.Col("workflows").InsertOne(ctx, w); err != nil {
return nil, err
}
@@ -187,6 +192,10 @@ func CreateWorkflow(w models.Workflow) (*models.Workflow, error) {
func UpdateWorkflow(id string, w models.Workflow) error {
ctx, cancel := wfCtx()
defer cancel()
if err := ValidateWorkflow(w); err != nil {
return err
}
normalizeInlineSteps(&w)
_, err := db.Col("workflows").UpdateOne(ctx, bson.M{"workflow_id": id}, bson.M{"$set": bson.M{
"name": w.Name,
"target_server_ids": w.TargetServerIDs,
@@ -196,6 +205,29 @@ func UpdateWorkflow(id string, w models.Workflow) error {
return err
}
// normalizeInlineSteps derives outputs for inline steps and strips fields that
// only belong to library steps.
func normalizeInlineSteps(w *models.Workflow) {
for i := range w.Steps {
in := w.Steps[i].Inline
if in == nil {
continue
}
in.DeclaredOutputs = DeriveOutputs(in.Script)
in.StepID = ""
in.Slug = ""
in.Source = ""
in.CreatedAt = time.Time{}
in.UpdatedAt = time.Time{}
if in.SecretRefs == nil {
in.SecretRefs = []string{}
}
if in.DeclaredInputs == nil {
in.DeclaredInputs = []models.InputParam{}
}
}
}
func DeleteWorkflow(id string) error {
ctx, cancel := wfCtx()
defer cancel()