Add artifact provenance and stage skip outcomes
This commit is contained in:
@@ -195,6 +195,9 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
|
||||
env.Logger.Debug("manifest saved", "stage", s.Name(), "transition", "running", "path", manifestPath)
|
||||
|
||||
result, err := s.Run(ctx, stageEnv, m)
|
||||
if err == nil {
|
||||
err = validateStageResult(result)
|
||||
}
|
||||
if err != nil {
|
||||
failedAt := nowUTC()
|
||||
m.MarkStageFailed(s.Name(), failedAt, err.Error())
|
||||
@@ -209,6 +212,25 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
|
||||
env.Logger.Info("stage failed", "stage", s.Name(), "error", err)
|
||||
return nil, fmt.Errorf("stage %q failed: %w", s.Name(), err)
|
||||
}
|
||||
if result != nil && result.Disposition == stage.StageDispositionSkipped {
|
||||
skipped = append(skipped, s.Name())
|
||||
skippedAt := nowUTC()
|
||||
m.MarkStageSkipped(s.Name(), skippedAt, result.SkipReason)
|
||||
clearStageResultDetails(m.Stages[s.Name()])
|
||||
applyStageResultToManifest(m, s.Name(), result)
|
||||
if err := env.ManifestStore.Save(ctx, manifestPath, m); err != nil {
|
||||
return nil, fmt.Errorf("save manifest after self-skip %q: %w", s.Name(), err)
|
||||
}
|
||||
runManifest.MarkStageSkipped(s.Name(), skippedAt, result.SkipReason)
|
||||
applyStageResultToRunManifest(runManifest, s.Name(), result)
|
||||
syncRunManifestIdentityFromSession(m, runManifest)
|
||||
if err := runManifestStore.SaveRun(ctx, runManifestPath, runManifest); err != nil {
|
||||
return nil, fmt.Errorf("save run manifest after self-skip %q: %w", s.Name(), err)
|
||||
}
|
||||
env.Logger.Debug("manifest saved", "stage", s.Name(), "transition", "skipped", "path", manifestPath)
|
||||
env.Logger.Info("stage skipped", "stage", s.Name(), "reason", result.SkipReason)
|
||||
continue
|
||||
}
|
||||
|
||||
outputs := mapResultOutputs(s.Name(), result, runID)
|
||||
succeededAt := nowUTC()
|
||||
@@ -407,26 +429,69 @@ func mapResultOutputs(stageName string, result *stage.StageResult, runID string)
|
||||
localPath = ref.RelativePath
|
||||
}
|
||||
kind := ref.Kind
|
||||
sourceID := ""
|
||||
if stageName == "analyze" {
|
||||
sourceID = artifacts.ConfiguredArtifactSourceID(ref.Kind)
|
||||
kind = "scriptorium_artifact"
|
||||
} else {
|
||||
sourceID = sourceIDForOutputKind(kind)
|
||||
sourceID := strings.TrimSpace(ref.SourceID)
|
||||
if sourceID == "" {
|
||||
if stageName == "analyze" {
|
||||
sourceID = artifacts.ConfiguredArtifactSourceID(ref.Kind)
|
||||
kind = "scriptorium_artifact"
|
||||
} else {
|
||||
sourceID = sourceIDForOutputKind(kind)
|
||||
}
|
||||
}
|
||||
out = append(out, manifest.ArtifactRecord{
|
||||
Kind: kind,
|
||||
SourceID: sourceID,
|
||||
LocalPath: localPath,
|
||||
ProducerRunID: runID,
|
||||
RemoteKey: ref.RemoteKey,
|
||||
Checksum: ref.Checksum,
|
||||
Kind: kind,
|
||||
SourceID: sourceID,
|
||||
LocalPath: localPath,
|
||||
Contract: cloneContractMetadata(ref.Contract),
|
||||
ExternalProvenance: cloneExternalProvenance(ref.ExternalProvenance),
|
||||
ProducerRunID: runID,
|
||||
RemoteKey: ref.RemoteKey,
|
||||
Checksum: ref.Checksum,
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func validateStageResult(result *stage.StageResult) error {
|
||||
if result == nil {
|
||||
return nil
|
||||
}
|
||||
switch result.Disposition {
|
||||
case stage.StageDispositionSucceeded:
|
||||
if strings.TrimSpace(result.SkipReason) != "" {
|
||||
return fmt.Errorf("successful result contains a skip reason")
|
||||
}
|
||||
return nil
|
||||
case stage.StageDispositionSkipped:
|
||||
if strings.TrimSpace(result.SkipReason) == "" {
|
||||
return fmt.Errorf("skipped result requires a skip reason")
|
||||
}
|
||||
if len(result.Outputs) != 0 {
|
||||
return fmt.Errorf("skipped result contains %d output(s)", len(result.Outputs))
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unsupported stage result disposition %q", result.Disposition)
|
||||
}
|
||||
}
|
||||
|
||||
func cloneContractMetadata(value *artifactmodel.ContractMetadata) *artifactmodel.ContractMetadata {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
cloned := *value
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func cloneExternalProvenance(value *artifactmodel.ExternalProvenance) *artifactmodel.ExternalProvenance {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
cloned := *value
|
||||
return &cloned
|
||||
}
|
||||
|
||||
func sourceIDForOutputKind(kind string) string {
|
||||
trimmed := strings.TrimSpace(kind)
|
||||
if trimmed == "" {
|
||||
@@ -462,6 +527,15 @@ func applyStageResultToManifest(m *manifest.Manifest, stageName string, result *
|
||||
}
|
||||
}
|
||||
|
||||
func clearStageResultDetails(sr *manifest.StageRecord) {
|
||||
if sr == nil {
|
||||
return
|
||||
}
|
||||
sr.Logs = nil
|
||||
sr.GeneratedConfigs = nil
|
||||
sr.Metadata = nil
|
||||
}
|
||||
|
||||
func ensureManifestIdentity(cfg *config.Config, m *manifest.Manifest, runID string) (bool, error) {
|
||||
if cfg == nil || cfg.Pipeline == nil || cfg.Session == nil || m == nil {
|
||||
return false, nil
|
||||
|
||||
Reference in New Issue
Block a user