Skip to content

feat(namespacesync): add NamespaceSyncController for per-namespace resource management - #3581

Open
jkhelil wants to merge 2 commits into
tektoncd:mainfrom
jkhelil:rfe-7814-namespace-sync-controller
Open

feat(namespacesync): add NamespaceSyncController for per-namespace resource management#3581
jkhelil wants to merge 2 commits into
tektoncd:mainfrom
jkhelil:rfe-7814-namespace-sync-controller

Conversation

@jkhelil

@jkhelil jkhelil commented Jun 26, 2026

Copy link
Copy Markdown
Member

Changes

Introduces a new NamespaceSyncController that replaces the
scan-based batch processing in rbac.go with an event-driven,
per-namespace reconciler. Addresses RFE-7814 (flexible service
account binding for Quay Robot credentials).

What this does

  • Adds NamespaceSyncController — a watch-based controller that
    reacts to Namespace, ServiceAccount, Secret, and TektonConfig
    events to ensure per-namespace Tekton resources are present and
    up to date in every user namespace
  • Creates pipeline SA, SCC RoleBinding, edit RoleBinding, and
    CA bundle ConfigMaps in each namespace
  • Manages openshift-pipelines-clusterinterceptors CRB
    incrementally (add/remove only the affected namespace's subject)
  • Supports Quay Robot secret binding via spec.platforms.openshift .namespaceSync.secretBindings (the core RFE-7814 use case)
  • Adds typed spec.platforms.openshift.namespaceSync API field to
    TektonConfig, replacing legacy spec.params entries
  • Removes ~1300 lines of dead per-namespace batch code from rbac.go
  • Registers the controller in the lifecycle container and adds
    LeaderAwareFuncs embedding (required by Knative sharedmain)
  • Adds namespaceSync schema to the TektonConfig CRD

Status

Work in progress — more e2e tests needed before merge:

  • E2E test: Quay Robot secret binding end-to-end
  • E2E test: namespace deletion removes CRB subject
  • E2E test: self-healing when pipeline SA is deleted manually
  • make test lint full pass

Verified on cluster

All per-namespace resources (pipeline SA, SCC RoleBinding, edit
RoleBinding, CA bundles, CRB subject) created correctly in new
namespaces within seconds. Established namespaces unaffected.

Submitter Checklist

Release Notes

Add NamespaceSyncController: event-driven per-namespace provisioning
of pipeline SA, SCC/edit RoleBindings, CA bundles, and Quay Robot
secret binding on OpenShift. Replaces legacy batch scan in rbac.go.
New typed API: spec.platforms.openshift.namespaceSync in TektonConfig.

The spec.params entries createRbacResource, createCABundleConfigMaps,
and legacyPipelineRbac are now deprecated in favor of the typed
namespaceSync fields (createPipelineSA, createCABundles,
createEditRoleBinding). They are still honored for backward
compatibility and are automatically migrated to the typed fields
(and removed from spec.params) during the first reconcile after
upgrade -- no manual action is required, but new configurations
should use namespaceSync directly.

Made with Cursor

@tekton-robot tekton-robot added release-note Denotes a PR that will be considered when it comes time to generate release notes. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. labels Jun 26, 2026
@tekton-robot tekton-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Jun 26, 2026
@jkhelil
jkhelil force-pushed the rfe-7814-namespace-sync-controller branch 3 times, most recently from c1be9cb to c2fc182 Compare June 29, 2026 11:22
@jkhelil jkhelil changed the title WIP: feat(namespacesync): add NamespaceSyncController for per-namespace resource management feat(namespacesync): add NamespaceSyncController for per-namespace resource management Jun 29, 2026
@jkhelil
jkhelil force-pushed the rfe-7814-namespace-sync-controller branch from c2fc182 to 481eaa9 Compare June 29, 2026 11:37
@jkhelil
jkhelil marked this pull request as ready for review June 29, 2026 11:39
@tekton-robot tekton-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 29, 2026
@tekton-robot
tekton-robot requested a review from enarha June 29, 2026 11:39
@jkhelil
jkhelil force-pushed the rfe-7814-namespace-sync-controller branch 2 times, most recently from a66cbfa to ba1e9f7 Compare July 2, 2026 08:26
@tekton-robot tekton-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 6, 2026
@anithapriyanatarajan anithapriyanatarajan self-assigned this Jul 30, 2026
@anithapriyanatarajan

Copy link
Copy Markdown
Contributor

@jkhelil - If you are still planning to pursue this PR, please rebase.

@jkhelil

jkhelil commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

@waveywaves @pratap0007 Would you mind review this please ?

@jkhelil

jkhelil commented Aug 13, 2026

Copy link
Copy Markdown
Member Author

/assign @waveywaves

@jkhelil
jkhelil force-pushed the rfe-7814-namespace-sync-controller branch from ba1e9f7 to 903059b Compare August 13, 2026 08:50
@tekton-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
To complete the pull request process, please ask for approval from anithapriyanatarajan after the PR has been reviewed.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tekton-robot tekton-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Aug 13, 2026
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 26.77%. Comparing base (13ff957) to head (df1befe).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3581      +/-   ##
==========================================
+ Coverage   26.44%   26.77%   +0.32%     
==========================================
  Files         465      467       +2     
  Lines       24991    25016      +25     
==========================================
+ Hits         6609     6698      +89     
+ Misses      17661    17621      -40     
+ Partials      721      697      -24     
Flag Coverage Δ
unit-tests 26.77% <ø> (+0.32%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

return err
}

cfg := tc.Spec.Platforms.OpenShift.NamespaceSync

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Older TektonConfig objects will not have this field, so namespace setup may stop after upgrade. Could we handle existing configurations and add an upgrade test?

if cfg.NamespaceSelector == nil {
return true
}
sel, err := metav1.LabelSelectorAsSelector(cfg.NamespaceSelector)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

{} matches every namespace, not none, and invalid selectors also match every namespace. Could we fix and test both cases?

})

