Skip to content

Commit 2e29183

Browse files
authored
Merge pull request #326 from Liujingfang1/master
Handle APIService speically when ServerSide apply is enabled
2 parents 47b04c2 + ec07a4f commit 2e29183

File tree

5 files changed

+128
-5
lines changed

5 files changed

+128
-5
lines changed

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -703,10 +703,6 @@ sigs.k8s.io/controller-runtime v0.6.0 h1:Fzna3DY7c4BIP6KwfSlrfnj20DJ+SeMBK8HSFvO
703703
sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo=
704704
sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0=
705705
sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU=
706-
sigs.k8s.io/kustomize/kyaml v0.10.6 h1:xUJxc/k8JoWqHUahaB8DTqY0KwEPxTbTGStvW8TOcDc=
707-
sigs.k8s.io/kustomize/kyaml v0.10.6/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k=
708-
sigs.k8s.io/kustomize/kyaml v0.10.7 h1:r0r8UEL0bL7X56HKUmhJZ+TP+nvRNGrDHHSLO7izlcQ=
709-
sigs.k8s.io/kustomize/kyaml v0.10.7/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k=
710706
sigs.k8s.io/kustomize/kyaml v0.10.9 h1:n3WNdvPPReRNDxW+XXd2JlyZ8EII721I21D1DBpBVBE=
711707
sigs.k8s.io/kustomize/kyaml v0.10.9/go.mod h1:K9yg1k/HB/6xNOf5VH3LhTo1DK9/5ykSZO5uIv+Y/1k=
712708
sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw=

