Skip to content

pkg: Stop installing function input CRDs and ignore disallowed kinds#6976

Merged
phisco merged 3 commits intocrossplane:mainfrom
adamwg:awg/no-more-input-crds
Jan 8, 2026
Merged

pkg: Stop installing function input CRDs and ignore disallowed kinds#6976
phisco merged 3 commits intocrossplane:mainfrom
adamwg:awg/no-more-input-crds

Conversation

@adamwg
Copy link
Copy Markdown
Member

@adamwg adamwg commented Dec 11, 2025

Description of your changes

This PR fixes three related issues in the package manager's handling of objects:

Enforce that functions can install only CRDs

The xpkg spec states that functions may include zero or more CRDs and no other resource types. The linter was not enforcing this, allowing functions to install other resource types such as XRDs and Compositions.

Add the IsCRD validator to the function linter to prevent other types from being accepted in functions.

Fixes #6975

Stop installing CRDs from Function packages

The xpkg spec says that CRDs included in Function packages will not be installed into the cluster, but until now they have been since Functions are treated the same as all other packages. Additionally, the previous commit allowed packages with arbitrary objects included to be installed, contrary to the spec (and desired behavior).

Filter objects in the establisher, so that regardless of what kinds of resources are present in a package we install only the desired ones.

Fixes #5294

Don't fail to install a package if it contains disallowed/unknown objects

Crossplane uses package linters in two places: at build time (in the CLI) and at install time (in the revision controller). In both cases, linter errors are treated as errors and stop the operation.