// Namespace Add/Update → reconcile that namespace.
if _, err := nsInf.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Deleting a namespace leaves its pipeline entry in the cluster-wide permission binding. Could we remove it on deletion?

// To avoid a thundering-herd on clusters that don't use secret bindings,
// we only enqueue when NamespaceSync has SecretBindings configured AND the
// secret name/labels match at least one binding rule.
if _, err := secretInf.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Secret label changes are not watched. Could we handle updates so matching Secrets are added and removed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed

}
}

if len(cfg.SecretBindings) > 0 {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing or removing secretBindings leaves old Secret references. Could we remove references added by this controller?

return nil
}

return retry.RetryOnConflict(retry.DefaultRetry, func() error {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Each retry uses the same outdated ServiceAccount, so it cannot resolve a conflict. Could we fetch it again on each retry?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixed

Comment thread pkg/reconciler/openshift/namespacesync/controller.go

logger.Infof("Creating edit RoleBinding in namespace %s", ns.Name)
_, err = rbClient.Create(ctx, &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

New RoleBindings have no owner, and incorrect existing bindings are not fixed. Could we preserve ownership and correct existing bindings?

jkhelil added a commit to jkhelil/operator that referenced this pull request Sep 2, 2026
Address review feedback on PR tektoncd#3581 (RFE-7814 namespace sync):

- Apply TektonConfig defaulting in-memory on every reconcile so
  pre-existing CRs created before this feature keep syncing without
  requiring a spec edit.
- Correct RoleBinding/OwnerReference drift instead of silently
  ignoring it, and always run secret binding cleanup even when
  secretBindings is empty.
- Track secrets this controller has bound via an annotation on the
  pipeline SA so stale references are removed when a binding rule
  (or the whole secretBindings list) is changed or removed, for
  both named and labelSelector bindings.
- Strip Secret payloads from the informer cache and watch for label
  changes that add/remove a Secret from a labelSelector binding.
- Treat an explicit empty namespaceSelector as "match nothing" and
  fail malformed selectors closed.
- Simplify ensureSecretBindings into smaller, single-purpose helpers.

Add e2e coverage for the two scenarios PR tektoncd#3581 flagged as missing
before merge: named/labelSelector secret binding lifecycle, and CRB
subject cleanup on namespace deletion. Adds
WaitForServiceAccountImagePullSecret and WaitForClusterRoleBindingSubject
helpers to test/resources/rbac.go.

Also fixes two test fixture gaps found while re-running the suite:
a missing "edit" ClusterRole fixture, and a namespace-deletion test
that never deleted the pipeline SA it was supposed to simulate away.

Signed-off-by: Jawed khelil <jkhelil@redhat.com>
Assisted-by: Claude Sonnet 5 (via Cursor)
@jkhelil
jkhelil force-pushed the rfe-7814-namespace-sync-controller branch from 903059b to ca9814b Compare September 2, 2026 12:24
Replace the O(N) scan-based per-namespace reconciliation in rbac.go
with a watch-based NamespaceSyncController that reacts to namespace,
ServiceAccount, Secret, and TektonConfig events instead of re-scanning
every namespace on each TektonConfig reconcile.

New typed API under TektonConfig.spec.platforms.openShift.namespaceSync:
- createPipelineSA: manage the pipeline SA (replaces createRbacResource)
- createCABundles: manage CA bundle ConfigMaps (replaces
  createCABundleConfigMaps)
- createEditRoleBinding: manage openshift-pipelines-edit RoleBinding
  (replaces legacyPipelineRbac)
- createSCCRoleBinding: manage pipelines-scc-rolebinding (default true)
- secretBindings: bind registry secrets (by name or label selector) to
  the pipeline SA
- namespaceSelector: restrict sync to namespaces matching labels

Signed-off-by: Jawed khelil <jkhelil@redhat.com>
Assisted-by: Claude Sonnet 4.6 (via Cursor)
Assisted-by: Claude Sonnet 5 (via Cursor)
Co-authored-by: Cursor <cursoragent@cursor.com>
@jkhelil
jkhelil force-pushed the rfe-7814-namespace-sync-controller branch from ca9814b to 7672846 Compare September 3, 2026 04:51
SetDefaults already migrates the deprecated createRbacResource,
createCABundleConfigMaps, and legacyPipelineRbac spec.params entries
onto the typed spec.platforms.openshift.namespaceSync fields, but only
in-memory on a deep copy on every reconcile. It never writes the
result back, so upgraded CRs kept carrying the legacy params forward
indefinitely with no way to tell they were deprecated.

Add migrateLegacyNamespaceSyncParams, a pre-upgrade job (following the
existing removeHubFromTektonConfig/removeDeprecatedDisableAffinity
Assistant pattern) that persists the same migration once per upgrade,
so the stored CR ends up on the typed schema and the legacy params are
actually removed from spec.params.

Signed-off-by: Jawed khelil <jkhelil@redhat.com>
Assisted-by: Claude Sonnet 5 (via Cursor)
Co-authored-by: Cursor <cursoragent@cursor.com>
jkhelil added a commit to jkhelil/release-tests-ginkgo that referenced this pull request Sep 3, 2026
…amespaceSync migration

SetDefaults() already migrates createRbacResource/createCABundleConfigMaps/
legacyPipelineRbac in-memory on every reconcile, but tektoncd/operator#3581
also added a one-time pre-upgrade job that persists this migration onto the
stored TektonConfig CR. Add a spec that forces that job to re-run (by
resetting the pre-upgrade-version status annotation, as if the operator had
just been upgraded) and asserts the legacy params are actually removed from
spec.params rather than just recomputed in memory.
@jkhelil

jkhelil commented Sep 3, 2026

Copy link
Copy Markdown
Member Author

NamespaceSyncController E2E Test Proof

Result: 15/15 specs PASSED, 0 failed, 0 errors.

Cluster under test

See cluster-evidence.txt for the raw command output. Summary:

Field Value
Cluster api.sq3p3-8mzcc-ss2.i5ut.p3.openshiftapps.com
Access cluster-admin
Operator image quay.io/jkhelil/operator-1d69a75f22dd094880847eac907fb2c1@sha256:bdc535f2...
TektonConfig status.version devel
TektonConfig Ready condition True

Per-spec results

# Spec Result Duration
1 Baseline sync on namespace creation > creates the pipeline SA, CA bundles, edit RoleBinding and SCC RoleBinding PASS 39.6s
2 Per-feature toggles > createPipelineSA PASS 35.6s
3 Per-feature toggles > createCABundles PASS 31.4s
4 Per-feature toggles > createEditRoleBinding PASS 31.9s
5 Per-feature toggles > createSCCRoleBinding PASS 29.2s
6 Secret binding — named secret > binds on create, unbinds on delete, and stays unbound once the binding rule is removed PASS 50.1s
7 Secret binding — label selector > binds all matching secrets and unbinds only the one that stops matching PASS 49.5s
8 Self-healing > recreates manually deleted resources and re-adds a manually removed secret ref PASS 60.0s
9 Namespace deletion > removes the pipeline SA subject from the cluster-scoped ClusterInterceptors CRB PASS 31.5s
10 namespaceSelector scoping > syncs only namespaces matching matchLabels PASS 34.4s
11 namespaceSelector scoping > an explicit empty namespaceSelector ({}) opts out every namespace PASS 16.7s
12 System namespace exclusion > never touches namespaces matching the system-namespace ignore pattern PASS 32.1s
13 Legacy spec.params migration > maps createRbacResource/createCABundleConfigMaps/legacyPipelineRbac onto typed defaults PASS 34.2s
14 Legacy spec.params migration > persists the migration onto the stored CR via the pre-upgrade job, not just in-memory defaulting PASS 28.0s
15 Upgrade compatibility > keeps syncing with documented defaults when namespaceSync is entirely absent PASS 31.3s

Artifacts in this folder

  • nssync-full-run.log — full raw console transcript of the run (ginkgo -v, every step, every oc call and its output).
  • nssync-junit.xml — JUnit XML report (tests=63 failures=0 errors=0 skipped=46; the 63 includes ginkgo's SynchronizedBeforeSuite/AfterSuite container nodes alongside the 15 focused specs and 46 skipped specs from the rest of the suite that weren't in scope for this focus).
  • nssync-report.json — Ginkgo's structured JSON report (machine-readable, one entry per spec with state/timing).
  • cluster-evidence.txt — raw oc output proving the cluster identity, operator pod status, image digest, and TektonConfig readiness at test time.

How this was run

PATH="/tmp/faux-bin:$PATH" go run github.com/onsi/ginkgo/v2/ginkgo -v \
  --focus "NamespaceSyncController" \
  --junit-report=nssync-junit.xml \
  --json-report=nssync-report.json \
  ./tests/operator/...

(/tmp/faux-bin contains a stub opc binary so the suite's ValidateOperatorInstallStatus prerequisite check passes; the OpenShift Pipelines CLI itself isn't required for anything these specs actually assert against.)

Note: an earlier run against this same cluster caught a real test-code bug (two legacy-params specs issued the namespaceSync reset and the params patch as separate API calls, racing with TektonConfig's mutating webhook). That was root-caused and fixed in commit 7f5fc8c before this proof run — see the PR for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants