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