Merge pull request #128273 from benluddy/cbor-apply

KEP-4222: Support CBOR encoding for apply requests.

Kubernetes-commit: 16f9fdc7057e1f69ff1a44e3dbbcf7b994c3cd29
This commit is contained in:
Kubernetes Publisher
2024-10-30 17:25:25 +00:00
14 changed files with 96 additions and 61 deletions

View File

@@ -207,7 +207,7 @@ func TestGoldenRequest(t *testing.T) {
if err != nil {
t.Fatalf("failed to load fixture: %v", err)
}
if diff := cmp.Diff(got, want); diff != "" {
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("unexpected difference from expected bytes:\n%s", diff)
}
}))

View File

@@ -25,12 +25,12 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/features"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/apply"
"k8s.io/client-go/util/consistencydetector"
"k8s.io/client-go/util/watchlist"
"k8s.io/klog/v2"
@@ -340,10 +340,6 @@ func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *uns
if err := validateNamespaceWithOptionalName(c.namespace, name); err != nil {
return nil, err
}
outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil {
return nil, err
}
accessor, err := meta.Accessor(obj)
if err != nil {
return nil, err
@@ -355,21 +351,16 @@ func (c *dynamicResourceClient) Apply(ctx context.Context, name string, obj *uns
}
patchOpts := opts.ToPatchOptions()
result := c.client.client.
Patch(types.ApplyPatchType).
AbsPath(append(c.makeURLSegments(name), subresources...)...).
Body(outBytes).
SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1).
Do(ctx)
if err := result.Error(); err != nil {
return nil, err
}
retBytes, err := result.Raw()
request, err := apply.NewRequest(c.client.client, obj.Object)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
if err := runtime.DecodeInto(unstructured.UnstructuredJSONScheme, retBytes, &out); err != nil {
if err := request.
AbsPath(append(c.makeURLSegments(name), subresources...)...).
SpecificallyVersionedParams(&patchOpts, dynamicParameterCodec, versionV1).
Do(ctx).Into(&out); err != nil {
return nil, err
}
return &out, nil

View File

@@ -2,8 +2,8 @@ PATCH /apis/flops/v1alpha1/namespaces/mops/flips/mips/fin?force=true HTTP/1.1
Host: example.com
Accept: application/json
Accept-Encoding: gzip
Content-Length: 29
Content-Length: 28
Content-Type: application/apply-patch+yaml
User-Agent: TestGoldenRequest
{"metadata":{"name":"mips"}}
{"metadata":{"name":"mips"}}

View File

@@ -2,8 +2,8 @@ PATCH /apis/flops/v1alpha1/namespaces/mops/flips/mips/status?force=true HTTP/1.1
Host: example.com
Accept: application/json
Accept-Encoding: gzip
Content-Length: 29
Content-Length: 28
Content-Type: application/apply-patch+yaml
User-Agent: TestGoldenRequest
{"metadata":{"name":"mips"}}
{"metadata":{"name":"mips"}}

View File

@@ -18,7 +18,6 @@ package gentype
import (
"context"
json "encoding/json"
"fmt"
"time"
@@ -27,6 +26,7 @@ import (
types "k8s.io/apimachinery/pkg/types"
watch "k8s.io/apimachinery/pkg/watch"
rest "k8s.io/client-go/rest"
"k8s.io/client-go/util/apply"
"k8s.io/client-go/util/consistencydetector"
"k8s.io/client-go/util/watchlist"
"k8s.io/klog/v2"
@@ -337,20 +337,21 @@ func (a *alsoApplier[T, C]) Apply(ctx context.Context, obj C, opts metav1.ApplyO
return *new(T), fmt.Errorf("object provided to Apply must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(obj)
if err != nil {
return *new(T), err
}
if obj.GetName() == nil {
return *new(T), fmt.Errorf("obj.Name must be provided to Apply")
}
err = a.client.client.Patch(types.ApplyPatchType).
request, err := apply.NewRequest(a.client.client, obj)
if err != nil {
return *new(T), err
}
err = request.
UseProtobufAsDefaultIfPreferred(a.client.prefersProtobuf).
NamespaceIfScoped(a.client.namespace, a.client.namespace != "").
Resource(a.client.resource).
Name(*obj.GetName()).
VersionedParams(&patchOpts, a.client.parameterCodec).
Body(data).
Do(ctx).
Into(result)
return result, err
@@ -362,24 +363,24 @@ func (a *alsoApplier[T, C]) ApplyStatus(ctx context.Context, obj C, opts metav1.
return *new(T), fmt.Errorf("object provided to Apply must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(obj)
if err != nil {
return *new(T), err
}
if obj.GetName() == nil {
return *new(T), fmt.Errorf("obj.Name must be provided to Apply")
}
request, err := apply.NewRequest(a.client.client, obj)
if err != nil {
return *new(T), err
}
result := a.client.newObject()
err = a.client.client.Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefaultIfPreferred(a.client.prefersProtobuf).
NamespaceIfScoped(a.client.namespace, a.client.namespace != "").
Resource(a.client.resource).
Name(*obj.GetName()).
SubResource("status").
VersionedParams(&patchOpts, a.client.parameterCodec).
Body(data).
Do(ctx).
Into(result)
return result, err

2
go.mod
View File

@@ -28,7 +28,7 @@ require (
google.golang.org/protobuf v1.34.2
gopkg.in/evanphx/json-patch.v4 v4.12.0
k8s.io/api v0.0.0-20241030034125-271c79cac830
k8s.io/apimachinery v0.0.0-20241025000453-124c262107b0
k8s.io/apimachinery v0.0.0-20241030172525-c56b2072eeef
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8

4
go.sum
View File

@@ -157,8 +157,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.0.0-20241030034125-271c79cac830 h1:zu5fMzQV/xp3VSln7QIaHNb2ghUQ3YJngYo1y52wpsc=
k8s.io/api v0.0.0-20241030034125-271c79cac830/go.mod h1:aywKYMtpaiAtWkkKBmWO/iXvZmEmVXmBwQwOuQqkP0c=
k8s.io/apimachinery v0.0.0-20241025000453-124c262107b0 h1:6dJVqURMs0HNPdaaIJ0UqpwB39zuTdaMxYMTNKXiAis=
k8s.io/apimachinery v0.0.0-20241025000453-124c262107b0/go.mod h1:y/FzDt/GaPgPceo5rJcCtD4qW5l8SwtbzESSMGEY6P8=
k8s.io/apimachinery v0.0.0-20241030172525-c56b2072eeef h1:rg539iNcCy2bp+skJ2t30U9/m5du8y8NUxOgtEJ3tFM=
k8s.io/apimachinery v0.0.0-20241030172525-c56b2072eeef/go.mod h1:y/FzDt/GaPgPceo5rJcCtD4qW5l8SwtbzESSMGEY6P8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240827152857-f7e401e7b4c2 h1:GKE9U8BH16uynoxQii0auTjmmmuZ3O0LFMN6S0lPPhI=

View File

@@ -20,7 +20,6 @@ package v1
import (
context "context"
json "encoding/json"
fmt "fmt"
appsv1 "k8s.io/api/apps/v1"
@@ -32,6 +31,7 @@ import (
applyconfigurationsautoscalingv1 "k8s.io/client-go/applyconfigurations/autoscaling/v1"
gentype "k8s.io/client-go/gentype"
scheme "k8s.io/client-go/kubernetes/scheme"
apply "k8s.io/client-go/util/apply"
)
// DeploymentsGetter has a method to return a DeploymentInterface.
@@ -120,20 +120,19 @@ func (c *deployments) ApplyScale(ctx context.Context, deploymentName string, sca
return nil, fmt.Errorf("scale provided to ApplyScale must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(scale)
request, err := apply.NewRequest(c.GetClient(), scale)
if err != nil {
return nil, err
}
result = &autoscalingv1.Scale{}
err = c.GetClient().Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("deployments").
Name(deploymentName).
SubResource("scale").
VersionedParams(&patchOpts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return

View File

@@ -20,7 +20,6 @@ package v1
import (
context "context"
json "encoding/json"
fmt "fmt"
appsv1 "k8s.io/api/apps/v1"
@@ -32,6 +31,7 @@ import (
applyconfigurationsautoscalingv1 "k8s.io/client-go/applyconfigurations/autoscaling/v1"
gentype "k8s.io/client-go/gentype"
scheme "k8s.io/client-go/kubernetes/scheme"
apply "k8s.io/client-go/util/apply"
)
// ReplicaSetsGetter has a method to return a ReplicaSetInterface.
@@ -120,20 +120,19 @@ func (c *replicaSets) ApplyScale(ctx context.Context, replicaSetName string, sca
return nil, fmt.Errorf("scale provided to ApplyScale must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(scale)
request, err := apply.NewRequest(c.GetClient(), scale)
if err != nil {
return nil, err
}
result = &autoscalingv1.Scale{}
err = c.GetClient().Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("replicasets").
Name(replicaSetName).
SubResource("scale").
VersionedParams(&patchOpts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return

View File

@@ -20,7 +20,6 @@ package v1
import (
context "context"
json "encoding/json"
fmt "fmt"
appsv1 "k8s.io/api/apps/v1"
@@ -32,6 +31,7 @@ import (
applyconfigurationsautoscalingv1 "k8s.io/client-go/applyconfigurations/autoscaling/v1"
gentype "k8s.io/client-go/gentype"
scheme "k8s.io/client-go/kubernetes/scheme"
apply "k8s.io/client-go/util/apply"
)
// StatefulSetsGetter has a method to return a StatefulSetInterface.
@@ -120,20 +120,19 @@ func (c *statefulSets) ApplyScale(ctx context.Context, statefulSetName string, s
return nil, fmt.Errorf("scale provided to ApplyScale must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(scale)
request, err := apply.NewRequest(c.GetClient(), scale)
if err != nil {
return nil, err
}
result = &autoscalingv1.Scale{}
err = c.GetClient().Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("statefulsets").
Name(statefulSetName).
SubResource("scale").
VersionedParams(&patchOpts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return

View File

@@ -20,7 +20,6 @@ package v1beta2
import (
context "context"
json "encoding/json"
fmt "fmt"
appsv1beta2 "k8s.io/api/apps/v1beta2"
@@ -30,6 +29,7 @@ import (
applyconfigurationsappsv1beta2 "k8s.io/client-go/applyconfigurations/apps/v1beta2"
gentype "k8s.io/client-go/gentype"
scheme "k8s.io/client-go/kubernetes/scheme"
apply "k8s.io/client-go/util/apply"
)
// StatefulSetsGetter has a method to return a StatefulSetInterface.
@@ -118,20 +118,19 @@ func (c *statefulSets) ApplyScale(ctx context.Context, statefulSetName string, s
return nil, fmt.Errorf("scale provided to ApplyScale must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(scale)
request, err := apply.NewRequest(c.GetClient(), scale)
if err != nil {
return nil, err
}
result = &appsv1beta2.Scale{}
err = c.GetClient().Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("statefulsets").
Name(statefulSetName).
SubResource("scale").
VersionedParams(&patchOpts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return

View File

@@ -20,7 +20,6 @@ package v1beta1
import (
context "context"
json "encoding/json"
fmt "fmt"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
@@ -30,6 +29,7 @@ import (
applyconfigurationsextensionsv1beta1 "k8s.io/client-go/applyconfigurations/extensions/v1beta1"
gentype "k8s.io/client-go/gentype"
scheme "k8s.io/client-go/kubernetes/scheme"
apply "k8s.io/client-go/util/apply"
)
// DeploymentsGetter has a method to return a DeploymentInterface.
@@ -118,20 +118,19 @@ func (c *deployments) ApplyScale(ctx context.Context, deploymentName string, sca
return nil, fmt.Errorf("scale provided to ApplyScale must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(scale)
request, err := apply.NewRequest(c.GetClient(), scale)
if err != nil {
return nil, err
}
result = &extensionsv1beta1.Scale{}
err = c.GetClient().Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("deployments").
Name(deploymentName).
SubResource("scale").
VersionedParams(&patchOpts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return

View File

@@ -20,7 +20,6 @@ package v1beta1
import (
context "context"
json "encoding/json"
fmt "fmt"
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
@@ -30,6 +29,7 @@ import (
applyconfigurationsextensionsv1beta1 "k8s.io/client-go/applyconfigurations/extensions/v1beta1"
gentype "k8s.io/client-go/gentype"
scheme "k8s.io/client-go/kubernetes/scheme"
apply "k8s.io/client-go/util/apply"
)
// ReplicaSetsGetter has a method to return a ReplicaSetInterface.
@@ -118,20 +118,19 @@ func (c *replicaSets) ApplyScale(ctx context.Context, replicaSetName string, sca
return nil, fmt.Errorf("scale provided to ApplyScale must not be nil")
}
patchOpts := opts.ToPatchOptions()
data, err := json.Marshal(scale)
request, err := apply.NewRequest(c.GetClient(), scale)
if err != nil {
return nil, err
}
result = &extensionsv1beta1.Scale{}
err = c.GetClient().Patch(types.ApplyPatchType).
err = request.
UseProtobufAsDefault().
Namespace(c.GetNamespace()).
Resource("replicasets").
Name(replicaSetName).
SubResource("scale").
VersionedParams(&patchOpts, scheme.ParameterCodec).
Body(data).
Do(ctx).
Into(result)
return

49
util/apply/apply.go Normal file
View File

@@ -0,0 +1,49 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apply
import (
"fmt"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/client-go/features"
"k8s.io/client-go/rest"
)
// NewRequest builds a new server-side apply request. The provided apply configuration object will
// be marshalled to the request's body using the default encoding, and the Content-Type header will
// be set to application/apply-patch with the appropriate structured syntax name suffix (today,
// either +yaml or +cbor, see
// https://www.iana.org/assignments/media-type-structured-suffix/media-type-structured-suffix.xhtml).
func NewRequest(client rest.Interface, applyConfiguration interface{}) (*rest.Request, error) {
pt := types.ApplyYAMLPatchType
marshal := json.Marshal
if features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientAllowsCBOR) && features.TestOnlyFeatureGates.Enabled(features.TestOnlyClientPrefersCBOR) {
pt = types.ApplyCBORPatchType
marshal = cbor.Marshal
}
body, err := marshal(applyConfiguration)
if err != nil {
return nil, fmt.Errorf("failed to marshal apply configuration: %w", err)
}
return client.Patch(pt).Body(body), nil
}