Repository: crossplane/crossplane (Go)

Issue:
v1.14/v1.15 - cannot change the kind or group of a composed resource
<!--
Thank you for helping to improve Crossplane!

Please be sure to search for open issues before raising a new one. We use issues
for bug reports and feature requests. Please find us at https://slack.crossplane.io
for questions, support, and discussion.
-->

### What happened?
We're working on moving our resources from the community AWS provider (https://github.com/crossplane-contrib/provider-aws) to the official Upbound AWS provider (https://github.com/upbound/provider-aws). 

Here's a brief overview of the steps involved:
1) Add `crossplane.io/paused` annotation and `deletionPolicy: Orphan` on all community managed resources
2) Shift all managed resources to the official provider.
3) In the Git repository, update the composition but keep the `order` and `spec.resources[].name` unchanged.
4) Apply the updated composition.
5) The composition will detect all new resources and update `resourceRefs[].apiVersion` and `resourceRefs[].Kind` in the **XR**, while the resource names remain the same.
6) Remove all community managed resources from the community AWS provider.

This process worked with Crossplane version `v1.13.x` but encountered issues starting with versions `v1.14.x`/`v1.15.x`. The error message encountered is:

```
cannot compose resources: cannot parse base template of composed resource 'iam': cannot change the kind or group of a composed resource from iam.aws.crossplane.io/v1beta1, Kind=Role to iam.aws.upbound.io/v1beta1, Kind=Role (possible composed resource template mismatch)
```

The change in behavior with Crossplane versions `v1.14.x` and `v1.15.x` raises questions, especially since migrating to the official provider is a common use case.

change of the behavior implemented with: https://github.com/crossplane/crossplane/pull/4500
recent change for version updates: https://github.com/crossplane/crossplane/pull/5369
discussion between @phisco and @turkenh  https://github.com/crossplane/crossplane/pull/5369#discussion_r1486019598

<!--
Please let us know what behaviour you expected and how Crossplane diverged from
that behaviour.
-->


### How can we reproduce it?
https://github.com/haarchri/xp-issue-5473
<!--
Help us to reproduce your bug as succinctly and precisely as possible. Artifacts
such as example manifests or a script that triggers the issue are highly
appreciated!
-->

### What environment did it happen in?
Crossplane version: v1.14.6-up.1, v1.15.0-up.1

<!--
Include at least the version or commit of Crossplane you were running. Consider
also including your:

* Cloud provider or hardware configuration
* Kubernetes version (use `kubectl version`)
* Kubernetes distribution (e.g. Tectonic, GKE, OpenShift)
* OS (e.g. from /etc/os-release)
* Kernel (e.g. `uname -a`)
-->



Discussion:

