Repository: crossplane/crossplane (Go)

Issue:
Panic error on EnvironmentConfig selector with `maxMatch`
### What happened?

When setting the `selector.maxMatch` field of an `EnvironmentConfig` Selector (with `mode: "Multiple"`) in a composition to any other value the 0 - while not having any matching `EnvironmentConfigs` on the cluster - results in a panic error.

Crossplane pod error message (for `maxMatch: 2`):

```
E1213 17:17:37.855553       1 runtime.go:79] Observed a panic: runtime.boundsError{x:2, y:0, signed:false, code:0x2} (runtime error: slice bounds out of range [:2] with capacity 0)
goroutine 645 [running]:
k8s.io/apimachinery/pkg/util/runtime.logPanic({0x1fa0ce0?, 0xc00351f8c0})
    k8s.io/apimachinery@v0.28.3/pkg/util/runtime/runtime.go:75 +0x85
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Reconcile.func1()
    sigs.k8s.io/controller-runtime@v0.16.3/pkg/internal/controller/controller.go:108 +0xb2
panic({0x1fa0ce0?, 0xc00351f8c0?})
    runtime/panic.go:914 +0x21f
github.com/crossplane/crossplane/internal/controller/apiextensions/composite.(*APIEnvironmentSelector).buildEnvironmentConfigRefFromSelector(0xc00066d7d0?, 0xc0002022a0, 0xc003585630)
    github.com/crossplane/crossplane/internal/controller/apiextensions/composite/environment_selector.go:165 +0x5c5
github.com/crossplane/crossplane/internal/controller/apiextensions/composite.(*APIEnvironmentSelector).SelectEnvironment(0xc0002020e0?, {0x242d7a8, 0xc0001faa10}, {0x244c578, 0xc002d736d0?}, 0xc0035ea380)
    github.com/crossplane/crossplane/internal/controller/apiextensions/composite/environment_selector.go:95 +0x3b2
github.com/crossplane/crossplane/internal/controller/apiextensions/composite.(*Reconciler).Reconcile(0xc0031a67e0, {0x242d700, 0xc00359d9e0}, {{{0x0, 0x0}, {0xc002684000, 0x12}}})
    github.com/crossplane/crossplane/internal/controller/apiextensions/composite/reconciler.go:596 +0x3592
github.com/crossplane/crossplane-runtime/pkg/errors.(*silentlyRequeueOnConflict).Reconcile(0xc0035a7800?, {0x242d700?, 0xc00359d9e0?}, {{{0x0?, 0xc00357f440?}, {0xc002684000?, 0xc002566c70?}}})
    github.com/crossplane/crossplane-runtime@v1.14.2/pkg/errors/reconcile.go:47 +0x2f
github.com/crossplane/crossplane-runtime/pkg/ratelimiter.(*Reconciler).Reconcile(0xc003170960, {0x242d700, 0xc00359d9e0}, {{{0x0?, 0x5?}, {0xc002684000?, 0xc002566d48?}}})
    github.com/crossplane/crossplane-runtime@v1.14.2/pkg/ratelimiter/reconciler.go:54 +0x151
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Reconcile(0x2430cd0?, {0x242d700?, 0xc00359d9e0?}, {{{0x0?, 0xb?}, {0xc002684000?, 0x0?}}})
    sigs.k8s.io/controller-runtime@v0.16.3/pkg/internal/controller/controller.go:119 +0xb7
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).reconcileHandler(0xc00033d720, {0x242d738, 0xc003170b90}, {0x1ece920?, 0xc003544bc0?})
    sigs.k8s.io/controller-runtime@v0.16.3/pkg/internal/controller/controller.go:316 +0x3c5
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).processNextWorkItem(0xc00033d720, {0x242d738, 0xc003170b90})
    sigs.k8s.io/controller-runtime@v0.16.3/pkg/internal/controller/controller.go:266 +0x1c9
sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func2.2()
    sigs.k8s.io/controller-runtime@v0.16.3/pkg/internal/controller/controller.go:227 +0x79
created by sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller).Start.func2 in goroutine 553
    sigs.k8s.io/controller-runtime@v0.16.3/pkg/internal/controller/controller.go:223 +0x565

```


### How can we reproduce it?

`environment` part in composition

```yaml
...
  environment:
    environmentConfigs:
      # - ref: # does not effect the behaviour
      #     name: envconfig1
      #   type: Reference
      ## optional Selector
      - type: Selector
        selector:
          mode: "Multiple"
          # minMatch: 0 # does not effect the behaviour
          maxMatch: 1
          matchLabels:
          - type: Value
            key: label1
            value: labelvalue
          - type: FromCompositeFieldPath
            key: label2
            valueFromFieldPath: spec.parameters.description
    policy:
      resolution: Optional # Required # does not effect the behaviour
      resolve: Always
...
```

### What environment did it happen in?
Crossplane version:  v1.14.4



Discussion:

Patch:
diff --git a/internal/controller/apiextensions/composite/environment_selector.go b/internal/controller/apiextensions/composite/environment_selector.go
index 5a196adf164..c9a6a1f80ae 100644
--- a/internal/controller/apiextensions/composite/environment_selector.go
+++ b/internal/controller/apiextensions/composite/environment_selector.go
@@ -135,7 +135,7 @@ func (s *APIEnvironmentSelector) lookUpConfigs(ctx context.Context, cr resource.
 	return res, nil
 }
 
-func (s *APIEnvironmentSelector) buildEnvironmentConfigRefFromSelector(cl *v1alpha1.EnvironmentConfigList, selector *v1.EnvironmentSourceSelector) ([]corev1.ObjectReference, error) {
+func (s *APIEnvironmentSelector) buildEnvironmentConfigRefFromSelector(cl *v1alpha1.EnvironmentConfigList, selector *v1.EnvironmentSourceSelector) ([]corev1.ObjectReference, error) { //nolint:gocyclo // TODO: refactor
 	ec := make([]v1alpha1.EnvironmentConfig, 0)
 
 	if cl == nil {
@@ -161,7 +161,7 @@ func (s *APIEnvironmentSelector) buildEnvironmentConfigRefFromSelector(cl *v1alp
 			return nil, err
 		}
 
-		if selector.MaxMatch != nil {
+		if selector.MaxMatch != nil && len(cl.Items) > int(*selector.MaxMatch) {
 			ec = append(ec, cl.Items[:*selector.MaxMatch]...)
 			break
 		}


Test patch:
diff --git a/internal/controller/apiextensions/composite/environment_selector_test.go b/internal/controller/apiextensions/composite/environment_selector_test.go
index 99412b3ae56..f9ec23755e7 100644
--- a/internal/controller/apiextensions/composite/environment_selector_test.go
+++ b/internal/controller/apiextensions/composite/environment_selector_test.go
@@ -36,7 +36,7 @@ import (
 	"github.com/crossplane/crossplane-runtime/pkg/test"
 
 	v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
-	v1alpha1 "github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
+	"github.com/crossplane/crossplane/apis/apiextensions/v1alpha1"
 )
 
 func TestSelect(t *testing.T) {
@@ -959,7 +959,7 @@ func TestSelect(t *testing.T) {
 									Type: v1.EnvironmentSourceTypeSelector,
 									Selector: &v1.EnvironmentSourceSelector{
 										Mode:            v1.EnvironmentSourceSelectorMultiMode,
-										MaxMatch:        ptr.To[uint64](3),
+										MaxMatch:        ptr.To[uint64](4),
 										SortByFieldPath: "data[int/weight]",
 										MatchLabels: []v1.EnvironmentSourceSelectorLabelMatcher{
 											{