You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by as...@apache.org on 2021/02/18 16:11:34 UTC

[camel-k] 03/03: chore: Fallback to client-side apply if necessary

This is an automated email from the ASF dual-hosted git repository.

astefanutti pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel-k.git

commit 70f7946cb5a4cdc224c7c25e49b232867a4f815e
Author: Antonin Stefanutti <an...@stefanutti.fr>
AuthorDate: Wed Feb 17 18:31:10 2021 +0100

    chore: Fallback to client-side apply if necessary
---
 pkg/trait/deployer.go | 82 +++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 76 insertions(+), 6 deletions(-)

diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go
index d9fa584..a9a11d7 100644
--- a/pkg/trait/deployer.go
+++ b/pkg/trait/deployer.go
@@ -18,8 +18,14 @@ limitations under the License.
 package trait
 
 import (
+	"net/http"
+
 	"github.com/pkg/errors"
 
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
+
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
@@ -38,6 +44,8 @@ type deployerTrait struct {
 
 var _ ControllerStrategySelector = &deployerTrait{}
 
+var hasServerSideApply = true
+
 func newDeployerTrait() Trait {
 	return &deployerTrait{
 		BaseTrait: NewBaseTrait("deployer", 900),
@@ -75,13 +83,23 @@ 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 {
 			for _, resource := range env.Resources.Items() {
-				target, err := patch.PositiveApplyPatch(resource)
-				if err != nil {
-					return err
+				// We assume that server-side apply is enabled by default.
+				// It is currently convoluted to check pro-actively 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 fallback to client-side apply at the first
+				// 415 error, and assume server-side apply is not available globally.
+				if hasServerSideApply {
+					if err := t.serverSideApply(env, resource); err == nil {
+						continue
+					} else if isIncompatibleServerError(err) {
+						t.L.Info("Fallback to client-side apply to patch resources")
+						hasServerSideApply = false
+					}
 				}
-				err = env.Client.Patch(env.C, target, client.Apply, client.ForceOwnership, client.FieldOwner("camel-k-operator"))
-				if err != nil {
-					return errors.Wrapf(err, "error during apply resource: %v", resource)
+				if err := t.clientSideApply(env, resource); err != nil {
+					return err
 				}
 			}
 			return nil
@@ -91,6 +109,58 @@ func (t *deployerTrait) Apply(e *Environment) error {
 	return nil
 }
 
+func (t *deployerTrait) serverSideApply(env *Environment, resource runtime.Object) error {
+	target, err := patch.PositiveApplyPatch(resource)
+	if err != nil {
+		return err
+	}
+	err = env.Client.Patch(env.C, target, client.Apply, client.ForceOwnership, client.FieldOwner("camel-k-operator"))
+	if err != nil {
+		return errors.Wrapf(err, "error during apply resource: %v", resource)
+	}
+	return nil
+}
+
+func (t *deployerTrait) clientSideApply(env *Environment, resource runtime.Object) error {
+	err := env.Client.Create(env.C, resource)
+	if err == nil {
+		return nil
+	} else if !k8serrors.IsAlreadyExists(err) {
+		return errors.Wrapf(err, "error during create resource: %v", resource)
+	}
+	key, err := client.ObjectKeyFromObject(resource)
+	if err != nil {
+		return err
+	}
+	object := resource.DeepCopyObject()
+	err = env.Client.Get(env.C, key, object)
+	if err != nil {
+		return err
+	}
+	p, err := patch.PositiveMergePatch(object, resource)
+	if err != nil {
+		return err
+	} else if len(p) == 0 {
+		// Avoid triggering a patch request for nothing
+		return nil
+	}
+	err = env.Client.Patch(env.C, resource, client.RawPatch(types.MergePatchType, p))
+	if err != nil {
+		return errors.Wrapf(err, "error during patch resource: %v", resource)
+	}
+	return nil
+}
+
+func isIncompatibleServerError(err error) bool {
+	// 415: Unsupported media type means we're talking to a server which doesn't
+	// support server-side apply.
+	if _, ok := err.(*k8serrors.StatusError); !ok {
+		// Non-StatusError means the error isn't because the server is incompatible.
+		return false
+	}
+	return err.(*k8serrors.StatusError).Status().Code == http.StatusUnsupportedMediaType
+}
+
 func (t *deployerTrait) SelectControllerStrategy(e *Environment) (*ControllerStrategy, error) {
 	if t.Enabled != nil && !*t.Enabled {
 		return nil, nil