pkg/apply/task/apply_task.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package task
66
import (
77
"context"
88
"io/ioutil"
9+
"strings"
910

1011
apierrors "k8s.io/apimachinery/pkg/api/errors"
1112
"k8s.io/apimachinery/pkg/api/meta"
@@ -184,6 +185,12 @@ func (a *ApplyTask) Start(taskContext *taskrunner.TaskContext) {
184185
ao.SetObjects([]*resource.Info{info})
185186
klog.V(5).Infof("applying %s/%s...", info.Namespace, info.Name)
186187
err = ao.Run()
188+
if err != nil && a.ServerSideOptions.ServerSideApply && isAPIService(obj) && isStreamError(err) {
189+
// Server-side Apply doesn't work with APIService before k8s 1.21
190+
// https://github.com/kubernetes/kubernetes/issues/89264
191+
// Thus APIService is handled specially using client-side apply.
192+
err = clientSideApply(info, taskContext.EventChannel(), a.DryRunStrategy, a.Factory)
193+
}
187194
if err != nil {
188195
if klog.V(4) {
189196
klog.Errorf("error applying (%s/%s) %s", info.Namespace, info.Name, err)
@@ -388,3 +395,23 @@ func sendBatchApplyEvents(taskContext *taskrunner.TaskContext, objects []*unstru
388395
taskContext.CaptureResourceFailure(id)
389396
}
390397
}
398+
399+
func isAPIService(obj *unstructured.Unstructured) bool {
400+
gk := obj.GroupVersionKind().GroupKind()
401+
return gk.Group == "apiregistration.k8s.io" && gk.Kind == "APIService"
402+
}
403+
404+
// isStreamError checks if the error is a StreamError. Since kubectl wraps the actual StreamError,
405+
// we can't check the error type.
406+
func isStreamError(err error) bool {
407+
return strings.Contains(err.Error(), "stream error: stream ID ")
408+
}
409+
410+
func clientSideApply(info *resource.Info, eventChannel chan event.Event, strategy common.DryRunStrategy, factory util.Factory) error {
411+
ao, _, err := applyOptionsFactoryFunc(eventChannel, common.ServerSideOptions{ServerSideApply: false}, strategy, factory)
412+
if err != nil {
413+
return err
414+
}
415+
ao.SetObjects([]*resource.Info{info})
416+
return ao.Run()
417+
}

test/e2e/common_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,30 @@ func deploymentManifest(namespace string) *unstructured.Unstructured {
128128
}
129129
}
130130

131+
func apiserviceManifest() *unstructured.Unstructured {
132+
apiservice := &unstructured.Unstructured{
133+
Object: map[string]interface{}{
134+
"apiVersion": "apiregistration.k8s.io/v1",
135+
"kind": "APIService",
136+
"metadata": map[string]interface{}{
137+
"name": "v1beta1.custom.metrics.k8s.io",
138+
},
139+
"spec": map[string]interface{}{
140+
"insecureSkipTLSVerify": true,
141+
"group": "custom.metrics.k8s.io",
142+
"groupPriorityMinimum": 100,
143+
"versionPriority": 100,
144+
"service": map[string]interface{}{
145+
"name": "custom-metrics-stackdriver-adapter",
146+
"namespace": "custome-metrics",
147+
},
148+
"version": "v1beta1",
149+
},
150+
},
151+
}
152+
return apiservice
153+
}
154+
131155
func manifestToUnstructured(manifest []byte) *unstructured.Unstructured {
132156
u := make(map[string]interface{})
133157
err := yaml.Unmarshal(manifest, &u)

test/e2e/e2e_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"k8s.io/kubectl/pkg/cmd/util"
1919
"k8s.io/kubectl/pkg/scheme"
2020
"sigs.k8s.io/cli-utils/pkg/apply"
21+
"sigs.k8s.io/cli-utils/pkg/common"
2122
"sigs.k8s.io/cli-utils/pkg/inventory"
2223
"sigs.k8s.io/cli-utils/pkg/provider"
2324
"sigs.k8s.io/cli-utils/pkg/util/factory"
@@ -109,6 +110,10 @@ var _ = Describe("Applier", func() {
109110
It("Apply continues on error", func() {
110111
continueOnErrorTest(c, invConfig, inventoryName, namespace.GetName())
111112
})
113+
114+
It("Server-Side Apply", func() {
115+
serversideApplyTest(c, invConfig, inventoryName, namespace.GetName())
116+
})
112117
})
113118

114119
Context("Inventory policy", func() {
@@ -205,7 +210,7 @@ func defaultInvSizeVerifyFunc(c client.Client, name, namespace string, count int
205210

206211
func defaultInvCountVerifyFunc(c client.Client, namespace string, count int) {
207212
var cmList v1.ConfigMapList
208-
err := c.List(context.TODO(), &cmList, client.InNamespace(namespace))
213+
err := c.List(context.TODO(), &cmList, client.InNamespace(namespace), client.HasLabels{common.InventoryLabel})
209214
Expect(err).NotTo(HaveOccurred())
210215
Expect(len(cmList.Items)).To(Equal(count))
211216
}

test/e2e/serverside_apply_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2020 The Kubernetes Authors.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package e2e
5+
6+
import (
7+
"context"
8+
"time"
9+
10+
. "github.com/onsi/ginkgo"
11+
. "github.com/onsi/gomega"
12+
appsv1 "k8s.io/api/apps/v1"
13+
v1 "k8s.io/api/core/v1"
14+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15+
"k8s.io/apimachinery/pkg/runtime/schema"
16+
"k8s.io/apimachinery/pkg/types"
17+
"sigs.k8s.io/cli-utils/pkg/apply"
18+
"sigs.k8s.io/cli-utils/pkg/common"
19+
"sigs.k8s.io/controller-runtime/pkg/client"
20+
)
21+
22+
func serversideApplyTest(c client.Client, invConfig InventoryConfig, inventoryName, namespaceName string) {
23+
By("Apply a Deployment and an APIService by server-side apply")
24+
applier := invConfig.ApplierFactoryFunc()
25+
26+
inv := invConfig.InvWrapperFunc(invConfig.InventoryFactoryFunc(inventoryName, namespaceName, "test"))
27+
firstResources := []*unstructured.Unstructured{
28+
deploymentManifest(namespaceName),
29+
apiserviceManifest(),
30+
}
31+
32+
runWithNoErr(applier.Run(context.TODO(), inv, firstResources, apply.Options{
33+
ReconcileTimeout: 2 * time.Minute,
34+
EmitStatusEvents: true,
35+
ServerSideOptions: common.ServerSideOptions{
36+
ServerSideApply: true,
37+
ForceConflicts: true,
38+
FieldManager: "test",
39+
},
40+
}))
41+
42+
By("Verify deployment is server-side applied")
43+
var d appsv1.Deployment
44+
err := c.Get(context.TODO(), types.NamespacedName{
45+
Namespace: namespaceName,
46+
Name: deploymentManifest(namespaceName).GetName(),
47+
}, &d)
48+
Expect(err).NotTo(HaveOccurred())
49+
_, found := d.ObjectMeta.Annotations[v1.LastAppliedConfigAnnotation]
50+
Expect(found).To(Equal(false))
51+
fields := d.GetManagedFields()
52+
Expect(fields[0].Manager).To(Equal("test"))
53+
54+
By("Verify APIService is client-side applied")
55+
var apiService = &unstructured.Unstructured{}
56+
apiService.SetGroupVersionKind(
57+
schema.GroupVersionKind{
58+
Group: "apiregistration.k8s.io",
59+
Version: "v1",
60+
Kind: "APIService",
61+
},
62+
)
63+
err = c.Get(context.TODO(), types.NamespacedName{
64+
Name: "v1beta1.custom.metrics.k8s.io",
65+
}, apiService)
66+
Expect(err).NotTo(HaveOccurred())
67+
_, found2 := apiService.GetAnnotations()[v1.LastAppliedConfigAnnotation]
68+
Expect(found2).To(Equal(true))
69+
fields2 := apiService.GetManagedFields()
70+
Expect(len(fields2)).To(Equal(0))
71+
}

0 commit comments

Comments
 (0)