Browse Source

Adding controller tests for cert-controller

Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
Gustavo Carvalho 4 years ago
parent
commit
3d9e5a9fdb

+ 203 - 140
pkg/controllers/crds/common_test.go

@@ -13,143 +13,206 @@ limitations under the License.
 */
 package crds
 
-// import (
-// 	"context"
-// 	"time"
-
-// 	. "github.com/onsi/ginkgo/v2"
-// 	. "github.com/onsi/gomega"
-// 	corev1 "k8s.io/api/core/v1"
-// 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-// 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-// 	"k8s.io/apimachinery/pkg/types"
-
-// 	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
-// )
-
-// type testCase struct {
-// 	crd    apiextensions.CustomResourceDefinition
-// 	assert func()
-// }
-
-// var _ = Describe("CRD reconcile", func() {
-// 	var test *testCase
-
-// 	BeforeEach(func() {
-// 		test = makeDefaultTestcase()
-// 	})
-
-// 	AfterEach(func() {
-// 		Expect(k8sClient.Delete(context.Background(), &test.crd)).ToNot(HaveOccurred())
-// 	})
-
-// 	// a invalid provider config should be reflected
-// 	// in the store status condition
-// 	PatchesCRD := func(tc *testCase) {
-// 		tc.assert = func() {
-// 			Eventually(func() bool {
-// 				ss := apiextensions.CustomResourceDefinition{}
-// 				err := k8sClient.Get(context.Background(), types.NamespacedName{
-// 					Name: "secrestores.external-secrets.io",
-// 				}, &ss)
-// 				if err != nil {
-// 					return false
-// 				}
-// 				return ss.Spec.Conversion.Strategy == "Webhook"
-// 			}).
-// 				WithTimeout(time.Second * 10).
-// 				WithPolling(time.Second).
-// 				Should(BeTrue())
-// 		}
-// 	}
-
-// 	// if controllerClass does not match the controller
-// 	// should not touch this store
-// 	ignoreNonTargetCRDs := func(tc *testCase) {
-// 		tc.assert = func() {
-// 			Consistently(func() bool {
-// 				ss := apiextensions.CustomResourceDefinition{}
-// 				err := k8sClient.Get(context.Background(), types.NamespacedName{
-// 					Name: defaultStoreName,
-// 				}, &ss)
-// 				if err != nil {
-// 					return false
-// 				}
-// 				return ss.Spec.Conversion == tc.crd.Spec.Conversion
-// 			}).
-// 				WithTimeout(time.Second * 3).
-// 				WithPolling(time.Millisecond * 500).
-// 				Should(BeTrue())
-// 		}
-// 	}
-
-// 	DescribeTable("Controller Reconcile logic", func(muts ...func(tc *testCase)) {
-// 		for _, mut := range muts {
-// 			mut(test)
-// 		}
-// 		err := k8sClient.Create(context.Background(), test.store.Copy())
-// 		Expect(err).ToNot(HaveOccurred())
-// 		test.assert()
-// 	},
-// 		// namespaced store
-// 		Entry("[namespace] invalid provider with secretStore should set InvalidStore condition", invalidProvider),
-// 		Entry("[namespace] ignore stores with non-matching class", ignoreControllerClass),
-// 		Entry("[namespace] valid provider has status=ready", validProvider),
-
-// 		// cluster store
-// 		Entry("[cluster] invalid provider with secretStore should set InvalidStore condition", invalidProvider, useClusterStore),
-// 		Entry("[cluster] ignore stores with non-matching class", ignoreControllerClass, useClusterStore),
-// 		Entry("[cluster] valid provider has status=ready", validProvider, useClusterStore),
-// 	)
-
-// })
-
-// const (
-// 	defaultStoreName       = "default-store"
-// 	defaultControllerClass = "test-ctrl"
-// )
-
-// func makeDefaultTestcase() *testCase {
-// 	return &testCase{
-// 		assert: func() {
-// 			// this is a noop by default
-// 		},
-// 		crd: &apiextensions.CustomResourceDefinition{
-// 			TypeMeta: metav1.TypeMeta{
-// 				Kind:       esapi.SecretStoreKind,
-// 				APIVersion: esapi.SecretStoreKindAPIVersion,
-// 			},
-// 			ObjectMeta: metav1.ObjectMeta{
-// 				Name:      defaultStoreName,
-// 				Namespace: "default",
-// 			},
-// 			Spec: esapi.SecretStoreSpec{
-// 				Controller: defaultControllerClass,
-// 				// empty provider
-// 				// a testCase mutator must fill in the concrete provider
-// 				Provider: &esapi.SecretStoreProvider{
-// 					Vault: &esapi.VaultProvider{
-// 						Version: esapi.VaultKVStoreV1,
-// 					},
-// 				},
-// 			},
-// 		},
-// 	}
-// }
-
-// func hasEvent(involvedKind, name, reason string) bool {
-// 	el := &corev1.EventList{}
-// 	err := k8sClient.List(context.Background(), el)
-// 	if err != nil {
-// 		return false
-// 	}
-// 	for i := range el.Items {
-// 		ev := el.Items[i]
-// 		if ev.InvolvedObject.Kind == involvedKind && ev.InvolvedObject.Name == name {
-// 			if ev.Reason == reason {
-// 				return true
-// 			}
-// 		}
-// 	}
-// 	return false
-// }
+import (
+	"context"
+	"encoding/json"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+type testCase struct {
+	crd     unstructured.Unstructured
+	crd2    unstructured.Unstructured
+	service corev1.Service
+	secret  corev1.Secret
+	assert  func()
+}
+
+var _ = Describe("CRD reconcile", func() {
+	var test *testCase
+
+	BeforeEach(func() {
+		test = makeDefaultTestcase()
+	})
+
+	AfterEach(func() {
+	})
+
+	// a invalid provider config should be reflected
+	// in the store status condition
+	PatchesCRD := func(tc *testCase) {
+		tc.assert = func() {
+			Consistently(func() bool {
+				ss := unstructured.Unstructured{}
+				ss.SetGroupVersionKind(schema.GroupVersionKind{Kind: "CustomResourceDefinition", Version: "v1", Group: "apiextensions.k8s.io"})
+				err := k8sClient.Get(context.Background(), types.NamespacedName{
+					Name: "secretstores.test.io",
+				}, &ss)
+				if err != nil {
+					return false
+				}
+				val, ok, err := unstructured.NestedString(ss.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
+				if err != nil || !ok {
+					return false
+				}
+				want, ok, err := unstructured.NestedString(tc.crd.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
+				if err != nil || !ok {
+					return false
+				}
+				return want != val
+			}).
+				WithTimeout(time.Second * 10).
+				WithPolling(time.Second).
+				Should(BeTrue())
+		}
+	}
+
+	// if controllerClass does not match the controller
+	// should not touch this store
+	ignoreNonTargetCRDs := func(tc *testCase) {
+		tc.assert = func() {
+			Consistently(func() bool {
+				ss := unstructured.Unstructured{}
+				ss.SetGroupVersionKind(schema.GroupVersionKind{Kind: "CustomResourceDefinition", Version: "v1", Group: "apiextensions.k8s.io"})
+				err := k8sClient.Get(context.Background(), types.NamespacedName{
+					Name: "some-other.test.io",
+				}, &ss)
+				if err != nil {
+					return false
+				}
+				got, ok, err := unstructured.NestedString(ss.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
+				if !ok || err != nil {
+					return false
+				}
+				want, ok, err := unstructured.NestedString(tc.crd2.Object, "spec", "conversion", "webhook", "clientConfig", "service", "name")
+				if !ok || err != nil {
+					return false
+				}
+				return got == want
+			}).
+				WithTimeout(time.Second * 3).
+				WithPolling(time.Millisecond * 500).
+				Should(BeTrue())
+		}
+	}
+
+	DescribeTable("Controller Reconcile logic", func(muts ...func(tc *testCase)) {
+		for _, mut := range muts {
+			mut(test)
+		}
+		ctx := context.Background()
+		k8sClient.Create(ctx, &test.secret)
+		k8sClient.Create(ctx, &test.service)
+		k8sClient.Create(ctx, &test.crd)
+		k8sClient.Create(ctx, &test.crd2)
+		test.assert()
+	},
+
+		Entry("[namespace] Ignore non Target CRDs", ignoreNonTargetCRDs),
+		Entry("[namespace] Patch target CRDs", PatchesCRD),
+	)
+
+})
+
+func makeUnstructuredCRD(plural, group string) unstructured.Unstructured {
+	crd := apiextensions.CustomResourceDefinition{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: plural + "." + group,
+		},
+		Spec: apiextensions.CustomResourceDefinitionSpec{
+			Versions: []apiextensions.CustomResourceDefinitionVersion{
+				{
+					Name:    "v1",
+					Served:  true,
+					Storage: true,
+					Schema: &apiextensions.CustomResourceValidation{
+						OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
+							Type: "object",
+						},
+					},
+				},
+			},
+			Group: group,
+			Scope: apiextensions.NamespaceScoped,
+			Names: apiextensions.CustomResourceDefinitionNames{
+				Plural:   plural,
+				Singular: "idc",
+				Kind:     "IDC",
+				ListKind: "IDCList",
+			},
+			Conversion: &apiextensions.CustomResourceConversion{
+				Strategy: "Webhook",
+				Webhook: &apiextensions.WebhookConversion{
+					ConversionReviewVersions: []string{"v1"},
+					ClientConfig: &apiextensions.WebhookClientConfig{
+						CABundle: []byte("foobar"),
+						Service: &apiextensions.ServiceReference{
+							Name:      "webhook",
+							Namespace: "default",
+						},
+					},
+				},
+			},
+		},
+	}
+	marshal, _ := json.Marshal(crd)
+	unmarshal := make(map[string]interface{})
+	json.Unmarshal(marshal, &unmarshal)
+	u := unstructured.Unstructured{
+		Object: unmarshal,
+	}
+	u.SetGroupVersionKind(schema.GroupVersionKind{Kind: "CustomResourceDefinition", Version: "v1", Group: "apiextensions.k8s.io"})
+	return u
+}
+
+func makeSecret() corev1.Secret {
+	return corev1.Secret{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo",
+			Namespace: "default",
+			Labels: map[string]string{
+				"foo": "bar",
+			},
+		},
+	}
+}
+
+func makeService() corev1.Service {
+	return corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "foo",
+			Namespace: "default",
+			Labels: map[string]string{
+				"foo": "bar",
+			},
+		},
+		Spec: corev1.ServiceSpec{
+			Ports: []corev1.ServicePort{
+				{
+					Name: "http",
+					Port: 80,
+				},
+			},
+		},
+	}
+}
+
+func makeDefaultTestcase() *testCase {
+	return &testCase{
+		assert: func() {
+			// this is a noop by default
+		},
+		crd:     makeUnstructuredCRD("secretstores", "test.io"),
+		crd2:    makeUnstructuredCRD("some-other", "test.io"),
+		secret:  makeSecret(),
+		service: makeService(),
+	}
+}

