Skip to content

Commit

Permalink
Feat: the multi-cluster management client supports non-Unstructured t…
Browse files Browse the repository at this point in the history
…ypes when the hub cluster does not have a corresponding GVR definition. (#104)

Signed-off-by: hanzhaoyang <[email protected]>
  • Loading branch information
msun1996 committed Aug 27, 2024
1 parent cff51a3 commit eaacca6
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 32 deletions.
137 changes: 105 additions & 32 deletions multicluster/remoteclusterclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,35 @@ func (in *remoteClusterClient) RESTMapper() meta.RESTMapper {
return in.defaultClient.RESTMapper()
}

func (in *remoteClusterClient) convertUnstructured(obj client.Object) (*unstructured.Unstructured, error) {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
gvk, err := apiutil.GVKForObject(obj, in.Scheme())
if err != nil {
return nil, err
}
unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
if err != nil {
return nil, err
}
u = &unstructured.Unstructured{Object: unstructuredMap}
u.SetGroupVersionKind(gvk)
}
return u, nil
}

// Create implements client.Client.
func (in *remoteClusterClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.Create(ctx, obj, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand All @@ -209,11 +230,15 @@ func (in *remoteClusterClient) Create(ctx context.Context, obj client.Object, op
// Update implements client.Client.
func (in *remoteClusterClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.Update(ctx, obj, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand All @@ -238,11 +263,15 @@ func (in *remoteClusterClient) Update(ctx context.Context, obj client.Object, op
// Delete implements client.Client.
func (in *remoteClusterClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.Delete(ctx, obj, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand All @@ -263,11 +292,15 @@ func (in *remoteClusterClient) Delete(ctx context.Context, obj client.Object, op
// DeleteAllOf implements client.Client.
func (in *remoteClusterClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.DeleteAllOf(ctx, obj, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand All @@ -288,11 +321,15 @@ func (in *remoteClusterClient) DeleteAllOf(ctx context.Context, obj client.Objec
// Patch implements client.Client.
func (in *remoteClusterClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.Patch(ctx, obj, patch, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand All @@ -319,11 +356,15 @@ func (in *remoteClusterClient) Patch(ctx context.Context, obj client.Object, pat
// Get implements client.Client.
func (in *remoteClusterClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.Get(ctx, key, obj, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

gvk := u.GroupVersionKind()

getOpts := client.GetOptions{}
Expand All @@ -350,13 +391,21 @@ func (in *remoteClusterClient) Get(ctx context.Context, key client.ObjectKey, ob
// List implements client.Client.
func (in *remoteClusterClient) List(ctx context.Context, obj client.ObjectList, opts ...client.ListOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.UnstructuredList)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.List(ctx, obj, opts...)
}

_u := &unstructured.Unstructured{}
_u.SetGroupVersionKind(u.GroupVersionKind())
u, ok := obj.(*unstructured.UnstructuredList)
if ok {
_u.SetGroupVersionKind(u.GroupVersionKind())
} else {
gvk, err := apiutil.GVKForObject(obj, in.Scheme())
if err != nil {
return err
}
_u.SetGroupVersionKind(gvk)
}
o, err := in.getObjMeta(cluster, _u)
if err != nil {
return err
Expand All @@ -376,14 +425,22 @@ func (in *remoteClusterClient) List(ctx context.Context, obj client.ObjectList,
// GetSubResource for client.SubResourceClient
func (in *remoteClusterClient) GetSubResource(ctx context.Context, obj, subResourceObj client.Object, subResource string, opts ...client.SubResourceGetOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok1 := obj.(*unstructured.Unstructured)
_, ok2 := subResourceObj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok1 || !ok2 {
if IsLocal(cluster) {
return in.defaultClient.SubResource(subResource).Get(ctx, obj, subResourceObj, opts...)
}

if subResourceObj.GetName() == "" {
subResourceObj.SetName(obj.GetName())
u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

sub, err := in.convertUnstructured(subResourceObj)
if err != nil {
return err
}

if sub.GetName() == "" {
sub.SetName(obj.GetName())
}

o, err := in.getObjMeta(cluster, u)
Expand All @@ -401,20 +458,28 @@ func (in *remoteClusterClient) GetSubResource(ctx context.Context, obj, subResou
SubResource(subResource).
VersionedParams(getOpts.AsGetOptions(), in.paramCodec).
Do(ctx).
Into(subResourceObj)
Into(sub)
}

// CreateSubResource for client.SubResourceClient
func (in *remoteClusterClient) CreateSubResource(ctx context.Context, obj, subResourceObj client.Object, subResource string, opts ...client.SubResourceCreateOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok1 := obj.(*unstructured.Unstructured)
_, ok2 := subResourceObj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok1 || !ok2 {
if IsLocal(cluster) {
return in.defaultClient.SubResource(subResource).Create(ctx, obj, subResourceObj, opts...)
}

if subResourceObj.GetName() == "" {
subResourceObj.SetName(obj.GetName())
u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

sub, err := in.convertUnstructured(subResourceObj)
if err != nil {
return err
}

if sub.GetName() == "" {
sub.SetName(obj.GetName())
}

o, err := in.getObjMeta(cluster, u)
Expand All @@ -430,20 +495,24 @@ func (in *remoteClusterClient) CreateSubResource(ctx context.Context, obj, subRe
Resource(o.resource()).
Name(o.GetName()).
SubResource(subResource).
Body(subResourceObj).
Body(sub).
VersionedParams(createOpts.AsCreateOptions(), in.paramCodec).
Do(ctx).
Into(subResourceObj)
Into(sub)
}

// UpdateSubResource for client.SubResourceClient
func (in *remoteClusterClient) UpdateSubResource(ctx context.Context, obj client.Object, subResource string, opts ...client.SubResourceUpdateOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.SubResource(subResource).Update(ctx, obj, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand Down Expand Up @@ -477,11 +546,15 @@ func (in *remoteClusterClient) UpdateSubResource(ctx context.Context, obj client
// PatchSubResource for client.SubResourceClient
func (in *remoteClusterClient) PatchSubResource(ctx context.Context, obj client.Object, subResource string, patch client.Patch, opts ...client.SubResourcePatchOption) error {
cluster, _ := ClusterFrom(ctx)
u, ok := obj.(*unstructured.Unstructured)
if IsLocal(cluster) || !ok {
if IsLocal(cluster) {
return in.defaultClient.SubResource(subResource).Patch(ctx, obj, patch, opts...)
}

u, err := in.convertUnstructured(obj)
if err != nil {
return err
}

o, err := in.getObjMeta(cluster, u)
if err != nil {
return err
Expand Down
28 changes: 28 additions & 0 deletions multicluster/remoteclusterclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,34 @@ var _ = Describe("Test remote multicluster client", func() {
Ω(err).To(Succeed())
Ω(k8s.ClearNamespace(ctx, c, namespace)).To(Succeed())

namespace, name = "test-"+rand.RandomString(4), "fake"
Ω(k8s.EnsureNamespace(ctx, c, namespace)).To(Succeed())
deploy2 := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
Spec: appsv1.DeploymentSpec{
Replicas: pointer.Int32(1),
Selector: &metav1.LabelSelector{MatchLabels: map[string]string{"app": "test2"}},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"app": "test2"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "test"}}},
},
},
}
Ω(c.Create(ctx, deploy2)).To(Succeed())
Ω(c.Get(ctx, controllerruntimeclient.ObjectKey{Namespace: namespace, Name: name}, deploy2)).To(Succeed())
Ω(c.Update(ctx, deploy2)).To(Succeed())
Ω(c.Patch(ctx, deploy2, controllerruntimeclient.Merge)).To(Succeed())
Ω(c.Status().Update(ctx, deploy2)).To(Succeed())
Ω(c.Status().Patch(ctx, deploy2, controllerruntimeclient.Merge)).To(Succeed())
updateBody2 := deploy2.DeepCopy()
updateBody2.SetName("")
updateBody2.SetNamespace("")
Ω(c.SubResource("status").Get(ctx, deploy2, updateBody2.DeepCopy())).To(Succeed())
Ω(c.SubResource("status").Update(ctx, deploy2, &controllerruntimeclient.SubResourceUpdateOptions{SubResourceBody: updateBody2})).To(Succeed())
Ω(c.SubResource("status").Patch(ctx, deploy2, controllerruntimeclient.Merge, &controllerruntimeclient.SubResourcePatchOptions{SubResourceBody: updateBody2})).To(Succeed())
Ω(c.Delete(ctx, deploy2)).To(Succeed())
Ω(k8s.ClearNamespace(ctx, c, namespace)).To(Succeed())

By("Test bad resource")
ar := &unstructured.Unstructured{}
ar.SetAPIVersion("xxx")
Expand Down

0 comments on commit eaacca6

Please sign in to comment.