Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4d4f894
Add namespace format validation to bundle config schema (#2468)
perdasilva Jan 30, 2026
e50144a
:seedling: Bump github.com/klauspost/compress from 1.18.2 to 1.18.3 (…
dependabot[bot] Jan 30, 2026
5c4bf7b
🌱 test: ensure single/own namespace increase coverage for both runtim…
camilamacedo86 Jan 30, 2026
ee4bab5
🐛 fix: Configuration validation errors now show "Failed" status inste…
camilamacedo86 Jan 30, 2026
492c7e5
🐛 (fix) Fix race condition in Helm to Boxcutter migration during OLM …
camilamacedo86 Jan 31, 2026
2bdb702
Fix up single-ownnamespace e2es (#2472)
perdasilva Feb 2, 2026
e617310
Add bundle olm.properties to revision annotations (#2471)
perdasilva Feb 2, 2026
df151ba
Merge branch 'main' into synchronize
Feb 2, 2026
baed47c
UPSTREAM: <carry>: Add OpenShift specific files
dtfranz Oct 26, 2023
3d473c6
UPSTREAM: <carry>: Add new tests for single/own namespaces install modes
camilamacedo86 Oct 6, 2025
ae62286
UPSTREAM: <carry>: Upgrade OCP image from 4.20 to 4.21
camilamacedo86 Oct 13, 2025
585b3eb
UPSTREAM: <carry>: [Default Catalog Tests] - Change logic to get ocp …
camilamacedo86 Oct 13, 2025
56b1551
UPSTREAM: <carry>: Update OCP catalogs to v4.21
tmshort Oct 13, 2025
991e450
UPSTREAM: <carry>: support singleown cases in disconnected
kuiwang02 Oct 16, 2025
490f1c8
UPSTREAM: <carry>: fix cases 81696 and 74618 for product code changes
kuiwang02 Oct 17, 2025
68ec15d
UPSTREAM: <carry>: Define Default timeouts and apply their usage accr…
camilamacedo86 Oct 22, 2025
ca87697
UPSTREAM: <carry>: Update to new feature-gate options in helm
tmshort Oct 22, 2025
021feff
UPSTREAM: <carry>: Fix flake for single/own ns tests by ensuring uniq…
camilamacedo86 Oct 22, 2025
fd7d39c
UPSTREAM: <carry>: [OTE]: Enhance single/own ns based on review comme…
camilamacedo86 Oct 24, 2025
7097786
UPSTREAM: <carry>: Update OwnSingle template to use spec.config.inlin…
kuiwang02 Nov 3, 2025
7255c69
UPSTREAM: <carry>: [OTE]: Add webhook cleanup validation on extension…
camilamacedo86 Nov 4, 2025
6e25b60
UPSTREAM: <carry>: Add [OTP] to migrated cases
kuiwang02 Nov 7, 2025
a9885cb
UPSTREAM: <carry>: [OTE]: Upgrade dependencies used
camilamacedo86 Nov 5, 2025
6fd39c9
UPSTREAM: <carry>: fix(OTE): fix OpenShift Kubernetes replace version…
camilamacedo86 Nov 10, 2025
9630d6a
UPSTREAM: <carry>: [Default Catalog Tests] Upgrade go 1.24.6 and depe…
camilamacedo86 Nov 11, 2025
0640a3b
UPSTREAM: <carry>: add disconnected environment support with custom p…
kuiwang02 Nov 12, 2025
502e0a8
UPSTREAM: <carry>: migrate jiazha test cases to OTE
jianzhangbjz Nov 14, 2025
621d833
UPSTREAM: <carry>: migrate clustercatalog case to ote
Xia-Zhao-rh Oct 17, 2025
12d5fd5
UPSTREAM: <carry>: migrate olmv1 QE stress cases
kuiwang02 Nov 20, 2025
506fe7c
UPSTREAM: <carry>: Use busybox/httpd to simulate probes
tmshort Nov 25, 2025
66e0608
UPSTREAM: <carry>: migrate olmv1 QE cases
Xia-Zhao-rh Nov 25, 2025
d7c6825
UPSTREAM: <carry>: add agent for olmv1 qe cases
kuiwang02 Oct 21, 2025
3706c3d
UPSTREAM: <carry>: Disable upstream PodDisruptionBudget
tmshort Dec 3, 2025
e2536cf
UPSTREAM: <carry>: Add AGENTS.md for AI code contributions
rashmigottipati Dec 11, 2025
b7bfd76
UPSTREAM: <carry>: address review comments through addl prompts
rashmigottipati Dec 11, 2025
8584540
UPSTREAM: <carry>: addressing some more review comments
rashmigottipati Dec 11, 2025
02ea4bd
UPSTREAM: <carry>: remove DCO line
rashmigottipati Dec 11, 2025
c03f12d
UPSTREAM: <carry>: migrate bandrade test cases to OTE
bandrade Nov 18, 2025
9d499ba
UPSTREAM: <carry>: update metadata
bandrade Dec 3, 2025
190ee81
UPSTREAM: <carry>: remove originalName
bandrade Dec 3, 2025
e922bc2
UPSTREAM: <carry>: update 80458's timeout to 180s
jianzhangbjz Dec 8, 2025
e555380
UPSTREAM: <carry>: update 83026 to specify the clustercatalog
jianzhangbjz Dec 15, 2025
a16c773
UPSTREAM: <carry>: Update to golang 1.25 and ocp 4.22
oceanc80 Dec 18, 2025
2048b94
UPSTREAM: <carry>: Use oc client for running e2e tests
pedjak Jan 13, 2026
73cc2cd
UPSTREAM: <carry>: Run upstream e2e tests tagged with `@catalogd-update`
pedjak Jan 14, 2026
d4a7d1a
UPSTREAM: <carry>: enhance case to make it more stable
kuiwang02 Jan 6, 2026
ce94fd0
UPSTREAM: <carry>: add service account to curl job
ehearne-redhat Jan 7, 2026
4a10f91
UPSTREAM: <carry>: move sa creation out of buildCurlJob()
ehearne-redhat Jan 8, 2026
59e10f3
UPSTREAM: <carry>: comment out delete service account
ehearne-redhat Jan 9, 2026
204ef23
UPSTREAM: <carry>: move defercleanup for sa for LIFO
ehearne-redhat Jan 9, 2026
5d4c453
UPSTREAM: <carry>: add polling so job fully deleted before proceed
ehearne-redhat Jan 12, 2026
7518e6b
UPSTREAM: <carry>: Revert "Merge pull request #594 from ehearne-redha…
sosiouxme Jan 20, 2026
4a43d59
UPSTREAM: <carry>: Remove openshift-redhat-marketplace catalog tests
camilamacedo86 Jan 8, 2026
c52812e
UPSTREAM: <carry>: config watchnamespace cases
kuiwang02 Jan 6, 2026
9300870
UPSTREAM: <carry>: enhance ocp-79770
Xia-Zhao-rh Jan 26, 2026
6556755
UPSTREAM: <carry>: upgrade version support case
kuiwang02 Jan 28, 2026
48c8bfc
UPSTREAM: <carry>: Remove installed condition check from auth preflig…
Jan 30, 2026
3f99578
UPSTREAM: <carry>: Add openshift/api dependency
Jan 30, 2026
96b2bc0
UPSTREAM: <carry>: Add boxcutter specific preflight auth test
Jan 30, 2026
4a769e6
UPSTREAM: <drop>: go mod vendor
Feb 2, 2026
56dc4b0
UPSTREAM: <drop>: remove upstream GitHub configuration
Feb 2, 2026
468e5a4
UPSTREAM: <drop>: configure the commit-checker
Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions api/v1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ const (
ReasonAbsent = "Absent"

// Progressing reasons
ReasonRollingOut = "RollingOut"
ReasonRetrying = "Retrying"
ReasonBlocked = "Blocked"
ReasonRollingOut = "RollingOut"
ReasonRetrying = "Retrying"
ReasonBlocked = "Blocked"
ReasonInvalidConfiguration = "InvalidConfiguration"

// Deprecation reasons
ReasonDeprecated = "Deprecated"
Expand Down
2 changes: 1 addition & 1 deletion commitchecker.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
expectedMergeBase: fbe909f7ba35a9f771da6ec0431bbde2ac45d5fb
expectedMergeBase: e617310cb535a41a5161d083ad293e7f5bd40077
upstreamBranch: main
upstreamOrg: operator-framework
upstreamRepo: operator-controller
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/google/go-containerregistry v0.20.7
github.com/google/renameio/v2 v2.0.2
github.com/gorilla/handlers v1.5.2
github.com/klauspost/compress v1.18.2
github.com/klauspost/compress v1.18.3
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/operator-framework/api v0.37.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
Expand Down
156 changes: 147 additions & 9 deletions internal/operator-controller/applier/boxcutter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/storage/driver"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -32,6 +33,7 @@ import (
ocv1 "github.com/operator-framework/operator-controller/api/v1"
"github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
"github.com/operator-framework/operator-controller/internal/operator-controller/labels"
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
"github.com/operator-framework/operator-controller/internal/shared/util/cache"
)

Expand Down Expand Up @@ -105,6 +107,27 @@ func (r *SimpleRevisionGenerator) GenerateRevision(
return nil, err
}

if revisionAnnotations == nil {
revisionAnnotations = map[string]string{}
}

// add bundle properties of interest to revision annotations
bundleAnnotations, err := getBundleAnnotations(bundleFS)
if err != nil {
return nil, fmt.Errorf("error getting bundle annotations: %w", err)
}

// we don't care about all of the bundle and csv annotations as they can be quite confusing
// e.g. 'createdAt', 'capabilities', etc.
for _, key := range []string{
// used by other operators that care about the bundle properties (e.g. maxOpenShiftVersion)
source.PropertyOLMProperties,
} {
if value, ok := bundleAnnotations[key]; ok {
revisionAnnotations[key] = value
}
}

// objectLabels
objs := make([]ocv1.ClusterExtensionRevisionObject, 0, len(plain))
for _, obj := range plain {
Expand Down Expand Up @@ -136,11 +159,6 @@ func (r *SimpleRevisionGenerator) GenerateRevision(
Object: unstr,
})
}

if revisionAnnotations == nil {
revisionAnnotations = map[string]string{}
}

return r.buildClusterExtensionRevision(objs, ext, revisionAnnotations), nil
}

Expand Down Expand Up @@ -244,8 +262,7 @@ func (m *BoxcutterStorageMigrator) Migrate(ctx context.Context, ext *ocv1.Cluste
return fmt.Errorf("listing ClusterExtensionRevisions before attempting migration: %w", err)
}
if len(existingRevisionList.Items) != 0 {
// No migration needed.
return nil
return m.ensureMigratedRevisionStatus(ctx, existingRevisionList.Items)
}

ac, err := m.ActionClientGetter.ActionClientFor(ctx, ext)
Expand All @@ -262,11 +279,36 @@ func (m *BoxcutterStorageMigrator) Migrate(ctx context.Context, ext *ocv1.Cluste
return err
}

// Only migrate from a Helm release that represents a deployed, working installation.
// If the latest revision is not deployed (e.g. FAILED), look through the history and
// select the most-recent deployed release instead.
if helmRelease == nil || helmRelease.Info == nil || helmRelease.Info.Status != release.StatusDeployed {
var err error
helmRelease, err = m.findLatestDeployedRelease(ac, ext.GetName())
if err != nil {
return err
}
if helmRelease == nil {
// No deployed release found in history - skip migration. The ClusterExtension
// controller will handle this via normal rollout.
return nil
}
}

rev, err := m.RevisionGenerator.GenerateRevisionFromHelmRelease(ctx, helmRelease, ext, objectLabels)
if err != nil {
return err
}

// Mark this revision as migrated from Helm so we can distinguish it from
// normal Boxcutter revisions. This label is critical for ensuring we only
// set Succeeded=True status on actually-migrated revisions, not on revision 1
// created during normal Boxcutter operation.
if rev.Labels == nil {
rev.Labels = make(map[string]string)
}
rev.Labels[labels.MigratedFromHelmKey] = "true"

// Set ownerReference for proper garbage collection when the ClusterExtension is deleted.
if err := controllerutil.SetControllerReference(ext, rev, m.Scheme); err != nil {
return fmt.Errorf("set ownerref: %w", err)
Expand All @@ -276,9 +318,105 @@ func (m *BoxcutterStorageMigrator) Migrate(ctx context.Context, ext *ocv1.Cluste
return err
}

// Re-fetch to get server-managed fields like Generation
// Set initial status on the migrated revision to mark it as succeeded.
//
// The revision must have a Succeeded=True status condition immediately after creation.
//
// A revision is only considered "Installed" (vs "RollingOut") when it has this condition.
// Without it, the system cannot determine what version is currently installed, which breaks:
// - Version resolution (can't compute upgrade paths from unknown starting point)
// - Status reporting (installed bundle appears as nil)
// - Subsequent upgrades (resolution fails without knowing current version)
//
// While the ClusterExtensionRevision controller would eventually reconcile and set this status,
// that creates a timing gap where the ClusterExtension reconciliation happens before the status
// is set, causing failures during the OLM upgrade window.
//
// Since we're creating this revision from a successfully deployed Helm release, we know it
// represents a working installation and can safely mark it as succeeded immediately.
return m.ensureRevisionStatus(ctx, rev)
}

// ensureMigratedRevisionStatus checks if revision 1 exists and needs its status set.
// This handles the case where revision creation succeeded but status update failed.
// Returns nil if no action is needed.
func (m *BoxcutterStorageMigrator) ensureMigratedRevisionStatus(ctx context.Context, revisions []ocv1.ClusterExtensionRevision) error {
for i := range revisions {
if revisions[i].Spec.Revision != 1 {
continue
}
// Skip if already succeeded - status is already set correctly.
if meta.IsStatusConditionTrue(revisions[i].Status.Conditions, ocv1.ClusterExtensionRevisionTypeSucceeded) {
return nil
}
// Ensure revision 1 status is set correctly, including for previously migrated
// revisions that may not carry the MigratedFromHelm label.
return m.ensureRevisionStatus(ctx, &revisions[i])
}
// No revision 1 found - migration not applicable (revisions created by normal operation).
return nil
}

// findLatestDeployedRelease searches the Helm release history for the most recent deployed release.
// Returns nil if no deployed release is found.
func (m *BoxcutterStorageMigrator) findLatestDeployedRelease(ac helmclient.ActionInterface, name string) (*release.Release, error) {
history, err := ac.History(name)
if errors.Is(err, driver.ErrReleaseNotFound) {
// no Helm Release history -> no prior installation.
return nil, nil
}
if err != nil {
return nil, err
}

var latestDeployed *release.Release
for _, rel := range history {
if rel == nil || rel.Info == nil {
continue
}
if rel.Info.Status != release.StatusDeployed {
continue
}
if latestDeployed == nil || rel.Version > latestDeployed.Version {
latestDeployed = rel
}
}

return latestDeployed, nil
}

// ensureRevisionStatus ensures the revision has the Succeeded status condition set.
// Returns nil if the status is already set or after successfully setting it.
// Only sets status on revisions that were actually migrated from Helm (marked with MigratedFromHelmKey label).
func (m *BoxcutterStorageMigrator) ensureRevisionStatus(ctx context.Context, rev *ocv1.ClusterExtensionRevision) error {
// Re-fetch to get latest version before checking status
if err := m.Client.Get(ctx, client.ObjectKeyFromObject(rev), rev); err != nil {
return fmt.Errorf("getting created revision: %w", err)
return fmt.Errorf("getting existing revision for status check: %w", err)
}

// Only set status if this revision was actually migrated from Helm.
// This prevents us from incorrectly marking normal Boxcutter revision 1 as succeeded
// when it's still in progress.
if rev.Labels[labels.MigratedFromHelmKey] != "true" {
return nil
}

// Check if status is already set to Succeeded=True
if meta.IsStatusConditionTrue(rev.Status.Conditions, ocv1.ClusterExtensionRevisionTypeSucceeded) {
return nil
}

// Set the Succeeded status condition
meta.SetStatusCondition(&rev.Status.Conditions, metav1.Condition{
Type: ocv1.ClusterExtensionRevisionTypeSucceeded,
Status: metav1.ConditionTrue,
Reason: ocv1.ReasonSucceeded,
Message: "Revision succeeded - migrated from Helm release",
ObservedGeneration: rev.GetGeneration(),
})

if err := m.Client.Status().Update(ctx, rev); err != nil {
return fmt.Errorf("updating migrated revision status: %w", err)
}

return nil
Expand Down
Loading