Browse Source

Feat: Status for secretsink with NotImplemented

TODO: Add unit tests and validation tests

Signed-off-by: William Young <will.young@engineerbetter.com>
Signed-off-by: Dominic Meddick <dom.meddick@engineerbetter.com>
Signed-off-by: Gustavo Carvalho <gustavo.carvalho@container-solutions.com>
William Young 4 years ago
parent
commit
a170da0a63

+ 8 - 0
apis/externalsecrets/v1alpha1/register.go

@@ -60,8 +60,16 @@ var (
 	ClusterSecretStoreGroupVersionKind = SchemeGroupVersion.WithKind(ClusterSecretStoreKind)
 )
 
+var (
+	SecretSinkKind             = reflect.TypeOf(SecretSink{}).Name()
+	SecretSinkGroupKind        = schema.GroupKind{Group: Group, Kind: SecretSinkKind}.String()
+	SecretSinkKindAPIVersion   = SecretSinkKind + "." + SchemeGroupVersion.String()
+	SecretSinkGroupVersionKind = SchemeGroupVersion.WithKind(SecretSinkKind)
+)
+
 func init() {
 	SchemeBuilder.Register(&ExternalSecret{}, &ExternalSecretList{})
 	SchemeBuilder.Register(&SecretStore{}, &SecretStoreList{})
 	SchemeBuilder.Register(&ClusterSecretStore{}, &ClusterSecretStoreList{})
+	SchemeBuilder.Register(&SecretSink{}, &SecretSinkList{})
 }

+ 3 - 6
apis/externalsecrets/v1alpha1/secretsink_types.go

