Skip to content

fix(compliance): Make tailored profiles extends attribute optional#19158

Open
guzalv wants to merge 6 commits intomasterfrom
master-base/gualvare/fix-compliance-make-tp-extends-optional
Open

fix(compliance): Make tailored profiles extends attribute optional#19158
guzalv wants to merge 6 commits intomasterfrom
master-base/gualvare/fix-compliance-make-tp-extends-optional

Conversation

@guzalv
Copy link
Contributor

@guzalv guzalv commented Feb 24, 2026

Description

While our current code requires tailored profiles to contain an "extends" attribute, it was made optional in CO v0.1.4 , to enable creating tailored profiles from scratch.

This PR makes the attribute optional and adds tests to ensure effective rules are computed correctly in all cases.

User-facing documentation

Testing and quality

  • the change is production ready: the change is GA, or otherwise the functionality is gated by a feature flag
  • CI results are inspected

Automated testing

  • added unit tests
  • added e2e tests
  • added regression tests
  • added compatibility tests
  • modified existing tests

How I validated my change

CI

@openshift-ci
Copy link

openshift-ci bot commented Feb 24, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In the Fetched profile not of type 'unstructured' log line, consider logging profileObj rather than obj so the reported type corresponds to the value actually being type-asserted.
  • The mock listers’ List and Get methods currently return (nil, nil); having them return an error or panic instead would make tests fail fast if those methods are accidentally used, preventing silent misbehavior in future changes.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the `Fetched profile not of type 'unstructured'` log line, consider logging `profileObj` rather than `obj` so the reported type corresponds to the value actually being type-asserted.
- The mock listers’ `List` and `Get` methods currently return `(nil, nil)`; having them return an error or panic instead would make tests fail fast if those methods are accidentally used, preventing silent misbehavior in future changes.

## Individual Comments

### Comment 1
<location path="sensor/kubernetes/complianceoperator/dispatchers/complianceoperatortailoredprofiles_test.go" line_range="79-88" />
<code_context>
+	baseProfile := &v1alpha1.Profile{
</code_context>
<issue_to_address>
**suggestion (testing):** Add assertions that the tailored profile inherits labels/annotations/description from the base profile when Extends is set

In `TestProcessEvent_ExtendsProfile`, the test only checks rule computation, but this change also preserves labels, annotations, and description from the base profile when `Extends` is set. Please set `Labels`, `Annotations`, and `Description` on `baseProfile`, leave the tailored profile’s `Spec.Description` empty, and assert that the resulting `ComplianceOperatorProfile` inherits those values. That will lock in the new `baseProfile` behavior and guard against regressions.

Suggested implementation:

```golang
		ObjectMeta: metav1.ObjectMeta{
			Name:      "ocp4-cis",
			Namespace: "openshift-compliance",
			Labels: map[string]string{
				"base-profile-label": "label-value",
			},
			Annotations: map[string]string{
				"base-profile-annotation": "annotation-value",
			},
		},

```

```golang
		ProfilePayload: v1alpha1.ProfilePayload{
			ID:          "xccdf_org.ssgproject.content_profile_cis",
			Description: "Base profile description",

```

To fully implement your review comment, the following additional changes are needed elsewhere in `TestProcessEvent_ExtendsProfile` (not visible in the provided snippet):

1. **TailoredProfile spec description**  
   - Locate the construction of the `*v1alpha1.TailoredProfile` used in this test.  
   - In `tp.Spec`, ensure `Description` is explicitly empty, e.g.:
     ```go
     Spec: v1alpha1.TailoredProfileSpec{
         // other fields...
         Description: "",
     }
     ```

2. **Assertions on the resulting ComplianceOperatorProfile**  
   - After the code under test processes the event and produces the resulting `ComplianceOperatorProfile` (let’s call it `cop`), add assertions verifying inherited metadata and description:
     ```go
     require.Equal(t, baseProfile.Labels, cop.Labels)
     require.Equal(t, baseProfile.Annotations, cop.Annotations)
     require.Equal(t, baseProfile.Description, cop.Spec.Description)
     ```
   - If the description is stored differently (e.g., `cop.Annotations["description"]` or a differently named field), adjust the assertion to target the actual field used for the profile description in your `ComplianceOperatorProfile` type.

These changes will ensure the test not only validates rule computation, but also that labels, annotations, and description from the base profile are preserved when `Extends` is set, guarding against regressions in the new `baseProfile` behavior.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@guzalv
Copy link
Contributor Author

guzalv commented Feb 24, 2026

@guzalv
Copy link
Contributor Author

guzalv commented Feb 24, 2026

/test ocp-4-20-compliance-e2e-tests

@rhacs-bot
Copy link
Contributor

rhacs-bot commented Feb 24, 2026

Images are ready for the commit at 74b4543.

To use with deploy scripts, first export MAIN_IMAGE_TAG=4.11.x-120-g74b4543406.

@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 49.59%. Comparing base (3749e29) to head (74b4543).
⚠️ Report is 75 commits behind head on master.

Files with missing lines Patch % Lines
.../dispatchers/complianceoperatortailoredprofiles.go 75.00% 6 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #19158      +/-   ##
==========================================
+ Coverage   49.50%   49.59%   +0.08%     
==========================================
  Files        2668     2675       +7     
  Lines      201442   201830     +388     
==========================================
+ Hits        99715   100088     +373     
- Misses      94280    94282       +2     
- Partials     7447     7460      +13     
Flag Coverage Δ
go-unit-tests 49.59% <75.00%> (+0.08%) ⬆️

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

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

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • In the tests, you index event.ForwardMessages[0] without asserting that event.ForwardMessages is non-nil and non-empty; consider adding a require.NotEmpty(t, event.ForwardMessages) (or similar) before indexing to avoid panics if the dispatcher behavior changes.
  • For tailored profiles without Spec.Extends, baseProfile is left at its zero value and you rely solely on enabled rules; consider explicitly documenting or guarding this path (e.g., early-return when Extends == "" and there are disabled rules) so the behavior is clearer and misconfigurations are easier to diagnose.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the tests, you index `event.ForwardMessages[0]` without asserting that `event.ForwardMessages` is non-nil and non-empty; consider adding a `require.NotEmpty(t, event.ForwardMessages)` (or similar) before indexing to avoid panics if the dispatcher behavior changes.
- For tailored profiles without `Spec.Extends`, `baseProfile` is left at its zero value and you rely solely on enabled rules; consider explicitly documenting or guarding this path (e.g., early-return when `Extends == ""` and there are disabled rules) so the behavior is clearer and misconfigurations are easier to diagnose.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@guzalv guzalv marked this pull request as ready for review February 24, 2026 15:23
@guzalv guzalv requested a review from a team as a code owner February 24, 2026 15:23
@guzalv guzalv changed the title fix(compliance): Make tailored profiles extends attibute optional fix(compliance): Make tailored profiles extends attribute optional Feb 24, 2026
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Contributor

@mtodor mtodor left a comment

Choose a reason for hiding this comment

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

Nice work! Changes look good, I have added a few nitpicks, but nothing that changes functionality.

Labels: complianceProfile.Labels,
Annotations: complianceProfile.Annotations,
Description: stringutils.FirstNonEmpty(tailoredProfile.Spec.Description, complianceProfile.Description),
Labels: baseProfile.Labels,
Copy link
Contributor

Choose a reason for hiding this comment

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

nitpick: It's good practice to use getters instead directly accessing fields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, I didn't change this here because the existing code does not use getters, so it would be a rather unrelated refactor, so I'd prefer to open a separate PR to use getters everywhere

Description: stringutils.FirstNonEmpty(tailoredProfile.Spec.Description, complianceProfile.Description),
Labels: baseProfile.Labels,
Annotations: baseProfile.Annotations,
Description: stringutils.FirstNonEmpty(tailoredProfile.Spec.Description, baseProfile.Description),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there are a reason why we are not using the same logic for Labels and Annotations?

Under "the same logic" - I'm referring to: Use information from tailoredProfile if it exists, and if not -> use info from baseProfile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I suppose the previous reason was that, since tailored profiles were always extending a base profile, the annotations of that base were the meaningful ones, the TP itself was likely to not have any annotations.

Good catch though, because now we support TPs which don't extend a profile so it's good to take these metadata from them.

I did this: 74b4543

However I'm not fully convinced, because that changes previous intended behavior: for tailored profiles that extend a base profile, and where the user added metadata to the TP itself, previously we were ignoring the TP's metadata altogether, while now we only take the TP's metadata and ignore the base profile.

We could reverse the precedence to keep behavior unchanged, but that would make the code look weird because for labels/annotations the base takes precedence while for description the TP takes precedence.

We could also merge everything.

Or we can keep the current behavior (revert that commit I added) and consider this in a separate PR. I think the correct approach is what you suggested and I implemented here, ignoring user-provided metadata feels wrong.

Thoughts?

"k8s.io/client-go/tools/cache"
)

