Merge pull request #91558 from liggitt/csr-e2e

Add e2e coverage for the CertificateSigningRequest API, enable patch support for approval subresource
This commit is contained in:
Kubernetes Prow Robot 2020-06-02 06:28:15 -07:00 committed by GitHub
commit 84861d2102
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 420 additions and 33 deletions

View File

@ -60148,21 +60148,42 @@
}
},
"/apis/certificates.k8s.io/v1beta1/certificatesigningrequests/{name}/approval": {
"get": {
"consumes": [
"*/*"
],
"description": "read approval of the specified CertificateSigningRequest",
"operationId": "readCertificatesV1beta1CertificateSigningRequestApproval",
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.certificates.v1beta1.CertificateSigningRequest"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"certificates_v1beta1"
],
"x-kubernetes-action": "get",
"x-kubernetes-group-version-kind": {
"group": "certificates.k8s.io",
"kind": "CertificateSigningRequest",
"version": "v1beta1"
}
},
"parameters": [
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.",
"in": "query",
"name": "fieldManager",
"type": "string",
"uniqueItems": true
},
{
"description": "name of the CertificateSigningRequest",
"in": "path",
@ -60179,6 +60200,75 @@
"uniqueItems": true
}
],
"patch": {
"consumes": [
"application/json-patch+json",
"application/merge-patch+json",
"application/strategic-merge-patch+json",
"application/apply-patch+yaml"
],
"description": "partially update approval of the specified CertificateSigningRequest",
"operationId": "patchCertificatesV1beta1CertificateSigningRequestApproval",
"parameters": [
{
"in": "body",
"name": "body",
"required": true,
"schema": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Patch"
}
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).",
"in": "query",
"name": "fieldManager",
"type": "string",
"uniqueItems": true
},
{
"description": "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.",
"in": "query",
"name": "force",
"type": "boolean",
"uniqueItems": true
}
],
"produces": [
"application/json",
"application/yaml",
"application/vnd.kubernetes.protobuf"
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/io.k8s.api.certificates.v1beta1.CertificateSigningRequest"
}
},
"401": {
"description": "Unauthorized"
}
},
"schemes": [
"https"
],
"tags": [
"certificates_v1beta1"
],
"x-kubernetes-action": "patch",
"x-kubernetes-group-version-kind": {
"group": "certificates.k8s.io",
"kind": "CertificateSigningRequest",
"version": "v1beta1"
}
},
"put": {
"consumes": [
"*/*"
@ -60193,6 +60283,20 @@
"schema": {
"$ref": "#/definitions/io.k8s.api.certificates.v1beta1.CertificateSigningRequest"
}
},
{
"description": "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed",
"in": "query",
"name": "dryRun",
"type": "string",
"uniqueItems": true
},
{
"description": "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.",
"in": "query",
"name": "fieldManager",
"type": "string",
"uniqueItems": true
}
],
"produces": [

View File

@ -109,9 +109,16 @@ func (r *ApprovalREST) New() runtime.Object {
return &certificates.CertificateSigningRequest{}
}
// Get retrieves the object from the storage. It is required to support Patch.
func (r *ApprovalREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return r.store.Get(ctx, name, options)
}
// Update alters the approval subset of an object.
func (r *ApprovalREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
// We are explicitly setting forceAllowCreate to false in the call to the underlying storage because
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
var _ = rest.Patcher(&ApprovalREST{})

View File

@ -20,13 +20,20 @@ import (
"context"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"encoding/pem"
"fmt"
"time"
certificatesv1beta1 "k8s.io/api/certificates/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
types "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
v1beta1client "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/apimachinery/pkg/watch"
certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/cert"
"k8s.io/kubernetes/test/e2e/framework"
"k8s.io/kubernetes/test/utils"
@ -37,9 +44,18 @@ import (
var _ = SIGDescribe("Certificates API", func() {
f := framework.NewDefaultFramework("certificates")
/*
Release: v1.19
Testname: CertificateSigningRequest API Client Certificate
Description:
- The certificatesigningrequests resource must accept a request for a certificate signed by kubernetes.io/kube-apiserver-client.
- The issued certificate must be valid as a client certificate used to authenticate to the kube-apiserver.
*/
ginkgo.It("should support building a client with a CSR", func() {
const commonName = "tester-csr"
csrClient := f.ClientSet.CertificatesV1beta1().CertificateSigningRequests()
pk, err := utils.NewPrivateKey()
framework.ExpectNoError(err)
@ -49,29 +65,59 @@ var _ = SIGDescribe("Certificates API", func() {
Bytes: pkder,
})
csrb, err := cert.MakeCSR(pk, &pkix.Name{CommonName: commonName, Organization: []string{"system:masters"}}, nil, nil)
csrb, err := cert.MakeCSR(pk, &pkix.Name{CommonName: commonName}, nil, nil)
framework.ExpectNoError(err)
csr := &certificatesv1beta1.CertificateSigningRequest{
apiserverClientSigner := certificatesv1beta1.KubeAPIServerClientSignerName
csrTemplate := &certificatesv1beta1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
GenerateName: commonName + "-",
},
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
Request: csrb,
Usages: []certificatesv1beta1.KeyUsage{
certificatesv1beta1.UsageSigning,
certificatesv1beta1.UsageDigitalSignature,
certificatesv1beta1.UsageKeyEncipherment,
certificatesv1beta1.UsageClientAuth,
},
SignerName: &apiserverClientSigner,
},
}
csrs := f.ClientSet.CertificatesV1beta1().CertificateSigningRequests()
// Grant permissions to the new user
clusterRole, err := f.ClientSet.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{GenerateName: commonName + "-"},
Rules: []rbacv1.PolicyRule{{Verbs: []string{"create"}, APIGroups: []string{"certificates.k8s.io"}, Resources: []string{"certificatesigningrequests"}}},
}, metav1.CreateOptions{})
if err != nil {
// Tolerate RBAC not being enabled
framework.Logf("error granting permissions to %s, create certificatesigningrequests permissions must be granted out of band: %v", commonName, err)
} else {
defer func() {
framework.ExpectNoError(f.ClientSet.RbacV1().ClusterRoles().Delete(context.TODO(), clusterRole.Name, metav1.DeleteOptions{}))
}()
}
clusterRoleBinding, err := f.ClientSet.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{GenerateName: commonName + "-"},
RoleRef: rbacv1.RoleRef{APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole", Name: clusterRole.Name},
Subjects: []rbacv1.Subject{{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: commonName}},
}, metav1.CreateOptions{})
if err != nil {
// Tolerate RBAC not being enabled
framework.Logf("error granting permissions to %s, create certificatesigningrequests permissions must be granted out of band: %v", commonName, err)
} else {
defer func() {
framework.ExpectNoError(f.ClientSet.RbacV1().ClusterRoleBindings().Delete(context.TODO(), clusterRoleBinding.Name, metav1.DeleteOptions{}))
}()
}
framework.Logf("creating CSR")
csr, err = csrs.Create(context.TODO(), csr, metav1.CreateOptions{})
csr, err := csrClient.Create(context.TODO(), csrTemplate, metav1.CreateOptions{})
framework.ExpectNoError(err)
csrName := csr.Name
defer func() {
framework.ExpectNoError(csrClient.Delete(context.TODO(), csr.Name, metav1.DeleteOptions{}))
}()
framework.Logf("approving CSR")
framework.ExpectNoError(wait.Poll(5*time.Second, time.Minute, func() (bool, error) {
@ -82,9 +128,9 @@ var _ = SIGDescribe("Certificates API", func() {
Message: "Set from an e2e test",
},
}
csr, err = csrs.UpdateApproval(context.TODO(), csr, metav1.UpdateOptions{})
csr, err = csrClient.UpdateApproval(context.TODO(), csr, metav1.UpdateOptions{})
if err != nil {
csr, _ = csrs.Get(context.TODO(), csrName, metav1.GetOptions{})
csr, _ = csrClient.Get(context.TODO(), csr.Name, metav1.GetOptions{})
framework.Logf("err updating approval: %v", err)
return false, nil
}
@ -93,7 +139,7 @@ var _ = SIGDescribe("Certificates API", func() {
framework.Logf("waiting for CSR to be signed")
framework.ExpectNoError(wait.Poll(5*time.Second, time.Minute, func() (bool, error) {
csr, err = csrs.Get(context.TODO(), csrName, metav1.GetOptions{})
csr, err = csrClient.Get(context.TODO(), csr.Name, metav1.GetOptions{})
if err != nil {
framework.Logf("error getting csr: %v", err)
return false, nil
@ -108,17 +154,247 @@ var _ = SIGDescribe("Certificates API", func() {
framework.Logf("testing the client")
rcfg, err := framework.LoadConfig()
framework.ExpectNoError(err)
rcfg = rest.AnonymousClientConfig(rcfg)
rcfg.TLSClientConfig.CertData = csr.Status.Certificate
rcfg.TLSClientConfig.KeyData = pkpem
rcfg.TLSClientConfig.CertFile = ""
rcfg.BearerToken = ""
rcfg.AuthProvider = nil
rcfg.Username = ""
rcfg.Password = ""
newClient, err := v1beta1client.NewForConfig(rcfg)
newClient, err := certificatesclient.NewForConfig(rcfg)
framework.ExpectNoError(err)
framework.ExpectNoError(newClient.CertificateSigningRequests().Delete(context.TODO(), csrName, metav1.DeleteOptions{}))
framework.Logf("creating CSR as new client")
newCSR, err := newClient.CertificateSigningRequests().Create(context.TODO(), csrTemplate, metav1.CreateOptions{})
framework.ExpectNoError(err)
defer func() {
framework.ExpectNoError(csrClient.Delete(context.TODO(), newCSR.Name, metav1.DeleteOptions{}))
}()
framework.ExpectEqual(newCSR.Spec.Username, commonName)
})
/*
Release: v1.19
Testname: CertificateSigningRequest API
Description:
- The certificates.k8s.io API group MUST exists in the /apis discovery document.
- The certificates.k8s.io/v1beta1 API group/version MUST exist in the /apis/certificates.k8s.io discovery document.
- The certificatesigningrequests, certificatesigningrequests/approval, and certificatesigningrequests/status
resources MUST exist in the /apis/certificates.k8s.io/v1beta1 discovery document.
- The certificatesigningrequests resource must support create, get, list, watch, update, patch, delete, and deletecollection.
- The certificatesigningrequests/approval resource must support get, update, patch.
- The certificatesigningrequests/status resource must support get, update, patch.
*/
ginkgo.It("should support CSR API operations [Privileged:ClusterAdmin]", func() {
// Setup
csrVersion := "v1beta1"
csrClient := f.ClientSet.CertificatesV1beta1().CertificateSigningRequests()
csrResource := certificatesv1beta1.SchemeGroupVersion.WithResource("certificatesigningrequests")
pk, err := utils.NewPrivateKey()
framework.ExpectNoError(err)
csrData, err := cert.MakeCSR(pk, &pkix.Name{CommonName: "e2e.example.com"}, []string{"e2e.example.com"}, nil)
framework.ExpectNoError(err)
certificateData, _, err := cert.GenerateSelfSignedCertKey("e2e.example.com", nil, []string{"e2e.example.com"})
framework.ExpectNoError(err)
certificateDataJSON, err := json.Marshal(certificateData)
framework.ExpectNoError(err)
signerName := "example.com/e2e-" + f.UniqueName
csrTemplate := &certificatesv1beta1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{GenerateName: "e2e-example-csr-"},
Spec: certificatesv1beta1.CertificateSigningRequestSpec{
Request: csrData,
SignerName: &signerName,
Usages: []certificatesv1beta1.KeyUsage{certificatesv1beta1.UsageDigitalSignature, certificatesv1beta1.UsageKeyEncipherment, certificatesv1beta1.UsageServerAuth},
},
}
// Discovery
ginkgo.By("getting /apis")
{
discoveryGroups, err := f.ClientSet.Discovery().ServerGroups()
framework.ExpectNoError(err)
found := false
for _, group := range discoveryGroups.Groups {
if group.Name == certificatesv1beta1.GroupName {
for _, version := range group.Versions {
if version.Version == csrVersion {
found = true
break
}
}
}
}
framework.ExpectEqual(found, true, fmt.Sprintf("expected certificates API group/version, got %#v", discoveryGroups.Groups))
}
ginkgo.By("getting /apis/certificates.k8s.io")
{
group := &metav1.APIGroup{}
err := f.ClientSet.Discovery().RESTClient().Get().AbsPath("/apis/certificates.k8s.io").Do(context.TODO()).Into(group)
framework.ExpectNoError(err)
found := false
for _, version := range group.Versions {
if version.Version == csrVersion {
found = true
break
}
}
framework.ExpectEqual(found, true, fmt.Sprintf("expected certificates API version, got %#v", group.Versions))
}
ginkgo.By("getting /apis/certificates.k8s.io/" + csrVersion)
{
resources, err := f.ClientSet.Discovery().ServerResourcesForGroupVersion(certificatesv1beta1.SchemeGroupVersion.String())
framework.ExpectNoError(err)
foundCSR, foundApproval, foundStatus := false, false, false
for _, resource := range resources.APIResources {
switch resource.Name {
case "certificatesigningrequests":
foundCSR = true
case "certificatesigningrequests/approval":
foundApproval = true
case "certificatesigningrequests/status":
foundStatus = true
}
}
framework.ExpectEqual(foundCSR, true, fmt.Sprintf("expected certificatesigningrequests, got %#v", resources.APIResources))
framework.ExpectEqual(foundApproval, true, fmt.Sprintf("expected certificatesigningrequests/approval, got %#v", resources.APIResources))
framework.ExpectEqual(foundStatus, true, fmt.Sprintf("expected certificatesigningrequests/status, got %#v", resources.APIResources))
}
// Main resource create/read/update/watch operations
ginkgo.By("creating")
_, err = csrClient.Create(context.TODO(), csrTemplate, metav1.CreateOptions{})
framework.ExpectNoError(err)
_, err = csrClient.Create(context.TODO(), csrTemplate, metav1.CreateOptions{})
framework.ExpectNoError(err)
createdCSR, err := csrClient.Create(context.TODO(), csrTemplate, metav1.CreateOptions{})
framework.ExpectNoError(err)
ginkgo.By("getting")
gottenCSR, err := csrClient.Get(context.TODO(), createdCSR.Name, metav1.GetOptions{})
framework.ExpectNoError(err)
framework.ExpectEqual(gottenCSR.UID, createdCSR.UID)
ginkgo.By("listing")
csrs, err := csrClient.List(context.TODO(), metav1.ListOptions{FieldSelector: "spec.signerName=" + signerName})
framework.ExpectNoError(err)
framework.ExpectEqual(len(csrs.Items), 3, "filtered list should have 3 items")
ginkgo.By("watching")
framework.Logf("starting watch")
csrWatch, err := csrClient.Watch(context.TODO(), metav1.ListOptions{ResourceVersion: csrs.ResourceVersion, FieldSelector: "metadata.name=" + createdCSR.Name})
framework.ExpectNoError(err)
ginkgo.By("patching")
patchedCSR, err := csrClient.Patch(context.TODO(), createdCSR.Name, types.MergePatchType, []byte(`{"metadata":{"annotations":{"patched":"true"}}}`), metav1.PatchOptions{})
framework.ExpectNoError(err)
framework.ExpectEqual(patchedCSR.Annotations["patched"], "true", "patched object should have the applied annotation")
ginkgo.By("updating")
csrToUpdate := patchedCSR.DeepCopy()
csrToUpdate.Annotations["updated"] = "true"
updatedCSR, err := csrClient.Update(context.TODO(), csrToUpdate, metav1.UpdateOptions{})
framework.ExpectNoError(err)
framework.ExpectEqual(updatedCSR.Annotations["updated"], "true", "updated object should have the applied annotation")
framework.Logf("waiting for watch events with expected annotations")
for sawAnnotations := false; !sawAnnotations; {
select {
case evt, ok := <-csrWatch.ResultChan():
framework.ExpectEqual(ok, true, "watch channel should not close")
framework.ExpectEqual(evt.Type, watch.Modified)
watchedCSR, isCSR := evt.Object.(*certificatesv1beta1.CertificateSigningRequest)
framework.ExpectEqual(isCSR, true, fmt.Sprintf("expected CSR, got %T", evt.Object))
if watchedCSR.Annotations["patched"] == "true" {
framework.Logf("saw patched and updated annotations")
sawAnnotations = true
csrWatch.Stop()
} else {
framework.Logf("missing expected annotations, waiting: %#v", watchedCSR.Annotations)
}
case <-time.After(wait.ForeverTestTimeout):
framework.Fail("timed out waiting for watch event")
}
}
// /approval subresource operations
ginkgo.By("getting /approval")
gottenApproval, err := f.DynamicClient.Resource(csrResource).Get(context.TODO(), createdCSR.Name, metav1.GetOptions{}, "approval")
framework.ExpectNoError(err)
framework.ExpectEqual(gottenApproval.GetObjectKind().GroupVersionKind(), certificatesv1beta1.SchemeGroupVersion.WithKind("CertificateSigningRequest"))
framework.ExpectEqual(gottenApproval.GetUID(), createdCSR.UID)
ginkgo.By("patching /approval")
patchedApproval, err := csrClient.Patch(context.TODO(), createdCSR.Name, types.MergePatchType,
[]byte(`{"metadata":{"annotations":{"patchedapproval":"true"}},"status":{"conditions":[{"type":"ApprovalPatch","status":"True","reason":"e2e"}]}}`),
metav1.PatchOptions{}, "approval")
framework.ExpectNoError(err)
framework.ExpectEqual(len(patchedApproval.Status.Conditions), 1, fmt.Sprintf("patched object should have the applied condition, got %#v", patchedApproval.Status.Conditions))
framework.ExpectEqual(string(patchedApproval.Status.Conditions[0].Type), "ApprovalPatch", fmt.Sprintf("patched object should have the applied condition, got %#v", patchedApproval.Status.Conditions))
framework.ExpectEqual(patchedApproval.Annotations["patchedapproval"], "true", "patched object should have the applied annotation")
ginkgo.By("updating /approval")
approvalToUpdate := patchedApproval.DeepCopy()
approvalToUpdate.Status.Conditions = append(approvalToUpdate.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: certificatesv1beta1.CertificateApproved,
Reason: "E2E",
Message: "Set from an e2e test",
})
updatedApproval, err := csrClient.UpdateApproval(context.TODO(), approvalToUpdate, metav1.UpdateOptions{})
framework.ExpectNoError(err)
framework.ExpectEqual(len(updatedApproval.Status.Conditions), 2, fmt.Sprintf("updated object should have the applied condition, got %#v", updatedApproval.Status.Conditions))
framework.ExpectEqual(updatedApproval.Status.Conditions[1].Type, certificatesv1beta1.CertificateApproved, fmt.Sprintf("updated object should have the approved condition, got %#v", updatedApproval.Status.Conditions))
// /status subresource operations
ginkgo.By("getting /status")
gottenStatus, err := f.DynamicClient.Resource(csrResource).Get(context.TODO(), createdCSR.Name, metav1.GetOptions{}, "status")
framework.ExpectNoError(err)
framework.ExpectEqual(gottenStatus.GetObjectKind().GroupVersionKind(), certificatesv1beta1.SchemeGroupVersion.WithKind("CertificateSigningRequest"))
framework.ExpectEqual(gottenStatus.GetUID(), createdCSR.UID)
ginkgo.By("patching /status")
patchedStatus, err := csrClient.Patch(context.TODO(), createdCSR.Name, types.MergePatchType,
[]byte(`{"metadata":{"annotations":{"patchedstatus":"true"}},"status":{"certificate":`+string(certificateDataJSON)+`}}`),
metav1.PatchOptions{}, "status")
framework.ExpectNoError(err)
framework.ExpectEqual(patchedStatus.Status.Certificate, certificateData, "patched object should have the applied certificate")
framework.ExpectEqual(patchedStatus.Annotations["patchedstatus"], "true", "patched object should have the applied annotation")
ginkgo.By("updating /status")
statusToUpdate := patchedStatus.DeepCopy()
statusToUpdate.Status.Conditions = append(statusToUpdate.Status.Conditions, certificatesv1beta1.CertificateSigningRequestCondition{
Type: "StatusUpdate",
Reason: "E2E",
Message: "Set from an e2e test",
})
updatedStatus, err := csrClient.UpdateStatus(context.TODO(), statusToUpdate, metav1.UpdateOptions{})
framework.ExpectNoError(err)
framework.ExpectEqual(len(updatedStatus.Status.Conditions), len(statusToUpdate.Status.Conditions), fmt.Sprintf("updated object should have the applied condition, got %#v", updatedStatus.Status.Conditions))
framework.ExpectEqual(string(updatedStatus.Status.Conditions[len(updatedStatus.Status.Conditions)-1].Type), "StatusUpdate", fmt.Sprintf("updated object should have the approved condition, got %#v", updatedStatus.Status.Conditions))
// main resource delete operations
ginkgo.By("deleting")
err = csrClient.Delete(context.TODO(), createdCSR.Name, metav1.DeleteOptions{})
framework.ExpectNoError(err)
_, err = csrClient.Get(context.TODO(), createdCSR.Name, metav1.GetOptions{})
framework.ExpectEqual(apierrors.IsNotFound(err), true, fmt.Sprintf("expected 404, got %#v", err))
csrs, err = csrClient.List(context.TODO(), metav1.ListOptions{FieldSelector: "spec.signerName=" + signerName})
framework.ExpectNoError(err)
framework.ExpectEqual(len(csrs.Items), 2, "filtered list should have 2 items")
ginkgo.By("deleting a collection")
err = csrClient.DeleteCollection(context.TODO(), metav1.DeleteOptions{}, metav1.ListOptions{FieldSelector: "spec.signerName=" + signerName})
framework.ExpectNoError(err)
csrs, err = csrClient.List(context.TODO(), metav1.ListOptions{FieldSelector: "spec.signerName=" + signerName})
framework.ExpectNoError(err)
framework.ExpectEqual(len(csrs.Items), 0, "filtered list should have 0 items")
})
})