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 <[email protected]>
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

Reply via email to