diff --git a/go.mod b/go.mod index efa673bf..d835deae 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ go 1.25.5 require ( github.com/buger/jsonparser v1.1.1 github.com/google/uuid v1.6.0 - github.com/meshery/meshery-operator v0.8.11 github.com/meshery/meshkit v0.8.54 github.com/myntra/pipeline v0.0.0-20180618182531-2babf4864ce8 github.com/sirupsen/logrus v1.9.3 @@ -21,6 +20,7 @@ require ( k8s.io/apimachinery v0.34.2 k8s.io/client-go v0.34.2 k8s.io/kubectl v0.34.2 + sigs.k8s.io/controller-runtime v0.20.1 sigs.k8s.io/yaml v1.6.0 ) @@ -50,6 +50,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -113,6 +114,8 @@ require ( github.com/nats-io/nkeys v0.4.9 // indirect github.com/nats-io/nuid v1.0.1 // indirect github.com/oapi-codegen/runtime v1.1.2 // indirect + github.com/onsi/ginkgo/v2 v2.22.2 // indirect + github.com/onsi/gomega v1.36.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect @@ -166,7 +169,6 @@ require ( k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect oras.land/oras-go/v2 v2.6.0 // indirect - sigs.k8s.io/controller-runtime v0.20.1 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect diff --git a/go.sum b/go.sum index 4cd64e07..387fa4a7 100644 --- a/go.sum +++ b/go.sum @@ -118,6 +118,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -229,8 +231,6 @@ github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/meshery/meshery-operator v0.8.11 h1:eDo2Sw0jjVrXsvvhF8LenADM58pK+7Z68ROPVIejTPc= -github.com/meshery/meshery-operator v0.8.11/go.mod h1:hQEtFKKa5Fr/Mskk6bV5ip3bQ0+3F0u1voYS3XisBp4= github.com/meshery/meshkit v0.8.54 h1:WxsYNITaK+IwzXFRbTzl2qkcygVVDRA7lVSPoQIA+ZQ= github.com/meshery/meshkit v0.8.54/go.mod h1:YmqM1i4vRD7W+Agf7KOXJchy6Haq/g1fABy7oYcnYo8= github.com/meshery/schemas v0.8.93 h1:kCib7RSzph+AbR6r2yQpFnLpK+zxyVxn0B3O6RbtXuM= @@ -404,6 +404,10 @@ go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= diff --git a/internal/config/crd_config.go b/internal/config/crd_config.go index 9b81d970..4995ab50 100644 --- a/internal/config/crd_config.go +++ b/internal/config/crd_config.go @@ -6,16 +6,18 @@ import ( "errors" "fmt" - "github.com/meshery/meshery-operator/pkg/client" "github.com/meshery/meshkit/utils" "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/dynamic" "k8s.io/client-go/rest" + "k8s.io/client-go/util/retry" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" ) var ( @@ -195,24 +197,57 @@ func filterBlacklistedPipelines(pipelines PipelineConfigs, blackList []string) P return result } -func PatchCRVersion(config *rest.Config) error { - meshsyncClient, err := client.New(config) +// PatchCRVersion updates the MeshSync CR version safely. +// It uses Controller Runtime patterns for idempotency and conflict handling. +func PatchCRVersion(ctx context.Context, config *rest.Config) error { + // 1. Init Controller Runtime Client + cl, err := ctrlclient.New(config, ctrlclient.Options{}) if err != nil { - return ErrInitConfig(fmt.Errorf("unable to update MeshSync configuration")) - } + return ErrInitConfig(fmt.Errorf("unable to create controller-runtime client: %w", err)) + } + + // 2. Define the Object (Using Unstructured to avoid circular dependencies) + obj := &unstructured.Unstructured{} + obj.SetGroupVersionKind(schema.GroupVersionKind{ + Group: group, + Version: version, + Kind: "MeshSync", + }) + + key := types.NamespacedName{Name: crName, Namespace: namespace} + + // 3. Retry Logic (Standard Kubernetes RetryOnConflict) + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + // Fetch current CR + if err := cl.Get(context.TODO(), key, obj); err != nil { + if k8serrors.IsNotFound(err) { + // GUARD: CR doesn't exist, so we skip patching (Fixes #422) + return nil + } + // Use existing error pattern + return ErrInitConfig(fmt.Errorf("unable to get MeshSync CR %s/%s: %w", namespace, crName, err)) + } - patchedResource := map[string]interface{}{ - "spec": map[string]interface{}{ - "version": Server["version"], - }, - } - byt, err := utils.Marshal(patchedResource) - if err != nil { - return ErrInitConfig(fmt.Errorf("unable to update MeshSync configuration")) - } - _, err = meshsyncClient.CoreV1Alpha1().MeshSyncs("meshery").Patch(context.TODO(), crName, types.MergePatchType, []byte(byt), metav1.PatchOptions{}) - if err != nil { - return ErrInitConfig(fmt.Errorf("unable to update MeshSync configuration")) - } - return nil + // Idempotency: Check if version already matches + specVersion, found, _ := unstructured.NestedString(obj.Object, "spec", "version") + if found && specVersion == Server["version"] { + // Version already matches, no update needed + return nil + } + + // Prepare Patch (MergeFrom handles JSON generation automatically) + patch := ctrlclient.MergeFrom(obj.DeepCopy()) + + // Update the version field in the object + if err := unstructured.SetNestedField(obj.Object, Server["version"], "spec", "version"); err != nil { + return ErrInitConfig(fmt.Errorf("unable to set version field: %w", err)) + } + + // Apply Patch + if err := cl.Patch(context.TODO(), obj, patch); err != nil { + return err // RetryOnConflict will catch this and retry if it's a conflict + } + + return nil + }) } diff --git a/internal/config/types.go b/internal/config/types.go index f8a43210..581999f0 100644 --- a/internal/config/types.go +++ b/internal/config/types.go @@ -33,7 +33,7 @@ func (p PipelineConfigs) Add(pc PipelineConfig) PipelineConfigs { func (p PipelineConfigs) Delete(pc PipelineConfig) PipelineConfigs { for index, pipelineConfig := range p { if pipelineConfig.Name == pc.Name { - p = slices.Delete[PipelineConfigs](p, index, index+1) + p = slices.Delete(p, index, index+1) break } } diff --git a/pkg/lib/meshsync/meshsync.go b/pkg/lib/meshsync/meshsync.go index 2a653b5c..ec8a410e 100644 --- a/pkg/lib/meshsync/meshsync.go +++ b/pkg/lib/meshsync/meshsync.go @@ -71,9 +71,13 @@ func Run(log logger.Handler, optsSetters ...OptionsSetter) error { } if useCRDFlag { - // this patch only make sense when CRD is present when in cluster - if errPatchCRVersion := config.PatchCRVersion(&kubeClient.RestConfig); errPatchCRVersion != nil { - log.Warnf("meshsync: %v", errPatchCRVersion) + // Only attempt to patch MeshSync CR/version if the MeshSync CR object exists. + if _, errGetCR := config.GetMeshsyncCRD(kubeClient.DynamicKubeClient); errGetCR == nil { + if errPatchCRVersion := config.PatchCRVersion(&kubeClient.RestConfig); errPatchCRVersion != nil { + log.Warnf("meshsync: %v", errPatchCRVersion) + } + } else { + log.Debugf("meshsync: skipping PatchCRVersion because MeshSync CR not found: %v", errGetCR) } }