Skip to content

Commit 662b462

Browse files
committed
Add Range Validation core logic
1 parent ace50e1 commit 662b462

4 files changed

Lines changed: 103 additions & 2 deletions

File tree

community/modules/scheduler/schedmd-slurm-gcp-v6-controller/metadata.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,13 @@ spec:
2121
- storage.googleapis.com
2222
ghpc:
2323
validators:
24+
- validator: range
25+
inputs:
26+
vars: [static_ips]
27+
max: 1
28+
error_message: "The Slurm modules supports 0 or 1 static IPs on controller instance."
2429
- validator: regex
2530
inputs:
2631
vars: [slurm_cluster_name]
2732
pattern: "^[a-z](?:[a-z0-9]{0,9})$"
28-
error_message: "'slurm_cluster_name' must contain only lowercase letters and numbers, start with a letter, and be 1-10 characters long."
33+
error_message: "'slurm_cluster_name' must contain abc only lowercase letters and numbers, start with a letter, and be 1-10 characters long."

pkg/validators/metadata_validator_helpers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,24 @@ type Target struct {
123123
IsBlueprint bool // true if came from blueprint vars, false if module.settings
124124
}
125125

126+
// parseIntInput retrieves an integer value from a map by key.
127+
// If the key is missing, it returns the defaultVal.
128+
func parseIntInput(inputs map[string]interface{}, key string, defaultVal int) (int, error) {
129+
v, ok := inputs[key]
130+
if !ok {
131+
return defaultVal, nil
132+
}
133+
134+
switch t := v.(type) {
135+
case int:
136+
return t, nil
137+
case float64:
138+
return int(t), nil
139+
default:
140+
return 0, fmt.Errorf("'%s' must be an integer, not %T", key, v)
141+
}
142+
}
143+
126144
// processModuleSettings processes a list of names interpreted as module settings.
127145
func processModuleSettings(bp config.Blueprint, mod config.Module, group config.Group, modIdx int, list []string, optional bool, handler func(Target) error) error {
128146
for _, s := range list {

pkg/validators/metadata_validators.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ package validators
1313

1414
import (
1515
"fmt"
16+
"math"
1617
"regexp"
1718
"strings"
1819

@@ -192,3 +193,79 @@ func (v *AllowedEnumValidator) Validate(
192193
return v.checkValues(t.Values, t.Path, allowedSet, allowedList, caseSensitive, allowNull, rule.ErrorMessage)
193194
})
194195
}
196+
197+
// RangeValidator implements the Validator interface for 'range' type.
198+
type RangeValidator struct{}
199+
200+
// Validate checks if the variables specified in the rule match the provided range pattern.
201+
// This function focuses on the predicate and uses IterateRuleTargets from targets.go to resolve targets.
202+
func (r *RangeValidator) Validate(
203+
bp config.Blueprint,
204+
mod config.Module,
205+
rule modulereader.ValidationRule,
206+
group config.Group,
207+
modIdx int) error {
208+
209+
modPath := config.Root.Groups.At(bp.GroupIndex(group.Name)).Modules.At(modIdx).Source
210+
// Extract min
211+
212+
min, err := parseIntInput(rule.Inputs, "min", math.MinInt)
213+
if err != nil {
214+
return config.BpError{
215+
Err: fmt.Errorf(
216+
"validation rule for module %q: %v", mod.ID, err),
217+
Path: modPath,
218+
}
219+
}
220+
// Extract max
221+
max, err := parseIntInput(rule.Inputs, "max", math.MaxInt)
222+
if err != nil {
223+
return config.BpError{
224+
Err: fmt.Errorf(
225+
"validation rule for module %q: %v", mod.ID, err),
226+
Path: modPath,
227+
}
228+
}
229+
230+
// helper: validate flattened cty.Values against range, returning first error
231+
validateValues := func(values []cty.Value, path config.Path) error {
232+
for _, val := range values {
233+
if val.IsNull() || !val.IsKnown() {
234+
continue
235+
}
236+
var checkValue int
237+
var isList bool
238+
if val.Type().IsListType() {
239+
checkValue = val.LengthInt()
240+
isList = true
241+
} else if val.Type() == cty.Number {
242+
f, _ := val.AsBigFloat().Float64()
243+
checkValue = int(f)
244+
} else {
245+
return config.BpError{
246+
Err: fmt.Errorf("range validator only supports numbers or lists, not %s", val.Type().FriendlyName()),
247+
Path: path,
248+
}
249+
}
250+
effectiveMin := min
251+
if isList {
252+
effectiveMin = 0 // Default min is 0 for lists if not specified
253+
}
254+
255+
if checkValue < effectiveMin || checkValue > max {
256+
msg := rule.ErrorMessage
257+
if msg == "" {
258+
msg = fmt.Sprintf("value %d is out of range [%d, %d]", checkValue, effectiveMin, max)
259+
}
260+
return config.BpError{Err: fmt.Errorf("error: %s", msg), Path: path}
261+
}
262+
}
263+
return nil
264+
}
265+
266+
// iterate targets using shared logic
267+
err = IterateRuleTargets(bp, mod, rule, group, modIdx, func(t Target) error {
268+
return validateValues(t.Values, t.Path)
269+
})
270+
return err
271+
}

pkg/validators/registry.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package validators
1616

1717
// Registry maps validation type strings to their corresponding validator implementation.
1818
var Registry = map[string]RuleValidator{
19-
"regex": &RegexValidator{},
19+
"regex": &RegexValidator{},
2020
"allowed_enum": &AllowedEnumValidator{},
21+
"range": &RangeValidator{},
2122
}

0 commit comments

Comments
 (0)