From 71ae0d2cfc5230b19e108275b3d60975d0b2321b Mon Sep 17 00:00:00 2001 From: mr Date: Mon, 1 Jun 2026 16:45:05 +0200 Subject: [PATCH] inout change vars regime --- models/common/models/access_configuration.go | 58 +++++++++++- models/common/models/inoutputs.go | 12 +-- models/workflow/workflow.go | 95 +++++++++++++++++++- 3 files changed, 157 insertions(+), 8 deletions(-) diff --git a/models/common/models/access_configuration.go b/models/common/models/access_configuration.go index 463754d..3d73a5b 100644 --- a/models/common/models/access_configuration.go +++ b/models/common/models/access_configuration.go @@ -1,9 +1,65 @@ package models +import "sort" + +// SortedArgValues returns arg Values: readonly args first (sorted by Index), then non-readonly in order. +func SortedArgValues(args []Arg) []string { + var ro, nro []Arg + for _, a := range args { + if a.IsReadonly { + ro = append(ro, a) + } else { + nro = append(nro, a) + } + } + sort.Slice(ro, func(i, j int) bool { return ro[i].Index < ro[j].Index }) + out := make([]string, 0, len(args)) + for _, a := range ro { + out = append(out, a.Value) + } + for _, a := range nro { + out = append(out, a.Value) + } + return out +} + +// ReadonlyArgValues returns only the readonly arg values sorted by Index. +func ReadonlyArgValues(args []Arg) []string { + var ro []Arg + for _, a := range args { + if a.IsReadonly { + ro = append(ro, a) + } + } + sort.Slice(ro, func(i, j int) bool { return ro[i].Index < ro[j].Index }) + out := make([]string, 0, len(ro)) + for _, a := range ro { + out = append(out, a.Value) + } + return out +} + +// NonReadonlyArgValues returns only the non-readonly arg values in their order. +func NonReadonlyArgValues(args []Arg) []string { + out := make([]string, 0) + for _, a := range args { + if !a.IsReadonly { + out = append(out, a.Value) + } + } + return out +} + +type Arg struct { + Value string `json:"value,omitempty" bson:"value,omitempty"` // Image is the container image TEMPO + Index int `json:"index,omitempty" bson:"index,omitempty"` + IsReadonly bool `json:"is_readonly,omitempty" bson:"is_readonly,omitempty"` +} + type PathSource struct { Source string `json:"source,omitempty" bson:"source,omitempty"` // Image is the container image TEMPO IsReachable bool `json:"is_reachable,omitempty" bson:"is_reachable,omitempty"` - Args string `json:"args,omitempty" bson:"args,omitempty"` // Args is the container arguments + Args []Arg `json:"args,omitempty" bson:"args,omitempty"` // Args is the container arguments Volumes map[string]string `json:"volumes,omitempty" bson:"volumes,omitempty"` // Volumes is the container volumes } diff --git a/models/common/models/inoutputs.go b/models/common/models/inoutputs.go index ad499d6..94410b5 100644 --- a/models/common/models/inoutputs.go +++ b/models/common/models/inoutputs.go @@ -7,12 +7,12 @@ type Artifact struct { } type Param struct { - Name string `json:"name" bson:"name" validate:"required"` - Attr string `json:"attr,omitempty" bson:"attr,omitempty"` - Value string `json:"value,omitempty" bson:"value,omitempty"` - Origin string `json:"origin,omitempty" bson:"origin,omitempty"` - Readonly bool `json:"readonly" bson:"readonly" default:"true"` - Optionnal bool `json:"optionnal" bson:"optionnal" default:"true"` + Name string `json:"name" bson:"name" validate:"required"` + Attr string `json:"attr,omitempty" bson:"attr,omitempty"` + Value string `json:"value,omitempty" bson:"value,omitempty"` + Origin string `json:"origin,omitempty" bson:"origin,omitempty"` + Readonly bool `json:"readonly" bson:"readonly" default:"true"` + Required bool `json:"required" bson:"required" default:"true"` } type InOutputs struct { diff --git a/models/workflow/workflow.go b/models/workflow/workflow.go index 57eb2a6..61e78d3 100644 --- a/models/workflow/workflow.go +++ b/models/workflow/workflow.go @@ -959,7 +959,8 @@ const ( ViolationVariableNotFound ViolationType = "variable_not_found" ViolationMissingComputeUnit ViolationType = "missing_compute_unit" ViolationCycle ViolationType = "cycle" - ViolationMissingDataStorage ViolationType = "missing_data_storage" + ViolationMissingDataStorage ViolationType = "missing_data_storage" + ViolationRequiredOutputMissing ViolationType = "required_output_missing" // Warnings — non-blocking, reported for UX ViolationInvertedArrow ViolationType = "inverted_arrow" @@ -997,6 +998,7 @@ func (v IntegrityViolation) IsWarning() bool { return v.Severity == SeverityWarn func (w *Workflow) ValidateIntegrity() []IntegrityViolation { var violations []IntegrityViolation violations = append(violations, w.validateVariables()...) + violations = append(violations, w.validateRequiredInputs()...) violations = append(violations, w.validateComputeLinks()...) violations = append(violations, w.detectCycles()...) violations = append(violations, w.validateDataStorageLinks()...) @@ -1234,6 +1236,97 @@ func (w *Workflow) validateDataStorageLinks() []IntegrityViolation { return violations } +// validateRequiredInputs checks that for each processing node with a required +// input, every immediate predecessor outputs a parameter with that name. +// Mirrors the requiredOutputMissing check in oc-front's checkTopology(). +func (w *Workflow) validateRequiredInputs() []IntegrityViolation { + var violations []IntegrityViolation + + procIDs := map[string]struct{}{} + for id, item := range w.Graph.Items { + if w.Graph.IsProcessing(item) || w.Graph.IsService(item) || w.Graph.IsNativeTool(item) { + procIDs[id] = struct{}{} + } + } + + // Build direct predecessors map. + predecessors := map[string][]string{} + for id := range procIDs { + predecessors[id] = []string{} + } + for _, link := range w.Graph.Links { + src, dst := link.Source.ID, link.Destination.ID + _, srcIsProc := procIDs[src] + _, dstIsProc := procIDs[dst] + if !srcIsProc || !dstIsProc { + continue + } + dir := int64(0) + if link.Style != nil { + dir = link.Style.ArrowDirection + } + if dir == arrowDirectionBackward { + predecessors[src] = append(predecessors[src], dst) + } else { + predecessors[dst] = append(predecessors[dst], src) + } + } + + for id, reqInputs := range w.Inputs { + if _, isProc := procIDs[id]; !isProc { + continue + } + for _, inp := range reqInputs { + if !inp.Required || inp.Name == "" { + continue + } + for _, predID := range predecessors[id] { + if !w.nodeHasOutput(predID, inp.Name) { + violations = append(violations, IntegrityViolation{ + Severity: SeverityError, + Type: ViolationRequiredOutputMissing, + ItemIDs: []string{id, predID}, + Message: fmt.Sprintf( + `"%s" requires input "%s" but "%s" does not output it`, + w.itemName(id), inp.Name, w.itemName(predID), + ), + }) + } + } + } + } + return violations +} + +// nodeHasOutput returns true if the given node outputs a parameter named name, +// either via workflow-level outputs or its resource's own outputs. +func (w *Workflow) nodeHasOutput(nodeID, name string) bool { + for _, p := range w.Outputs[nodeID] { + if p.Name == name { + return true + } + } + item, ok := w.Graph.Items[nodeID] + if !ok { + return false + } + var res resources.ResourceInterface + switch { + case item.Processing != nil: + res = item.Processing + case item.Service != nil: + res = item.Service + } + if res != nil { + for _, p := range res.GetOutputs() { + if p.Name == name { + return true + } + } + } + return false +} + // detectInvertedArrows warns when a link between two processing nodes uses a // backward arrow direction — mirroring the invertedArrow warning in oc-front. func (w *Workflow) detectInvertedArrows() []IntegrityViolation {