// mockNamespaceLister implements cache.GenericNamespaceLister
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we use already existing mock: sensor/kubernetes/listener/resources/mocks/dispatcher.go? As I see, it's one level higher and there are some functions related to tailored profiles. Tbh, I'm not sure where is it used and how.

Beside that, we usually keep mocks in separate package and in most cases they are generated with mockgen. I'm not sure if we can use that here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The existing mock is for Dispatcher interface, but we need to mock cache.GenericLister (to fetch the base profile).

Regarding mockgen, since this is a simple external interface with custom behavior (store/retrieve by namespace) - this seemed simpler than setting up expectations per test.

So I think we can use it but it would be less compact than this. Happy to change it if you prefer though!

for i, r := range profile.GetRules() {
ruleNames[i] = r.GetName()
}
assert.Len(t, ruleNames, 3)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit-nitpick: maybe sort and use Equal to []string{"ocp4-api-server-anonymous-auth", "ocp4-api-server-encryption-provider-cipher", "ocp4-audit-log-forwarding-enabled"} - easier to read/understand.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That results in nicer code indeed, thanks! Here: 89fe9bc

guzalv and others added 2 commits February 26, 2026 10:11
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ofile

This is relevant now that TPs without a base profile are supported.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@guzalv
Copy link
Contributor Author

guzalv commented Feb 26, 2026

/test ocp-4-20-compliance-e2e-tests

@openshift-ci
Copy link

openshift-ci bot commented Feb 26, 2026

@guzalv: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/ocp-4-20-nongroovy-e2e-tests 74b4543 link false /test ocp-4-20-nongroovy-e2e-tests
ci/prow/ocp-4-20-compliance-e2e-tests 74b4543 link false /test ocp-4-20-compliance-e2e-tests

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@guzalv guzalv mentioned this pull request Feb 26, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants