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
