201 lines
7.6 KiB
Go
201 lines
7.6 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/framework/contracts"
|
|
)
|
|
|
|
type CheckpointFingerprint struct {
|
|
Name string `json:"name"`
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
type CheckpointRecorder interface {
|
|
SourceRunning(moduleKey string) error
|
|
SourceSucceeded(moduleKey string, doc *source.SourceDocument) error
|
|
SourceFailed(moduleKey string, err error) error
|
|
ExtractRunning(laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
|
ExtractSucceeded(laneID string, moduleKey string, dependencies []CheckpointFingerprint, outputs []CheckpointArtifact, rejected []contracts.RejectedOutput, warnings []contracts.Warning) error
|
|
ExtractFailed(laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
|
MergeRunning(laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
|
MergeSucceeded(laneID string, moduleKey string, dependencies []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error
|
|
MergeRejected(laneID string, moduleKey string, dependencies []CheckpointFingerprint, rejected contracts.RejectedOutput) error
|
|
MergeFailed(laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
|
NormalizeRunning(laneID string, moduleKey string, dependencies []CheckpointFingerprint) error
|
|
NormalizeSucceeded(laneID string, moduleKey string, dependencies []CheckpointFingerprint, output CheckpointArtifact, warnings []contracts.Warning) error
|
|
NormalizeRejected(laneID string, moduleKey string, dependencies []CheckpointFingerprint, rejected contracts.RejectedOutput) error
|
|
NormalizeFailed(laneID string, moduleKey string, dependencies []CheckpointFingerprint, err error) error
|
|
}
|
|
|
|
type CheckpointDecision struct {
|
|
Reused bool `json:"reused"`
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
type CheckpointEvent struct {
|
|
Stage string `json:"stage"`
|
|
LaneID string `json:"lane_id,omitempty"`
|
|
ModuleKey string `json:"module_key,omitempty"`
|
|
Action string `json:"action"`
|
|
Reason string `json:"reason,omitempty"`
|
|
}
|
|
|
|
type SourceCheckpoint struct {
|
|
Document *source.SourceDocument
|
|
}
|
|
|
|
// CheckpointArtifact is the durable, domain-neutral value stored at a lane
|
|
// checkpoint boundary.
|
|
type CheckpointArtifact struct {
|
|
LaneID string
|
|
ModuleKey string
|
|
SourceID string
|
|
ChunkID string
|
|
ChunkIndex int
|
|
ChunkRef source.SourceRef
|
|
Artifact contracts.SerializedArtifact
|
|
SchemaDigest string
|
|
}
|
|
|
|
type ExtractCheckpoint struct {
|
|
Outputs []CheckpointArtifact
|
|
Rejected []contracts.RejectedOutput
|
|
Warnings []contracts.Warning
|
|
}
|
|
|
|
type MergeCheckpoint struct {
|
|
Output CheckpointArtifact
|
|
Warnings []contracts.Warning
|
|
}
|
|
type NormalizeCheckpoint struct {
|
|
Output CheckpointArtifact
|
|
Warnings []contracts.Warning
|
|
}
|
|
|
|
type CheckpointLoader interface {
|
|
Enabled() bool
|
|
Source(moduleKey string) (SourceCheckpoint, CheckpointDecision)
|
|
Extract(laneID string, moduleKey string, dependencies []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision)
|
|
Merge(laneID string, moduleKey string, dependencies []CheckpointFingerprint) (MergeCheckpoint, CheckpointDecision)
|
|
Normalize(laneID string, moduleKey string, dependencies []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision)
|
|
}
|
|
|
|
type noopCheckpointRecorder struct{}
|
|
type noopCheckpointLoader struct{}
|
|
|
|
func NoopCheckpointRecorder() CheckpointRecorder { return noopCheckpointRecorder{} }
|
|
func NoopCheckpointLoader() CheckpointLoader { return noopCheckpointLoader{} }
|
|
|
|
func (noopCheckpointRecorder) SourceRunning(string) error { return nil }
|
|
func (noopCheckpointRecorder) SourceSucceeded(string, *source.SourceDocument) error { return nil }
|
|
func (noopCheckpointRecorder) SourceFailed(string, error) error { return nil }
|
|
func (noopCheckpointRecorder) ExtractRunning(string, string, []CheckpointFingerprint) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) ExtractSucceeded(string, string, []CheckpointFingerprint, []CheckpointArtifact, []contracts.RejectedOutput, []contracts.Warning) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) ExtractFailed(string, string, []CheckpointFingerprint, error) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) MergeRunning(string, string, []CheckpointFingerprint) error { return nil }
|
|
func (noopCheckpointRecorder) MergeSucceeded(string, string, []CheckpointFingerprint, CheckpointArtifact, []contracts.Warning) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) MergeRejected(string, string, []CheckpointFingerprint, contracts.RejectedOutput) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) MergeFailed(string, string, []CheckpointFingerprint, error) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeRunning(string, string, []CheckpointFingerprint) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeSucceeded(string, string, []CheckpointFingerprint, CheckpointArtifact, []contracts.Warning) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeRejected(string, string, []CheckpointFingerprint, contracts.RejectedOutput) error {
|
|
return nil
|
|
}
|
|
func (noopCheckpointRecorder) NormalizeFailed(string, string, []CheckpointFingerprint, error) error {
|
|
return nil
|
|
}
|
|
|
|
func (noopCheckpointLoader) Enabled() bool { return false }
|
|
func (noopCheckpointLoader) Source(string) (SourceCheckpoint, CheckpointDecision) {
|
|
return SourceCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
|
}
|
|
func (noopCheckpointLoader) Extract(string, string, []CheckpointFingerprint) (ExtractCheckpoint, CheckpointDecision) {
|
|
return ExtractCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
|
}
|
|
func (noopCheckpointLoader) Merge(string, string, []CheckpointFingerprint) (MergeCheckpoint, CheckpointDecision) {
|
|
return MergeCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
|
}
|
|
func (noopCheckpointLoader) Normalize(string, string, []CheckpointFingerprint) (NormalizeCheckpoint, CheckpointDecision) {
|
|
return NormalizeCheckpoint{}, CheckpointDecision{Reason: "checkpoint loading disabled"}
|
|
}
|
|
|
|
func digestFingerprints(name string, digest string) []CheckpointFingerprint {
|
|
digest = strings.TrimSpace(digest)
|
|
if digest == "" {
|
|
return nil
|
|
}
|
|
return []CheckpointFingerprint{{Name: name, Value: digest}}
|
|
}
|
|
|
|
func joinedChunkDigest(chunks []source.Chunk) (string, error) {
|
|
if len(chunks) == 0 {
|
|
return "", nil
|
|
}
|
|
values := make([]string, 0, len(chunks))
|
|
for _, chunk := range chunks {
|
|
digest, err := source.DigestChunk(chunk)
|
|
if err != nil {
|
|
return "", fmt.Errorf("digest chunk %q: %w", chunk.ID, err)
|
|
}
|
|
values = append(values, chunk.ID+"="+digest)
|
|
}
|
|
sort.Strings(values)
|
|
sum := sha256.Sum256([]byte(strings.Join(values, "\n")))
|
|
return "sha256:" + hex.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
func normalizeCheckpointFingerprints(values []CheckpointFingerprint) []CheckpointFingerprint {
|
|
if len(values) == 0 {
|
|
return nil
|
|
}
|
|
byName := make(map[string]string, len(values))
|
|
for _, value := range values {
|
|
name := strings.TrimSpace(value.Name)
|
|
fingerprint := strings.TrimSpace(value.Value)
|
|
if name == "" || fingerprint == "" {
|
|
continue
|
|
}
|
|
byName[name] = fingerprint
|
|
}
|
|
if len(byName) == 0 {
|
|
return nil
|
|
}
|
|
names := make([]string, 0, len(byName))
|
|
for name := range byName {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
out := make([]CheckpointFingerprint, 0, len(names))
|
|
for _, name := range names {
|
|
out = append(out, CheckpointFingerprint{Name: name, Value: byName[name]})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func checkpointContentDigest(content []byte) string {
|
|
sum := sha256.Sum256(content)
|
|
return "sha256:" + hex.EncodeToString(sum[:])
|
|
}
|