Files
narratio/internal/config/effective_digest.go

96 lines
3.0 KiB
Go

package config
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"gopkg.in/yaml.v3"
)
const pipelineDefaultOwnershipSource = "default"
// PipelineProfileProvenance identifies the profile selected while resolving a
// pipeline. It contains only non-secret selection metadata.
type PipelineProfileProvenance struct {
Name string
Source string
OverlayPath string
}
// SelectedPipelineProfile reports the profile used to resolve cfg, if any.
func SelectedPipelineProfile(cfg *PipelineConfig) (*PipelineProfileProvenance, bool) {
if cfg == nil || cfg.resolution == nil || cfg.resolution.selectedProfile == nil {
return nil, false
}
selection := cfg.resolution.selectedProfile
return &PipelineProfileProvenance{Name: selection.name, Source: selection.source, OverlayPath: selection.overlayPath}, true
}
// EffectivePipelineDigest reports the deterministic secret-free digest for a
// resolved pipeline.
func EffectivePipelineDigest(cfg *PipelineConfig) string {
if cfg == nil || cfg.resolution == nil {
return ""
}
return cfg.resolution.effectiveDigest
}
func finalizePipelineResolution(cfg *PipelineConfig) error {
if cfg == nil || cfg.resolution == nil {
return fmt.Errorf("pipeline resolution metadata is required")
}
declared := make(map[string][]string, len(cfg.resolution.ownership))
for _, ownership := range cfg.resolution.ownership {
declared[ownership.path] = append([]string(nil), ownership.sources...)
}
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("serialize normalized effective pipeline: %w", err)
}
document, err := parseCompositionBytes("normalized effective pipeline", data)
if err != nil {
return err
}
records, err := document.semanticRecords()
if err != nil {
return err
}
ownership := make([]pipelineFieldOwnership, 0, len(records))
for _, record := range records {
sources := declared[record.Path]
if len(sources) == 0 {
sources = []string{pipelineDefaultOwnershipSource}
}
ownership = append(ownership, pipelineFieldOwnership{
path: record.Path, sources: append([]string(nil), sources...),
})
}
cfg.resolution.ownership = ownership
return recomputePipelineEffectiveDigest(cfg)
}
// recomputePipelineEffectiveDigest is the single package-owned hook for
// refreshing provenance after later resolution expands concrete pipeline
// values. Composition declarations and runtime provenance are not serialized.
func recomputePipelineEffectiveDigest(cfg *PipelineConfig) error {
if cfg == nil || cfg.resolution == nil {
return fmt.Errorf("pipeline resolution metadata is required")
}
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("serialize normalized effective pipeline: %w", err)
}
document, err := parseCompositionBytes("normalized effective pipeline", data)
if err != nil {
return err
}
canonical, err := document.canonicalDigestInput()
if err != nil {
return err
}
digest := sha256.Sum256(canonical)
cfg.resolution.effectiveDigest = hex.EncodeToString(digest[:])
return nil
}