Add support for v1 CSRs to kubectl certificate commands

Kubernetes-commit: ca234db60151ccffdd4dc8ceb2ec3c69fd83af69
This commit is contained in:
Jordan Liggitt 2020-06-04 11:39:02 -04:00 committed by Kubernetes Publisher
parent 17351e39d1
commit fe32c3454c
3 changed files with 447 additions and 51 deletions

View File

@ -23,14 +23,18 @@ import (
"github.com/spf13/cobra"
certificatesv1 "k8s.io/api/certificates/v1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource"
certificatesv1beta1client "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
clientset "k8s.io/client-go/kubernetes"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n"
@ -63,7 +67,7 @@ type CertificateOptions struct {
csrNames []string
outputStyle string
clientSet certificatesv1beta1client.CertificatesV1beta1Interface
clientSet clientset.Interface
builder *resource.Builder
genericclioptions.IOStreams
@ -92,11 +96,7 @@ func (o *CertificateOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
o.builder = f.NewBuilder()
clientConfig, err := f.ToRESTConfig()
if err != nil {
return err
}
o.clientSet, err = certificatesv1beta1client.NewForConfig(clientConfig)
o.clientSet, err = f.KubernetesClientSet()
if err != nil {
return err
}
@ -146,24 +146,12 @@ func NewCmdCertificateApprove(f cmdutil.Factory, ioStreams genericclioptions.IOS
}
func (o *CertificateOptions) RunCertificateApprove(force bool) error {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificatesv1beta1.CertificateSigningRequest) (*certificatesv1beta1.CertificateSigningRequest, bool) {
var alreadyApproved bool
for _, c := range csr.Status.Conditions {
if c.Type == certificatesv1beta1.CertificateApproved {
alreadyApproved = true
}
}
if alreadyApproved {
return csr, true
}
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateApproved,
Reason: "KubectlApprove",
Message: "This CSR was approved by kubectl certificate approve.",
LastUpdateTime: metav1.Now(),
})
return csr, false
})
return o.modifyCertificateCondition(
o.builder,
o.clientSet,
force,
addConditionIfNeeded(string(certificatesv1.CertificateDenied), string(certificatesv1.CertificateApproved), "KubectlApprove", "This CSR was approved by kubectl certificate approve."),
)
}
func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobra.Command {
@ -196,33 +184,21 @@ func NewCmdCertificateDeny(f cmdutil.Factory, ioStreams genericclioptions.IOStre
}
func (o *CertificateOptions) RunCertificateDeny(force bool) error {
return o.modifyCertificateCondition(o.builder, o.clientSet, force, func(csr *certificatesv1beta1.CertificateSigningRequest) (*certificatesv1beta1.CertificateSigningRequest, bool) {
var alreadyDenied bool
for _, c := range csr.Status.Conditions {
if c.Type == certificatesv1beta1.CertificateDenied {
alreadyDenied = true
}
}
if alreadyDenied {
return csr, true
}
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateDenied,
Reason: "KubectlDeny",
Message: "This CSR was denied by kubectl certificate deny.",
LastUpdateTime: metav1.Now(),
})
return csr, false
})
return o.modifyCertificateCondition(
o.builder,
o.clientSet,
force,
addConditionIfNeeded(string(certificatesv1.CertificateApproved), string(certificatesv1.CertificateDenied), "KubectlDeny", "This CSR was denied by kubectl certificate deny."),
)
}
func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet certificatesv1beta1client.CertificatesV1beta1Interface, force bool, modify func(csr *certificatesv1beta1.CertificateSigningRequest) (*certificatesv1beta1.CertificateSigningRequest, bool)) error {
func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builder, clientSet clientset.Interface, force bool, modify func(csr runtime.Object) (runtime.Object, bool, error)) error {
var found int
r := builder.
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
Unstructured().
ContinueOnError().
FilenameParam(false, &o.FilenameOptions).
ResourceNames("certificatesigningrequests.v1beta1.certificates.k8s.io", o.csrNames...).
ResourceNames("certificatesigningrequests", o.csrNames...).
RequireObject(true).
Flatten().
Latest().
@ -232,13 +208,41 @@ func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builde
return err
}
for i := 0; ; i++ {
csr, ok := info.Object.(*certificatesv1beta1.CertificateSigningRequest)
obj, ok := info.Object.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("can only handle certificates.k8s.io/v1beta1 certificate signing requests")
return fmt.Errorf("expected *unstructured.Unstructured, got %T", obj)
}
if want, got := certificatesv1.Kind("CertificateSigningRequest"), obj.GetObjectKind().GroupVersionKind().GroupKind(); want != got {
return fmt.Errorf("can only handle %s objects, got %s", want.String(), got.String())
}
var csr runtime.Object
// get a typed object
// first try v1
csr, err = clientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
if apierrors.IsNotFound(err) {
// fall back to v1beta1
csr, err = clientSet.CertificatesV1beta1().CertificateSigningRequests().Get(context.TODO(), obj.GetName(), metav1.GetOptions{})
}
if apierrors.IsNotFound(err) {
return fmt.Errorf("could not find v1 or v1beta1 version of %s: %v", obj.GetName(), err)
}
if err != nil {
return err
}
modifiedCSR, hasCondition, err := modify(csr)
if err != nil {
return err
}
csr, hasCondition := modify(csr)
if !hasCondition || force {
_, err = clientSet.CertificateSigningRequests().UpdateApproval(context.TODO(), csr, metav1.UpdateOptions{})
switch modifiedCSR := modifiedCSR.(type) {
case *certificatesv1.CertificateSigningRequest:
_, err = clientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), modifiedCSR.Name, modifiedCSR, metav1.UpdateOptions{})
case *certificatesv1beta1.CertificateSigningRequest:
_, err = clientSet.CertificatesV1beta1().CertificateSigningRequests().UpdateApproval(context.TODO(), modifiedCSR, metav1.UpdateOptions{})
default:
return fmt.Errorf("can only handle certificates.k8s.io CertificateSigningRequest objects, got %T", modifiedCSR)
}
if errors.IsConflict(err) && i < 10 {
if err := info.Get(); err != nil {
return err
@ -255,8 +259,61 @@ func (o *CertificateOptions) modifyCertificateCondition(builder *resource.Builde
return o.PrintObj(info.Object, o.Out)
})
if found == 0 {
if found == 0 && err == nil {
fmt.Fprintf(o.Out, "No resources found\n")
}
return err
}
func addConditionIfNeeded(mustNotHaveConditionType, conditionType, reason, message string) func(runtime.Object) (runtime.Object, bool, error) {
return func(csr runtime.Object) (runtime.Object, bool, error) {
switch csr := csr.(type) {
case *certificatesv1.CertificateSigningRequest:
var alreadyHasCondition bool
for _, c := range csr.Status.Conditions {
if string(c.Type) == mustNotHaveConditionType {
return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
}
if string(c.Type) == conditionType {
alreadyHasCondition = true
}
}
if alreadyHasCondition {
return csr, true, nil
}
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{
Type: certificatesv1.RequestConditionType(conditionType),
Status: corev1.ConditionTrue,
Reason: reason,
Message: message,
LastUpdateTime: metav1.Now(),
})
return csr, false, nil
case *certificatesv1beta1.CertificateSigningRequest:
var alreadyHasCondition bool
for _, c := range csr.Status.Conditions {
if string(c.Type) == mustNotHaveConditionType {
return nil, false, fmt.Errorf("certificate signing request %q is already %s", csr.Name, c.Type)
}
if string(c.Type) == conditionType {
alreadyHasCondition = true
}
}
if alreadyHasCondition {
return csr, true, nil
}
csr.Status.Conditions = append(csr.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.RequestConditionType(conditionType),
Status: corev1.ConditionTrue,
Reason: reason,
Message: message,
LastUpdateTime: metav1.Now(),
})
return csr, false, nil
default:
return csr, false, nil
}
}
}

View File

@ -0,0 +1,338 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package certificates
import (
"bytes"
"io/ioutil"
"net/http"
"reflect"
"strings"
"testing"
"github.com/spf13/cobra"
certificatesv1 "k8s.io/api/certificates/v1"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/resource"
"k8s.io/client-go/rest/fake"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme"
)
func TestCertificates(t *testing.T) {
testcases := []struct {
name string
nov1 bool
nov1beta1 bool
command string
force bool
args []string
expectFailure bool
expectActions []string
expectOutput string
expectErrOutput string
}{
{
name: "approve existing",
command: "approve",
args: []string{"existing"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // typed get
`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/existing/approval`,
},
expectOutput: `approved`,
},
{
name: "approve existing, no v1",
nov1: true,
command: "approve",
args: []string{"certificatesigningrequests.v1beta1.certificates.k8s.io/existing"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // typed get, 404
`GET /apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing`, // typed get fallback
`PUT /apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing/approval`,
},
expectOutput: `approved`,
},
{
name: "approve existing, no v1 or v1beta1",
nov1: true,
nov1beta1: true,
command: "approve",
args: []string{"existing"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get, 404
},
expectFailure: true,
expectErrOutput: `could not find the requested resource`,
},
{
name: "approve already approved",
command: "approve",
args: []string{"approved"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // typed get
},
expectOutput: `approved`,
},
{
name: "approve already approved, force",
command: "approve",
args: []string{"approved"},
force: true,
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // typed get
`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/approved/approval`,
},
expectOutput: `approved`,
},
{
name: "approve already denied",
command: "approve",
args: []string{"denied"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // typed get
},
expectFailure: true,
expectErrOutput: `is already Denied`,
},
{
name: "deny existing",
command: "deny",
args: []string{"existing"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // typed get
`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/existing/approval`,
},
expectOutput: `denied`,
},
{
name: "deny existing, no v1",
nov1: true,
command: "deny",
args: []string{"certificatesigningrequests.v1beta1.certificates.k8s.io/existing"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // typed get, 404
`GET /apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing`, // typed get fallback
`PUT /apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing/approval`,
},
expectOutput: `denied`,
},
{
name: "deny existing, no v1 or v1beta1",
nov1: true,
nov1beta1: true,
command: "deny",
args: []string{"existing"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/existing`, // unstructured get, 404
},
expectFailure: true,
expectErrOutput: `could not find the requested resource`,
},
{
name: "deny already denied",
command: "deny",
args: []string{"denied"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // typed get
},
expectOutput: `denied`,
},
{
name: "deny already denied, force",
command: "deny",
args: []string{"denied"},
force: true,
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/denied`, // typed get
`PUT /apis/certificates.k8s.io/v1/certificatesigningrequests/denied/approval`,
},
expectOutput: `denied`,
},
{
name: "deny already approved",
command: "deny",
args: []string{"approved"},
expectActions: []string{
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // unstructured get
`GET /apis/certificates.k8s.io/v1/certificatesigningrequests/approved`, // typed get
},
expectFailure: true,
expectErrOutput: `is already Approved`,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
existingV1 := &certificatesv1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"},
ObjectMeta: metav1.ObjectMeta{Name: "existing"},
}
existingV1beta1 := &certificatesv1beta1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest"},
ObjectMeta: metav1.ObjectMeta{Name: "existing"},
}
approvedV1 := &certificatesv1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"},
ObjectMeta: metav1.ObjectMeta{Name: "approved"},
Status: certificatesv1.CertificateSigningRequestStatus{Conditions: []certificatesv1.CertificateSigningRequestCondition{{Type: certificatesv1.CertificateApproved}}},
}
approvedV1beta1 := &certificatesv1beta1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest"},
ObjectMeta: metav1.ObjectMeta{Name: "existing"},
Status: certificatesv1beta1.CertificateSigningRequestStatus{Conditions: []certificatesv1beta1.CertificateSigningRequestCondition{{Type: certificatesv1beta1.CertificateApproved}}},
}
deniedV1 := &certificatesv1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"},
ObjectMeta: metav1.ObjectMeta{Name: "denied"},
Status: certificatesv1.CertificateSigningRequestStatus{Conditions: []certificatesv1.CertificateSigningRequestCondition{{Type: certificatesv1.CertificateDenied}}},
}
deniedV1beta1 := &certificatesv1beta1.CertificateSigningRequest{
TypeMeta: metav1.TypeMeta{APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest"},
ObjectMeta: metav1.ObjectMeta{Name: "denied"},
Status: certificatesv1beta1.CertificateSigningRequestStatus{Conditions: []certificatesv1beta1.CertificateSigningRequestCondition{{Type: certificatesv1beta1.CertificateDenied}}},
}
actions := []string{}
fakeClient := fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
actions = append(actions, req.Method+" "+req.URL.Path)
switch p, m := req.URL.Path, req.Method; {
case tc.nov1 && strings.HasPrefix(p, "/apis/certificates.k8s.io/v1/"):
return &http.Response{StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(bytes.NewBuffer([]byte{}))}, nil
case tc.nov1beta1 && strings.HasPrefix(p, "/apis/certificates.k8s.io/v1beta1/"):
return &http.Response{StatusCode: http.StatusNotFound, Body: ioutil.NopCloser(bytes.NewBuffer([]byte{}))}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/missing" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusNotFound}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/missing" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusNotFound}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/existing" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, existingV1)}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/existing/approval" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, existingV1)}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, existingV1beta1)}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/existing/approval" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, existingV1beta1)}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/approved" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, approvedV1)}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/approved/approval" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, approvedV1)}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/approved" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, approvedV1beta1)}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/approved/approval" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, approvedV1beta1)}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/denied" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, deniedV1)}, nil
case p == "/apis/certificates.k8s.io/v1/certificatesigningrequests/denied/approval" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, deniedV1)}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/denied" && m == http.MethodGet:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, deniedV1beta1)}, nil
case p == "/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/denied/approval" && m == http.MethodPut:
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, deniedV1beta1)}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
})
tf.UnstructuredClientForMappingFunc = func(gv schema.GroupVersion) (resource.RESTClient, error) {
versionedAPIPath := ""
if gv.Group == "" {
versionedAPIPath = "/api/" + gv.Version
} else {
versionedAPIPath = "/apis/" + gv.Group + "/" + gv.Version
}
return &fake.RESTClient{
VersionedAPIPath: versionedAPIPath,
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fakeClient,
}, nil
}
tf.Client = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fakeClient,
}
streams, _, buf, errbuf := genericclioptions.NewTestIOStreams()
defer func() {
// Restore cmdutil behavior.
cmdutil.DefaultBehaviorOnFatal()
}()
// Check exit code.
cmdutil.BehaviorOnFatal(func(e string, code int) {
if !tc.expectFailure {
t.Log(e)
t.Errorf("unexpected failure exit code %d", code)
}
errbuf.Write([]byte(e))
})
var cmd *cobra.Command
switch tc.command {
case "approve":
cmd = NewCmdCertificateApprove(tf, streams)
case "deny":
cmd = NewCmdCertificateDeny(tf, streams)
default:
t.Errorf("unknown command: %s", tc.command)
}
if tc.force {
cmd.Flags().Set("force", "true")
}
cmd.Run(cmd, tc.args)
if !strings.Contains(buf.String(), tc.expectOutput) {
t.Errorf("expected output to contain %q:\n%s", tc.expectOutput, buf.String())
}
if !strings.Contains(errbuf.String(), tc.expectErrOutput) {
t.Errorf("expected error output to contain %q:\n%s", tc.expectErrOutput, errbuf.String())
}
if !reflect.DeepEqual(tc.expectActions, actions) {
t.Logf("stdout:\n%s", buf.String())
t.Logf("stderr:\n%s", errbuf.String())
t.Errorf("expected\n%s\ngot\n%s", strings.Join(tc.expectActions, "\n"), strings.Join(actions, "\n"))
}
})
}
}

View File

@ -519,6 +519,7 @@ func (f *TestFactory) KubernetesClientSet() (*kubernetes.Clientset, error) {
clientset.AutoscalingV2beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.BatchV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.BatchV2alpha1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.CertificatesV1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.CertificatesV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.ExtensionsV1beta1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
clientset.RbacV1alpha1().RESTClient().(*restclient.RESTClient).Client = fakeClient.Client