1
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`,
59 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`,
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`,
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`,
82 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`,
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`,
93 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`,
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`,
104 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`,
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`,
116 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`,
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`,
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`,
139 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`,
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`,
150 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`,
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`,
161 `GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`,
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
243 cmdutil.DefaultBehaviorOnFatal()
244 }()
245
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