Skip to content

fix(pruner): ensure enforcedConfigLevel is always defaulted and DeepCopy is correct#3688

Merged
tekton-robot merged 1 commit into
tektoncd:mainfrom
infernus01:preserve-enforcedConfigLevel
Jul 10, 2026
Merged

fix(pruner): ensure enforcedConfigLevel is always defaulted and DeepCopy is correct#3688
tekton-robot merged 1 commit into
tektoncd:mainfrom
infernus01:preserve-enforcedConfigLevel

Conversation

@infernus01

Copy link
Copy Markdown
Member

Changes

  • Fix enforcedConfigLevel silently vanishing from the pruner global config
    when a user provides a partial global-config (e.g. only ttlSecondsAfterFinished)
  • Fix DeepCopyInto shallow copy bug that prevented the defaulting webhook
    from detecting changes made by SetDefaults

Problem

When a user provides any global-config field without explicitly setting
enforcedConfigLevel, the v0.79.1 SetDefaults skips defaulting entirely
because GlobalConfig is non-nil. Combined with omitempty struct tags in
the vendored pruner, enforcedConfigLevel is omitted from the ConfigMap.
The pruner controller then falls back to the internal default (resource
level), which never consults namespace-level ConfigMaps — all selector-based
pruning rules silently stop working.

On top of this, DeepCopyInto for TektonPrunerConfig does *out = *in,
which copies the *GlobalConfig pointer instead of the struct. The Knative
defaulting webhook deep-copies the object before calling SetDefaults, then
compares it with the original to generate a patch. Because both copies share
the same pointer, the webhook always sees no difference (PatchBytes: null)
and never applies the defaults.

Root Cause

Commit 774867c changed GlobalConfig from a value type to a pointer and
guarded SetDefaults() with if p.GlobalConfig == nil to prevent
historyLimit from resetting to 100 when set to null. The side effect was
that enforcedConfigLevel was never filled in for partial configs. The
existing DeepCopyInto was not updated for the pointer type change.

Fix

tektonpruner_defaults.go: Always fill in enforcedConfigLevel when
nil, without touching other fields:

if p.GlobalConfig == nil {
    p.GlobalConfig = &config.GlobalConfig{}
    p.GlobalConfig.SetDefaults()
} else if p.GlobalConfig.EnforcedConfigLevel == nil {
    v := config.EnforcedConfigLevelGlobal
    p.GlobalConfig.EnforcedConfigLevel = &v
}

tektonpruner_types.go: Deep-copy the GlobalConfig pointer:

func (in *TektonPrunerConfig) DeepCopyInto(out *TektonPrunerConfig) {
    *out = *in
    if in.GlobalConfig != nil {
        out.GlobalConfig = new(config.GlobalConfig)
        *out.GlobalConfig = *in.GlobalConfig
    }
}

Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

See the contribution guide for more details.

Release Notes

NONE

@tekton-robot tekton-robot added the release-note-none Denotes a PR that doesnt merit a release note. label Jul 9, 2026
@tekton-robot tekton-robot added the size/XS Denotes a PR that changes 0-9 lines, ignoring generated files. label Jul 9, 2026
@infernus01

Copy link
Copy Markdown
Member Author

…opy is correct

  The SetDefaults change in commit 774867c guarded GlobalConfig.SetDefaults()
  behind a nil check to allow users to set historyLimit to null without it
  resetting. However, this meant enforcedConfigLevel was never filled in when
  a user provided a partial global-config (e.g. only ttlSecondsAfterFinished).
  The pruner controller then defaulted to the "resource" level internally,
  which silently ignores all namespace-level selector rules.

  Additionally, DeepCopyInto for TektonPrunerConfig copied the *GlobalConfig
  pointer instead of the underlying struct. This caused the Knative defaulting
  webhook to see no difference between the original and the copy after
  SetDefaults ran, so the patch was always null and defaults were never applied.

Signed-off-by: Shubham Bhardwaj <shubbhar@redhat.com>
@infernus01 infernus01 force-pushed the preserve-enforcedConfigLevel branch from d949c0c to f718bcf Compare July 9, 2026 08:09
@codecov-commenter

codecov-commenter commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 24.48%. Comparing base (6ce06ca) to head (f718bcf).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3688      +/-   ##
==========================================
- Coverage   24.49%   24.48%   -0.01%     
==========================================
  Files         470      470              
  Lines       24919    24924       +5     
==========================================
- Hits         6103     6102       -1     
- Misses      18122    18126       +4     
- Partials      694      696       +2     
Flag Coverage Δ
unit-tests 24.48% <ø> (-0.01%) ⬇️

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.

@infernus01

Copy link
Copy Markdown
Member Author

/retest

@infernus01 infernus01 closed this Jul 9, 2026
@infernus01 infernus01 reopened this Jul 9, 2026
@anithapriyanatarajan

Copy link
Copy Markdown
Contributor

@infernus01 did we verify the impact on upgrade due to this change?

@infernus01

Copy link
Copy Markdown
Member Author

@anithapriyanatarajan There can be 4 cases for the upgrade scenario:
Case 1: User explicitly set enforcedConfigLevel: namespace
The else if p.GlobalConfig.EnforcedConfigLevel == nil is false — their value is non-nil. Nothing changes. No impact.

Case 2: User explicitly set enforcedConfigLevel: global
Same — non-nil, skipped. No impact.

Case 3: User never set enforcedConfigLevel, but old operator (<=v0.78.1) filled it in
On versions <=v0.78.1, SetDefaults always ran and set global. The field already exists. This fix skips it. No impact.

Case 4: User never set enforcedConfigLevel, running v0.79.1+ where it was silently dropped
This is the only case where behavior changes. Let's say, enforcedConfigLevel is missing from the ConfigMap(which can, without this fix). A user sets per-namespace overrides in their global config:

  tektonpruner:
    global-config:
      ttlSecondsAfterFinished: 60
      namespaces:
        dev:
          ttlSecondsAfterFinished: 30

Without explicitly setting enforcedConfigLevel. The broken SetDefaults never filled it in, so it's missing from the ConfigMap. The pruner's fallback is resource level, which happens to check globalSpec.Namespaces["dev"] and finds TTL=30. And this I haven't yet verified will work or not. But after this fix, enforcedConfigLevel: global is filled in and at global level, the pruner only uses root-level settings. The namespaces: section is skipped. The dev namespace override will not work.
So this case is technically possible but unlikely in practice.

@anithapriyanatarajan

Copy link
Copy Markdown
Contributor

/lgtm

@tekton-robot tekton-robot added the lgtm Indicates that a PR is ready to be merged. label Jul 10, 2026
@tekton-robot

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: pramodbindal

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

The pull request process is described 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 added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 10, 2026
@tekton-robot tekton-robot merged commit 477bca6 into tektoncd:main Jul 10, 2026
26 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesnt merit a release note. size/XS Denotes a PR that changes 0-9 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants