Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions staging/operator-lifecycle-manager/cmd/catalog/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ func newRootCmd() *cobra.Command {
if o.debug {
logger.SetLevel(logrus.DebugLevel)
}
// OCPBUGS-35210: use millisecond timestamps so OLM and audit log
// entries can be correlated at sub-second precision.
// Set on both the local logger AND the global package logger so that
// code using logrus.WithFields() directly also emits milliseconds.
msFormatter := &logrus.TextFormatter{
TimestampFormat: "2006-01-02T15:04:05.000000Z07:00",
FullTimestamp: true,
Comment on lines +52 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
file="staging/operator-lifecycle-manager/cmd/catalog/start.go"
sed -n '52,61p' "$file"

Repository: openshift/operator-framework-olm

Length of output: 642


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file="staging/operator-lifecycle-manager/cmd/catalog/start.go"
printf '%s\n' '--- start.go context ---'
sed -n '1,100p' "$file"

printf '%s\n' '--- related timestamp references ---'
rg -n -S 'OCPBUGS-35210|TimestampFormat|millisecond|microsecond|FullTimestamp|SetFormatter' \
  staging/operator-lifecycle-manager staging 2>/dev/null | head -200

printf '%s\n' '--- repository status and focused diff ---'
git status --short
git diff --stat -- "$file"
git diff -- "$file"

Repository: openshift/operator-framework-olm

Length of output: 10505


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
layout = "2006-01-02T15:04:05.000000Z07:00"
fraction = layout.split("T", 1)[1].split("Z", 1)[0].split(".", 1)[1]
print(f"fractional layout: .{fraction}")
print(f"fractional digits: {len(fraction)}")
print("precision: " + ("milliseconds" if fraction == "000" else
                       "microseconds" if fraction == "000000" else "other"))

sample = "2026-08-01T12:34:56.123456Z"
rendered_fraction = sample.split(".", 1)[1].split("Z", 1)[0]
print(f"sample fractional digits: {len(rendered_fraction)}")
PY

printf '%s\n' '--- logrus formatter references ---'
rg -n -S 'type TextFormatter struct|TimestampFormat|FullTimestamp' \
  . -g '*.go' -g 'go.mod' -g 'vendor/**' 2>/dev/null | head -120

printf '%s\n' '--- module version ---'
rg -n '^github.com/sirupsen/logrus' go.mod staging/operator-lifecycle-manager/go.mod 2>/dev/null || true

Repository: openshift/operator-framework-olm

Length of output: 8605


Use millisecond timestamp formatting.

The OCPBUGS-35210 comments require milliseconds, but .000000 emits microseconds. Change the layout to .000.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@staging/operator-lifecycle-manager/cmd/catalog/start.go` around lines 52 -
58, Update the TimestampFormat in the msFormatter configuration to use the
millisecond layout `.000` instead of `.000000`, while preserving the existing
timestamp format and logger setup.

Comment on lines +56 to +58

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail
rg -n --glob '*.go' --glob '*.yaml' --glob '*.yml' \
  'TimestampFormat|Z07:00|UTC\(\)|time\.Local|TZ' .

Repository: openshift/operator-framework-olm

Length of output: 50389


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file="staging/operator-lifecycle-manager/cmd/catalog/start.go"
printf '%s\n' '--- target file ---'
sed -n '1,110p' "$file"

printf '%s\n' '--- relevant repository references ---'
rg -n --glob '*.go' --glob '*.yaml' --glob '*.yml' \
  'msFormatter|TimestampFormat|SetFormatter|time\.Local|TZ|UTC\(\)|TZ=' \
  staging/operator-lifecycle-manager deploy config manifests 2>/dev/null | head -n 300 || true

printf '%s\n' '--- module/logrus version ---'
rg -n 'sirupsen/logrus|logrus v' go.mod go.sum staging/operator-lifecycle-manager 2>/dev/null | head -n 40 || true

Repository: openshift/operator-framework-olm

Length of output: 32321


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

p = Path("staging/operator-lifecycle-manager/cmd/catalog/start.go")
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if 45 <= i <= 70:
        print(f"{i:4}: {line}")
PY

Repository: openshift/operator-framework-olm

Length of output: 1107


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

file="staging/operator-lifecycle-manager/cmd/catalog/start.go"
sed -n '1,110p' "$file"

rg -n --glob '*.go' --glob '*.yaml' --glob '*.yml' \
  'msFormatter|TimestampFormat|SetFormatter|time\.Local|TZ|UTC\(\)|TZ=' \
  staging/operator-lifecycle-manager deploy config manifests 2>/dev/null | head -n 300 || true

rg -n 'sirupsen/logrus|logrus v' go.mod go.sum staging/operator-lifecycle-manager 2>/dev/null | head -n 40 || true

Repository: openshift/operator-framework-olm

Length of output: 32455


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

p = Path("staging/operator-lifecycle-manager/cmd/catalog/start.go")
for i, line in enumerate(p.read_text().splitlines(), 1):
    if 45 <= i <= 70:
        print(f"{i:4}: {line}")
PY

Repository: openshift/operator-framework-olm

Length of output: 1107


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- Logrus v1.9.4 formatter behavior ---'
curl -fsSL \
  https://raw.githubusercontent.com/sirupsen/logrus/v1.9.4/text_formatter.go |
  rg -n -C 8 'TimestampFormat|entry\.Time|time\.Now\(\)|Format\('

printf '%s\n' '--- Go time layout references ---'
rg -n --glob '*.go' --glob '*.md' --glob '*.yaml' --glob '*.yml' \
  '2006-01-02T15:04:05\.(000|000000)|millisecond timestamps|microsecond|RFC3339' \
  staging/operator-lifecycle-manager | head -n 100

Repository: openshift/operator-framework-olm

Length of output: 5208


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- candidate catalog deployment and image files ---'
git ls-files | rg -i 'catalog|operator.*deployment|deployment.*operator|olm.*operator' | head -n 250

printf '%s\n' '--- timezone settings in candidate deployment/config files ---'
files=$(git ls-files | rg -i 'catalog|operator.*deployment|deployment.*operator|olm.*operator' | head -n 250)
if [ -n "$files" ]; then
  rg -n -i '(^|[[:space:]-])(env|name|value|TZ|timezone|utc)(:|[[:space:]])|TZ=' $files 2>/dev/null | head -n 200 || true
fi

Repository: openshift/operator-framework-olm

Length of output: 43950


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

curl -fsSL \
  https://raw.githubusercontent.com/sirupsen/logrus/v1.9.4/text_formatter.go |
  rg -n -C 8 'TimestampFormat|entry\.Time|time\.Now\(\)|Format\('

rg -n --glob '*.go' --glob '*.md' --glob '*.yaml' --glob '*.yml' \
  '2006-01-02T15:04:05\.(000|000000)|millisecond timestamps|microsecond|RFC3339' \
  staging/operator-lifecycle-manager | head -n 100

Repository: openshift/operator-framework-olm

Length of output: 5133


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

git ls-files | rg -i 'catalog|operator.*deployment|deployment.*operator|olm.*operator' | head -n 250

files=$(git ls-files | rg -i 'catalog|operator.*deployment|deployment.*operator|olm.*operator' | head -n 250)
if [ -n "$files" ]; then
  rg -n -i '(^|[[:space:]-])(env|name|value|TZ|timezone|utc)(:|[[:space:]])|TZ=' \
    $files 2>/dev/null | head -n 200 || true
fi

Repository: openshift/operator-framework-olm

Length of output: 42988


Use millisecond precision and normalize timestamps to UTC.

000000 emits six fractional digits, not milliseconds; use 000. Logrus formats entry.Time directly, and Z07:00 preserves non-UTC offsets. Normalize timestamps to UTC before formatting when UTC output is required.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@staging/operator-lifecycle-manager/cmd/catalog/start.go` around lines 56 -
58, Update the logrus.TextFormatter configuration in the start logging setup to
use three fractional-second digits and UTC-normalized timestamps. Change
TimestampFormat from six-digit microsecond precision to millisecond precision
and ensure the formatted entry time is converted to UTC before formatting,
preserving FullTimestamp behavior.

}
logger.SetFormatter(msFormatter)
logrus.SetFormatter(msFormatter)
logger.Infof("log level %s", logger.Level)

ctx, cancel := context.WithCancel(signals.Context())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2100,13 +2100,33 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {

logger.Info("syncing")

// OCPBUGS-35210: log the step statuses this reconcile sees at start.
// Proves whether this loop received a stale cached plan (NotPresent) or
// the post-UpdateStatus version (Created) for the BundleSecret step.
if len(plan.Status.Plan) > 0 {
for i, step := range plan.Status.Plan {
if step.Resource.Kind == "BundleSecret" || step.Resource.Kind == "ServiceAccount" {
logger.WithFields(logrus.Fields{
"resourceVersion": plan.ResourceVersion,
"stepIndex": i,
"kind": step.Resource.Kind,
"name": step.Resource.Name,
"status": step.Status,
}).Debug("installplan step status at reconcile start")
}
}
}

if len(plan.Status.Plan) == 0 && len(plan.Status.BundleLookups) == 0 {
logger.Info("skip processing installplan without status - subscription sync responsible for initial status")
return
}

// Complete and Failed are terminal phases
if plan.Status.Phase == v1alpha1.InstallPlanPhaseFailed || plan.Status.Phase == v1alpha1.InstallPlanPhaseComplete {
// OCPBUGS-35210: log so we can confirm terminal-phase early exit in the timeline.
// Loops that see phase=Complete exit here without executing any steps.
logger.WithField("phase", plan.Status.Phase).Debug("phase is terminal, skipping execution")
return
}

Expand Down Expand Up @@ -2169,8 +2189,26 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {

defer o.requeueSubscriptionForInstallPlan(plan, logger)

// OCPBUGS-35210: log what step statuses are being persisted and the
// resourceVersion. A concurrent reconcile that reads before this write
// will have an older resourceVersion and see different step statuses.
{
fields := logrus.Fields{
"resourceVersion": outInstallPlan.ResourceVersion,
"phase": outInstallPlan.Status.Phase,
}
for i, step := range outInstallPlan.Status.Plan {
if step.Resource.Kind == "BundleSecret" || step.Resource.Kind == "ServiceAccount" {
fields[fmt.Sprintf("step[%d].%s", i, step.Resource.Kind)] = string(step.Status)
}
}
logger.WithFields(fields).Debug("calling UpdateStatus")
}

// Update InstallPlan with status of transition. Log errors if we can't write them to the status.
if _, err := o.client.OperatorsV1alpha1().InstallPlans(plan.GetNamespace()).UpdateStatus(context.TODO(), outInstallPlan, metav1.UpdateOptions{}); err != nil {
if updatedPlan, err := o.client.OperatorsV1alpha1().InstallPlans(plan.GetNamespace()).UpdateStatus(context.TODO(), outInstallPlan, metav1.UpdateOptions{}); err != nil {
// OCPBUGS-35210: a 409 here means step statuses were NOT persisted.
// A concurrent reconcile that already read NotPresent will re-execute the BundleSecret step.
logger = logger.WithField("updateError", err.Error())
updateErr := errors.New("error updating InstallPlan status: " + err.Error())
if syncError == nil {
Expand All @@ -2179,6 +2217,13 @@ func (o *Operator) syncInstallPlans(obj interface{}) (syncError error) {
}
logger.Info("error transitioning InstallPlan")
syncError = fmt.Errorf("error transitioning InstallPlan: %s and error updating InstallPlan status: %s", syncError, updateErr)
} else {
// OCPBUGS-35210: log the new resourceVersion after a successful write.
// Any reconcile loop that read a lower resourceVersion saw stale data.
logger.WithFields(logrus.Fields{
"newResourceVersion": updatedPlan.ResourceVersion,
"phase": updatedPlan.Status.Phase,
}).Debug("UpdateStatus succeeded")
}

return
Expand Down Expand Up @@ -2474,7 +2519,7 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
o.logger.Errorf("failed to get a client for plan execution- %v", err)
return err
}
b := newBuilder(plan, o.lister.OperatorsV1alpha1().ClusterServiceVersionLister(), builderKubeClient, builderDynamicClient, r, o.logger, o.recorder)
b := newBuilder(plan, o.lister.OperatorsV1alpha1().ClusterServiceVersionLister(), builderKubeClient, o.client, builderDynamicClient, r, o.logger, o.recorder)

for i, step := range plan.Status.Plan {
if err := func(i int, step *v1alpha1.Step) error {
Expand Down Expand Up @@ -2521,14 +2566,25 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
}

switch step.Status {
case v1alpha1.StepStatusPresent, v1alpha1.StepStatusCreated, v1alpha1.StepStatusWaitingForAPI:
case v1alpha1.StepStatusPresent, v1alpha1.StepStatusCreated:
// OCPBUGS-35210: log skipped steps so we can confirm which reconcile
// loop sees Created (and skips) vs NotPresent (and re-executes).
if step.Resource.Kind == "BundleSecret" || step.Resource.Kind == "ServiceAccount" {
o.logger.WithFields(logrus.Fields{
"kind": step.Resource.Kind,
"name": step.Resource.Name,
"status": step.Status,
}).Debug("skipping step — already Created/Present")
}
return nil
case v1alpha1.StepStatusWaitingForAPI:
return nil
case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent:
manifest, err := r.ManifestForStep(step)
if err != nil {
return err
}
o.logger.WithFields(logrus.Fields{"kind": step.Resource.Kind, "name": step.Resource.Name}).Debug("execute resource")
o.logger.WithFields(logrus.Fields{"kind": step.Resource.Kind, "name": step.Resource.Name, "stepIndex": i}).Debug("execute resource")
switch step.Resource.Kind {
case v1alpha1.ClusterServiceVersionKind:
// Marshal the manifest into a CSV instance.
Expand Down Expand Up @@ -2623,6 +2679,17 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
}
s.Labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue

// OCPBUGS-35210: log the plan resourceVersion at the moment of creation.
// This ties the secret creation to a specific plan version, letting us
// confirm whether a concurrent reconcile held a stale or current view.
o.logger.WithFields(logrus.Fields{
"secret": s.Name,
"sa": s.Annotations[corev1.ServiceAccountNameKey],
"planRV": plan.ResourceVersion,
"stepIndex": i,
"stepStatus": step.Status,
}).Debug("ExecutePlan: creating BundleSecret (OCPBUGS-35210)")
Comment on lines +2682 to +2691

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Move the BundleSecret creation log to the active execution path.

b.create dispatches BundleSecret steps to NewBundleSecretStep, and doStep returns at Line 2565. This fallback resolver.BundleSecretKind case does not execute. Move this diagnostic into NewBundleSecretStep, or remove it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go`
around lines 2682 - 2691, Remove the unreachable BundleSecret creation
diagnostic from the fallback resolver.BundleSecretKind case in doStep. Add the
equivalent log to the active NewBundleSecretStep creation path, preserving the
existing fields and message so it records the plan resourceVersion when the
secret is actually created.


status, err := ensurer.EnsureBundleSecret(plan.Namespace, &s)
if err != nil {
return err
Expand Down Expand Up @@ -2915,8 +2982,25 @@ func (o *Operator) ExecutePlan(plan *v1alpha1.InstallPlan) error {
return notFoundErr
}
}
// OCPBUGS-35210: log the step that caused ExecutePlan to fail.
// This error becomes syncError in syncInstallPlans and appears in the
// UpdateStatus WRITE log — it is NOT a UpdateStatus error itself.
o.logger.WithFields(logrus.Fields{
"kind": step.Resource.Kind,
"name": step.Resource.Name,
"stepIndex": i,
"error": err.Error(),
}).Debug("step execution failed — ExecutePlan returning error")
return err
}
// OCPBUGS-35210: log the result of each step so we can see exactly
// which steps ran and what status they reached in this reconcile loop.
o.logger.WithFields(logrus.Fields{
"kind": step.Resource.Kind,
"name": step.Resource.Name,
"stepIndex": i,
"result": plan.Status.Plan[i].Status,
}).Debug("step execution result")
}

// Loop over one final time to check and see if everything is good.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package catalog

import (
"context"
"encoding/json"
"fmt"

"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
Expand All @@ -19,10 +21,12 @@ import (
"k8s.io/client-go/util/retry"

"github.com/operator-framework/api/pkg/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned"
listersv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/operators/internal/alongside"
crdlib "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/crd"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/ownerutil"
)

// Stepper manages cluster interactions based on the step.
Expand All @@ -43,6 +47,7 @@ type builder struct {
plan *v1alpha1.InstallPlan
csvLister listersv1alpha1.ClusterServiceVersionLister
opclient operatorclient.ClientInterface
olmClient versioned.Interface
dynamicClient dynamic.Interface
manifestResolver ManifestResolver
logger logrus.FieldLogger
Expand All @@ -51,11 +56,12 @@ type builder struct {
annotator alongside.Annotator
}

func newBuilder(plan *v1alpha1.InstallPlan, csvLister listersv1alpha1.ClusterServiceVersionLister, opclient operatorclient.ClientInterface, dynamicClient dynamic.Interface, manifestResolver ManifestResolver, logger logrus.FieldLogger, er record.EventRecorder) *builder {
func newBuilder(plan *v1alpha1.InstallPlan, csvLister listersv1alpha1.ClusterServiceVersionLister, opclient operatorclient.ClientInterface, olmClient versioned.Interface, dynamicClient dynamic.Interface, manifestResolver ManifestResolver, logger logrus.FieldLogger, er record.EventRecorder) *builder {
return &builder{
plan: plan,
csvLister: csvLister,
opclient: opclient,
olmClient: olmClient,
dynamicClient: dynamicClient,
manifestResolver: manifestResolver,
logger: logger,
Expand Down Expand Up @@ -91,6 +97,8 @@ func (b *builder) create(step v1alpha1.Step) (Stepper, error) {
case crdlib.V1Beta1Version:
return b.NewCRDV1Beta1Step(b.opclient.ApiextensionsInterface().ApiextensionsV1beta1(), &step, manifest), nil
}
case resolver.BundleSecretKind:
return b.NewBundleSecretStep(&step, manifest), nil
}
return nil, notSupportedStepperErr{fmt.Sprintf("stepper interface does not support %s", step.Resource.Kind)}
}
Expand Down Expand Up @@ -318,3 +326,92 @@ func setInstalledAlongsideAnnotation(a alongside.Annotator, dst metav1.Object, n

a.ToObject(dst, nns)
}

// NewBundleSecretStep returns a StepperFunc for BundleSecret steps (OCPBUGS-35210 Fix 2).
//
// SA-token Secrets must not be created before their owning ServiceAccount exists — the
// Kubernetes token controller (KCM) immediately deletes orphaned token secrets, and
// EnsureBundleSecret would mark the step Created permanently, preventing any retry.
//
// This StepperFunc returns WaitingForAPI when the SA is absent so that NeedsRequeue()
// keeps phase=Installing and OLM retries after 5 s. On the retry the SA has been
// created (it appears later in the plan), and the secret is created successfully.
// WaitingForAPI in the StepperFunc path is handled here directly — it never reaches
// the main ExecutePlan switch that would otherwise skip the step.
func (b *builder) NewBundleSecretStep(step *v1alpha1.Step, manifest string) StepperFunc {
return func() (v1alpha1.StepStatus, error) {
switch step.Status {
case v1alpha1.StepStatusPresent, v1alpha1.StepStatusCreated:
return step.Status, nil
}

namespace := b.plan.GetNamespace()

var s corev1.Secret
if err := json.Unmarshal([]byte(manifest), &s); err != nil {
return v1alpha1.StepStatusUnknown, err
}

saName := s.Annotations[corev1.ServiceAccountNameKey]
if s.Type == corev1.SecretTypeServiceAccountToken && saName != "" {
_, saErr := b.opclient.KubernetesInterface().CoreV1().
ServiceAccounts(namespace).Get(context.TODO(), saName, metav1.GetOptions{})
if apierrors.IsNotFound(saErr) {
logrus.WithFields(logrus.Fields{
"secret": s.Name,
"sa": saName,
}).Info("BundleSecretStep: SA not yet created — returning WaitingForAPI (OCPBUGS-35210)")
return v1alpha1.StepStatusWaitingForAPI, nil
}
if saErr != nil {
return v1alpha1.StepStatusUnknown, saErr
}
}

s.SetNamespace(namespace)
if s.Labels == nil {
s.Labels = map[string]string{}
}
s.Labels[install.OLMManagedLabelKey] = install.OLMManagedLabelValue

// Mirror the original owner-ref logic from operator.go: add CSV owner ref
// with live API UID lookup (same as getUpdatedOwnerReferences) so the secret
// is GC'd when the operator is uninstalled.
if step.Resolving != "" {
owner := &v1alpha1.ClusterServiceVersion{}
owner.SetNamespace(namespace)
owner.SetName(step.Resolving)
ownerutil.AddNonBlockingOwner(&s, owner)
// Update the empty UID with the current CSV UID via live API call.
if csv, err := b.olmClient.OperatorsV1alpha1().
ClusterServiceVersions(namespace).Get(context.TODO(), step.Resolving, metav1.GetOptions{}); err == nil {
refs := s.GetOwnerReferences()
for i := range refs {
if refs[i].Kind == v1alpha1.ClusterServiceVersionKind && refs[i].Name == step.Resolving {
refs[i].UID = csv.GetUID()
}
}
s.SetOwnerReferences(refs)
} else if !apierrors.IsNotFound(err) {
return v1alpha1.StepStatusUnknown, err
} else {
// CSV not found — clear the empty-UID owner ref to avoid API rejection.
s.SetOwnerReferences(nil)
}
Comment on lines +395 to +400

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve existing owner references when the CSV is absent.

Line 399 removes every owner reference. It must remove only the empty-UID CSV reference added at Line 384. Otherwise, a Secret manifest with other owner references loses them and can outlive its intended owner.

Proposed fix
-				s.SetOwnerReferences(nil)
+				refs := s.GetOwnerReferences()
+				filtered := refs[:0]
+				for _, ref := range refs {
+					if ref.Kind == v1alpha1.ClusterServiceVersionKind &&
+						ref.Name == step.Resolving && ref.UID == "" {
+						continue
+					}
+					filtered = append(filtered, ref)
+				}
+				s.SetOwnerReferences(filtered)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if !apierrors.IsNotFound(err) {
return v1alpha1.StepStatusUnknown, err
} else {
// CSV not found — clear the empty-UID owner ref to avoid API rejection.
s.SetOwnerReferences(nil)
}
} else if !apierrors.IsNotFound(err) {
return v1alpha1.StepStatusUnknown, err
} else {
// CSV not found — clear the empty-UID owner ref to avoid API rejection.
refs := s.GetOwnerReferences()
filtered := refs[:0]
for _, ref := range refs {
if ref.Kind == v1alpha1.ClusterServiceVersionKind &&
ref.Name == step.Resolving && ref.UID == "" {
continue
}
filtered = append(filtered, ref)
}
s.SetOwnerReferences(filtered)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step.go`
around lines 395 - 400, Update the CSV-not-found branch in the step status logic
to remove only the empty-UID CSV owner reference created earlier, rather than
clearing all references via SetOwnerReferences(nil). Preserve every unrelated
existing owner reference while retaining the intended cleanup of the absent CSV
reference.

}

_, createErr := b.opclient.KubernetesInterface().CoreV1().
Secrets(namespace).Create(context.TODO(), &s, metav1.CreateOptions{})
Comment on lines +403 to +404

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Create BundleSecret resources with the attenuated client.

NewBundleSecretStep creates a namespaced Secret through b.opclient. ExecutePlan supplies builderKubeClient, which comes from the unattenuated factory for CRD installation. BundleSecret creation now bypasses the OperatorGroup attenuated ServiceAccount and uses default OLM credentials.

  • staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step.go#L403-L404: Use a scoped client for ServiceAccount and Secret operations.
  • staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go#L2522-L2522: Pass both the privileged CRD client and the attenuated namespaced client, then select the scoped client for BundleSecret steps.
📍 Affects 2 files
  • staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step.go#L403-L404 (this comment)
  • staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go#L2522-L2522
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step.go`
around lines 403 - 404, The BundleSecret flow must use the
OperatorGroup-attenuated namespaced client rather than the privileged client. In
staging/operator-lifecycle-manager/pkg/controller/operators/catalog/step.go:403-404,
update NewBundleSecretStep’s Secret creation to use the scoped client for
ServiceAccount and Secret operations; in
staging/operator-lifecycle-manager/pkg/controller/operators/catalog/operator.go:2522,
pass both the privileged CRD client and attenuated namespaced client through
ExecutePlan and select the scoped client for BundleSecret steps.

if createErr == nil {
return v1alpha1.StepStatusCreated, nil
}
if apierrors.IsAlreadyExists(createErr) {
s.SetNamespace(namespace)
if _, updateErr := b.opclient.UpdateSecret(&s); updateErr != nil {
return v1alpha1.StepStatusUnknown, updateErr
}
return v1alpha1.StepStatusPresent, nil
}
return v1alpha1.StepStatusUnknown, createErr
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@ func NewStepResourceFromBundle(bundle *api.Bundle, namespace, replaces, catalogS
}
steps := []v1alpha1.StepResource{step}

// Original ordering: bundle objects first, then synthesized SA/RBAC last.
// This is the UNFIXED ordering that causes OCPBUGS-35210.
for _, object := range bundle.Object {
dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(object), 10)
unst := &unstructured.Unstructured{}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading