diff --git a/e2e/fleet_attachedcluster_test.go b/e2e/fleet_attachedcluster_test.go index 4d06c223..5bb6cd69 100644 --- a/e2e/fleet_attachedcluster_test.go +++ b/e2e/fleet_attachedcluster_test.go @@ -59,7 +59,7 @@ var _ = ginkgo.Describe("[AttachedClusters] AttachedClusters testing", func() { ginkgo.It("Create Fleet", func() { // create fleet and checkout fleet status - fleetCreateErr := resources.CreateFleet(kuratorClient, fleet) + fleetCreateErr := resources.CreateOrUpdateFleet(kuratorClient, fleet) gomega.Expect(fleetCreateErr).ShouldNot(gomega.HaveOccurred()) resources.WaitFleetFitWith(kuratorClient, namespace, fleetname, func(fleet *fleetv1a1.Fleet) bool { return fleet.Status.Phase == fleetv1a1.ReadyPhase diff --git a/e2e/resources/attachedcluster.go b/e2e/resources/attachedcluster.go index 3a02eeda..ae5dd238 100644 --- a/e2e/resources/attachedcluster.go +++ b/e2e/resources/attachedcluster.go @@ -22,6 +22,7 @@ import ( "github.com/onsi/gomega" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" clusterv1a1 "kurator.dev/kurator/pkg/apis/cluster/v1alpha1" kurator "kurator.dev/kurator/pkg/client-go/generated/clientset/versioned" @@ -43,34 +44,39 @@ func NewAttachedCluster(namespace string, name string, config clusterv1a1.Secret } } -// CreateAttachedCluster create AttachedCluster. -func CreateAttachedCluster(client kurator.Interface, attachedCluster *clusterv1a1.AttachedCluster) error { - _, err := client.ClusterV1alpha1().AttachedClusters(attachedCluster.Namespace).Create(context.TODO(), attachedCluster, metav1.CreateOptions{}) - if err != nil { - if apierrors.IsAlreadyExists(err) { - return UpdateAttachedCluster(client, attachedCluster) +// CreateAttachedCluster create or update AttachedCluster. +func CreateOrUpdateAttachedCluster(client kurator.Interface, attachedCluster *clusterv1a1.AttachedCluster) error { + _, createErr := client.ClusterV1alpha1().AttachedClusters(attachedCluster.GetNamespace()).Create(context.TODO(), attachedCluster, metav1.CreateOptions{}) + if createErr != nil { + if apierrors.IsAlreadyExists(createErr) { + originalAttachedCluster, getErr := client.ClusterV1alpha1().AttachedClusters(attachedCluster.GetNamespace()).Get(context.TODO(), attachedCluster.GetName(), metav1.GetOptions{}) + if getErr != nil { + return getErr + } + modifiedObjectMeta := ModifiedObjectMeta(originalAttachedCluster.ObjectMeta, attachedCluster.ObjectMeta) + oldAttachedCluster := clusterv1a1.AttachedCluster{ + ObjectMeta: originalAttachedCluster.ObjectMeta, + Spec: originalAttachedCluster.Spec, + } + modAttachedCluster := clusterv1a1.AttachedCluster{ + ObjectMeta: modifiedObjectMeta, + Spec: attachedCluster.Spec, + } + attachedClusterPatchData, createPatchErr := CreatePatchData(oldAttachedCluster, modAttachedCluster) + if createPatchErr != nil { + return createPatchErr + } + _, patchErr := client.ClusterV1alpha1().AttachedClusters(attachedCluster.GetNamespace()).Patch(context.TODO(), attachedCluster.GetName(), types.MergePatchType, attachedClusterPatchData, metav1.PatchOptions{}) + if patchErr != nil { + return patchErr + } } else { - return err + return createErr } } return nil } -// UpdateAttachedCluster update AttachedCluster -func UpdateAttachedCluster(client kurator.Interface, attachedCluster *clusterv1a1.AttachedCluster) error { - attachedClusterPresentOnCluster, attacattachedClusterGetErr := client.ClusterV1alpha1().AttachedClusters(attachedCluster.Namespace).Get(context.TODO(), attachedCluster.Name, metav1.GetOptions{}) - if attacattachedClusterGetErr != nil { - return attacattachedClusterGetErr - } - DCattachedcluster := attachedClusterPresentOnCluster.DeepCopy() - DCattachedcluster.Spec = attachedCluster.Spec - _, err := client.ClusterV1alpha1().AttachedClusters(DCattachedcluster.Namespace).Update(context.TODO(), DCattachedcluster, metav1.UpdateOptions{}) - if err != nil { - return err - } - return nil -} - func RemoveAttachedCluster(client kurator.Interface, namespace, name string) error { err := client.ClusterV1alpha1().AttachedClusters(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) if err != nil { diff --git a/e2e/resources/fleet.go b/e2e/resources/fleet.go index 85cbbee2..2aae959d 100644 --- a/e2e/resources/fleet.go +++ b/e2e/resources/fleet.go @@ -23,6 +23,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" fleetv1a1 "kurator.dev/kurator/pkg/apis/fleet/v1alpha1" kurator "kurator.dev/kurator/pkg/client-go/generated/clientset/versioned" @@ -45,28 +46,38 @@ func NewFleet(namespace string, name string, clusters []*corev1.ObjectReference) } } -// CreateAttachedCluster create AttachedCluster. -func CreateFleet(client kurator.Interface, fleet *fleetv1a1.Fleet) error { - _, err := client.FleetV1alpha1().Fleets(fleet.Namespace).Create(context.TODO(), fleet, metav1.CreateOptions{}) - if err != nil { - if apierrors.IsAlreadyExists(err) { - return UpdateFleet(client, fleet) +// CreateAttachedCluster create or update Fleet. +func CreateOrUpdateFleet(client kurator.Interface, fleet *fleetv1a1.Fleet) error { + _, createErr := client.FleetV1alpha1().Fleets(fleet.GetNamespace()).Create(context.TODO(), fleet, metav1.CreateOptions{}) + if createErr != nil { + if apierrors.IsAlreadyExists(createErr) { + originalFleet, getErr := client.FleetV1alpha1().Fleets(fleet.GetNamespace()).Get(context.TODO(), fleet.GetName(), metav1.GetOptions{}) + if getErr != nil { + return getErr + } + modifiedObjectMeta := ModifiedObjectMeta(originalFleet.ObjectMeta, fleet.ObjectMeta) + oldFleet := fleetv1a1.Fleet{ + ObjectMeta: originalFleet.ObjectMeta, + Spec: originalFleet.Spec, + } + modFleet := fleetv1a1.Fleet{ + ObjectMeta: modifiedObjectMeta, + Spec: fleet.Spec, + } + fleetPatchData, createPatchErr := CreatePatchData(oldFleet, modFleet) + if createPatchErr != nil { + return createPatchErr + } + if _, patchErr := client.FleetV1alpha1().Fleets(fleet.GetNamespace()).Patch(context.TODO(), fleet.GetName(), types.MergePatchType, fleetPatchData, metav1.PatchOptions{}); patchErr != nil { + return patchErr + } } else { - return err + return createErr } } return nil } -// UpdateAttachedCluster update AttachedCluster -func UpdateFleet(client kurator.Interface, fleet *fleetv1a1.Fleet) error { - _, err := client.FleetV1alpha1().Fleets(fleet.Namespace).Update(context.TODO(), fleet, metav1.UpdateOptions{}) - if err != nil { - return err - } - return nil -} - func RemoveFleet(client kurator.Interface, namespace, name string) error { err := client.FleetV1alpha1().Fleets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) if err != nil { diff --git a/e2e/resources/secret.go b/e2e/resources/secret.go index 78a69ee5..50d723a9 100644 --- a/e2e/resources/secret.go +++ b/e2e/resources/secret.go @@ -22,6 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ) @@ -40,28 +41,39 @@ func NewSecret(namespace string, name string, data map[string][]byte) *corev1.Se } } -// CreateSecret create Secret. -func CreateSecret(client kubernetes.Interface, secret *corev1.Secret) error { - _, err := client.CoreV1().Secrets(secret.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) - if err != nil { - if apierrors.IsAlreadyExists(err) { - return UpdateSecret(client, secret) +// CreateSecret create or update Secret. +func CreateOrUpdateSecret(client kubernetes.Interface, secret *corev1.Secret) error { + _, createErr := client.CoreV1().Secrets(secret.GetNamespace()).Create(context.TODO(), secret, metav1.CreateOptions{}) + if createErr != nil { + if apierrors.IsAlreadyExists(createErr) { + originalSecret, getErr := client.CoreV1().Secrets(secret.GetNamespace()).Get(context.TODO(), secret.GetName(), metav1.GetOptions{}) + if getErr != nil { + return getErr + } + modifiedObjectMeta := ModifiedObjectMeta(originalSecret.ObjectMeta, secret.ObjectMeta) + oldSecret := corev1.Secret{ + ObjectMeta: originalSecret.ObjectMeta, + Data: originalSecret.Data, + } + modSecret := corev1.Secret{ + ObjectMeta: modifiedObjectMeta, + Data: secret.Data, + } + secretPatchData, createPatchErr := CreatePatchData(oldSecret, modSecret) + if createPatchErr != nil { + return createPatchErr + } + _, patchErr := client.CoreV1().Secrets(secret.GetNamespace()).Patch(context.TODO(), secret.GetName(), types.StrategicMergePatchType, secretPatchData, metav1.PatchOptions{}) + if patchErr != nil { + return patchErr + } } else { - return err + return createErr } } return nil } -// UpdateSecret update Secret -func UpdateSecret(client kubernetes.Interface, secret *corev1.Secret) error { - _, err := client.CoreV1().Secrets(secret.Namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) - if err != nil { - return err - } - return nil -} - func RemoveSecret(client kubernetes.Interface, namespace, name string) error { err := client.CoreV1().Secrets(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) if err != nil { diff --git a/e2e/resources/util.go b/e2e/resources/util.go new file mode 100644 index 00000000..e50d3d95 --- /dev/null +++ b/e2e/resources/util.go @@ -0,0 +1,70 @@ +/* +Copyright Kurator Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resources + +import ( + "encoding/json" + + jsonpatch "github.com/evanphx/json-patch" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func CreatePatchData(original, modified interface{}) ([]byte, error) { + originalData, originalErr := json.Marshal(original) + if originalErr != nil { + return nil, originalErr + } + modifiedData, modifiedErr := json.Marshal(modified) + if modifiedErr != nil { + return nil, modifiedErr + } + patchData, createErr := jsonpatch.CreateMergePatch(originalData, modifiedData) + if createErr != nil { + return nil, createErr + } + return patchData, nil +} + +func ModifiedObjectMeta(original, modified metav1.ObjectMeta) metav1.ObjectMeta { + if modified.Labels == nil { + modified.Labels = original.Labels + } else { + for k, v := range original.Labels { + if modified.Labels[k] == "" { + modified.Labels[k] = v + } + } + } + + if modified.Annotations == nil { + modified.Annotations = original.Annotations + } else { + for k, v := range original.Annotations { + if modified.Annotations[k] == "" { + modified.Annotations[k] = v + } + } + } + + if modified.Finalizers == nil { + modified.Finalizers = original.Finalizers + } + if modified.ResourceVersion == "" { + modified.ResourceVersion = original.ResourceVersion + } + return modified +} diff --git a/e2e/suite_test.go b/e2e/suite_test.go index a1644087..6619e8bb 100644 --- a/e2e/suite_test.go +++ b/e2e/suite_test.go @@ -92,10 +92,10 @@ var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { } attachedcluster = resources.NewAttachedCluster(namespace, memberClusterName, secretKeyRef) - secretCreateErr := resources.CreateSecret(kubeClient, secret) + secretCreateErr := resources.CreateOrUpdateSecret(kubeClient, secret) gomega.Expect(secretCreateErr).ShouldNot(gomega.HaveOccurred()) - attachedCreateErr := resources.CreateAttachedCluster(kuratorClient, attachedcluster) + attachedCreateErr := resources.CreateOrUpdateAttachedCluster(kuratorClient, attachedcluster) gomega.Expect(attachedCreateErr).ShouldNot(gomega.HaveOccurred()) resources.WaitAttachedClusterFitWith(kuratorClient, namespace, memberClusterName, func(attachedCluster *clusterv1a1.AttachedCluster) bool { return attachedCluster.Status.Ready diff --git a/go.mod b/go.mod index 726227de..722dac7a 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/awslabs/goformation/v4 v4.19.5 github.com/cert-manager/cert-manager v1.10.0 github.com/coreos/go-semver v0.3.0 + github.com/evanphx/json-patch v5.6.0+incompatible github.com/fluxcd/flagger v1.34.0 github.com/fluxcd/helm-controller/api v0.32.2 github.com/fluxcd/kustomize-controller/api v1.0.0-rc.1 @@ -117,7 +118,6 @@ require ( github.com/emicklei/go-restful/v3 v3.10.2 // indirect github.com/envoyproxy/go-control-plane v0.11.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/camelcase v1.0.0 // indirect diff --git a/hack/e2e-test/install-kurator.sh b/hack/e2e-test/install-kurator.sh index fbc8ebcb..7b06cce1 100755 --- a/hack/e2e-test/install-kurator.sh +++ b/hack/e2e-test/install-kurator.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash - +# shellcheck disable=SC2086,SC1090,SC2206,SC1091 set -o errexit set -o nounset set -o pipefail