Skip to content

Commit 82c9e5c

Browse files
authored
Merge pull request kubernetes#87217 from prameshj/patch-helper
Add PatchService method in service/helper.
2 parents 6a156c9 + f370b78 commit 82c9e5c

File tree

4 files changed

+120
-0
lines changed

4 files changed

+120
-0
lines changed

staging/src/k8s.io/cloud-provider/go.sum

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

staging/src/k8s.io/cloud-provider/service/helpers/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ go_library(
88
visibility = ["//visibility:public"],
99
deps = [
1010
"//staging/src/k8s.io/api/core/v1:go_default_library",
11+
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
12+
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
13+
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
1114
"//vendor/k8s.io/utils/net:go_default_library",
1215
],
1316
)
@@ -33,6 +36,7 @@ go_test(
3336
deps = [
3437
"//staging/src/k8s.io/api/core/v1:go_default_library",
3538
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
39+
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
3640
"//vendor/k8s.io/utils/net:go_default_library",
3741
],
3842
)

staging/src/k8s.io/cloud-provider/service/helpers/helper.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ limitations under the License.
1717
package helpers
1818

1919
import (
20+
"encoding/json"
2021
"fmt"
2122
"strings"
2223

2324
v1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/types"
26+
"k8s.io/apimachinery/pkg/util/strategicpatch"
27+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
2428
utilnet "k8s.io/utils/net"
2529
)
2630

@@ -121,6 +125,40 @@ func LoadBalancerStatusEqual(l, r *v1.LoadBalancerStatus) bool {
121125
return ingressSliceEqual(l.Ingress, r.Ingress)
122126
}
123127

128+
// PatchService patches the given service's Status or ObjectMeta based on the original and
129+
// updated ones. Change to spec will be ignored.
130+
func PatchService(c corev1.CoreV1Interface, oldSvc, newSvc *v1.Service) (*v1.Service, error) {
131+
// Reset spec to make sure only patch for Status or ObjectMeta.
132+
newSvc.Spec = oldSvc.Spec
133+
134+
patchBytes, err := getPatchBytes(oldSvc, newSvc)
135+
if err != nil {
136+
return nil, err
137+
}
138+
139+
return c.Services(oldSvc.Namespace).Patch(oldSvc.Name, types.StrategicMergePatchType, patchBytes, "status")
140+
141+
}
142+
143+
func getPatchBytes(oldSvc, newSvc *v1.Service) ([]byte, error) {
144+
oldData, err := json.Marshal(oldSvc)
145+
if err != nil {
146+
return nil, fmt.Errorf("failed to Marshal oldData for svc %s/%s: %v", oldSvc.Namespace, oldSvc.Name, err)
147+
}
148+
149+
newData, err := json.Marshal(newSvc)
150+
if err != nil {
151+
return nil, fmt.Errorf("failed to Marshal newData for svc %s/%s: %v", newSvc.Namespace, newSvc.Name, err)
152+
}
153+
154+
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Service{})
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to CreateTwoWayMergePatch for svc %s/%s: %v", oldSvc.Namespace, oldSvc.Name, err)
157+
}
158+
return patchBytes, nil
159+
160+
}
161+
124162
func ingressSliceEqual(lhs, rhs []v1.LoadBalancerIngress) bool {
125163
if len(lhs) != len(rhs) {
126164
return false

staging/src/k8s.io/cloud-provider/service/helpers/helper_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,13 @@ limitations under the License.
1717
package helpers
1818

1919
import (
20+
"reflect"
2021
"strings"
2122
"testing"
2223

2324
v1 "k8s.io/api/core/v1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/client-go/kubernetes/fake"
2527
utilnet "k8s.io/utils/net"
2628
)
2729

@@ -269,3 +271,78 @@ func TestHasLBFinalizer(t *testing.T) {
269271
})
270272
}
271273
}
274+
275+
func TestPatchService(t *testing.T) {
276+
svcOrigin := &v1.Service{
277+
ObjectMeta: metav1.ObjectMeta{
278+
Name: "test-patch",
279+
Annotations: map[string]string{},
280+
},
281+
Spec: v1.ServiceSpec{
282+
ClusterIP: "10.0.0.1",
283+
},
284+
}
285+
fakeCs := fake.NewSimpleClientset(svcOrigin)
286+
287+
// Issue a separate update and verify patch doesn't fail after this.
288+
svcToUpdate := svcOrigin.DeepCopy()
289+
addAnnotations(svcToUpdate)
290+
if _, err := fakeCs.CoreV1().Services(svcOrigin.Namespace).Update(svcToUpdate); err != nil {
291+
t.Fatalf("Failed to update service: %v", err)
292+
}
293+
294+
// Attempt to patch based the original service.
295+
svcToPatch := svcOrigin.DeepCopy()
296+
svcToPatch.Finalizers = []string{"foo"}
297+
svcToPatch.Spec.ClusterIP = "10.0.0.2"
298+
svcToPatch.Status = v1.ServiceStatus{
299+
LoadBalancer: v1.LoadBalancerStatus{
300+
Ingress: []v1.LoadBalancerIngress{
301+
{IP: "8.8.8.8"},
302+
},
303+
},
304+
}
305+
svcPatched, err := PatchService(fakeCs.CoreV1(), svcOrigin, svcToPatch)
306+
if err != nil {
307+
t.Fatalf("Failed to patch service: %v", err)
308+
}
309+
310+
// Service returned by patch will contain latest content (e.g from
311+
// the separate update).
312+
addAnnotations(svcToPatch)
313+
if !reflect.DeepEqual(svcPatched, svcToPatch) {
314+
t.Errorf("PatchStatus() = %+v, want %+v", svcPatched, svcToPatch)
315+
}
316+
// Explicitly validate if spec is unchanged from origin.
317+
if !reflect.DeepEqual(svcPatched.Spec, svcOrigin.Spec) {
318+
t.Errorf("Got spec = %+v, want %+v", svcPatched.Spec, svcOrigin.Spec)
319+
}
320+
}
321+
322+
func Test_getPatchBytes(t *testing.T) {
323+
origin := &v1.Service{
324+
ObjectMeta: metav1.ObjectMeta{
325+
Name: "test-patch-bytes",
326+
Finalizers: []string{"foo"},
327+
},
328+
}
329+
updated := &v1.Service{
330+
ObjectMeta: metav1.ObjectMeta{
331+
Name: "test-patch-bytes",
332+
Finalizers: []string{"foo", "bar"},
333+
},
334+
}
335+
336+
b, err := getPatchBytes(origin, updated)
337+
if err != nil {
338+
t.Fatal(err)
339+
}
340+
expected := `{"metadata":{"$setElementOrder/finalizers":["foo","bar"],"finalizers":["bar"]}}`
341+
if string(b) != expected {
342+
t.Errorf("getPatchBytes(%+v, %+v) = %s ; want %s", origin, updated, string(b), expected)
343+
}
344+
}
345+
346+
func addAnnotations(svc *v1.Service) {
347+
svc.Annotations["foo"] = "bar"
348+
}

0 commit comments

Comments
 (0)