...

Source file src/k8s.io/kubectl/pkg/cmd/certificates/certificates_test.go

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

     1  /*
     2  Copyright 2020 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 certificates
    18  
    19  import (
    20  	"bytes"
    21  	"io"
    22  	"net/http"
    23  	"reflect"
    24  	"strings"
    25  	"testing"
    26  
    27  	"github.com/spf13/cobra"
    28  
    29  	certificatesv1 "k8s.io/api/certificates/v1"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/cli-runtime/pkg/genericiooptions"
    33  	"k8s.io/cli-runtime/pkg/resource"
    34  	"k8s.io/client-go/rest/fake"
    35  	cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
    36  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    37  	"k8s.io/kubectl/pkg/scheme"
    38  )
    39  
    40  func TestCertificates(t *testing.T) {
    41  	testcases := []struct {
    42  		name            string
    43  		nov1            bool
    44  		nov1beta1       bool
    45  		command         string
    46  		force           bool
    47  		args            []string
    48  		expectFailure   bool
    49  		expectActions   []string
    50  		expectOutput    string
    51  		expectErrOutput string
    52  	}{
    53  		{
    54  			name:    "approve existing",
    55  			command: "approve",
    56  			args:    []string{"existing"},
    57  			expectActions: []string{
    58  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get
    59  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // typed get
    60  				`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/existing/approval`,
    61  			},
    62  			expectOutput: `approved`,
    63  		},
    64  		{
    65  			name:      "approve existing, no v1",
    66  			nov1:      true,
    67  			nov1beta1: true,
    68  			command:   "approve",
    69  			args:      []string{"existing"},
    70  			expectActions: []string{
    71  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get, 404
    72  			},
    73  			expectFailure:   true,
    74  			expectErrOutput: `could not find the requested resource`,
    75  		},
    76  		{
    77  			name:    "approve already approved",
    78  			command: "approve",
    79  			args:    []string{"approved"},
    80  			expectActions: []string{
    81  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // unstructured get
    82  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // typed get
    83  			},
    84  			expectOutput: `approved`,
    85  		},
    86  		{
    87  			name:    "approve already approved, force",
    88  			command: "approve",
    89  			args:    []string{"approved"},
    90  			force:   true,
    91  			expectActions: []string{
    92  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // unstructured get
    93  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // typed get
    94  				`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/approved/approval`,
    95  			},
    96  			expectOutput: `approved`,
    97  		},
    98  		{
    99  			name:    "approve already denied",
   100  			command: "approve",
   101  			args:    []string{"denied"},
   102  			expectActions: []string{
   103  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // unstructured get
   104  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // typed get
   105  			},
   106  			expectFailure:   true,
   107  			expectErrOutput: `is already Denied`,
   108  		},
   109  
   110  		{
   111  			name:    "deny existing",
   112  			command: "deny",
   113  			args:    []string{"existing"},
   114  			expectActions: []string{
   115  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get
   116  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // typed get
   117  				`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/existing/approval`,
   118  			},
   119  			expectOutput: `denied`,
   120  		},
   121  		{
   122  			name:      "deny existing, no v1",
   123  			nov1:      true,
   124  			nov1beta1: true,
   125  			command:   "deny",
   126  			args:      []string{"existing"},
   127  			expectActions: []string{
   128  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get, 404
   129  			},
   130  			expectFailure:   true,
   131  			expectErrOutput: `could not find the requested resource`,
   132  		},
   133  		{
   134  			name:    "deny already denied",
   135  			command: "deny",
   136  			args:    []string{"denied"},
   137  			expectActions: []string{
   138  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // unstructured get
   139  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // typed get
   140  			},
   141  			expectOutput: `denied`,
   142  		},
   143  		{
   144  			name:    "deny already denied, force",
   145  			command: "deny",
   146  			args:    []string{"denied"},
   147  			force:   true,
   148  			expectActions: []string{
   149  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // unstructured get
   150  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // typed get
   151  				`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/denied/approval`,
   152  			},
   153  			expectOutput: `denied`,
   154  		},
   155  		{
   156  			name:    "deny already approved",
   157  			command: "deny",
   158  			args:    []string{"approved"},
   159  			expectActions: []string{
   160  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // unstructured get
   161  				`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // typed get
   162  			},
   163  			expectFailure:   true,
   164  			expectErrOutput: `is already Approved`,
   165  		},
   166  	}
   167  
   168  	for _, tc := range testcases {
   169  		t.Run(tc.name, func(t *testing.T) {
   170  			tf := cmdtesting.NewTestFactory().WithNamespace("test")
   171  			defer tf.Cleanup()
   172  			codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
   173  
   174  			existingV1 := &certificatesv1.CertificateSigningRequest{
   175  				TypeMeta:   metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"},
   176  				ObjectMeta: metav1.ObjectMeta{Name: "existing"},
   177  			}
   178  
   179  			approvedV1 := &certificatesv1.CertificateSigningRequest{
   180  				TypeMeta:   metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"},
   181  				ObjectMeta: metav1.ObjectMeta{Name: "approved"},
   182  				Status:     certificatesv1.CertificateSigningRequestStatus{Conditions: []certificatesv1.CertificateSigningRequestCondition{{Type: certificatesv1.CertificateApproved}}},
   183  			}
   184  
   185  			deniedV1 := &certificatesv1.CertificateSigningRequest{
   186  				TypeMeta:   metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"},
   187  				ObjectMeta: metav1.ObjectMeta{Name: "denied"},
   188  				Status:     certificatesv1.CertificateSigningRequestStatus{Conditions: []certificatesv1.CertificateSigningRequestCondition{{Type: certificatesv1.CertificateDenied}}},
   189  			}
   190  
   191  			actions := []string{}
   192  			fakeClient := fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
   193  				actions = append(actions, req.Method+" "+req.URL.Path)
   194  				switch p, m := req.URL.Path, req.Method; {
   195  				case tc.nov1 && strings.HasPrefix(p, "/apis/certificates.k8s.io/v1/"):
   196  					return &http.Response{StatusCode: http.StatusNotFound, Body: io.NopCloser(bytes.NewBuffer([]byte{}))}, nil
   197  
   198  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/missing" && m == http.MethodGet:
   199  					return &http.Response{StatusCode: http.StatusNotFound}, nil
   200  
   201  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/existing" && m == http.MethodGet:
   202  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, existingV1)}, nil
   203  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/existing/approval" && m == http.MethodPut:
   204  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, existingV1)}, nil
   205  
   206  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/approved" && m == http.MethodGet:
   207  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, approvedV1)}, nil
   208  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/approved/approval" && m == http.MethodPut:
   209  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, approvedV1)}, nil
   210  
   211  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/denied" && m == http.MethodGet:
   212  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, deniedV1)}, nil
   213  				case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/denied/approval" && m == http.MethodPut:
   214  					return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, deniedV1)}, nil
   215  
   216  				default:
   217  					t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   218  					return nil, nil
   219  				}
   220  			})
   221  			tf.UnstructuredClientForMappingFunc = func(gv schema.GroupVersion) (resource.RESTClient, error) {
   222  				versionedAPIPath := ""
   223  				if gv.Group == "" {
   224  					versionedAPIPath = "/api/" + gv.Version
   225  				} else {
   226  					versionedAPIPath = "/apis/" + gv.Group + "/" + gv.Version
   227  				}
   228  				return &fake.RESTClient{
   229  					VersionedAPIPath:     versionedAPIPath,
   230  					NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   231  					Client:               fakeClient,
   232  				}, nil
   233  			}
   234  			tf.Client = &fake.RESTClient{
   235  				NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
   236  				Client:               fakeClient,
   237  			}
   238  			streams, _, buf, errbuf := genericiooptions.NewTestIOStreams()
   239  			tf.ClientConfigVal.Transport = fakeClient.Transport
   240  
   241  			defer func() {
   242  				// Restore cmdutil behavior.
   243  				cmdutil.DefaultBehaviorOnFatal()
   244  			}()
   245  			// Check exit code.
   246  			cmdutil.BehaviorOnFatal(func(e string, code int) {
   247  				if !tc.expectFailure {
   248  					t.Log(e)
   249  					t.Errorf("unexpected failure exit code %d", code)
   250  				}
   251  				errbuf.Write([]byte(e))
   252  			})
   253  
   254  			var cmd *cobra.Command
   255  			switch tc.command {
   256  			case "approve":
   257  				cmd = NewCmdCertificateApprove(tf, streams)
   258  			case "deny":
   259  				cmd = NewCmdCertificateDeny(tf, streams)
   260  			default:
   261  				t.Errorf("unknown command: %s", tc.command)
   262  			}
   263  
   264  			if tc.force {
   265  				cmd.Flags().Set("force", "true")
   266  			}
   267  			cmd.Run(cmd, tc.args)
   268  
   269  			if !strings.Contains(buf.String(), tc.expectOutput) {
   270  				t.Errorf("expected output to contain %q:\n%s", tc.expectOutput, buf.String())
   271  			}
   272  			if !strings.Contains(errbuf.String(), tc.expectErrOutput) {
   273  				t.Errorf("expected error output to contain %q:\n%s", tc.expectErrOutput, errbuf.String())
   274  			}
   275  			if !reflect.DeepEqual(tc.expectActions, actions) {
   276  				t.Logf("stdout:\n%s", buf.String())
   277  				t.Logf("stderr:\n%s", errbuf.String())
   278  				t.Errorf("expected\n%s\ngot\n%s", strings.Join(tc.expectActions, "\n"), strings.Join(actions, "\n"))
   279  			}
   280  		})
   281  	}
   282  }
   283  

View as plain text