+ 10 - 4
pkg/controllers/crds/crds_controller.go

@@ -129,16 +129,22 @@ func (r *Reconciler) updateCRD(ctx context.Context, req ctrl.Request) error {
 	if err != nil {
 		return err
 	}
-	if len(svcList.Items) != 1 {
-		return errors.New("multiple services match labels")
+	if len(svcList.Items) == 0 {
+		return fmt.Errorf("no service matches the labels %v", r.SvcLabels)
+	}
+	if len(svcList.Items) > 1 {
+		return fmt.Errorf("multiple services match labels: %v", svcList.Items)
 	}
 	secretList := corev1.SecretList{}
 	err = r.List(context.Background(), &secretList, client.MatchingLabels(r.SecretLabels))
 	if err != nil {
 		return err
 	}
-	if len(secretList.Items) != 1 {
-		return errors.New("multiple secrets match labels")
+	if len(secretList.Items) == 0 {
+		return fmt.Errorf("no secret matches the labels %v", r.SvcLabels)
+	}
+	if len(secretList.Items) > 1 {
+		return fmt.Errorf("multiple secrets match labels: %v", svcList.Items)
 	}
 	updatedResource := &unstructured.Unstructured{}
 	updatedResource.SetGroupVersionKind(crdGVK)

+ 1 - 1
pkg/controllers/crds/suite_test.go

@@ -79,7 +79,7 @@ var _ = BeforeSuite(func() {
 		Log:                    ctrl.Log.WithName("controllers").WithName("CustomResourceDefinition"),
 		SvcLabels:              map[string]string{"foo": "bar"},
 		SecretLabels:           map[string]string{"foo": "bar"},
-		CrdResources:           []string{"externalsecrets.external-secrets.io", "secretstores.external-secrets.io", "clustersecretstores.external-secrets.io"},
+		CrdResources:           []string{"externalsecrets.test.io", "secretstores.test.io", "clustersecretstores.test.io"},
 		CertDir:                "my/cert/dir",
 		CAName:                 "external-secrets",
 		CAOrganization:         "external-secrets",