Patch:
diff --git a/internal/controller/apiextensions/composite/composition_render.go b/internal/controller/apiextensions/composite/composition_render.go
index 2cb2cb2ff3b..ee7e2dd024f 100644
--- a/internal/controller/apiextensions/composite/composition_render.go
+++ b/internal/controller/apiextensions/composite/composition_render.go
@@ -32,8 +32,8 @@ const (
 	errMarshalProtoStruct = "cannot marshal protobuf Struct to JSON"
 	errSetControllerRef   = "cannot set controller reference"
 
-	errFmtKindOrGroupChanged = "cannot change the kind or group of a composed resource from %s to %s (possible composed resource template mismatch)"
-	errFmtNamePrefixLabel    = "cannot find top-level composite resource name label %q in composite resource metadata"
+	errFmtKindChanged     = "cannot change the kind of a composed resource from %s to %s (possible composed resource template mismatch)"
+	errFmtNamePrefixLabel = "cannot find top-level composite resource name label %q in composite resource metadata"
 
 	// TODO(negz): Include more detail such as field paths if they exist.
 	// Perhaps require each patch type to have a String() method to help
@@ -61,16 +61,18 @@ func RenderFromJSON(o resource.Object, data []byte) error {
 	o.SetName(name)
 	o.SetNamespace(namespace)
 
-	// This resource already had a GK (probably because it already exists), but
+	// This resource already had a Kind (probably because it already exists), but
 	// when we rendered its template it changed. This shouldn't happen. Either
-	// someone changed the kind or group in the template, or we're trying to use the
+	// someone changed the kind in the template, or we're trying to use the
 	// wrong template (e.g. because the order of an array of anonymous templates
 	// changed).
 	// Please note, we don't check for version changes, as versions can change. For example,
 	// if a composed resource was created with a template that has a version of "v1alpha1",
 	// and then the template is updated to "v1beta1", the composed resource will still be valid.
-	if !gvk.Empty() && o.GetObjectKind().GroupVersionKind().GroupKind() != gvk.GroupKind() {
-		return errors.Errorf(errFmtKindOrGroupChanged, gvk, o.GetObjectKind().GroupVersionKind())
+	// We also don't check for group changes, as groups can change during
+	// migrations.
+	if !gvk.Empty() && o.GetObjectKind().GroupVersionKind().Kind != gvk.Kind {
+		return errors.Errorf(errFmtKindChanged, gvk, o.GetObjectKind().GroupVersionKind())
 	}
 
 	return nil


Test patch:
diff --git a/internal/controller/apiextensions/composite/composition_render_test.go b/internal/controller/apiextensions/composite/composition_render_test.go
index e4ecfb8a3f0..a2dd96f24a2 100644
--- a/internal/controller/apiextensions/composite/composition_render_test.go
+++ b/internal/controller/apiextensions/composite/composition_render_test.go
@@ -62,38 +62,37 @@ func TestRenderFromJSON(t *testing.T) {
 				err: errors.Wrap(errInvalidChar, errUnmarshalJSON),
 			},
 		},
-		"ExistingGroupChanged": {
-			reason: "We should return an error if unmarshalling the base template changed the composed resource's group.",
+		"ExistingKindChanged": {
+			reason: "We should return an error if unmarshalling the base template changed the composed resource's kind.",
 			args: args{
 				o: composed.New(composed.FromReference(corev1.ObjectReference{
 					APIVersion: "example.org/v1",
 					Kind:       "Potato",
 				})),
-				data: []byte(`{"apiVersion": "foo.io/v1", "kind": "Potato"}`),
+				data: []byte(`{"apiVersion": "example.org/v1", "kind": "Different"}`),
 			},
 			want: want{
 				o: composed.New(composed.FromReference(corev1.ObjectReference{
-					APIVersion: "foo.io/v1",
-					Kind:       "Potato",
+					APIVersion: "example.org/v1",
+					Kind:       "Different",
 				})),
-				err: errors.Errorf(errFmtKindOrGroupChanged, "example.org/v1, Kind=Potato", "foo.io/v1, Kind=Potato"),
+				err: errors.Errorf(errFmtKindChanged, "example.org/v1, Kind=Potato", "example.org/v1, Kind=Different"),
 			},
 		},
-		"ExistingKindChanged": {
-			reason: "We should return an error if unmarshalling the base template changed the composed resource's kind.",
+		"GroupCanChange": {
+			reason: "We should accept group changes in the base template.",
 			args: args{
 				o: composed.New(composed.FromReference(corev1.ObjectReference{
 					APIVersion: "example.org/v1",
 					Kind:       "Potato",
 				})),
-				data: []byte(`{"apiVersion": "example.org/v1", "kind": "Different"}`),
+				data: []byte(`{"apiVersion": "foo.io/v1", "kind": "Potato"}`),
 			},
 			want: want{
 				o: composed.New(composed.FromReference(corev1.ObjectReference{
-					APIVersion: "example.org/v1",
-					Kind:       "Different",
+					APIVersion: "foo.io/v1",
+					Kind:       "Potato",
 				})),
-				err: errors.Errorf(errFmtKindOrGroupChanged, "example.org/v1, Kind=Potato", "example.org/v1, Kind=Different"),
 			},
 		},
 		"VersionCanChange": {