...

Source file src/k8s.io/kubectl/pkg/cmd/patch/patch_test.go

Documentation: k8s.io/kubectl/pkg/cmd/patch

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  				// ensure patched object reflects successful
    49  				// patch edits from the client
    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  	// uses the name from the response
    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  	// uses the name from the response
   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  	// Patched
   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  	// make sure the value returned by the server is used
   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  				// ensure patched object reflects successful
   214  				// patch edits from the client
   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  	// check the status.phase value is updated in the response
   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