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/12/07 13:23:51 UTC
[camel-k] 02/04: chore: Use Server-Side Apply to install bundled Kamelets
This is an automated email from the ASF dual-hosted git repository.
astefanutti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git
commit 8ed4b47951bef17c2856b3c90ffc22ad548bd9d7
Author: Antonin Stefanutti <an...@stefanutti.fr>
AuthorDate: Mon Dec 6 15:39:52 2021 +0100
chore: Use Server-Side Apply to install bundled Kamelets
---
pkg/install/kamelets.go | 117 +++++++++++++++++++++++++++++++++++-------------
1 file changed, 86 insertions(+), 31 deletions(-)
diff --git a/pkg/install/kamelets.go b/pkg/install/kamelets.go
index b2ad752..e0f353e 100644
--- a/pkg/install/kamelets.go
+++ b/pkg/install/kamelets.go
@@ -19,8 +19,10 @@ package install
import (
"context"
+ "errors"
"fmt"
"io/fs"
+ "net/http"
"os"
"path"
"path/filepath"
@@ -29,15 +31,18 @@ import (
"golang.org/x/sync/errgroup"
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"
- "github.com/pkg/errors"
+ ctrl "sigs.k8s.io/controller-runtime/pkg/client"
"github.com/apache/camel-k/pkg/apis/camel/v1alpha1"
"github.com/apache/camel-k/pkg/client"
"github.com/apache/camel-k/pkg/util"
"github.com/apache/camel-k/pkg/util/defaults"
"github.com/apache/camel-k/pkg/util/kubernetes"
+ "github.com/apache/camel-k/pkg/util/patch"
)
const (
@@ -45,6 +50,8 @@ const (
defaultKameletDir = "/kamelets/"
)
+var hasServerSideApply = true
+
// KameletCatalog installs the bundled Kamelets into the specified namespace.
func KameletCatalog(ctx context.Context, c client.Client, namespace string) error {
kameletDir := os.Getenv(kameletDirEnv)
@@ -75,7 +82,7 @@ func KameletCatalog(ctx context.Context, c client.Client, namespace string) erro
}
// We may want to throttle the creation of Go routines if the number of bundled Kamelets increases.
g.Go(func() error {
- return createOrReplaceKamelet(gCtx, c, path.Join(kameletDir, f.Name()), namespace)
+ return applyKamelet(gCtx, c, path.Join(kameletDir, f.Name()), namespace)
})
return nil
})
@@ -86,9 +93,7 @@ func KameletCatalog(ctx context.Context, c client.Client, namespace string) erro
return g.Wait()
}
-func createOrReplaceKamelet(ctx context.Context, c client.Client, path string, namespace string) error {
- fmt.Printf("Install file: %s in %s", path, namespace)
-
+func applyKamelet(ctx context.Context, c client.Client, path string, namespace string) error {
content, err := util.ReadFile(path)
if err != nil {
return err
@@ -98,38 +103,88 @@ func createOrReplaceKamelet(ctx context.Context, c client.Client, path string, n
if err != nil {
return err
}
- if k, ok := obj.(*v1alpha1.Kamelet); ok {
- existing := &v1alpha1.Kamelet{}
- err = c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: k.Name}, existing)
- if err != nil {
- if k8serrors.IsNotFound(err) {
- existing = nil
- } else {
- return err
- }
- }
+ kamelet, ok := obj.(*v1alpha1.Kamelet)
+ if !ok {
+ return fmt.Errorf("cannot load Kamelet from file %q", path)
+ }
+
+ kamelet.Namespace = namespace
+
+ if kamelet.GetAnnotations() == nil {
+ kamelet.SetAnnotations(make(map[string]string))
+ }
+ kamelet.GetAnnotations()[kamelVersionAnnotation] = defaults.Version
- if existing == nil || existing.Labels[v1alpha1.KameletBundledLabel] == "true" {
- if k.GetAnnotations() == nil {
- k.SetAnnotations(make(map[string]string))
- }
- k.GetAnnotations()[kamelVersionAnnotation] = defaults.Version
-
- if k.GetLabels() == nil {
- k.SetLabels(make(map[string]string))
- }
- k.GetLabels()[v1alpha1.KameletBundledLabel] = "true"
- k.GetLabels()[v1alpha1.KameletReadOnlyLabel] = "true"
-
- err := ObjectOrCollect(ctx, c, namespace, nil, true, k)
- if err != nil {
- return errors.Wrapf(err, "could not create resource from file %q", path)
- }
+ if kamelet.GetLabels() == nil {
+ kamelet.SetLabels(make(map[string]string))
+ }
+ kamelet.GetLabels()[v1alpha1.KameletBundledLabel] = "true"
+ kamelet.GetLabels()[v1alpha1.KameletReadOnlyLabel] = "true"
+
+ if hasServerSideApply {
+ err := serverSideApply(ctx, c, kamelet)
+ switch {
+ case err == nil:
+ break
+ case isIncompatibleServerError(err):
+ hasServerSideApply = false
+ default:
+ return fmt.Errorf("could not apply Kamelet from file %q: %w", path, err)
}
+ } else {
+ return clientSideApply(ctx, c, kamelet)
}
+
return nil
}
+func serverSideApply(ctx context.Context, c client.Client, resource runtime.Object) error {
+ target, err := patch.PositiveApplyPatch(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 {
+ err := c.Create(ctx, resource)
+ if err == nil {
+ return nil
+ } else if !k8serrors.IsAlreadyExists(err) {
+ return fmt.Errorf("error during create resource: %s/%s: %w", resource.GetNamespace(), resource.GetName(), err)
+ }
+ object := &unstructured.Unstructured{}
+ object.SetNamespace(resource.GetNamespace())
+ object.SetName(resource.GetName())
+ object.SetGroupVersionKind(resource.GetObjectKind().GroupVersionKind())
+ err = c.Get(ctx, ctrl.ObjectKeyFromObject(object), object)
+ if err != nil {
+ return err
+ }
+ p, err := patch.PositiveMergePatch(object, resource)
+ if err != nil {
+ return err
+ } else if len(p) == 0 {
+ return nil
+ }
+ 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
+}
+
// 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, "/viewer/user-global-kamelet-viewer-role.yaml"); err != nil {