diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc index 64e5125b58..77c844a218 100644 --- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc +++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc @@ -6877,6 +6877,7 @@ bool | +Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. diff --git a/docs/modules/traits/pages/deployer.adoc b/docs/modules/traits/pages/deployer.adoc index a220f1b3c4..aa03c1034d 100755 --- a/docs/modules/traits/pages/deployer.adoc +++ b/docs/modules/traits/pages/deployer.adoc @@ -36,7 +36,8 @@ The following configuration options are available: | deployer.use-ssa | bool -| Use server-side apply to update the owned resources (default `true`). +| Deprecated: won't be able to enforce client side update in the future. +Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. |=== diff --git a/helm/camel-k/crds/camel-k-crds.yaml b/helm/camel-k/crds/camel-k-crds.yaml index cfdae68b14..449de8c308 100644 --- a/helm/camel-k/crds/camel-k-crds.yaml +++ b/helm/camel-k/crds/camel-k-crds.yaml @@ -4079,6 +4079,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -6220,6 +6221,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -8263,6 +8265,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -10283,6 +10286,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -18343,6 +18347,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -26766,6 +26771,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -37689,6 +37695,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean diff --git a/pkg/apis/camel/v1/trait/deployer.go b/pkg/apis/camel/v1/trait/deployer.go index 70d140ec49..c76dc927a9 100644 --- a/pkg/apis/camel/v1/trait/deployer.go +++ b/pkg/apis/camel/v1/trait/deployer.go @@ -26,6 +26,7 @@ type DeployerTrait struct { // Allows to explicitly select the desired deployment kind between `deployment`, `cron-job` or `knative-service` when creating the resources for running the integration. // +kubebuilder:validation:Enum=deployment;cron-job;knative-service Kind string `property:"kind" json:"kind,omitempty"` + // Deprecated: won't be able to enforce client side update in the future. // Use server-side apply to update the owned resources (default `true`). // Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. UseSSA *bool `property:"use-ssa" json:"useSSA,omitempty"` diff --git a/pkg/client/apply.go b/pkg/client/apply.go index 7fe872e848..aa43f5df9e 100644 --- a/pkg/client/apply.go +++ b/pkg/client/apply.go @@ -19,6 +19,7 @@ package client import ( "context" + "encoding/json" "errors" "fmt" "net/http" @@ -31,7 +32,6 @@ import ( k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -78,12 +78,17 @@ func (a *ServerOrClientSideApplier) Apply(ctx context.Context, object ctrl.Objec return nil } -func (a *ServerOrClientSideApplier) serverSideApply(ctx context.Context, resource runtime.Object) error { +func (a *ServerOrClientSideApplier) serverSideApply(ctx context.Context, resource ctrl.Object) error { target, err := patch.ApplyPatch(resource) if err != nil { return err } - return a.Client.Patch(ctx, target, ctrl.Apply, ctrl.ForceOwnership, ctrl.FieldOwner("camel-k-operator")) + if err = a.Client.Patch(ctx, target, ctrl.Apply, ctrl.ForceOwnership, ctrl.FieldOwner("camel-k-operator")); err != nil { + return fmt.Errorf("error during apply resource: %s/%s: %w", resource.GetNamespace(), resource.GetName(), err) + } + + // Update the resource with the response returned from the API server + return unstructuredToRuntimeObject(target, resource) } func (a *ServerOrClientSideApplier) clientSideApply(ctx context.Context, resource ctrl.Object) error { @@ -105,7 +110,8 @@ func (a *ServerOrClientSideApplier) clientSideApply(ctx context.Context, resourc if err != nil { return err } else if len(p) == 0 { - return nil + // Update the resource with the object returned from the API server + return unstructuredToRuntimeObject(object, resource) } return a.Client.Patch(ctx, resource, ctrl.RawPatch(types.MergePatchType, p)) } @@ -124,3 +130,11 @@ func isIncompatibleServerError(err error) bool { // Non-StatusError means the error isn't because the server is incompatible. return false } + +func unstructuredToRuntimeObject(u *unstructured.Unstructured, obj ctrl.Object) error { + data, err := json.Marshal(u) + if err != nil { + return err + } + return json.Unmarshal(data, obj) +} diff --git a/pkg/install/kamelets.go b/pkg/install/kamelets.go index 479eb84638..daaa7894e6 100644 --- a/pkg/install/kamelets.go +++ b/pkg/install/kamelets.go @@ -19,24 +19,15 @@ package install import ( "context" - "encoding/json" - "errors" "fmt" "io/fs" - "net/http" "os" "path/filepath" "strings" - "sync" - "sync/atomic" "golang.org/x/sync/errgroup" - k8serrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" @@ -44,7 +35,7 @@ import ( "github.com/apache/camel-k/v2/pkg/util" "github.com/apache/camel-k/v2/pkg/util/defaults" "github.com/apache/camel-k/v2/pkg/util/kubernetes" - "github.com/apache/camel-k/v2/pkg/util/patch" + "github.com/apache/camel-k/v2/pkg/util/log" ) const ( @@ -52,13 +43,6 @@ const ( defaultKameletDir = "/kamelets/" ) -var ( - log = logf.Log - - hasServerSideApply atomic.Value - tryServerSideApply sync.Once -) - // KameletCatalog installs the bundled Kamelets into the specified namespace. func KameletCatalog(ctx context.Context, c client.Client, namespace string) error { kameletDir := os.Getenv(kameletDirEnv) @@ -76,6 +60,7 @@ func KameletCatalog(ctx context.Context, c client.Client, namespace string) erro } g, gCtx := errgroup.WithContext(ctx) + applier := c.ServerOrClientSideApplier() err = filepath.WalkDir(kameletDir, func(p string, f fs.DirEntry, err error) error { if err != nil { @@ -93,38 +78,10 @@ func KameletCatalog(ctx context.Context, c client.Client, namespace string) erro if err != nil { return err } - once := false - tryServerSideApply.Do(func() { - once = true - if err = serverSideApply(gCtx, c, kamelet); err != nil { - if isIncompatibleServerError(err) { - log.Info("Fallback to client-side apply for installing bundled Kamelets") - hasServerSideApply.Store(false) - err = nil - } else { - // Unexpected error occurred - err = fmt.Errorf("unexpected error occurred whilst validating kamelet: %w", err) - log.Error(err, "Error occurred whilst loading kamelets") - } - } else { - hasServerSideApply.Store(true) - } - }) - if err != nil { - return err - } - v := hasServerSideApply.Load() - var bundleKamError error - if vb, ok := v.(bool); ok && vb { - if !once { - bundleKamError = serverSideApply(gCtx, c, kamelet) - } - } else { - bundleKamError = clientSideApply(gCtx, c, kamelet) - } + err = applier.Apply(gCtx, kamelet) // We only log the error. If we returned the error, the creation of the ITP would have stopped - if bundleKamError != nil { - log.Error(bundleKamError, "Error occurred whilst applying bundled kamelet") + if err != nil { + log.Error(err, "Error occurred whilst applying bundled kamelet") } return nil }) @@ -137,43 +94,6 @@ func KameletCatalog(ctx context.Context, c client.Client, namespace string) erro return g.Wait() } -func serverSideApply(ctx context.Context, c client.Client, resource runtime.Object) error { - target, err := patch.ApplyPatch(resource) - if err != nil { - return err - } - return c.Patch(ctx, target, ctrl.Apply, ctrl.ForceOwnership, ctrl.FieldOwner("camel-k-operator")) -} - -func clientSideApply(ctx context.Context, c client.Client, resource ctrl.Object) error { - if err := c.Create(ctx, resource); err == nil { - return nil - } else if !k8serrors.IsAlreadyExists(err) { - return fmt.Errorf("error during create resource: %s/%s: %w", resource.GetNamespace(), resource.GetName(), err) - } - // Directly use the serialized resource as JSON merge patch since it's prescriptive - p, err := json.Marshal(resource) - if err != nil { - return err - } - return c.Patch(ctx, resource, ctrl.RawPatch(types.MergePatchType, p)) -} - -func isIncompatibleServerError(err error) bool { - // First simpler check for older servers (i.e. OpenShift 3.11) - if strings.Contains(err.Error(), "415: Unsupported Media Type") { - return true - } - // 415: Unsupported media type means we're talking to a server which doesn't - // support server-side apply. - var serr *k8serrors.StatusError - if errors.As(err, &serr) { - return serr.Status().Code == http.StatusUnsupportedMediaType - } - // Non-StatusError means the error isn't because the server is incompatible. - return false -} - func loadKamelet(path string, namespace string) (ctrl.Object, error) { content, err := util.ReadFile(path) if err != nil { @@ -212,8 +132,5 @@ func loadKamelet(path string, namespace string) (ctrl.Object, error) { // KameletViewerRole installs the role that allows any user ro access kamelets in the global namespace. func KameletViewerRole(ctx context.Context, c client.Client, namespace string) error { - if err := Resource(ctx, c, namespace, true, IdentityResourceCustomizer, "/resources/viewer/user-global-kamelet-viewer-role.yaml"); err != nil { - return err - } return Resource(ctx, c, namespace, true, IdentityResourceCustomizer, "/resources/viewer/user-global-kamelet-viewer-role-binding.yaml") } diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml index 01bd356320..68c15d04c6 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationplatforms.yaml @@ -924,6 +924,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -3065,6 +3066,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml index 5987791c85..1e9642cb4c 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrationprofiles.yaml @@ -792,6 +792,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean @@ -2812,6 +2813,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml index e6fa68441a..20be245607 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml @@ -6812,6 +6812,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean diff --git a/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml b/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml index 2660bd748b..74341212a0 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_kameletbindings.yaml @@ -6879,6 +6879,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean diff --git a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml index 83d2f95d67..713ea91870 100644 --- a/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml +++ b/pkg/resources/config/crd/bases/camel.apache.org_pipes.yaml @@ -6877,6 +6877,7 @@ spec: type: string useSSA: description: |- + Deprecated: won't be able to enforce client side update in the future. Use server-side apply to update the owned resources (default `true`). Note that it automatically falls back to client-side patching, if SSA is not available, e.g., on old Kubernetes clusters. type: boolean diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go index a308663a13..65051e53a2 100644 --- a/pkg/trait/deployer.go +++ b/pkg/trait/deployer.go @@ -18,21 +18,18 @@ limitations under the License. package trait import ( - "encoding/json" - "errors" "fmt" - "net/http" - "strings" + v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1" + traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" + "github.com/apache/camel-k/v2/pkg/util/patch" + corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/json" "k8s.io/utils/ptr" - ctrl "sigs.k8s.io/controller-runtime/pkg/client" - - traitv1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1/trait" - "github.com/apache/camel-k/v2/pkg/util/patch" ) const ( @@ -47,8 +44,6 @@ type deployerTrait struct { var _ ControllerStrategySelector = &deployerTrait{} -var hasServerSideApply = true - func newDeployerTrait() Trait { return &deployerTrait{ BasePlatformTrait: NewBasePlatformTrait(deployerTraitID, deployerTraitOrder), @@ -56,35 +51,34 @@ func newDeployerTrait() Trait { } func (t *deployerTrait) Configure(e *Environment) (bool, *TraitCondition, error) { - return e.Integration != nil, nil, nil + var condition *TraitCondition + //nolint: staticcheck + if !ptr.Deref(t.UseSSA, true) { + condition = NewIntegrationCondition( + "Deployer", + v1.IntegrationConditionTraitInfo, + corev1.ConditionTrue, + traitConfigurationReason, + "The use-ssa parameter is deprecated and may be removed in future releases.", + ) + } + return e.Integration != nil, condition, nil } func (t *deployerTrait) Apply(e *Environment) error { // Register a post action that patches the resources generated by the traits e.PostActions = append(e.PostActions, func(env *Environment) error { + applier := e.Client.ServerOrClientSideApplier() for _, resource := range env.Resources.Items() { - // We assume that server-side apply is enabled by default. - // It is currently convoluted to check proactively whether server-side apply - // is enabled. This is possible to fetch the OpenAPI endpoint, which returns - // the entire server API document, then lookup the resource PATCH endpoint, and - // check its list of accepted MIME types. - // As a simpler solution, we fall back to client-side apply at the first - // 415 error, and assume server-side apply is not available globally. - if hasServerSideApply && ptr.Deref(t.UseSSA, true) { - err := t.serverSideApply(env, resource) - switch { - case err == nil: - continue - case isIncompatibleServerError(err): - t.L.Info("Fallback to client-side apply to patch resources") - hasServerSideApply = false - default: - // Keep server-side apply unless server is incompatible with it + //nolint: staticcheck + if ptr.Deref(t.UseSSA, true) { + if err := applier.Apply(e.Ctx, resource); err != nil { + return err + } + } else { + if err := t.clientSideApply(env, resource); err != nil { return err } - } - if err := t.clientSideApply(env, resource); err != nil { - return err } } return nil @@ -93,19 +87,7 @@ func (t *deployerTrait) Apply(e *Environment) error { return nil } -func (t *deployerTrait) serverSideApply(env *Environment, resource ctrl.Object) error { - target, err := patch.ApplyPatch(resource) - if err != nil { - return err - } - err = env.Client.Patch(env.Ctx, target, ctrl.Apply, ctrl.ForceOwnership, ctrl.FieldOwner("camel-k-operator")) - if err != nil { - return fmt.Errorf("error during apply resource: %s/%s: %w", resource.GetNamespace(), resource.GetName(), err) - } - // Update the resource with the response returned from the API server - return t.unstructuredToRuntimeObject(target, resource) -} - +// Deprecated: use ServerOrClientSideApplier() instead. func (t *deployerTrait) clientSideApply(env *Environment, resource ctrl.Object) error { err := env.Client.Create(env.Ctx, resource) if err == nil { @@ -135,6 +117,7 @@ func (t *deployerTrait) clientSideApply(env *Environment, resource ctrl.Object) return nil } +// Deprecated: use ServerOrClientSideApplier() instead. func (t *deployerTrait) unstructuredToRuntimeObject(u *unstructured.Unstructured, obj ctrl.Object) error { data, err := json.Marshal(u) if err != nil { @@ -143,23 +126,6 @@ func (t *deployerTrait) unstructuredToRuntimeObject(u *unstructured.Unstructured return json.Unmarshal(data, obj) } -func isIncompatibleServerError(err error) bool { - // First simpler check for older servers (i.e. OpenShift 3.11) - if strings.Contains(err.Error(), "415: Unsupported Media Type") { - return true - } - - // 415: Unsupported media type means we're talking to a server which doesn't - // support server-side apply. - var serr *k8serrors.StatusError - if errors.As(err, &serr) { - return serr.Status().Code == http.StatusUnsupportedMediaType - } - - // Non-StatusError means the error isn't because the server is incompatible. - return false -} - func (t *deployerTrait) SelectControllerStrategy(e *Environment) (*ControllerStrategy, error) { if t.Kind != "" { strategy := ControllerStrategy(t.Kind)