1
16
17 package patch
18
19 import (
20 "net/http"
21 "strings"
22 "testing"
23
24 jsonpath "github.com/exponent-io/jsonpath"
25 corev1 "k8s.io/api/core/v1"
26 "k8s.io/cli-runtime/pkg/genericiooptions"
27 "k8s.io/cli-runtime/pkg/resource"
28 "k8s.io/client-go/rest/fake"
29 cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
30 "k8s.io/kubectl/pkg/scheme"
31 )
32
33 func TestPatchObject(t *testing.T) {
34 _, svc, _ := cmdtesting.TestData()
35
36 tf := cmdtesting.NewTestFactory().WithNamespace("test")
37 defer tf.Cleanup()
38
39 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
40
41 tf.UnstructuredClient = &fake.RESTClient{
42 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
43 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
44 switch p, m := req.URL.Path, req.Method; {
45 case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
46 obj := svc.Items[0]
47
48
49
50 if m == "PATCH" {
51 obj.Spec.Type = "NodePort"
52 }
53 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &obj)}, nil
54 default:
55 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
56 return nil, nil
57 }
58 }),
59 }
60 stream, _, buf, _ := genericiooptions.NewTestIOStreams()
61
62 cmd := NewCmdPatch(tf, stream)
63 cmd.Flags().Set("namespace", "test")
64 cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`)
65 cmd.Flags().Set("output", "name")
66 cmd.Run(cmd, []string{"services/frontend"})
67
68
69 if buf.String() != "service/baz\n" {
70 t.Errorf("unexpected output: %s", buf.String())
71 }
72 }
73
74 func TestPatchObjectFromFile(t *testing.T) {
75 _, svc, _ := cmdtesting.TestData()
76
77 tf := cmdtesting.NewTestFactory().WithNamespace("test")
78 defer tf.Cleanup()
79
80 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
81
82 tf.UnstructuredClient = &fake.RESTClient{
83 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
84 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
85 switch p, m := req.URL.Path, req.Method; {
86 case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"):
87 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
88 default:
89 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
90 return nil, nil
91 }
92 }),
93 }
94 stream, _, buf, _ := genericiooptions.NewTestIOStreams()
95
96 cmd := NewCmdPatch(tf, stream)
97 cmd.Flags().Set("namespace", "test")
98 cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`)
99 cmd.Flags().Set("output", "name")
100 cmd.Flags().Set("filename", "../../../testdata/frontend-service.yaml")
101 cmd.Run(cmd, []string{})
102
103
104 if buf.String() != "service/baz\n" {
105 t.Errorf("unexpected output: %s", buf.String())
106 }
107 }
108
109 func TestPatchNoop(t *testing.T) {
110 _, svc, _ := cmdtesting.TestData()
111 getObject := &svc.Items[0]
112 patchObject := &svc.Items[0]
113
114 tf := cmdtesting.NewTestFactory().WithNamespace("test")
115 defer tf.Cleanup()
116
117 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
118
119 tf.UnstructuredClient = &fake.RESTClient{
120 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
121 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
122 switch p, m := req.URL.Path, req.Method; {
123 case p == "/namespaces/test/services/frontend" && m == "PATCH":
124 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, patchObject)}, nil
125 case p == "/namespaces/test/services/frontend" && m == "GET":
126 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, getObject)}, nil
127 default:
128 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
129 return nil, nil
130 }
131 }),
132 }
133
134
135 {
136 patchObject = patchObject.DeepCopy()
137 if patchObject.Annotations == nil {
138 patchObject.Annotations = map[string]string{}
139 }
140 patchObject.Annotations["foo"] = "bar"
141 stream, _, buf, _ := genericiooptions.NewTestIOStreams()
142 cmd := NewCmdPatch(tf, stream)
143 cmd.Flags().Set("namespace", "test")
144 cmd.Flags().Set("patch", `{"metadata":{"annotations":{"foo":"bar"}}}`)
145 cmd.Run(cmd, []string{"services", "frontend"})
146 if buf.String() != "service/baz patched\n" {
147 t.Errorf("unexpected output: %s", buf.String())
148 }
149 }
150 }
151
152 func TestPatchObjectFromFileOutput(t *testing.T) {
153 _, svc, _ := cmdtesting.TestData()
154
155 svcCopy := svc.Items[0].DeepCopy()
156 if svcCopy.Labels == nil {
157 svcCopy.Labels = map[string]string{}
158 }
159 svcCopy.Labels["post-patch"] = "post-patch-value"
160
161 tf := cmdtesting.NewTestFactory().WithNamespace("test")
162 defer tf.Cleanup()
163
164 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
165
166 tf.UnstructuredClient = &fake.RESTClient{
167 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
168 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
169 switch p, m := req.URL.Path, req.Method; {
170 case p == "/namespaces/test/services/frontend" && m == "GET":
171 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil
172 case p == "/namespaces/test/services/frontend" && m == "PATCH":
173 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svcCopy)}, nil
174 default:
175 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
176 return nil, nil
177 }
178 }),
179 }
180 stream, _, buf, _ := genericiooptions.NewTestIOStreams()
181
182 cmd := NewCmdPatch(tf, stream)
183 cmd.Flags().Set("namespace", "test")
184 cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`)
185 cmd.Flags().Set("output", "yaml")
186 cmd.Flags().Set("filename", "../../../testdata/frontend-service.yaml")
187 cmd.Run(cmd, []string{})
188
189 t.Log(buf.String())
190
191 if !strings.Contains(buf.String(), "post-patch: post-patch-value") {
192 t.Errorf("unexpected output: %s", buf.String())
193 }
194 }
195
196 func TestPatchSubresource(t *testing.T) {
197 pod := cmdtesting.SubresourceTestData()
198
199 tf := cmdtesting.NewTestFactory().WithNamespace("test")
200 defer tf.Cleanup()
201
202 expectedStatus := corev1.PodRunning
203
204 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
205
206 tf.UnstructuredClient = &fake.RESTClient{
207 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
208 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
209 switch p, m := req.URL.Path, req.Method; {
210 case p == "/namespaces/test/pods/foo/status" && (m == "PATCH" || m == "GET"):
211 obj := pod
212
213
214
215 if m == "PATCH" {
216 obj.Status.Phase = expectedStatus
217 }
218 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, obj)}, nil
219 default:
220 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
221 return nil, nil
222 }
223 }),
224 }
225 stream, _, buf, _ := genericiooptions.NewTestIOStreams()
226
227 cmd := NewCmdPatch(tf, stream)
228 cmd.Flags().Set("namespace", "test")
229 cmd.Flags().Set("patch", `{"status":{"phase":"Running"}}`)
230 cmd.Flags().Set("output", "json")
231 cmd.Flags().Set("subresource", "status")
232 cmd.Run(cmd, []string{"pod/foo"})
233
234 decoder := jsonpath.NewDecoder(buf)
235 var actualStatus corev1.PodPhase
236 decoder.SeekTo("status", "phase")
237 decoder.Decode(&actualStatus)
238
239 if actualStatus != expectedStatus {
240 t.Errorf("unexpected pod status to be set to %s got: %s", expectedStatus, actualStatus)
241 }
242 }
243
View as plain text