diff --git a/pkg/cvo/cvo.go b/pkg/cvo/cvo.go index ed56669f89..c649c46d59 100644 --- a/pkg/cvo/cvo.go +++ b/pkg/cvo/cvo.go @@ -938,7 +938,7 @@ func hasReachedLevel(cv *configv1.ClusterVersion, desired configv1.Update) bool func (optr *Operator) defaultPreconditionChecks() precondition.List { return []precondition.Precondition{ - preconditioncv.NewRollback(optr.currentVersion), + preconditioncv.NewMetadataUpdate(optr.currentVersion), preconditioncv.NewUpgradeable(optr.cvLister), preconditioncv.NewRecentEtcdBackup(optr.cvLister, optr.coLister), preconditioncv.NewRecommendedUpdate(optr.cvLister), diff --git a/pkg/cvo/sync_worker.go b/pkg/cvo/sync_worker.go index b19ff4e072..0d3178a888 100644 --- a/pkg/cvo/sync_worker.go +++ b/pkg/cvo/sync_worker.go @@ -371,6 +371,7 @@ func (w *SyncWorker) syncPayload(ctx context.Context, work *SyncWork, } else { if block, err := precondition.Summarize(w.preconditions.RunAll(ctx, precondition.ReleaseContext{ DesiredVersion: payloadUpdate.Release.Version, + Previous: payloadUpdate.Previous, }), work.Desired.Force); err != nil { klog.V(2).Infof("Precondition error (force %t, block %t): %v", work.Desired.Force, block, err) if block { diff --git a/pkg/payload/payload.go b/pkg/payload/payload.go index 6ac6e9402f..baff913edd 100644 --- a/pkg/payload/payload.go +++ b/pkg/payload/payload.go @@ -114,6 +114,9 @@ type Update struct { Architecture string + // Previous is a slice of valid previous versions. + Previous []string + // manifestHash is a hash of the manifests included in this payload ManifestHash string Manifests []manifest.Manifest @@ -313,7 +316,7 @@ func loadUpdatePayloadMetadata(dir, releaseImage, clusterProfile string) (*Updat releaseDir = filepath.Join(dir, ReleaseManifestDir) ) - release, arch, err := loadReleaseFromMetadata(releaseDir) + release, arch, previous, err := loadReleaseFromMetadata(releaseDir) if err != nil { return nil, nil, err } @@ -334,6 +337,7 @@ func loadUpdatePayloadMetadata(dir, releaseImage, clusterProfile string) (*Updat Release: release, ImageRef: imageRef, Architecture: arch, + Previous: previous, }, tasks, nil } @@ -357,7 +361,7 @@ func getPayloadTasks(releaseDir, cvoDir, releaseImage, clusterProfile string) [] }} } -func loadReleaseFromMetadata(releaseDir string) (configv1.Release, string, error) { +func loadReleaseFromMetadata(releaseDir string) (configv1.Release, string, []string, error) { var release configv1.Release path := filepath.Join(releaseDir, cincinnatiJSONFile) data, err := os.ReadFile(path) @@ -418,7 +422,9 @@ func loadReleaseFromMetadata(releaseDir string) (configv1.Release, string, error } } - return release, arch, nil + // FIXME: load Metadata["previous"] + + return release, arch, previous, nil } func loadImageReferences(releaseDir string) (*imagev1.ImageStream, error) { diff --git a/pkg/payload/precondition/clusterversion/metadata_update.go b/pkg/payload/precondition/clusterversion/metadata_update.go new file mode 100644 index 0000000000..e537ed6667 --- /dev/null +++ b/pkg/payload/precondition/clusterversion/metadata_update.go @@ -0,0 +1,50 @@ +package clusterversion + +import ( + "context" + "fmt" + + configv1 "github.com/openshift/api/config/v1" + + precondition "github.com/openshift/cluster-version-operator/pkg/payload/precondition" +) + +// currentRelease is a callback for returning the version that is currently being reconciled. +type currentRelease func() configv1.Release + +// MetadataUpdate blocks metadataUpdates from the version that is currently being reconciled. +type MetadataUpdate struct { + currentRelease +} + +// NewMetadataUpdate returns a new MetadataUpdate precondition check. +func NewMetadataUpdate(fn currentRelease) *MetadataUpdate { + return &MetadataUpdate{ + currentRelease: fn, + } +} + +// Name returns Name for the precondition. +func (pf *MetadataUpdate) Name() string { return "ClusterVersionMetadataUpdate" } + +// Run runs the MetadataUpdate precondition, blocking metadataUpdates from the +// version that is currently being reconciled. It returns a +// PreconditionError when possible. +func (p *MetadataUpdate) Run(ctx context.Context, releaseContext precondition.ReleaseContext) error { + currentRelease := p.currentRelease() + if releaseContext.DesiredVersion == currentRelease { + return nil + } + + for _, previous in := range releaseContext.Previous { + if currentRelease == previous { + return nil + } + } + + return &precondition.Error{ + Reason: "InvalidDesiredVersion", + Message: fmt.Sprintf("The current version %q is neither the desired version %q nor a version declared in the desired version's previous-release update metadata: %v", currentVersion, releaseContext.DesiredVersion, releaseContext.Previous) + Name: p.Name(), + } +} diff --git a/pkg/payload/precondition/clusterversion/rollback_test.go b/pkg/payload/precondition/clusterversion/metadata_update_test.go similarity index 100% rename from pkg/payload/precondition/clusterversion/rollback_test.go rename to pkg/payload/precondition/clusterversion/metadata_update_test.go diff --git a/pkg/payload/precondition/clusterversion/rollback.go b/pkg/payload/precondition/clusterversion/rollback.go deleted file mode 100644 index ca7ea46c9a..0000000000 --- a/pkg/payload/precondition/clusterversion/rollback.go +++ /dev/null @@ -1,65 +0,0 @@ -package clusterversion - -import ( - "context" - "fmt" - - "github.com/blang/semver/v4" - configv1 "github.com/openshift/api/config/v1" - - precondition "github.com/openshift/cluster-version-operator/pkg/payload/precondition" -) - -// currentRelease is a callback for returning the version that is currently being reconciled. -type currentRelease func() configv1.Release - -// Rollback blocks rollbacks from the version that is currently being reconciled. -type Rollback struct { - currentRelease -} - -// NewRollback returns a new Rollback precondition check. -func NewRollback(fn currentRelease) *Rollback { - return &Rollback{ - currentRelease: fn, - } -} - -// Name returns Name for the precondition. -func (pf *Rollback) Name() string { return "ClusterVersionRollback" } - -// Run runs the Rollback precondition, blocking rollbacks from the -// version that is currently being reconciled. It returns a -// PreconditionError when possible. -func (p *Rollback) Run(ctx context.Context, releaseContext precondition.ReleaseContext) error { - currentRelease := p.currentRelease() - currentVersion, err := semver.Parse(currentRelease.Version) - if err != nil { - return &precondition.Error{ - Nested: err, - Reason: "InvalidCurrentVersion", - Message: err.Error(), - Name: p.Name(), - } - } - - targetVersion, err := semver.Parse(releaseContext.DesiredVersion) - if err != nil { - return &precondition.Error{ - Nested: err, - Reason: "InvalidDesiredVersion", - Message: err.Error(), - Name: p.Name(), - } - } - - if targetVersion.LT(currentVersion) { - return &precondition.Error{ - Reason: "LowDesiredVersion", - Message: fmt.Sprintf("%s is less than the current target %s, but rollbacks and downgrades are not recommended", targetVersion, currentVersion), - Name: p.Name(), - } - } - - return nil -} diff --git a/pkg/payload/precondition/precondition.go b/pkg/payload/precondition/precondition.go index 3725ad84aa..bb288a2fe8 100644 --- a/pkg/payload/precondition/precondition.go +++ b/pkg/payload/precondition/precondition.go @@ -37,6 +37,9 @@ type ReleaseContext struct { // where the author decided to use a different naming scheme, or // to leave the version completely unset. DesiredVersion string + + // Previous is a slice of valid previous versions. + Previous []string } // Precondition defines the precondition check for a payload.