Add structured destination comparison details
This commit is contained in:
@@ -30,37 +30,75 @@ type DestinationStatus struct {
|
||||
type Comparison struct {
|
||||
Outcome Outcome
|
||||
Reason string
|
||||
Detail ComparisonDetail
|
||||
}
|
||||
|
||||
type ComparisonDetailKind string
|
||||
|
||||
const (
|
||||
ComparisonDetailNone ComparisonDetailKind = ""
|
||||
ComparisonDetailInvalidState ComparisonDetailKind = "invalid_state"
|
||||
ComparisonDetailUnmanagedContent ComparisonDetailKind = "unmanaged_content"
|
||||
ComparisonDetailPipelineIDMismatch ComparisonDetailKind = "pipeline_id_mismatch"
|
||||
ComparisonDetailDestinationIDMismatch ComparisonDetailKind = "destination_id_mismatch"
|
||||
ComparisonDetailDifferentSourceID ComparisonDetailKind = "different_source_id"
|
||||
ComparisonDetailSameCreatedDigestConflict ComparisonDetailKind = "same_created_digest_conflict"
|
||||
ComparisonDetailDestinationNewer ComparisonDetailKind = "destination_newer"
|
||||
ComparisonDetailSharedRootOwnerAbsent ComparisonDetailKind = "shared_root_owner_absent"
|
||||
ComparisonDetailSharedRootOutputOwner ComparisonDetailKind = "shared_root_output_owner_conflict"
|
||||
)
|
||||
|
||||
type ComparisonDetail struct {
|
||||
Kind ComparisonDetailKind
|
||||
CurrentPipelineID string
|
||||
CurrentDestinationID string
|
||||
DestinationPipelineID string
|
||||
DestinationDestinationID string
|
||||
CurrentSourceID string
|
||||
DestinationSourceID string
|
||||
CurrentSourceDigest string
|
||||
DestinationSourceDigest string
|
||||
Path string
|
||||
CurrentOwner OwnerScope
|
||||
ConflictingOwner OwnerScope
|
||||
}
|
||||
|
||||
func CompareSharedRootOwner(source bundle.Manifest, scope OwnerScope, status DestinationStatus) Comparison {
|
||||
if status.StateErr != nil {
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error()}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||
}
|
||||
if status.SharedRoot != nil {
|
||||
if err := ValidateSharedRoot(*status.SharedRoot); err != nil {
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error()}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||
}
|
||||
owner, ok := status.SharedRoot.Owner(scope)
|
||||
if !ok {
|
||||
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: fmt.Sprintf("destination owner %s/%s is absent", scope.PipelineID, scope.DestinationID)}
|
||||
return Comparison{
|
||||
Outcome: OutcomeDestinationAbsent,
|
||||
Reason: fmt.Sprintf("destination owner %s/%s is absent", scope.PipelineID, scope.DestinationID),
|
||||
Detail: ComparisonDetail{
|
||||
Kind: ComparisonDetailSharedRootOwnerAbsent,
|
||||
CurrentOwner: scope,
|
||||
},
|
||||
}
|
||||
}
|
||||
return compareManifests(source, owner.Source.Manifest)
|
||||
}
|
||||
if status.State != nil {
|
||||
destinationState := *status.State
|
||||
if err := Validate(destinationState); err != nil {
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error()}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||
}
|
||||
if destinationState.PipelineID != scope.PipelineID {
|
||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("pipeline id %q does not match %q", destinationState.PipelineID, scope.PipelineID)}
|
||||
return pipelineIDMismatchComparison(destinationState.PipelineID, scope.PipelineID)
|
||||
}
|
||||
if destinationState.DestinationID != scope.DestinationID {
|
||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("destination id %q does not match %q", destinationState.DestinationID, scope.DestinationID)}
|
||||
return destinationIDMismatchComparison(destinationState.DestinationID, scope.DestinationID)
|
||||
}
|
||||
return compareManifests(source, destinationState.Source.Manifest)
|
||||
}
|
||||
if status.HasContents {
|
||||
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state"}
|
||||
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state", Detail: ComparisonDetail{Kind: ComparisonDetailUnmanagedContent}}
|
||||
}
|
||||
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: "destination state is absent"}
|
||||
}
|
||||
@@ -70,45 +108,95 @@ func compareManifests(source, destination bundle.Manifest) Comparison {
|
||||
return Comparison{Outcome: OutcomeSameSource, Reason: "destination source manifest matches source"}
|
||||
}
|
||||
if destination.ID != source.ID {
|
||||
return Comparison{Outcome: OutcomeDifferentSourceConflict, Reason: "destination source id differs from source"}
|
||||
return Comparison{
|
||||
Outcome: OutcomeDifferentSourceConflict,
|
||||
Reason: "destination source id differs from source",
|
||||
Detail: ComparisonDetail{
|
||||
Kind: ComparisonDetailDifferentSourceID,
|
||||
CurrentSourceID: source.ID,
|
||||
DestinationSourceID: destination.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
if destination.Created.Before(source.Created) {
|
||||
return Comparison{Outcome: OutcomeDestinationOlder, Reason: "destination source is older than source"}
|
||||
}
|
||||
if destination.Created.After(source.Created) {
|
||||
return Comparison{Outcome: OutcomeDestinationNewer, Reason: "destination source is newer than source"}
|
||||
return Comparison{
|
||||
Outcome: OutcomeDestinationNewer,
|
||||
Reason: "destination source is newer than source",
|
||||
Detail: ComparisonDetail{
|
||||
Kind: ComparisonDetailDestinationNewer,
|
||||
CurrentSourceID: source.ID,
|
||||
DestinationSourceID: destination.ID,
|
||||
},
|
||||
}
|
||||
}
|
||||
if destination.Digest != source.Digest {
|
||||
return Comparison{Outcome: OutcomeSameCreatedConflict, Reason: "destination source has same id and created time but different digest"}
|
||||
return Comparison{
|
||||
Outcome: OutcomeSameCreatedConflict,
|
||||
Reason: "destination source has same id and created time but different digest",
|
||||
Detail: ComparisonDetail{
|
||||
Kind: ComparisonDetailSameCreatedDigestConflict,
|
||||
CurrentSourceID: source.ID,
|
||||
DestinationSourceID: destination.ID,
|
||||
CurrentSourceDigest: source.Digest,
|
||||
DestinationSourceDigest: destination.Digest,
|
||||
},
|
||||
}
|
||||
}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: "destination source differs from source without a supported comparison outcome"}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: "destination source differs from source without a supported comparison outcome", Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||
}
|
||||
|
||||
func Compare(source bundle.Manifest, pipelineID, destinationID string, status DestinationStatus) Comparison {
|
||||
if status.StateErr != nil {
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error()}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: status.StateErr.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||
}
|
||||
if status.State == nil {
|
||||
if status.HasContents {
|
||||
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state"}
|
||||
return Comparison{Outcome: OutcomeDestinationUnmanaged, Reason: "destination has content but no distributor state", Detail: ComparisonDetail{Kind: ComparisonDetailUnmanagedContent}}
|
||||
}
|
||||
return Comparison{Outcome: OutcomeDestinationAbsent, Reason: "destination state is absent"}
|
||||
}
|
||||
|
||||
destinationState := *status.State
|
||||
if err := Validate(destinationState); err != nil {
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error()}
|
||||
return Comparison{Outcome: OutcomeInvalidState, Reason: err.Error(), Detail: ComparisonDetail{Kind: ComparisonDetailInvalidState}}
|
||||
}
|
||||
if destinationState.PipelineID != pipelineID {
|
||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("pipeline id %q does not match %q", destinationState.PipelineID, pipelineID)}
|
||||
return pipelineIDMismatchComparison(destinationState.PipelineID, pipelineID)
|
||||
}
|
||||
if destinationState.DestinationID != destinationID {
|
||||
return Comparison{Outcome: OutcomeIdentityMismatch, Reason: fmt.Sprintf("destination id %q does not match %q", destinationState.DestinationID, destinationID)}
|
||||
return destinationIDMismatchComparison(destinationState.DestinationID, destinationID)
|
||||
}
|
||||
|
||||
return compareManifests(source, destinationState.Source.Manifest)
|
||||
}
|
||||
|
||||
func pipelineIDMismatchComparison(destinationPipelineID, currentPipelineID string) Comparison {
|
||||
return Comparison{
|
||||
Outcome: OutcomeIdentityMismatch,
|
||||
Reason: fmt.Sprintf("pipeline id %q does not match %q", destinationPipelineID, currentPipelineID),
|
||||
Detail: ComparisonDetail{
|
||||
Kind: ComparisonDetailPipelineIDMismatch,
|
||||
CurrentPipelineID: currentPipelineID,
|
||||
DestinationPipelineID: destinationPipelineID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func destinationIDMismatchComparison(destinationDestinationID, currentDestinationID string) Comparison {
|
||||
return Comparison{
|
||||
Outcome: OutcomeIdentityMismatch,
|
||||
Reason: fmt.Sprintf("destination id %q does not match %q", destinationDestinationID, currentDestinationID),
|
||||
Detail: ComparisonDetail{
|
||||
Kind: ComparisonDetailDestinationIDMismatch,
|
||||
CurrentDestinationID: currentDestinationID,
|
||||
DestinationDestinationID: destinationDestinationID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func manifestsEqual(a, b bundle.Manifest) bool {
|
||||
if a.SchemaVersion != b.SchemaVersion ||
|
||||
a.ID != b.ID ||
|
||||
|
||||
Reference in New Issue
Block a user