@@ -61,10 +61,7 @@ type SecretSinkData struct {
 type SecretSinkConditionType string
 
 const (
-	SecretSinkSynced         SecretSinkConditionType = "Synced"
-	SecretSinkNotSynced      SecretSinkConditionType = "NotSynced"
-	SecretSinkError          SecretSinkConditionType = "Error"
-	SecretSinkNotImplemented SecretSinkConditionType = "NotImplemented"
+	SecretSinkReady SecretSinkConditionType = "Ready"
 )
 
 // SecretSinkStatusCondition indicates the status of the SecretSink.
@@ -82,7 +79,6 @@ type SecretSinkStatusCondition struct {
 	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
 }
 
-// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
 // SecretSinkStatus indicates the history of the status of SecretSink.
 type SecretSinkStatus struct {
 	// +nullable
@@ -100,6 +96,7 @@ type SecretSinkStatus struct {
 // +kubebuilder:object:root=true
 // +kubebuilder:storageversion
 // Secretsinks is the Schema for the secretsinks API.
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
 // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
 // +kubebuilder:subresource:status
 // +kubebuilder:resource:scope=Namespaced,categories={secretsinks}
@@ -113,7 +110,7 @@ type SecretSink struct {
 }
 
 // +kubebuilder:object:root=true
-
+// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
 // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
 // SecretSinkList contains a list of SecretSink resources.
 type SecretSinkList struct {

+ 11 - 0
cmd/root.go

@@ -37,6 +37,7 @@ import (
 	esv1beta1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1beta1"
 	"github.com/external-secrets/external-secrets/pkg/controllers/clusterexternalsecret"
 	"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret"
+	"github.com/external-secrets/external-secrets/pkg/controllers/secretsink"
 	"github.com/external-secrets/external-secrets/pkg/controllers/secretstore"
 )
 
@@ -146,6 +147,16 @@ var rootCmd = &cobra.Command{
 			setupLog.Error(err, errCreateController, "controller", "ExternalSecret")
 			os.Exit(1)
 		}
+		if err = (&secretsink.Reconciler{
+			Client:          mgr.GetClient(),
+			Log:             ctrl.Log.WithName("controllers").WithName("SecretSink"),
+			Scheme:          mgr.GetScheme(),
+			ControllerClass: controllerClass,
+			RequeueInterval: time.Hour,
+		}).SetupWithManager(mgr); err != nil {
+			setupLog.Error(err, errCreateController, "controller", "SecretSink")
+			os.Exit(1)
+		}
 		if enableClusterExternalSecretReconciler {
 			if err = (&clusterexternalsecret.Reconciler{
 				Client:          mgr.GetClient(),

+ 3 - 0
config/crds/bases/external-secrets.io_secretsinks.yaml

@@ -17,6 +17,9 @@ spec:
   scope: Namespaced
   versions:
   - additionalPrinterColumns:
+    - jsonPath: .metadata.creationTimestamp
+      name: AGE
+      type: date
     - jsonPath: .status.conditions[?(@.type=="Ready")].reason
       name: Status
       type: string

+ 3 - 0
deploy/crds/bundle.yaml

@@ -3033,6 +3033,9 @@ spec:
   scope: Namespaced
   versions:
     - additionalPrinterColumns:
+        - jsonPath: .metadata.creationTimestamp
+          name: AGE
+          type: date
         - jsonPath: .status.conditions[?(@.type=="Ready")].reason
           name: Status
           type: string

+ 127 - 0
pkg/controllers/secretsink/secretsink_controller.go

@@ -0,0 +1,127 @@
+/*
+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 secretsink
+
+import (
+	"context"
+	"time"
+
+	"github.com/go-logr/logr"
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/record"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+)
+
+const (
+	errNotImplemented = "secret sink not implemented"
+	errPatchStatus    = "error merging"
+)
+
+type Reconciler struct {
+	client.Client
+	Log             logr.Logger
+	Scheme          *runtime.Scheme
+	recorder        record.EventRecorder
+	RequeueInterval time.Duration
+	ControllerClass string
+}
+
+func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+	log := r.Log.WithValues("secretsink", req.NamespacedName)
+	var ss esapi.SecretSink
+	err := r.Get(ctx, req.NamespacedName, &ss)
+	if apierrors.IsNotFound(err) {
+		return ctrl.Result{}, nil
+	} else if err != nil {
+		log.Error(err, "unable to get SecretSink")
+		return ctrl.Result{}, err
+	}
+	p := client.MergeFrom(ss.DeepCopy())
+	defer func() {
+		err := r.Client.Status().Patch(ctx, &ss, p)
+		if err != nil {
+			log.Error(err, errPatchStatus)
+		}
+	}()
+	cond := NewSecretSinkCondition(esapi.SecretSinkReady, v1.ConditionFalse, "NotImplementedError", errNotImplemented)
+	ss = SetSecretSinkCondition(ss, *cond)
+	// Set status for SecretSink
+	return ctrl.Result{}, nil
+}
+
+func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
+	r.recorder = mgr.GetEventRecorderFor("secret-sink")
+
+	return ctrl.NewControllerManagedBy(mgr).
+		For(&esapi.SecretSink{}).
+		Complete(r)
+}
+
+func NewSecretSinkCondition(condType esapi.SecretSinkConditionType, status v1.ConditionStatus, reason, message string) *esapi.SecretSinkStatusCondition {
+	return &esapi.SecretSinkStatusCondition{
+		Type:               condType,
+		Status:             status,
+		LastTransitionTime: metav1.Now(),
+		Reason:             reason,
+		Message:            message,
+	}
+}
+
+func SetSecretSinkCondition(gs esapi.SecretSink, condition esapi.SecretSinkStatusCondition) esapi.SecretSink {
+	status := gs.Status
+	currentCond := GetSecretSinkCondition(status, condition.Type)
+	if currentCond != nil && currentCond.Status == condition.Status &&
+		currentCond.Reason == condition.Reason && currentCond.Message == condition.Message {
+		return gs
+	}
+
+	// Do not update lastTransitionTime if the status of the condition doesn't change.
+	if currentCond != nil && currentCond.Status == condition.Status {
+		condition.LastTransitionTime = currentCond.LastTransitionTime
+	}
+
+	status.Conditions = append(filterOutCondition(status.Conditions, condition.Type), condition)
+	gs.Status = status
+	return gs
+}
+
+// filterOutCondition returns an empty set of conditions with the provided type.
+func filterOutCondition(conditions []esapi.SecretSinkStatusCondition, condType esapi.SecretSinkConditionType) []esapi.SecretSinkStatusCondition {
+	newConditions := make([]esapi.SecretSinkStatusCondition, 0, len(conditions))
+	for _, c := range conditions {
+		if c.Type == condType {
+			continue
+		}
+		newConditions = append(newConditions, c)
+	}
+	return newConditions
+}
+
+// GetSecretStoreCondition returns the condition with the provided type.
+func GetSecretSinkCondition(status esapi.SecretSinkStatus, condType esapi.SecretSinkConditionType) *esapi.SecretSinkStatusCondition {
+	for i := range status.Conditions {
+		c := status.Conditions[i]
+		if c.Type == condType {
+			return &c
+		}
+	}
+	return nil
+}

+ 15 - 0
pkg/controllers/secretsink/secretsink_controller_test.go

@@ -0,0 +1,15 @@
+/*
+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 secretsink

+ 102 - 0
pkg/controllers/secretsink/suite_test.go

@@ -0,0 +1,102 @@
+/*
+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 secretsink
+
+import (
+	"context"
+	"path/filepath"
+	"testing"
+	"time"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"go.uber.org/zap/zapcore"
+	"k8s.io/client-go/kubernetes/scheme"
+	"k8s.io/client-go/rest"
+	ctrl "sigs.k8s.io/controller-runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/envtest"
+	logf "sigs.k8s.io/controller-runtime/pkg/log"
+	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+
+	esv1alpha1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1alpha1"
+)
+
+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
+
+var cfg *rest.Config
+var k8sClient client.Client
+var testEnv *envtest.Environment
+var cancel context.CancelFunc
+
+func TestAPIs(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Controller Suite")
+}
+
+var _ = BeforeSuite(func() {
+	log := zap.New(zap.WriteTo(GinkgoWriter), zap.Level(zapcore.DebugLevel))
+
+	logf.SetLogger(log)
+
+	By("bootstrapping test environment")
+	testEnv = &envtest.Environment{
+		CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "deploy", "crds")},
+	}
+
+	var ctx context.Context
+	ctx, cancel = context.WithCancel(context.Background())
+
+	var err error
+	cfg, err = testEnv.Start()
+	Expect(err).ToNot(HaveOccurred())
+	Expect(cfg).ToNot(BeNil())
+
+	err = esv1alpha1.AddToScheme(scheme.Scheme)
+	Expect(err).NotTo(HaveOccurred())
+
+	k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
+		Scheme:             scheme.Scheme,
+		MetricsBindAddress: "0", // avoid port collision when testing
+	})
+	Expect(err).ToNot(HaveOccurred())
+
+	// do not use k8sManager.GetClient()
+	// see https://github.com/kubernetes-sigs/controller-runtime/issues/343#issuecomment-469435686
+	k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
+	Expect(k8sClient).ToNot(BeNil())
+	Expect(err).ToNot(HaveOccurred())
+
+	err = (&Reconciler{
+		Client:          k8sClient,
+		Scheme:          k8sManager.GetScheme(),
+		Log:             ctrl.Log.WithName("controllers").WithName("SecretSink"),
+		RequeueInterval: time.Second,
+	}).SetupWithManager(k8sManager)
+	Expect(err).ToNot(HaveOccurred())
+
+	go func() {
+		defer GinkgoRecover()
+		Expect(k8sManager.Start(ctx)).ToNot(HaveOccurred())
+	}()
+})
+
+var _ = AfterSuite(func() {
+	By("tearing down the test environment")
+	cancel() // stop manager
+	err := testEnv.Stop()
+	Expect(err).ToNot(HaveOccurred())
+})