1/* 2Copyright 2015 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package patch 18 19import ( 20 "net/http" 21 "strings" 22 "testing" 23 24 "k8s.io/cli-runtime/pkg/genericclioptions" 25 "k8s.io/cli-runtime/pkg/resource" 26 "k8s.io/client-go/rest/fake" 27 cmdtesting "k8s.io/kubectl/pkg/cmd/testing" 28 "k8s.io/kubectl/pkg/scheme" 29) 30 31func TestPatchObject(t *testing.T) { 32 _, svc, _ := cmdtesting.TestData() 33 34 tf := cmdtesting.NewTestFactory().WithNamespace("test") 35 defer tf.Cleanup() 36 37 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 38 39 tf.UnstructuredClient = &fake.RESTClient{ 40 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 41 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 42 switch p, m := req.URL.Path, req.Method; { 43 case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): 44 obj := svc.Items[0] 45 46 // ensure patched object reflects successful 47 // patch edits from the client 48 if m == "PATCH" { 49 obj.Spec.Type = "NodePort" 50 } 51 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &obj)}, nil 52 default: 53 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) 54 return nil, nil 55 } 56 }), 57 } 58 stream, _, buf, _ := genericclioptions.NewTestIOStreams() 59 60 cmd := NewCmdPatch(tf, stream) 61 cmd.Flags().Set("namespace", "test") 62 cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`) 63 cmd.Flags().Set("output", "name") 64 cmd.Run(cmd, []string{"services/frontend"}) 65 66 // uses the name from the response 67 if buf.String() != "service/baz\n" { 68 t.Errorf("unexpected output: %s", buf.String()) 69 } 70} 71 72func TestPatchObjectFromFile(t *testing.T) { 73 _, svc, _ := cmdtesting.TestData() 74 75 tf := cmdtesting.NewTestFactory().WithNamespace("test") 76 defer tf.Cleanup() 77 78 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 79 80 tf.UnstructuredClient = &fake.RESTClient{ 81 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 82 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 83 switch p, m := req.URL.Path, req.Method; { 84 case p == "/namespaces/test/services/frontend" && (m == "PATCH" || m == "GET"): 85 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil 86 default: 87 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) 88 return nil, nil 89 } 90 }), 91 } 92 stream, _, buf, _ := genericclioptions.NewTestIOStreams() 93 94 cmd := NewCmdPatch(tf, stream) 95 cmd.Flags().Set("namespace", "test") 96 cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`) 97 cmd.Flags().Set("output", "name") 98 cmd.Flags().Set("filename", "../../../testdata/frontend-service.yaml") 99 cmd.Run(cmd, []string{}) 100 101 // uses the name from the response 102 if buf.String() != "service/baz\n" { 103 t.Errorf("unexpected output: %s", buf.String()) 104 } 105} 106 107func TestPatchNoop(t *testing.T) { 108 _, svc, _ := cmdtesting.TestData() 109 getObject := &svc.Items[0] 110 patchObject := &svc.Items[0] 111 112 tf := cmdtesting.NewTestFactory().WithNamespace("test") 113 defer tf.Cleanup() 114 115 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 116 117 tf.UnstructuredClient = &fake.RESTClient{ 118 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 119 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 120 switch p, m := req.URL.Path, req.Method; { 121 case p == "/namespaces/test/services/frontend" && m == "PATCH": 122 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, patchObject)}, nil 123 case p == "/namespaces/test/services/frontend" && m == "GET": 124 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, getObject)}, nil 125 default: 126 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) 127 return nil, nil 128 } 129 }), 130 } 131 132 // Patched 133 { 134 patchObject = patchObject.DeepCopy() 135 if patchObject.Annotations == nil { 136 patchObject.Annotations = map[string]string{} 137 } 138 patchObject.Annotations["foo"] = "bar" 139 stream, _, buf, _ := genericclioptions.NewTestIOStreams() 140 cmd := NewCmdPatch(tf, stream) 141 cmd.Flags().Set("namespace", "test") 142 cmd.Flags().Set("patch", `{"metadata":{"annotations":{"foo":"bar"}}}`) 143 cmd.Run(cmd, []string{"services", "frontend"}) 144 if buf.String() != "service/baz patched\n" { 145 t.Errorf("unexpected output: %s", buf.String()) 146 } 147 } 148} 149 150func TestPatchObjectFromFileOutput(t *testing.T) { 151 _, svc, _ := cmdtesting.TestData() 152 153 svcCopy := svc.Items[0].DeepCopy() 154 if svcCopy.Labels == nil { 155 svcCopy.Labels = map[string]string{} 156 } 157 svcCopy.Labels["post-patch"] = "post-patch-value" 158 159 tf := cmdtesting.NewTestFactory().WithNamespace("test") 160 defer tf.Cleanup() 161 162 codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 163 164 tf.UnstructuredClient = &fake.RESTClient{ 165 NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer, 166 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 167 switch p, m := req.URL.Path, req.Method; { 168 case p == "/namespaces/test/services/frontend" && m == "GET": 169 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &svc.Items[0])}, nil 170 case p == "/namespaces/test/services/frontend" && m == "PATCH": 171 return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, svcCopy)}, nil 172 default: 173 t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) 174 return nil, nil 175 } 176 }), 177 } 178 stream, _, buf, _ := genericclioptions.NewTestIOStreams() 179 180 cmd := NewCmdPatch(tf, stream) 181 cmd.Flags().Set("namespace", "test") 182 cmd.Flags().Set("patch", `{"spec":{"type":"NodePort"}}`) 183 cmd.Flags().Set("output", "yaml") 184 cmd.Flags().Set("filename", "../../../testdata/frontend-service.yaml") 185 cmd.Run(cmd, []string{}) 186 187 t.Log(buf.String()) 188 // make sure the value returned by the server is used 189 if !strings.Contains(buf.String(), "post-patch: post-patch-value") { 190 t.Errorf("unexpected output: %s", buf.String()) 191 } 192} 193