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:32 UTC
[camel-k] 01/03: feat: Use server-side apply to create and patch
owned resources
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 cf48cd2f7553a04c624c10520b5a80461f915ddf
Author: Antonin Stefanutti <an...@stefanutti.fr>
AuthorDate: Mon Feb 15 18:16:58 2021 +0100
feat: Use server-side apply to create and patch owned resources
Server-side apply has been introduced to improve co-operation
of controllers in defining the desire state for shared resources.
We implemented a custom strategy relying on a combination of merge
patch and pruning of nil values, but that fails to leverage the
semantic provided by the strategic merge patch field markers,
as strategic merge patch is not supported by CRDs, like Knative
Service.
With server-side apply, markers can be applied to data
structures (Lists, Maps), to make valid and finer-grained merge
strategies.
This particularly helps cooperation of the Camel K operator with
its Knative counterpart, into updating the Knative service pod
template fields concurrently, such as the `Env []EnvVar` field,
that declares the `+patchStrategy=merge` marker, which translates
into the server-side apply `listType` and `+listMapKey` markers.
---
pkg/trait/deployer.go | 42 +++++------------------------
pkg/util/kubernetes/replace.go | 22 ++++++---------
pkg/util/patch/patch.go | 61 +++++++++++++++---------------------------
3 files changed, 36 insertions(+), 89 deletions(-)
diff --git a/pkg/trait/deployer.go b/pkg/trait/deployer.go
index 062b91d..d9fa584 100644
--- a/pkg/trait/deployer.go
+++ b/pkg/trait/deployer.go
@@ -19,12 +19,10 @@ package trait
import (
"github.com/pkg/errors"
- "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
- "github.com/apache/camel-k/pkg/util/kubernetes"
"github.com/apache/camel-k/pkg/util/patch"
)
@@ -62,15 +60,6 @@ func (t *deployerTrait) Configure(e *Environment) (bool, error) {
func (t *deployerTrait) Apply(e *Environment) error {
switch e.Integration.Status.Phase {
- case v1.IntegrationPhaseNone, v1.IntegrationPhaseWaitingForPlatform, v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying:
- // Register a post action that updates the resources generated by the traits
- e.PostActions = append(e.PostActions, func(env *Environment) error {
- if err := kubernetes.ReplaceResources(env.C, env.Client, env.Resources.Items()); err != nil {
- return errors.Wrap(err, "error during replace resource")
- }
- return nil
- })
-
case v1.IntegrationPhaseBuildingKit, v1.IntegrationPhaseResolvingKit:
if e.IntegrationKitInPhase(v1.IntegrationKitPhaseReady) {
e.PostProcessors = append(e.PostProcessors, func(environment *Environment) error {
@@ -80,38 +69,19 @@ func (t *deployerTrait) Apply(e *Environment) error {
})
}
- case v1.IntegrationPhaseRunning, v1.IntegrationPhaseWaitingForBindings:
+ case v1.IntegrationPhaseNone, v1.IntegrationPhaseInitialization,
+ v1.IntegrationPhaseWaitingForPlatform, v1.IntegrationPhaseWaitingForBindings,
+ v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning:
// 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() {
- key, err := client.ObjectKeyFromObject(resource)
+ target, err := patch.PositiveApplyPatch(resource)
if err != nil {
return err
}
-
- object := resource.DeepCopyObject()
- err = env.Client.Get(env.C, key, object)
+ err = env.Client.Patch(env.C, target, client.Apply, client.ForceOwnership, client.FieldOwner("camel-k-operator"))
if err != nil {
- return err
- }
-
- // If both objects have "ObjectMeta" and "Spec" fields and they contain all the expected fields
- // (plus optional others), then avoid patching.
- if !patch.ObjectMetaEqualDeepDerivative(object, resource) ||
- !patch.SpecEqualDeepDerivative(object, resource) {
-
- p, err := patch.PositiveMergePatch(object, resource)
- if err != nil {
- return err
- } else if len(p) == 0 {
- // Avoid triggering a patch request for nothing
- continue
- }
-
- err = env.Client.Patch(env.C, resource, client.RawPatch(types.MergePatchType, p))
- if err != nil {
- return errors.Wrap(err, "error during patch resource")
- }
+ return errors.Wrapf(err, "error during apply resource: %v", resource)
}
}
return nil
diff --git a/pkg/util/kubernetes/replace.go b/pkg/util/kubernetes/replace.go
index 55c8dcf..a9f045d 100644
--- a/pkg/util/kubernetes/replace.go
+++ b/pkg/util/kubernetes/replace.go
@@ -20,27 +20,21 @@ package kubernetes
import (
"context"
- "github.com/apache/camel-k/pkg/client"
- routev1 "github.com/openshift/api/route/v1"
"github.com/pkg/errors"
+
corev1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
- serving "knative.dev/serving/pkg/apis/serving/v1"
+
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
-)
-// ReplaceResources allows to completely replace a list of resources on Kubernetes, taking care of immutable fields and resource versions
-func ReplaceResources(ctx context.Context, c client.Client, objects []runtime.Object) error {
- for _, object := range objects {
- err := ReplaceResource(ctx, c, object)
- if err != nil {
- return err
- }
- }
- return nil
-}
+ serving "knative.dev/serving/pkg/apis/serving/v1"
+
+ routev1 "github.com/openshift/api/route/v1"
+
+ "github.com/apache/camel-k/pkg/client"
+)
// ReplaceResource allows to completely replace a resource on Kubernetes, taking care of immutable fields and resource versions
func ReplaceResource(ctx context.Context, c client.Client, res runtime.Object) error {
diff --git a/pkg/util/patch/patch.go b/pkg/util/patch/patch.go
index e2c54b1..09c4265 100644
--- a/pkg/util/patch/patch.go
+++ b/pkg/util/patch/patch.go
@@ -21,7 +21,8 @@ import (
"reflect"
jsonpatch "github.com/evanphx/json-patch"
- "k8s.io/apimachinery/pkg/api/equality"
+
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
@@ -63,6 +64,26 @@ func PositiveMergePatch(source runtime.Object, target runtime.Object) ([]byte, e
return json.Marshal(positivePatch)
}
+func PositiveApplyPatch(source runtime.Object) (runtime.Object, error) {
+ sourceJSON, err := json.Marshal(source)
+ if err != nil {
+ return nil, err
+ }
+
+ var positivePatch map[string]interface{}
+ err = json.Unmarshal(sourceJSON, &positivePatch)
+ if err != nil {
+ return nil, err
+ }
+
+ // The following is a work-around to remove null fields from the apply patch,
+ // so that ownership is not taken for non-managed fields.
+ // See https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2155-clientgo-apply
+ removeNilValues(reflect.ValueOf(positivePatch), reflect.Value{})
+
+ return &unstructured.Unstructured{Object: positivePatch}, nil
+}
+
func removeNilValues(v reflect.Value, parent reflect.Value) {
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
v = v.Elem()
@@ -90,41 +111,3 @@ func removeNilValues(v reflect.Value, parent reflect.Value) {
}
}
}
-
-func ObjectMetaEqualDeepDerivative(object runtime.Object, expected runtime.Object) (res bool) {
- defer func() {
- if r := recover(); r != nil {
- res = false
- }
- }()
-
- if expected == nil {
- return true
- } else if object == nil {
- return false
- }
-
- objectMeta := reflect.ValueOf(object).Elem().FieldByName("ObjectMeta").Interface()
- expectedMeta := reflect.ValueOf(expected).Elem().FieldByName("ObjectMeta").Interface()
-
- return equality.Semantic.DeepDerivative(expectedMeta, objectMeta)
-}
-
-func SpecEqualDeepDerivative(object runtime.Object, expected runtime.Object) (res bool) {
- defer func() {
- if r := recover(); r != nil {
- res = false
- }
- }()
-
- if expected == nil {
- return true
- } else if object == nil {
- return false
- }
-
- objectSpec := reflect.ValueOf(object).Elem().FieldByName("Spec").Interface()
- expectedSpec := reflect.ValueOf(expected).Elem().FieldByName("Spec").Interface()
-
- return equality.Semantic.DeepDerivative(expectedSpec, objectSpec)
-}