Separate config normalization from validation
This commit is contained in:
@@ -2,7 +2,6 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -16,24 +15,19 @@ func validateNotarius(cfg *NotariusConfig, scriptorium *ScriptoriumConfig) error
|
||||
return nil
|
||||
}
|
||||
|
||||
binary := strings.TrimSpace(cfg.Binary)
|
||||
configPath := filepath.Clean(cfg.ConfigPath)
|
||||
pipelineID := strings.TrimSpace(cfg.PipelineID)
|
||||
timeout := strings.TrimSpace(cfg.Timeout)
|
||||
workingDirectory := filepath.Clean(cfg.WorkingDirectory)
|
||||
if binary == "" {
|
||||
if strings.TrimSpace(cfg.Binary) == "" {
|
||||
return fmt.Errorf("pipeline.notarius.binary is required when pipeline.notarius.enabled is true")
|
||||
}
|
||||
if strings.TrimSpace(cfg.ConfigPath) == "" {
|
||||
return fmt.Errorf("pipeline.notarius.config_path is required when pipeline.notarius.enabled is true")
|
||||
}
|
||||
if pipelineID == "" {
|
||||
if strings.TrimSpace(cfg.PipelineID) == "" {
|
||||
return fmt.Errorf("pipeline.notarius.pipeline_id is required when pipeline.notarius.enabled is true")
|
||||
}
|
||||
if timeout == "" {
|
||||
if strings.TrimSpace(cfg.Timeout) == "" {
|
||||
return fmt.Errorf("pipeline.notarius.timeout must be a valid duration")
|
||||
}
|
||||
if err := validateDuration("pipeline.notarius.timeout", timeout); err != nil {
|
||||
if err := validateDuration("pipeline.notarius.timeout", cfg.Timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(cfg.WorkingDirectory) == "" {
|
||||
@@ -46,22 +40,32 @@ func validateNotarius(cfg *NotariusConfig, scriptorium *ScriptoriumConfig) error
|
||||
return fmt.Errorf("pipeline.notarius.references must contain at most %d bindings", MaxNotariusReferenceBindings)
|
||||
}
|
||||
|
||||
references, err := normalizeNotariusReferences(cfg.References)
|
||||
if err != nil {
|
||||
if err := validateNotariusCanonicalScalars(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
outputs, err := normalizeNotariusOutputs(cfg.Outputs, scriptorium)
|
||||
if err != nil {
|
||||
if err := validateNotariusReferences(cfg.References); err != nil {
|
||||
return err
|
||||
}
|
||||
return validateNotariusOutputs(cfg.Outputs, scriptorium)
|
||||
}
|
||||
|
||||
cfg.Binary = binary
|
||||
cfg.ConfigPath = configPath
|
||||
cfg.PipelineID = pipelineID
|
||||
cfg.Timeout = timeout
|
||||
cfg.WorkingDirectory = workingDirectory
|
||||
cfg.References = references
|
||||
cfg.Outputs = outputs
|
||||
func validateNotariusCanonicalScalars(cfg *NotariusConfig) error {
|
||||
fields := []struct {
|
||||
name string
|
||||
got string
|
||||
want string
|
||||
}{
|
||||
{name: "binary", got: cfg.Binary, want: strings.TrimSpace(cfg.Binary)},
|
||||
{name: "config_path", got: cfg.ConfigPath, want: cleanNotariusPath(cfg.ConfigPath)},
|
||||
{name: "pipeline_id", got: cfg.PipelineID, want: strings.TrimSpace(cfg.PipelineID)},
|
||||
{name: "timeout", got: cfg.Timeout, want: strings.TrimSpace(cfg.Timeout)},
|
||||
{name: "working_directory", got: cfg.WorkingDirectory, want: cleanNotariusPath(cfg.WorkingDirectory)},
|
||||
}
|
||||
for _, field := range fields {
|
||||
if field.got != field.want {
|
||||
return fmt.Errorf("pipeline.notarius.%s must be normalized", field.name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -88,19 +92,12 @@ func normalizeNotariusReferences(references map[string]string) (map[string]strin
|
||||
owners[selector] = rawSelector
|
||||
|
||||
source := strings.TrimSpace(references[rawSelector])
|
||||
if source == "" {
|
||||
return nil, fmt.Errorf("pipeline.notarius.references.%s source is required", selector)
|
||||
}
|
||||
if _, ok := artifactpolicy.DescribePreparedInputSource(source); !ok {
|
||||
return nil, fmt.Errorf("pipeline.notarius.references.%s source %q is not a supported prepared input source", selector, source)
|
||||
}
|
||||
normalized[selector] = source
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func normalizeNotariusOutputs(outputs map[string]NotariusOutputConfig, scriptorium *ScriptoriumConfig) (map[string]NotariusOutputConfig, error) {
|
||||
reservedSources := notariusReservedSources(scriptorium)
|
||||
func normalizeNotariusOutputs(outputs map[string]NotariusOutputConfig) (map[string]NotariusOutputConfig, error) {
|
||||
keys := make([]string, 0, len(outputs))
|
||||
for key := range outputs {
|
||||
keys = append(keys, key)
|
||||
@@ -109,20 +106,13 @@ func normalizeNotariusOutputs(outputs map[string]NotariusOutputConfig, scriptori
|
||||
|
||||
normalized := make(map[string]NotariusOutputConfig, len(outputs))
|
||||
sourceOwners := make(map[string]string, len(outputs))
|
||||
laneOwners := make(map[string]string, len(outputs))
|
||||
for _, rawKey := range keys {
|
||||
output := outputs[rawKey]
|
||||
key := strings.TrimSpace(rawKey)
|
||||
if !artifactpolicy.IsConfiguredKey(key) {
|
||||
return nil, fmt.Errorf("pipeline.notarius.outputs keys must match ^[a-z][a-z0-9_]*$")
|
||||
}
|
||||
sourceID := artifactpolicy.ExtractionSourceID(key)
|
||||
if previousKey, ok := sourceOwners[sourceID]; ok {
|
||||
return nil, fmt.Errorf("pipeline.notarius.outputs keys %q and %q normalize to duplicate source %q", previousKey, rawKey, sourceID)
|
||||
}
|
||||
if owner, ok := reservedSources[sourceID]; ok {
|
||||
return nil, fmt.Errorf("pipeline.notarius.outputs.%s source %q collides with %s", key, sourceID, owner)
|
||||
}
|
||||
sourceOwners[sourceID] = rawKey
|
||||
|
||||
output.LaneID = strings.TrimSpace(output.LaneID)
|
||||
@@ -130,28 +120,89 @@ func normalizeNotariusOutputs(outputs map[string]NotariusOutputConfig, scriptori
|
||||
output.SchemaID = strings.TrimSpace(output.SchemaID)
|
||||
output.SchemaVersion = strings.TrimSpace(output.SchemaVersion)
|
||||
output.ModuleKey = strings.TrimSpace(output.ModuleKey)
|
||||
prefix := "pipeline.notarius.outputs." + key
|
||||
if output.LaneID == "" {
|
||||
return nil, fmt.Errorf("%s.lane_id is required", prefix)
|
||||
}
|
||||
if previousKey, ok := laneOwners[output.LaneID]; ok {
|
||||
return nil, fmt.Errorf("%s.lane_id %q duplicates pipeline.notarius.outputs.%s.lane_id", prefix, output.LaneID, previousKey)
|
||||
}
|
||||
laneOwners[output.LaneID] = key
|
||||
if output.MediaType == "" {
|
||||
return nil, fmt.Errorf("%s.media_type is required", prefix)
|
||||
}
|
||||
if output.SchemaID == "" {
|
||||
return nil, fmt.Errorf("%s.schema_id is required", prefix)
|
||||
}
|
||||
if output.SchemaVersion == "" {
|
||||
return nil, fmt.Errorf("%s.schema_version is required", prefix)
|
||||
}
|
||||
normalized[key] = output
|
||||
}
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
func validateNotariusReferences(references map[string]string) error {
|
||||
selectors := make([]string, 0, len(references))
|
||||
for selector := range references {
|
||||
selectors = append(selectors, selector)
|
||||
}
|
||||
sort.Strings(selectors)
|
||||
for _, selector := range selectors {
|
||||
canonical, err := notariusref.NormalizeSelector(selector)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pipeline.notarius.references selector %q is invalid: %w", selector, err)
|
||||
}
|
||||
if canonical != selector {
|
||||
return fmt.Errorf("pipeline.notarius.references selector %q must be normalized", selector)
|
||||
}
|
||||
source := references[selector]
|
||||
if strings.TrimSpace(source) == "" {
|
||||
return fmt.Errorf("pipeline.notarius.references.%s source is required", selector)
|
||||
}
|
||||
if source != strings.TrimSpace(source) {
|
||||
return fmt.Errorf("pipeline.notarius.references.%s source must be normalized", selector)
|
||||
}
|
||||
if _, ok := artifactpolicy.DescribePreparedInputSource(source); !ok {
|
||||
return fmt.Errorf("pipeline.notarius.references.%s source %q is not a supported prepared input source", selector, source)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNotariusOutputs(outputs map[string]NotariusOutputConfig, scriptorium *ScriptoriumConfig) error {
|
||||
reservedSources := notariusReservedSources(scriptorium)
|
||||
keys := make([]string, 0, len(outputs))
|
||||
for key := range outputs {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
laneOwners := make(map[string]string, len(outputs))
|
||||
for _, key := range keys {
|
||||
output := outputs[key]
|
||||
if strings.TrimSpace(key) != key || !artifactpolicy.IsConfiguredKey(key) {
|
||||
return fmt.Errorf("pipeline.notarius.outputs keys must match ^[a-z][a-z0-9_]*$")
|
||||
}
|
||||
sourceID := artifactpolicy.ExtractionSourceID(key)
|
||||
if owner, ok := reservedSources[sourceID]; ok {
|
||||
return fmt.Errorf("pipeline.notarius.outputs.%s source %q collides with %s", key, sourceID, owner)
|
||||
}
|
||||
prefix := "pipeline.notarius.outputs." + key
|
||||
fields := []struct{ name, value string }{
|
||||
{name: "lane_id", value: output.LaneID},
|
||||
{name: "media_type", value: output.MediaType},
|
||||
{name: "schema_id", value: output.SchemaID},
|
||||
{name: "schema_version", value: output.SchemaVersion},
|
||||
{name: "module_key", value: output.ModuleKey},
|
||||
}
|
||||
for _, field := range fields {
|
||||
if field.value != strings.TrimSpace(field.value) {
|
||||
return fmt.Errorf("%s.%s must be normalized", prefix, field.name)
|
||||
}
|
||||
}
|
||||
if output.LaneID == "" {
|
||||
return fmt.Errorf("%s.lane_id is required", prefix)
|
||||
}
|
||||
if previousKey, ok := laneOwners[output.LaneID]; ok {
|
||||
return fmt.Errorf("%s.lane_id %q duplicates pipeline.notarius.outputs.%s.lane_id", prefix, output.LaneID, previousKey)
|
||||
}
|
||||
laneOwners[output.LaneID] = key
|
||||
if output.MediaType == "" {
|
||||
return fmt.Errorf("%s.media_type is required", prefix)
|
||||
}
|
||||
if output.SchemaID == "" {
|
||||
return fmt.Errorf("%s.schema_id is required", prefix)
|
||||
}
|
||||
if output.SchemaVersion == "" {
|
||||
return fmt.Errorf("%s.schema_version is required", prefix)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func notariusReservedSources(scriptorium *ScriptoriumConfig) map[string]string {
|
||||
reserved := make(map[string]string)
|
||||
for _, spec := range artifactmodel.RuntimeTranscriptArtifacts() {
|
||||
|
||||
Reference in New Issue
Block a user