Skip to content

Commit cf5470b

Browse files
committed
fix: refactor filter impl
Signed-off-by: Jonathan Ogilvie <jonathan.ogilvie@sumologic.com>
1 parent 538a161 commit cf5470b

10 files changed

Lines changed: 931 additions & 255 deletions

File tree

.requirements/20260522T174102Z_refactor_resource_filter_architecture/REQUIREMENTS.md

Lines changed: 320 additions & 0 deletions
Large diffs are not rendered by default.

cmd/diff/client/crossplane/composition_client.go

Lines changed: 123 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
un "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1313
"k8s.io/apimachinery/pkg/runtime"
1414
"k8s.io/apimachinery/pkg/runtime/schema"
15+
k8stypes "k8s.io/apimachinery/pkg/types"
1516

1617
"github.com/crossplane/crossplane-runtime/v2/pkg/errors"
1718
"github.com/crossplane/crossplane-runtime/v2/pkg/logging"
@@ -20,8 +21,6 @@ import (
2021
)
2122

2223
// CompositionClient handles operations related to Compositions.
23-
//
24-
//nolint:interfacebloat // Composition operations are cohesive; splitting would fragment the API.
2524
type CompositionClient interface {
2625
core.Initializable
2726

@@ -34,16 +33,16 @@ type CompositionClient interface {
3433
// GetComposition gets a composition by name
3534
GetComposition(ctx context.Context, name string) (*apiextensionsv1.Composition, error)
3635

37-
// FindCompositesUsingComposition finds all composites (XRs and Claims) that use the specified composition
38-
FindCompositesUsingComposition(ctx context.Context, compositionName string, namespace string) ([]*un.Unstructured, error)
39-
40-
// GetCompositesByName fetches the user-named composites (XRs or Claims) for a composition.
41-
// For each ResourceRef, the XR GVK derived from comp.Spec.CompositeTypeRef is tried first, then the
42-
// claim GVK derived from the XRD if defined. A ref is "matched" only when (a) the cluster lookup
43-
// succeeds AND (b) the resource references this composition by name. Refs whose lookups all 404, or
44-
// that exist but reference a different composition, are returned in `unmatched` (not as an error).
36+
// FindComposites locates composites (XRs and Claims) that reference a composition.
37+
// When opts.Refs is non-empty, it performs ref-based lookup: each ref is resolved against the
38+
// composition's XR GVK (then claim GVK on 404 if the XRD defines a claim type). A ref is included
39+
// in the result only when (a) the cluster lookup succeeds AND (b) the resource references this
40+
// composition by name. Refs that don't satisfy both are silently omitted; callers derive
41+
// "unmatched" from the diff between input refs and returned objects.
42+
// When opts.Refs is empty, it performs default discovery scoped to opts.Namespace, listing all
43+
// XRs (and Claims, if the XRD defines them) of the appropriate GVK and filtering by composition.
4544
// NotFound responses are tolerated; non-NotFound transport errors propagate.
46-
GetCompositesByName(ctx context.Context, comp *apiextensionsv1.Composition, refs []dtypes.ResourceRef) (matched []*un.Unstructured, unmatched []dtypes.ResourceRef, err error)
45+
FindComposites(ctx context.Context, comp *apiextensionsv1.Composition, opts dtypes.FindCompositesOptions) ([]*un.Unstructured, error)
4746
}
4847

4948
// DefaultCompositionClient implements CompositionClient.
@@ -702,7 +701,11 @@ func (c *DefaultCompositionClient) findByTypeReference(ctx context.Context, _ *u
702701
}
703702

704703
// FindCompositesUsingComposition finds all composites (XRs and Claims) that use the specified composition.
705-
func (c *DefaultCompositionClient) FindCompositesUsingComposition(ctx context.Context, compositionName string, namespace string) ([]*un.Unstructured, error) {
704+
// findByListing implements default-discovery for FindComposites: list every XR (and Claim, if the
705+
// XRD defines one) of the composition's target GVK, scoped to namespace, and filter by composition.
706+
// Pre-existing behavior: if the composition itself isn't in the cluster (net-new), the GetComposition
707+
// lookup fails and the error propagates to the caller, which is expected to handle "net-new" gracefully.
708+
func (c *DefaultCompositionClient) findByListing(ctx context.Context, compositionName string, namespace string) ([]*un.Unstructured, error) {
706709
c.logger.Debug("Finding composites using composition",
707710
"compositionName", compositionName,
708711
"namespace", namespace)
@@ -837,106 +840,150 @@ func (c *DefaultCompositionClient) resourceUsesComposition(resource *un.Unstruct
837840

838841
// GetCompositesByName fetches the user-named composites for a composition.
839842
// See the CompositionClient interface for the full contract.
840-
func (c *DefaultCompositionClient) GetCompositesByName(ctx context.Context, comp *apiextensionsv1.Composition, refs []dtypes.ResourceRef) ([]*un.Unstructured, []dtypes.ResourceRef, error) {
843+
// findByRefs implements ref-based lookup for FindComposites: each ref is resolved against the
844+
// composition's XR GVK first, then the claim GVK on 404 if the XRD defines a claim type. Refs that
845+
// fail both lookups, or that exist but reference a different composition, are silently dropped from
846+
// the result. NotFound responses are tolerated; other errors propagate.
847+
//
848+
// Step 4 will split this into resolveCompositeTypes + lookupRef.
849+
// findByRefs implements ref-based lookup for FindComposites: each ref is resolved against the
850+
// composition's XR GVK first, then the claim GVK on 404 if the XRD defines a claim type.
851+
func (c *DefaultCompositionClient) findByRefs(ctx context.Context, comp *apiextensionsv1.Composition, refs []k8stypes.NamespacedName) ([]*un.Unstructured, error) {
841852
if len(refs) == 0 {
842-
return nil, nil, nil
853+
return nil, nil
843854
}
844855

845-
// Resolve XR GVK from the (possibly net-new) composition file. No cluster lookup needed.
846-
gv, err := schema.ParseGroupVersion(comp.Spec.CompositeTypeRef.APIVersion)
856+
types, err := c.resolveCompositeTypes(ctx, comp)
847857
if err != nil {
848-
return nil, nil, errors.Wrapf(err, "cannot parse compositeTypeRef apiVersion %q", comp.Spec.CompositeTypeRef.APIVersion)
858+
return nil, err
849859
}
850860

851-
xrGVK := schema.GroupVersionKind{
852-
Group: gv.Group,
853-
Version: gv.Version,
854-
Kind: comp.Spec.CompositeTypeRef.Kind,
861+
var matched []*un.Unstructured
862+
863+
for _, ref := range refs {
864+
obj, err := c.lookupRef(ctx, ref, types, comp.GetName())
865+
if err != nil {
866+
return nil, err
867+
}
868+
869+
if obj != nil {
870+
matched = append(matched, obj)
871+
}
855872
}
856873

857-
// Resolve claim GVK best-effort. Missing XRD or no claimNames → claim lookups are skipped.
858-
var claimGVK schema.GroupVersionKind
874+
return matched, nil
875+
}
876+
877+
// compositeTypes holds the GVKs needed to look up a composite by name. claimGVK is empty
878+
// when the XRD has no claimNames or could not be retrieved (graceful degradation).
879+
type compositeTypes struct {
880+
xrGVK schema.GroupVersionKind
881+
claimGVK schema.GroupVersionKind
882+
}
883+
884+
// resolveCompositeTypes derives the XR GVK from the composition's CompositeTypeRef and
885+
// best-effort resolves the claim GVK from the XRD. XRD lookup failures and missing claimNames
886+
// produce an empty claim GVK without erroring (graceful degradation).
887+
func (c *DefaultCompositionClient) resolveCompositeTypes(ctx context.Context, comp *apiextensionsv1.Composition) (compositeTypes, error) {
888+
gv, err := schema.ParseGroupVersion(comp.Spec.CompositeTypeRef.APIVersion)
889+
if err != nil {
890+
return compositeTypes{}, errors.Wrapf(err, "cannot parse compositeTypeRef apiVersion %q", comp.Spec.CompositeTypeRef.APIVersion)
891+
}
892+
893+
types := compositeTypes{
894+
xrGVK: schema.GroupVersionKind{
895+
Group: gv.Group,
896+
Version: gv.Version,
897+
Kind: comp.Spec.CompositeTypeRef.Kind,
898+
},
899+
}
859900

860-
xrd, xrdErr := c.definitionClient.GetXRDForXR(ctx, xrGVK)
901+
xrd, xrdErr := c.definitionClient.GetXRDForXR(ctx, types.xrGVK)
861902

862903
switch {
863904
case xrdErr != nil:
864905
c.logger.Debug("XRD lookup failed; skipping claim-GVK fallback for --resource lookups",
865-
"xrGVK", xrGVK.String(), "error", xrdErr)
906+
"xrGVK", types.xrGVK.String(), "error", xrdErr)
866907
case xrd != nil:
867908
gvk, err := c.getClaimTypeFromXRD(xrd)
868909
if err != nil {
869910
c.logger.Debug("could not extract claim type from XRD; skipping claim-GVK fallback",
870911
"xrd", xrd.GetName(), "error", err)
871912
} else {
872-
claimGVK = gvk
913+
types.claimGVK = gvk
873914
}
874915
}
875916

876-
var (
877-
matched []*un.Unstructured
878-
unmatched []dtypes.ResourceRef
879-
)
917+
return types, nil
918+
}
880919

881-
for _, ref := range refs {
882-
// Try XR-GVK first.
883-
obj, err := c.resourceClient.GetResource(ctx, xrGVK, ref.Namespace, ref.Name)
920+
// lookupRef resolves a single ref against (compName, types). Tries the XR GVK first, then the
921+
// claim GVK on 404 if non-empty. Returns the matched object, or nil if not found anywhere or
922+
// found but referencing a different composition. Non-NotFound cluster errors propagate.
923+
//
924+
// NOTE: when XR GVK returns a hit but the resource references a different composition, this
925+
// returns nil WITHOUT trying the claim GVK. Follow-up F1 (Copilot review on PR #322) tracks
926+
// switching this to fall through to the claim path so same-name XR+Claim collisions resolve
927+
// correctly. Step 4 preserves the existing behavior.
928+
func (c *DefaultCompositionClient) lookupRef(ctx context.Context, ref k8stypes.NamespacedName, types compositeTypes, compName string) (*un.Unstructured, error) {
929+
// Try XR GVK first.
930+
obj, err := c.resourceClient.GetResource(ctx, types.xrGVK, ref.Namespace, ref.Name)
884931

885-
switch {
886-
case err == nil:
887-
if c.resourceUsesComposition(obj, comp.GetName()) {
888-
c.logger.Debug("matched ref via XR GVK",
889-
"ref", ref.String(), "composition", comp.GetName())
932+
switch {
933+
case err == nil:
934+
if c.resourceUsesComposition(obj, compName) {
935+
c.logger.Debug("matched ref via XR GVK",
936+
"ref", ref.String(), "composition", compName)
890937

891-
matched = append(matched, obj)
938+
return obj, nil
939+
}
892940

893-
continue
894-
}
941+
c.logger.Debug("ref exists as XR but does not reference this composition",
942+
"ref", ref.String(), "composition", compName)
895943

896-
c.logger.Debug("ref exists as XR but does not reference this composition",
897-
"ref", ref.String(), "composition", comp.GetName())
944+
return nil, nil
945+
case !apierrors.IsNotFound(err):
946+
return nil, errors.Wrapf(err, "cannot fetch composite %s as %s", ref.String(), types.xrGVK)
947+
}
898948

899-
unmatched = append(unmatched, ref)
949+
// XR GVK was 404. Try claim GVK if available.
950+
if types.claimGVK.Empty() {
951+
c.logger.Debug("ref not found as XR and no claim GVK to try",
952+
"ref", ref.String())
900953

901-
continue
902-
case !apierrors.IsNotFound(err):
903-
return nil, nil, errors.Wrapf(err, "cannot fetch composite %s as %s", ref.String(), xrGVK)
904-
}
954+
return nil, nil
955+
}
905956

906-
// XR-GVK was 404. Try claim GVK if available.
907-
if claimGVK.Empty() {
908-
c.logger.Debug("ref not found as XR and no claim GVK to try",
909-
"ref", ref.String())
957+
obj, err = c.resourceClient.GetResource(ctx, types.claimGVK, ref.Namespace, ref.Name)
910958

911-
unmatched = append(unmatched, ref)
959+
switch {
960+
case err == nil:
961+
if c.resourceUsesComposition(obj, compName) {
962+
c.logger.Debug("matched ref via claim GVK",
963+
"ref", ref.String(), "composition", compName)
912964

913-
continue
965+
return obj, nil
914966
}
915967

916-
obj, err = c.resourceClient.GetResource(ctx, claimGVK, ref.Namespace, ref.Name)
968+
c.logger.Debug("ref exists as claim but does not reference this composition",
969+
"ref", ref.String(), "composition", compName)
917970

918-
switch {
919-
case err == nil:
920-
if c.resourceUsesComposition(obj, comp.GetName()) {
921-
c.logger.Debug("matched ref via claim GVK",
922-
"ref", ref.String(), "composition", comp.GetName())
971+
return nil, nil
972+
case apierrors.IsNotFound(err):
973+
c.logger.Debug("ref not found as XR or claim", "ref", ref.String())
923974

924-
matched = append(matched, obj)
925-
926-
continue
927-
}
928-
929-
c.logger.Debug("ref exists as claim but does not reference this composition",
930-
"ref", ref.String(), "composition", comp.GetName())
931-
932-
unmatched = append(unmatched, ref)
933-
case apierrors.IsNotFound(err):
934-
c.logger.Debug("ref not found as XR or claim", "ref", ref.String())
935-
unmatched = append(unmatched, ref)
936-
default:
937-
return nil, nil, errors.Wrapf(err, "cannot fetch composite %s as %s", ref.String(), claimGVK)
938-
}
975+
return nil, nil
976+
default:
977+
return nil, errors.Wrapf(err, "cannot fetch composite %s as %s", ref.String(), types.claimGVK)
939978
}
979+
}
940980

941-
return matched, unmatched, nil
981+
// FindComposites dispatches to ref-based or listing-based discovery based on opts.Refs.
982+
func (c *DefaultCompositionClient) FindComposites(ctx context.Context, comp *apiextensionsv1.Composition, opts dtypes.FindCompositesOptions) ([]*un.Unstructured, error) {
983+
switch {
984+
case len(opts.Refs) > 0:
985+
return c.findByRefs(ctx, comp, opts.Refs)
986+
default:
987+
return c.findByListing(ctx, comp.GetName(), opts.Namespace)
988+
}
942989
}

0 commit comments

Comments
 (0)