At build time, this makes sense: we should be strict about the packages we produce. At install time, this means we may reject packages that we could successfully install. For example (as described in #6525) if we allow new resource types in a package in a new version of Crossplane (as we've done with Operations and MRDs), older versions should still be able to install packages that contain them; the new resources would just be ignored. If package authors want to limit the versions their package works with, they can set a version constraint in the metadata.

Introduce a new Validator type, which is identical to a linter except in name. Add validators for all package types, which are identical to their linters except that they allow any object to be included.

In the revision controller, use validators to determine whether package installation should be attempted, where we previously used linters. Run the linters as well and record any lint errors as events, to help the user diagnose any unexpected behavior.

Fixes #6525

I have:

  • Read and followed Crossplane's contribution process.
  • Run earthly +reviewable to ensure this PR is ready for review.
  • Added or updated unit tests.
    - [ ] Added or updated e2e tests.
    - [ ] Linked a PR or a docs tracking issue to document this change.
    - [ ] Added backport release-x.y labels to auto-backport this PR.
    - [ ] Followed the API promotion workflow if this PR introduces, removes, or promotes an API.

The xpkg spec states that functions may include zero or more CRDs and no other
resource types. The linter was not enforcing this, allowing functions to install
other resource types such as XRDs and Compositions.

Add the `IsCRD` validator to the function linter to prevent other types from
being accepted in functions.

Fixes crossplane#6975

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 11, 2025

📝 Walkthrough

Walkthrough

Adds a FilteringEstablisher that filters package payload objects by allowed GroupKinds before delegating, and injects package Validators into the Reconciler. Reconciliation now validates packages (short-circuiting on failure and emitting a Validate event) before running linting; controllers are wired with type-specific validators.

Changes

Cohort / File(s) Summary
Filtering establisher
internal/controller/pkg/revision/establisher.go
New FilteringEstablisher type and NewFilteringEstablisher constructor. Establish() filters []runtime.Object by allowed schema.GroupKind values (uses slices.Contains) then delegates to wrapped Establisher. ReleaseObjects() delegates to wrapped establisher.
Filtering establisher tests
internal/controller/pkg/revision/establisher_test.go
Added TestFilteringEstablisherEstablish covering partial/full/no-match filtering scenarios and propagation of wrapped-establisher errors.
Reconciler integration and wiring
internal/controller/pkg/revision/reconciler.go
Added WithValidator(xpkg.Validator) ReconcilerOption and validator xpkg.Validator field on Reconciler. Reconcile flow runs r.validator.Lint (emits reasonValidate, wraps failures with errValidatePackage) before existing linting. Provider/Configuration/Function revision setup now create a FilteringEstablisher and pass type-specific validators.
Reconciler tests / event recording
internal/controller/pkg/revision/reconciler_test.go
Updated MockEstablisher and helpers to new Establish(ctx, objects, parent, control) signature. Added testRecorder to capture/assert events; tests updated for validation semantics and event expectations.
Validator and linter types
internal/xpkg/lint.go, internal/xpkg/validate.go
Introduced Validator (alias of parser.Linter) and factory functions: NewProviderValidator(), NewConfigurationValidator(), NewFunctionValidator(). Added NewConfigurationLinter() and adjusted NewFunctionLinter() to include CRD checks.

Sequence Diagram(s)

sequenceDiagram
    participant Reconciler
    participant Validator
    participant Linter
    participant Establisher
    participant API_Server

    Reconciler->>Validator: Lint(package)
    alt validation fails
        Validator-->>Reconciler: validation error
        Reconciler->>Reconciler: wrap errValidatePackage, record event (ValidatePackage)
        Note right of Reconciler: Stop reconcile (mark unhealthy)
    else validation succeeds
        Validator-->>Reconciler: valid
        Reconciler->>Linter: Lint(package) (non-blocking)
        Linter-->>Reconciler: lint findings (events)
        Reconciler->>Establisher: Establish(filtered objects)
        Establisher->>API_Server: apply allowed resources
        API_Server-->>Establisher: refs / errors
        Establisher-->>Reconciler: refs / error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas to focus review on:
    • Filtering logic in FilteringEstablisher.Establish() (GroupKind matching, behavior when filter is empty).
    • Reconciler validation short-circuit, error wrapping (errValidatePackage) and event semantics (reasonValidate).
    • Test updates: MockEstablisher signature changes, testRecorder correctness and event assertions.
    • Validator/linter composition to ensure package-type checks match intended constraints.

Suggested reviewers

  • bobh66
  • turkenh

Thanks — do you want me to grep for other establisher usages to confirm all call sites were updated?

Pre-merge checks

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)
Check name Status Explanation Resolution
Feature Gate Requirement ❌ Error Behavioral changes to validation and package installation occur unconditionally without feature gate protection despite existing feature gate infrastructure in codebase. Implement feature gate to guard validator invocation and lint-error-tolerant installation logic, enabling backward compatibility and user opt-in control.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main objectives of the PR: stopping installation of function input CRDs and filtering disallowed kinds, directly aligning with the core changes.
Description check ✅ Passed The description comprehensively explains three related changes: enforcing CRD-only validation for functions, filtering CRDs from installation, and handling unknown objects gracefully at install time.
Linked Issues check ✅ Passed The PR implementation fully addresses all three linked issues: adds IsCRD validator for function enforcement [#6975], implements FilteringEstablisher to prevent CRD installation [#5294], and introduces permissive validators for install-time flexibility [#6525].
Out of Scope Changes check ✅ Passed All changes align with stated objectives: FilteringEstablisher/validators support filtering, reconciler integration enables validation flow, and test updates properly validate new behavior. No unrelated changes detected.
Breaking Changes ✅ Passed The breaking changes check is not applicable because no modifications were made to files under the apis/** or cmd/** directories; all changes are confined to internal/ implementation details.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f854548 and 3f6e8c2.

📒 Files selected for processing (5)
  • internal/controller/pkg/revision/establisher.go (3 hunks)
  • internal/controller/pkg/revision/establisher_test.go (1 hunks)
  • internal/controller/pkg/revision/reconciler.go (10 hunks)
  • internal/controller/pkg/revision/reconciler_test.go (23 hunks)
  • internal/xpkg/lint.go (2 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

⚙️ CodeRabbit configuration file

**/*.go: Enforce Crossplane-specific patterns: Use crossplane-runtime/pkg/errors
for wrapping. Check variable naming (short for local scope, descriptive
for wider scope). Ensure 'return early' pattern. Verify error scoping
(declare in conditionals when possible). For nolint directives, require
specific linter names and explanations. CRITICAL: Ensure all error
messages are meaningful to end users, not just developers - avoid
technical jargon, include context about what the user was trying to do,
and suggest next steps when possible.

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/establisher.go
  • internal/controller/pkg/revision/reconciler_test.go
  • internal/controller/pkg/revision/reconciler.go
  • internal/xpkg/lint.go
**/*_test.go

⚙️ CodeRabbit configuration file

**/*_test.go: Enforce table-driven test structure: PascalCase test names (no
underscores), args/want pattern, use cmp.Diff with
cmpopts.EquateErrors() for error testing. Check for proper test case
naming and reason fields. Ensure no third-party test frameworks (no
Ginkgo, Gomega, Testify).

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/reconciler_test.go
**/internal/controller/**

⚙️ CodeRabbit configuration file

**/internal/controller/**: Review controller logic for proper reconciliation patterns, error
handling, and resource management. Pay special attention to conditions
and events: Conditions must be actionable for users (not developers),
stable/deterministic, with proper Type/Reason/Message format. Events
only when something actually happens, with specific details about what
changed. No transient errors in conditions/events. All error messages
must be meaningful to end users - include context about what
resource/operation failed and why.

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/establisher.go
  • internal/controller/pkg/revision/reconciler_test.go
  • internal/controller/pkg/revision/reconciler.go
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Review code for functional correctness, proper documentation and intent explanation, adherence to Coding Style guidelines, idiomatic alignment with existing Crossplane APIs, sufficient test coverage, DCO sign-off, and passing CI jobs
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Use crossplane-runtime/pkg/errors to wrap errors with context rather than using error constants

Applied to files:

  • internal/controller/pkg/revision/establisher.go
  • internal/controller/pkg/revision/reconciler_test.go
  • internal/controller/pkg/revision/reconciler.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Do not emit events for transient issues like apiserver conflict errors; instead, silently requeue the reconciliation

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Follow Crossplane's Observability Developer Guide

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Use cmpopts.EquateErrors to test that code returns expected errors; prefer cmpopts.AnyError for simple cases where you only care if an error was returned

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-11-06T08:30:43.529Z
Learnt from: negz
Repo: crossplane/crossplane PR: 6906
File: internal/xcrd/compatibility_test.go:78-105
Timestamp: 2025-11-06T08:30:43.529Z
Learning: In Go tests using go-cmp, `cmpopts.AnyError` is a sentinel error value (not a cmp.Option) that can be used with `cmpopts.EquateErrors()` to match any non-nil error in test assertions. The pattern `cmp.Diff(tc.want.err, got, cmpopts.EquateErrors())` with `want.err = cmpopts.AnyError` is the correct way to assert that an error occurred without caring about the specific error value.

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*_test.go : Use https://github.com/google/go-cmp/ for comparisons in tests to keep test output consistent

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
🧬 Code graph analysis (3)
internal/controller/pkg/revision/establisher.go (1)
internal/protection/usage/finder.go (1)
  • Object (107-111)
internal/controller/pkg/revision/reconciler_test.go (3)
internal/controller/pkg/revision/reconciler.go (4)
  • WithValidator (215-219)
  • WithLinter (208-212)
  • ReconcilerOption (126-126)
  • WithRecorder (158-162)
apis/pkg/v1/register.go (1)
  • ProviderRevisionGroupVersionKind (72-72)
apis/pkg/v1/revision_types.go (1)
  • PackageRevisionActive (30-30)
internal/controller/pkg/revision/reconciler.go (2)
internal/controller/pkg/signature/reconciler.go (3)
  • WithValidator (99-103)
  • ReconcilerOption (59-59)
  • Reconciler (106-116)
internal/controller/pkg/revision/establisher.go (2)
  • NewFilteringEstablisher (593-598)
  • NewAPIEstablisher (98-104)
🔍 Remote MCP

Summary of Additional Context for PR Review

Function Package Specification Requirements

Function packages follow the Crossplane Function Package specification, which is distinct from Provider and Configuration specifications. The changes in this PR directly address requirements that were previously not enforced in Function packages.

Key Design Rationale

Validator vs. Linter Distinction:
The PR introduces a new Validator type that is more permissive than linters. Based on the xpkg pattern:

  • Linters like NewConfigurationLinter(), NewFunctionLinter(), and NewProviderLinter() are convenience functions for creating package linters for different package types
  • The new validators allow packages to pass through the installation phase (allowing supplementary controllers to handle unknown types) while linters still run and report issues as events

FilteringEstablisher's Role:
The IsCRD check validates that an object is a CustomResourceDefinition, and the FilteringEstablisher uses this to implement filtering by GroupKind. This enables Function packages to be filtered at installation time rather than failing the entire installation process.

Business Justification from Linked Issues

The PR addresses three interconnected problems:

  1. #6975: Function packages were allowed to contain non-CRD resources, violating the xpkg specification
  2. #5294: Function CRDs (used only for input schema documentation) were being installed into the cluster, causing management and compatibility issues
  3. #6525: Package installation was failing on unknown resource types, preventing forward/backward compatibility and third-party extension

The solution uses a validation-based approach where unknown objects are ignored during installation (allowing supplementary controllers to handle them later), while strict linting is still performed and errors are recorded as events for visibility.

🔇 Additional comments (12)
internal/controller/pkg/revision/establisher_test.go (1)

1234-1384: Well-structured tests for FilteringEstablisher — thank you for the thorough coverage!

The test cases cover the key scenarios nicely: partial matching, full matching, no matches, empty filters, and error propagation from the wrapped establisher. The table-driven approach with PascalCase names follows the project conventions well.

One small clarifying question: In the FilterNoMatches and FilterEmpty cases (lines 1318 and 1338), you're comparing against []runtime.Object{}. Have you verified this works correctly with cmp.Diff when the actual result is make([]runtime.Object, 0, len(objects))? Both should be empty slices, but I just want to confirm the comparison behaves as expected. If you've already validated this, great!

internal/xpkg/lint.go (2)

57-85: Nice separation of concerns between Validator and Linter!

I appreciate the thoughtful design here — the Validator type provides semantic clarity about its purpose (blocking vs. non-blocking checks), and the empty ObjectLinterFns() for validators aligns well with the PR objective of allowing installation to proceed even when unknown object types are present.

Could you help me understand the rationale for including PackageValidSemver in the validators? I'm assuming it's because invalid semver constraints could cause issues during dependency resolution, making it a "must-fix" rather than a "warning"?


109-116: Function linter now enforces CRD-only constraint — addresses issue #6975.

Adding IsCRD to NewFunctionLinter ensures Function packages are linted against the xpkg specification requirement that they may only contain CRDs. This is a welcome addition for spec compliance!

internal/controller/pkg/revision/establisher.go (1)

585-616: Clean implementation of FilteringEstablisher — thanks for the clear design!

The wrapper pattern here is elegant and maintains good separation of concerns. The use of slices.Contains for filtering is appropriate given the expected small size of the gks slice.

I have a clarifying question about the ReleaseObjects method: it delegates directly without filtering. I assume this is intentional because we want to release all objects the parent previously owned (stored in status.objectRefs), regardless of the current filter configuration. Is that the correct understanding?

internal/controller/pkg/revision/reconciler.go (3)

214-219: WithValidator option looks good!

The option follows the same pattern as the existing WithLinter option, maintaining consistency across the codebase.


431-437: Empty FilteringEstablisher for Functions — great solution for issue #5294!

The comment clearly explains the intent: per the xpkg spec, CRDs in function packages should not be installed. Using an empty filter elegantly achieves this without special-casing the establishment logic.


831-852: Validator/Linter flow nicely implements the PR objectives — thank you!

The two-phase approach is clear:

  1. Validator runs first and blocks installation on failure (structural issues)
  2. Linter runs second but only records events (advisory warnings)

This allows packages with unknown object types to install while still surfacing lint issues to users. The TODO comment on line 850-851 about persisting lint errors in status is a good call-out for future work.

One small question: I notice WithValidator is always called in all three Setup* functions (SetupProviderReconciler, SetupConfigurationReconciler, SetupFunctionReconciler), which guarantees the validator is initialized before Reconcile is invoked. That said, since NewReconciler doesn't initialize a default validator like it does for the linter, would it be safer to add a nil check at line 833 as a defensive measure? That way, if someone instantiates a reconciler without going through the Setup functions, we avoid a potential panic. Just curious about the intent here.

internal/controller/pkg/revision/reconciler_test.go (5)

67-90: MockEstablisher updates align with the new interface — looks good!

The signature changes correctly reflect the updated Establish method signature in the interface.


759-809: Test renamed from "ErrLint" to "ErrValidate" — reflects the new validation flow well!

This test now correctly validates that validation failures (not just lint failures) block package installation, which aligns with the new validator/linter distinction.


1046-1115: Excellent test case for "SuccessfulWithLintErrors"!

This is a key test that validates the PR's behavioral change: lint errors should record events but not block installation. The use of newTestRecorder to assert the expected events is a nice addition.

Thank you for including both the lint warning event and the success event in the expected output — this clearly demonstrates the intended behavior.


2031-2048: Simple and effective testRecorder implementation!

The recorder captures events for assertion without over-engineering. The WithAnnotations method returning r maintains the fluent interface pattern.

One small question: Would it be helpful to also capture the runtime.Object passed to Event for more comprehensive assertions in future tests, or is the current approach sufficient for your needs?


2014-2019: Nice conditional event assertion!

Only checking events when a testRecorder is present keeps the existing tests working without modification while enabling event assertions where needed.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
internal/controller/pkg/revision/reconciler.go (1)

309-315: FilteringEstablisher setup for ProviderRevision looks correct.

I see the typos from the previous review have been addressed — thank you for fixing those! The GroupKinds now correctly reference:

  • CustomResourceDefinition in apiextensions.k8s.io
  • ValidatingWebhookConfiguration and MutatingWebhookConfiguration in admissionregistration.k8s.io
🧹 Nitpick comments (3)
internal/controller/pkg/revision/establisher_test.go (1)

1234-1384: Nice comprehensive test coverage for FilteringEstablisher! 👍

The test cases cover the key scenarios well: partial matching, full matching, no matches, empty filter, and error propagation. Thank you for including the empty filter test case — it nicely validates the Function package behavior where no objects should be established.

One small suggestion for robustness: On line 1379, could we add cmpopts.EquateEmpty() to the comparison? This would make the test resilient to differences between nil and empty slices, which could vary based on implementation details:

-			if diff := cmp.Diff(tc.want.refs, refs); diff != "" {
+			if diff := cmp.Diff(tc.want.refs, refs, cmpopts.EquateEmpty()); diff != "" {

This aligns with the pattern used in other tests in this file (e.g., line 826).

internal/controller/pkg/revision/reconciler.go (2)

462-482: Could we add a default validator for defensive safety?

I noticed that NewReconciler sets a default for linter (line 470) but doesn't set one for validator. While all the Setup* functions correctly call WithValidator, if someone creates a Reconciler directly without it, line 835 (r.validator.Lint(pkg)) would panic.

Would it make sense to add a default validator for consistency with the linter pattern? Something like:

 	r := &Reconciler{
 		client:     mgr.GetClient(),
 		cache:      xpkg.NewNopCache(),
 		revision:   resource.NewAPIFinalizer(mgr.GetClient(), finalizer),
 		objects:    NewNopEstablisher(),
 		parser:     parser.New(nil, nil),
 		linter:     parser.NewPackageLinter(nil, nil, nil),
+		validator:  parser.NewPackageLinter(nil, nil, nil),
 		versioner:  version.New(),
 		// ...
 	}

This is just a suggestion for defensive coding — I understand the current production paths are all covered.


833-854: Validation and lint flow implementation looks correct.

This nicely implements the intended behavior:

  • Validation (lines 835-844): Failures block installation with errValidatePackage and emit a reasonValidate event — appropriate for structural package issues.
  • Linting (lines 849-854): Failures only emit a warning event but allow installation to proceed — good for non-blocking concerns.

The TODO on lines 852-853 is a thoughtful callout. Since events are ephemeral, persisting lint warnings in status could help users diagnose issues after the fact. Would you like me to open an issue to track that enhancement?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f6e8c2 and 122a30a.

📒 Files selected for processing (4)
  • internal/controller/pkg/revision/establisher.go (3 hunks)
  • internal/controller/pkg/revision/establisher_test.go (1 hunks)
  • internal/controller/pkg/revision/reconciler.go (10 hunks)
  • internal/controller/pkg/revision/reconciler_test.go (23 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/pkg/revision/establisher.go
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

⚙️ CodeRabbit configuration file

**/*.go: Enforce Crossplane-specific patterns: Use crossplane-runtime/pkg/errors
for wrapping. Check variable naming (short for local scope, descriptive
for wider scope). Ensure 'return early' pattern. Verify error scoping
(declare in conditionals when possible). For nolint directives, require
specific linter names and explanations. CRITICAL: Ensure all error
messages are meaningful to end users, not just developers - avoid
technical jargon, include context about what the user was trying to do,
and suggest next steps when possible.

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/reconciler_test.go
  • internal/controller/pkg/revision/reconciler.go
**/*_test.go

⚙️ CodeRabbit configuration file

**/*_test.go: Enforce table-driven test structure: PascalCase test names (no
underscores), args/want pattern, use cmp.Diff with
cmpopts.EquateErrors() for error testing. Check for proper test case
naming and reason fields. Ensure no third-party test frameworks (no
Ginkgo, Gomega, Testify).

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/reconciler_test.go
**/internal/controller/**

⚙️ CodeRabbit configuration file

**/internal/controller/**: Review controller logic for proper reconciliation patterns, error
handling, and resource management. Pay special attention to conditions
and events: Conditions must be actionable for users (not developers),
stable/deterministic, with proper Type/Reason/Message format. Events
only when something actually happens, with specific details about what
changed. No transient errors in conditions/events. All error messages
must be meaningful to end users - include context about what
resource/operation failed and why.

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/reconciler_test.go
  • internal/controller/pkg/revision/reconciler.go
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Review code for functional correctness, proper documentation and intent explanation, adherence to Coding Style guidelines, idiomatic alignment with existing Crossplane APIs, sufficient test coverage, DCO sign-off, and passing CI jobs
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Do not emit events for transient issues like apiserver conflict errors; instead, silently requeue the reconciliation

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Use crossplane-runtime/pkg/errors to wrap errors with context rather than using error constants

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
  • internal/controller/pkg/revision/reconciler.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Follow Crossplane's Observability Developer Guide

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Use cmpopts.EquateErrors to test that code returns expected errors; prefer cmpopts.AnyError for simple cases where you only care if an error was returned

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-11-06T08:30:43.529Z
Learnt from: negz
Repo: crossplane/crossplane PR: 6906
File: internal/xcrd/compatibility_test.go:78-105
Timestamp: 2025-11-06T08:30:43.529Z
Learning: In Go tests using go-cmp, `cmpopts.AnyError` is a sentinel error value (not a cmp.Option) that can be used with `cmpopts.EquateErrors()` to match any non-nil error in test assertions. The pattern `cmp.Diff(tc.want.err, got, cmpopts.EquateErrors())` with `want.err = cmpopts.AnyError` is the correct way to assert that an error occurred without caring about the specific error value.

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*_test.go : Use https://github.com/google/go-cmp/ for comparisons in tests to keep test output consistent

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-09-09T21:39:13.640Z
Learnt from: negz
Repo: crossplane/crossplane PR: 6777
File: internal/circuit/mapfunc.go:37-41
Timestamp: 2025-09-09T21:39:13.640Z
Learning: In Kubernetes/controller-runtime, real objects that come through watches and reconciliation always have their GVK populated by the API server and client. obj.GetObjectKind().GroupVersionKind() will never be empty for actual Kubernetes objects in production code paths.

Applied to files:

  • internal/controller/pkg/revision/reconciler.go
🧬 Code graph analysis (1)
internal/controller/pkg/revision/reconciler.go (5)
internal/controller/pkg/signature/reconciler.go (3)
  • WithValidator (99-103)
  • ReconcilerOption (59-59)
  • Reconciler (106-116)
internal/xpkg/lint.go (3)
  • Validator (58-58)
  • NewProviderValidator (62-67)
  • NewConfigurationValidator (71-76)
internal/controller/pkg/revision/establisher.go (1)
  • NewFilteringEstablisher (593-598)
internal/controller/pkg/revision/dependency.go (1)
  • NewPackageDependencyManager (71-78)
apis/apiextensions/v1/register.go (1)
  • CompositionGroupVersionKind (56-56)
🔇 Additional comments (7)
internal/controller/pkg/revision/reconciler.go (2)

363-371: Configuration package establishment filter looks appropriate.

The allowed GroupKinds cover the expected resource types for Configuration packages: XRDs, Compositions, ManagedResourceActivationPolicy, and the various Operation types. This aligns well with the package specification.


433-439: Good documentation of the Function package filtering rationale.

The inline comment clearly explains why an empty filter is used — per the xpkg spec, CRDs in Function packages are for schema/documentation purposes and should not be installed. This directly addresses issue #5294. Thank you for the clear explanation!

internal/controller/pkg/revision/reconciler_test.go (5)

67-90: MockEstablisher signature update is consistent and correct.

The updated signature properly matches the Establisher interface with the context.Context, []runtime.Object, v1.PackageRevision, and bool parameters. The implementation on lines 88-90 correctly delegates to the mock function.


759-809: ErrValidate test case correctly validates the new validation path.

This test properly verifies that:

  1. When the validator fails, the reconciler returns errValidatePackage
  2. The status is marked unhealthy with the appropriate message
  3. The linter is not invoked when validation fails (since it's configured to succeed)

The separation of WithValidator (failing) and WithLinter (succeeding) makes the test intent clear.


1046-1115: Excellent test for the new lint-errors-don't-block-installation behavior! 🎉

This test case is crucial for validating the PR's objective that packages can be installed even when linting fails. The key aspects are well-tested:

  1. Validator passes, linter fails → reconciliation still succeeds
  2. Status shows RevisionHealthy() despite lint errors
  3. Events are verified: both the lint warning and successful sync are recorded

The use of testRecorder (lines 1106-1109) to verify exact event output is a nice pattern. Thank you for adding this coverage!


2031-2049: Clean testRecorder implementation for event verification.

This is a nicely minimal mock that does exactly what's needed. The Want/Got pattern makes the assertion on lines 2015-2018 straightforward to understand. The WithAnnotations implementation correctly returns the same recorder to maintain state.


2014-2020: Event verification integration is well-designed.

The conditional check for testRecorder (line 2015) keeps the test harness backward-compatible — tests that don't need event verification continue to work unchanged, while tests like SuccessfulWithLintErrors can opt into event assertions. This is a thoughtful approach that avoids breaking existing test patterns.

@adamwg
Copy link
Copy Markdown
Member Author

adamwg commented Dec 11, 2025

If anyone wants to give this a test drive, I've put together a (publicly pullable) function package that demonstrates the changes at docker.io/adamwg/bad-function:v0.1.3. This is built from the Go function template, so it has an input type, and it also includes an XRD. If you install it, you should see that neither the input CRD nor the XRD get installed. You should also see an event on the FunctionRevision indicating that there was a non-CRD object in the package.

Copy link
Copy Markdown
Member

@negz negz left a comment

Choose a reason for hiding this comment

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

Nice, this looks great. I don't feel strongly about the filename suggestion I made.

Comment on lines +57 to +58
// Validator validates packages before installation is attempted.
type Validator parser.Linter
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.

Should these live in a new validate.go - not lint.go?

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.

Makes sense to me 👍

Crossplane uses package linters in two places: at build time (in the CLI) and at
install time (in the revision controller). In both cases, linter errors are
treated as errors and stop the operation.

At build time, this makes sense: we should be strict about the packages we
produce. At install time, this means we may reject packages that we could
successfully install. For example (as described in crossplane#6525) if we allow new
resource types in a package in a new version of Crossplane (as we've done with
Operations and MRDs), older versions should still be able to install packages
that contain them; the new resources would just be ignored. If package authors
want to limit the versions their package works with, they can set a version
constraint in the metadata.

Introduce a new Validator type, which is identical to a linter except in
name. Add validators for all package types, which are identical to their linters
except that they allow any object to be included.

In the revision controller, use validators to determine whether package
installation should be attempted, where we previously used linters. Run the
linters as well and record any lint errors as events, to help the user diagnose
any unexpected behavior.

Fixes crossplane#6525

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
The xpkg spec says that CRDs included in Function packages will not be installed
into the cluster, but until now they have been since Functions are treated the
same as all other packages. Additionally, the previous commit allowed packages
with arbitrary objects included to be installed, contrary to the spec (and
desired behavior).

Filter objects in the establisher, so that regardless of what kinds of resources
are present in a package we install only the desired ones.

Fixes crossplane#5294

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
@adamwg adamwg force-pushed the awg/no-more-input-crds branch from 122a30a to 36f59be Compare December 15, 2025 16:46
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
internal/xpkg/validate.go (1)

21-49: Clear separation between validators and linters — could you add a brief doc comment explaining the distinction?

I appreciate the clean design here. Looking at the implementation:

  • Validators check only metadata (OneMeta, Is{Type}, PackageValidSemver) with an empty ObjectLinterFns() for objects.
  • Linters (in lint.go) check metadata plus validate object kinds against allowed types.

This separation enables the new behavior where validation failures block installation, but lint failures only emit warnings.

Could you consider adding a brief doc comment on the Validator type (around line 21-22) explaining this distinction? Something like:

// Validator validates packages before installation is attempted. Unlike
// linters, validators only check package metadata (meta type, semver
// constraints) and do not validate included objects. Validation failures
// block installation, whereas lint failures only emit warnings.
type Validator = parser.Linter

This would help future contributors understand why validators and linters exist as separate concepts despite having the same underlying type.

internal/controller/pkg/revision/reconciler.go (1)

833-854: Validation and linting flow correctly implements the new behavior.

The two-phase approach is well-implemented:

  1. Validation (lines 835-844): Uses the package-specific validator. Failures block installation with errValidatePackage and emit a reasonValidate event.

  2. Linting (lines 849-854): Runs after successful validation. Failures emit a warning event but do not block installation.

This aligns perfectly with the PR objectives from issue #6525.

Regarding the TODO at lines 852-853: I agree that persisting lint errors in status would be valuable since events are ephemeral. This could be a follow-up enhancement. Would you like me to open an issue to track this, or do you have plans to address it in this PR?

internal/controller/pkg/revision/reconciler_test.go (1)

1046-1115: New test case validates the key behavioral change — lint errors don't block installation.

This test (SuccessfulWithLintErrors) is crucial for verifying the PR's core objective. It:

  1. Configures a passing validator and failing linter
  2. Expects successful reconciliation (err: nil)
  3. Uses newTestRecorder to verify that:
    • A warning event is emitted for the lint failure
    • A success event is emitted for the sync

This test gives confidence that packages with lint warnings will install successfully while still informing users of potential issues.

One minor suggestion: the test name SuccessfulWithLintErrors is clear, but the reason field at line 1047 could be slightly more descriptive. Perhaps: "We should record a warning event for lint errors but successfully install the package."? This would make the test output more informative if it fails.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 122a30a and 36f59be.

📒 Files selected for processing (5)
  • internal/controller/pkg/revision/establisher.go (3 hunks)
  • internal/controller/pkg/revision/establisher_test.go (1 hunks)
  • internal/controller/pkg/revision/reconciler.go (10 hunks)
  • internal/controller/pkg/revision/reconciler_test.go (23 hunks)
  • internal/xpkg/validate.go (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.go

⚙️ CodeRabbit configuration file

**/*.go: Enforce Crossplane-specific patterns: Use crossplane-runtime/pkg/errors
for wrapping. Check variable naming (short for local scope, descriptive
for wider scope). Ensure 'return early' pattern. Verify error scoping
(declare in conditionals when possible). For nolint directives, require
specific linter names and explanations. CRITICAL: Ensure all error
messages are meaningful to end users, not just developers - avoid
technical jargon, include context about what the user was trying to do,
and suggest next steps when possible.

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/xpkg/validate.go
  • internal/controller/pkg/revision/reconciler.go
  • internal/controller/pkg/revision/establisher.go
  • internal/controller/pkg/revision/reconciler_test.go
**/*_test.go

⚙️ CodeRabbit configuration file

**/*_test.go: Enforce table-driven test structure: PascalCase test names (no
underscores), args/want pattern, use cmp.Diff with
cmpopts.EquateErrors() for error testing. Check for proper test case
naming and reason fields. Ensure no third-party test frameworks (no
Ginkgo, Gomega, Testify).

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/reconciler_test.go
**/internal/controller/**

⚙️ CodeRabbit configuration file

**/internal/controller/**: Review controller logic for proper reconciliation patterns, error
handling, and resource management. Pay special attention to conditions
and events: Conditions must be actionable for users (not developers),
stable/deterministic, with proper Type/Reason/Message format. Events
only when something actually happens, with specific details about what
changed. No transient errors in conditions/events. All error messages
must be meaningful to end users - include context about what
resource/operation failed and why.

Files:

  • internal/controller/pkg/revision/establisher_test.go
  • internal/controller/pkg/revision/reconciler.go
  • internal/controller/pkg/revision/establisher.go
  • internal/controller/pkg/revision/reconciler_test.go
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Review code for functional correctness, proper documentation and intent explanation, adherence to Coding Style guidelines, idiomatic alignment with existing Crossplane APIs, sufficient test coverage, DCO sign-off, and passing CI jobs
📚 Learning: 2025-09-09T21:39:13.640Z
Learnt from: negz
Repo: crossplane/crossplane PR: 6777
File: internal/circuit/mapfunc.go:37-41
Timestamp: 2025-09-09T21:39:13.640Z
Learning: In Kubernetes/controller-runtime, real objects that come through watches and reconciliation always have their GVK populated by the API server and client. obj.GetObjectKind().GroupVersionKind() will never be empty for actual Kubernetes objects in production code paths.

Applied to files:

  • internal/controller/pkg/revision/reconciler.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Review code for functional correctness, proper documentation and intent explanation, adherence to Coding Style guidelines, idiomatic alignment with existing Crossplane APIs, sufficient test coverage, DCO sign-off, and passing CI jobs

Applied to files:

  • internal/controller/pkg/revision/reconciler.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Use crossplane-runtime/pkg/errors to wrap errors with context rather than using error constants

Applied to files:

  • internal/controller/pkg/revision/reconciler.go
  • internal/controller/pkg/revision/establisher.go
  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Do not emit events for transient issues like apiserver conflict errors; instead, silently requeue the reconciliation

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Follow Crossplane's Observability Developer Guide

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*.go : Use cmpopts.EquateErrors to test that code returns expected errors; prefer cmpopts.AnyError for simple cases where you only care if an error was returned

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-11-06T08:30:43.529Z
Learnt from: negz
Repo: crossplane/crossplane PR: 6906
File: internal/xcrd/compatibility_test.go:78-105
Timestamp: 2025-11-06T08:30:43.529Z
Learning: In Go tests using go-cmp, `cmpopts.AnyError` is a sentinel error value (not a cmp.Option) that can be used with `cmpopts.EquateErrors()` to match any non-nil error in test assertions. The pattern `cmp.Diff(tc.want.err, got, cmpopts.EquateErrors())` with `want.err = cmpopts.AnyError` is the correct way to assert that an error occurred without caring about the specific error value.

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
📚 Learning: 2025-12-02T14:41:54.485Z
Learnt from: CR
Repo: crossplane/crossplane PR: 0
File: contributing/README.md:0-0
Timestamp: 2025-12-02T14:41:54.485Z
Learning: Applies to contributing/**/*_test.go : Use https://github.com/google/go-cmp/ for comparisons in tests to keep test output consistent

Applied to files:

  • internal/controller/pkg/revision/reconciler_test.go
🧬 Code graph analysis (5)
internal/controller/pkg/revision/establisher_test.go (2)
internal/controller/pkg/revision/establisher.go (2)
  • Establisher (66-69)
  • NewFilteringEstablisher (593-598)
internal/controller/pkg/revision/reconciler_test.go (1)
  • MockEstablisher (66-69)
internal/xpkg/validate.go (1)
internal/xpkg/lint.go (5)
  • OneMeta (89-95)
  • IsProvider (98-105)
  • PackageValidSemver (154-169)
  • IsConfiguration (108-115)
  • IsFunction (118-125)
internal/controller/pkg/revision/reconciler.go (7)
internal/xpkg/validate.go (4)
  • Validator (22-22)
  • NewProviderValidator (26-31)
  • NewConfigurationValidator (35-40)
  • NewFunctionValidator (44-49)
internal/controller/pkg/revision/establisher.go (2)
  • NewFilteringEstablisher (593-598)
  • NewAPIEstablisher (98-104)
apis/apiextensions/v1alpha1/register.go (2)
  • ManagedResourceDefinitionGroupVersionKind (56-56)
  • ManagedResourceActivationPolicyGroupVersionKind (61-61)
apis/pkg/v1/register.go (5)
  • Group (28-28)
  • SchemeGroupVersion (34-34)
  • ProviderGroupVersionKind (64-64)
  • ConfigurationGroupVersionKind (48-48)
  • FunctionGroupVersionKind (80-80)
internal/xpkg/lint.go (3)
  • NewProviderLinter (59-68)
  • NewConfigurationLinter (72-77)
  • NewFunctionLinter (81-86)
apis/apiextensions/v1/register.go (1)
  • CompositionGroupVersionKind (56-56)
apis/ops/v1alpha1/register.go (3)
  • OperationGroupVersionKind (45-45)
  • CronOperationGroupVersionKind (53-53)
  • WatchOperationGroupVersionKind (61-61)
internal/controller/pkg/revision/establisher.go (3)
internal/protection/usage/finder.go (1)
  • Object (107-111)
apis/pkg/v1/interfaces.go (1)
  • PackageRevision (532-577)
internal/controller/pkg/revision/imageback.go (1)
  • PackageRevision (204-213)
internal/controller/pkg/revision/reconciler_test.go (2)
internal/controller/pkg/revision/reconciler.go (8)
  • WithValidator (217-221)
  • WithLinter (210-214)
  • ReconcilerOption (128-128)
  • WithParser (188-192)
  • WithEstablisher (181-185)
  • WithCache (139-143)
  • WithConfigStore (203-207)
  • WithRecorder (160-164)
internal/controller/pkg/runtime/reconciler.go (3)
  • ReconcilerOption (77-77)
  • WithConfigStore (141-145)
  • WithRecorder (94-98)
🔍 Remote MCP

The Context7 tools are unavailable due to authentication issues. Let me try searching the web for relevant Crossplane documentation and specifications.## Summary of Additional Context for PR #6976 Review

Based on the linked issues and package manager architecture, here are the key facts relevant to this PR:

Key Design Rationale

Functions were never supposed to use actual CRs, and the package manager was never supposed to install their CRDs. A Function package may optionally define a schema for its input using a CRD to capture the input schema, since it's a KRM-like object, mostly useful for documentation purposes.

Most Go functions that want to take an input define an input Go type the same way they'd define a CRD - using controller-tools. They then deserialize the input from the RunFunctionRequest into the Go type. It's this use of controller-tools that lead to accidentally creating a CRD.

Impact of This PR's Changes

Backward compatibility consideration: The package manager should ignore unknown or disallowed resource kinds instead of failing installation, allowing Crossplane instances to install packages containing types they do not recognize so newer package payloads won't block older Crossplane versions.

Package structure specification: The package.yaml file MUST contain a valid YAML stream. All other content in either the xpkg base layer, or the full image filesystem is ignored by Crossplane.

Parser and Linter Architecture

The Linter executes all linter functions against a package. PackageLinterFn lints an entire package, and if the function applies a check for multiple objects, consider using an ObjectLinterFn. The PR introduces Validator as a type alias to parser.Linter, enabling permissive validation that doesn't block installation on unknown object types.

Review Focus Points

  1. FilteringEstablisher wrapper - Correctly filters objects by GroupKind before delegation to the wrapped Establisher
  2. Validator vs Linter distinction - Validators permit unknown kinds while linters enforce strict requirements; linting failures now emit events but don't block installation
  3. Package-type-specific validators - Each package type (Provider, Configuration, Function) gets its own validator configured with appropriate allowed resource kinds
  4. Event recording - Validation and lint issues are now recorded as Kubernetes events for observability while allowing installation to proceed
🔇 Additional comments (10)
internal/controller/pkg/revision/establisher_test.go (1)

1234-1384: Thorough test coverage for FilteringEstablisher — nicely done!

Thank you for adding comprehensive tests covering the key scenarios. The table-driven structure with clear reasons makes these tests easy to understand and maintain. A few observations:

  1. The test validates filtered objects inside the mock callback using cmp.Diff, which is a solid approach for verifying the filtering logic.

  2. I noticed that in the FilterNoMatches and FilterEmpty cases (lines 1318 and 1338), you're comparing against []runtime.Object{} — could you help me understand why an empty slice rather than nil is expected here? Looking at the implementation in establisher.go, the filtered slice is initialized with make([]runtime.Object, 0, len(objects)), so it would indeed be an empty slice, not nil. Just wanted to confirm this is intentional.

  3. One edge case that might be worth considering: what happens if an object in the input slice has an empty/unset GVK? The current implementation would not match it against any filter. This might be fine in practice (since parsed objects should have GVK set), but a test documenting this behavior could be helpful for future maintainers. Would you like to add one?

internal/controller/pkg/revision/establisher.go (2)

600-611: Clean decorator implementation for filtering — LGTM!

The filtering logic is straightforward and correct. I appreciate that:

  1. Pre-allocating the filtered slice with cap=len(objects) avoids reallocation in the common case.
  2. Using slices.Contains keeps the code readable.
  3. The method delegates to the wrapped establisher after filtering, preserving the original interface contract.

One small thought: if a package contains many objects and only a small subset match the filter, we're allocating more capacity than needed. This is a micro-optimization and probably not worth changing given typical package sizes, but I wanted to mention it in case you had considered it.


613-616: ReleaseObjects delegation is correct.

Thank you for not filtering in ReleaseObjects. The release operation should work on all objects previously established (tracked in status.objectRefs), regardless of the current filter configuration. This ensures proper cleanup even if the filter changes between install and uninstall.

internal/controller/pkg/revision/reconciler.go (3)

309-316: Provider FilteringEstablisher configuration looks correct.

Thank you for fixing the typos from the previous review! The GroupKind definitions now properly use the scheme constants:

  • k8sextv1.SchemeGroupVersion.Group for CRD
  • admv1.SchemeGroupVersion.Group for webhook configurations

One question: I see ManagedResourceDefinition is included here via extv1alpha1.ManagedResourceDefinitionGroupVersionKind.GroupKind(). Is this currently feature-gated (I notice there's a EnableBetaCustomToManagedResourceConversion flag used later in the reconcile loop)? Would it be worth adding a comment noting that MRDs are established only when the conversion actually happens, or is the filter inclusion harmless when the feature is disabled?


363-372: Configuration FilteringEstablisher includes all expected resource types.

This covers the full set of configuration package objects:

  • CompositeResourceDefinition (XRD)
  • Composition
  • ManagedResourceActivationPolicy
  • Operation, CronOperation, WatchOperation

I verified these match what's expected in NewConfigurationLinter() from lint.go (which checks for IsXRD, IsActivationPolicy, IsComposition, IsOperation, IsCronOperation, IsWatchOperation).


433-439: Excellent documentation for the empty Function filter!

The comment at lines 433-436 clearly explains why no objects are installed from function packages, which aligns with the xpkg spec and directly addresses issue #5294. This is exactly the kind of context that helps future maintainers understand the intent.

internal/controller/pkg/revision/reconciler_test.go (4)

67-90: MockEstablisher signature update correctly matches the interface.

The updated signature func(context.Context, []runtime.Object, v1.PackageRevision, bool) ([]xpv1.TypedReference, error) properly reflects the Establish method parameters, enabling tests to verify the objects passed to the establisher when needed.


759-808: Test correctly renamed from ErrLint to ErrValidate with updated semantics.

The test now:

  1. Configures a failing validator (WithValidator(&MockLinter{MockLint: NewMockLintFn(errBoom)}))
  2. Configures a passing linter (WithLinter(&MockLinter{MockLint: NewMockLintFn(nil)}))
  3. Expects errValidatePackage as the wrapped error
  4. Expects the status message to say "validating package contents failed"

This accurately reflects the new behavior where validation failures (not lint failures) block installation.


2031-2049: Simple and effective testRecorder implementation.

The testRecorder cleanly captures events for assertion without additional complexity. I appreciate that:

  1. It stores both Want and Got for direct comparison with cmp.Diff
  2. The WithAnnotations method returns r to maintain chainability
  3. The factory function newTestRecorder makes test setup clean

One thought: the Event method discards the runtime.Object parameter. If you ever need to assert which object the event was recorded for, you'd need to extend this. But for current needs, this is perfectly adequate.


2014-2019: Event assertion integration is cleanly implemented.

The type assertion if tr, ok := r.record.(*testRecorder); ok ensures event assertions only run when a test explicitly opts in via WithRecorder(newTestRecorder(...)). This keeps existing tests working without modification while enabling event verification where needed.

Copy link
Copy Markdown
Contributor

@phisco phisco left a comment

Choose a reason for hiding this comment

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

🎉 long due cleanup, thanks @adamwg!

@phisco phisco merged commit cb388c4 into crossplane:main Jan 8, 2026
24 of 25 checks passed
@negz negz mentioned this pull request Jan 31, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants