From d4f2f020414c804bd6037b1a9dd230e1d57a3528 Mon Sep 17 00:00:00 2001 From: Marcin Maciaszczyk Date: Mon, 8 Jul 2024 15:42:38 +0200 Subject: [PATCH] feat: Resync resources in the namespaces on NamespaceCredential changes (#1143) --- ...yments.plural.sh_namespacecredentials.yaml | 3 + .../v1alpha1/clusterrestoretrigger_types.go | 6 - controller/api/v1alpha1/common_types.go | 5 +- .../v1alpha1/namespacecredentials_types.go | 5 + .../api/v1alpha1/zz_generated.deepcopy.go | 20 +--- ...yments.plural.sh_namespacecredentials.yaml | 3 + controller/internal/client/console.go | 3 +- .../internal/controller/cluster_controller.go | 5 +- .../controller/cluster_controller_test.go | 14 +-- .../controller/clusterrestore_controller.go | 5 +- .../clusterrestore_controller_test.go | 2 +- .../clusterrestoretrigger_controller.go | 5 +- .../controller/customstackrun_controller.go | 5 +- .../controller/customstackrun_test.go | 4 +- .../deploymentsettings_controller.go | 5 +- .../controller/deploymentsettings_test.go | 2 +- .../controller/globalservice_controller.go | 14 +-- .../internal/controller/globalservice_test.go | 2 +- .../infrastructurestack_controller.go | 10 +- .../controller/infrastructurestack_test.go | 4 +- .../controller/managednamespace_controller.go | 5 +- .../controller/managenamespace_test.go | 2 +- .../namespace_credentials_controller.go | 7 +- .../notificationrouter_controller.go | 5 +- .../controller/notificationrouter_test.go | 2 +- .../controller/notificationsink_controller.go | 5 +- .../controller/notificationsink_test.go | 2 +- .../controller/pipeline_controller.go | 5 +- .../controller/pipeline_controller_test.go | 4 +- .../controller/pipelinecontext_controller.go | 5 +- .../pipelinecontext_controller_test.go | 2 +- .../prautomationtrigger_controller.go | 5 +- .../controller/service_account_controller.go | 13 ++- .../servicedeployment_controller.go | 12 +- .../servicedeployment_controller_test.go | 6 +- .../credentials/{credentials.go => cache.go} | 17 ++- controller/internal/credentials/fake.go | 4 +- controller/internal/credentials/kubernetes.go | 106 ++++++++++++++++++ controller/internal/utils/kubernetes.go | 20 ---- controller/internal/utils/sha.go | 9 +- ...yments.plural.sh_namespacecredentials.yaml | 3 + 41 files changed, 241 insertions(+), 120 deletions(-) rename controller/internal/credentials/{credentials.go => cache.go} (88%) create mode 100644 controller/internal/credentials/kubernetes.go diff --git a/charts/controller/crds/deployments.plural.sh_namespacecredentials.yaml b/charts/controller/crds/deployments.plural.sh_namespacecredentials.yaml index 183d603405..18317f5c5d 100644 --- a/charts/controller/crds/deployments.plural.sh_namespacecredentials.yaml +++ b/charts/controller/crds/deployments.plural.sh_namespacecredentials.yaml @@ -138,6 +138,9 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + tokenSHA: + description: TokenSHA contains SHA of last token seen. + type: string type: object required: - spec diff --git a/controller/api/v1alpha1/clusterrestoretrigger_types.go b/controller/api/v1alpha1/clusterrestoretrigger_types.go index ba1a58e2e4..3427fea44d 100644 --- a/controller/api/v1alpha1/clusterrestoretrigger_types.go +++ b/controller/api/v1alpha1/clusterrestoretrigger_types.go @@ -29,12 +29,6 @@ type ClusterRestoreTriggerSpec struct { ClusterRestoreRef *corev1.ObjectReference `json:"clusterRestoreRef,omitempty"` } -// ClusterRestoreTriggerStatus defines the observed state of ClusterRestoreTrigger -type ClusterRestoreTriggerStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file -} - //+kubebuilder:object:root=true //+kubebuilder:subresource:status diff --git a/controller/api/v1alpha1/common_types.go b/controller/api/v1alpha1/common_types.go index ee3f32aaaf..015d6ae2ba 100644 --- a/controller/api/v1alpha1/common_types.go +++ b/controller/api/v1alpha1/common_types.go @@ -142,8 +142,9 @@ func (c ConditionMessage) String() string { } const ( - ReadonlyTrueConditionMessage ConditionMessage = "Running in read-only mode" - SynchronizedNotFoundConditionMessage ConditionMessage = "Could not find resource in Console API" + ReadonlyTrueConditionMessage ConditionMessage = "Running in read-only mode" + SynchronizedNotFoundConditionMessage ConditionMessage = "Could not find resource in Console API" + NamespacedCredentialsConditionMessage ConditionMessage = "Using default credentials" ) // GitRef ... diff --git a/controller/api/v1alpha1/namespacecredentials_types.go b/controller/api/v1alpha1/namespacecredentials_types.go index 44d4b604d0..a39aac9eae 100644 --- a/controller/api/v1alpha1/namespacecredentials_types.go +++ b/controller/api/v1alpha1/namespacecredentials_types.go @@ -50,6 +50,11 @@ type NamespaceCredentialsSpec struct { } type NamespaceCredentialsStatus struct { + // TokenSHA contains SHA of last token seen. + // +kubebuilder:validation:Optional + // +kubebuilder:validation:Type:=string + TokenSHA *string `json:"tokenSHA,omitempty"` + // Conditions represent the observations of a NamespaceCredentials current state. // +patchMergeKey=type // +patchStrategy=merge diff --git a/controller/api/v1alpha1/zz_generated.deepcopy.go b/controller/api/v1alpha1/zz_generated.deepcopy.go index e1bc38ffe0..ceb79dec9c 100644 --- a/controller/api/v1alpha1/zz_generated.deepcopy.go +++ b/controller/api/v1alpha1/zz_generated.deepcopy.go @@ -558,21 +558,6 @@ func (in *ClusterRestoreTriggerSpec) DeepCopy() *ClusterRestoreTriggerSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterRestoreTriggerStatus) DeepCopyInto(out *ClusterRestoreTriggerStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterRestoreTriggerStatus. -func (in *ClusterRestoreTriggerStatus) DeepCopy() *ClusterRestoreTriggerStatus { - if in == nil { - return nil - } - out := new(ClusterRestoreTriggerStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) { *out = *in @@ -1755,6 +1740,11 @@ func (in *NamespaceCredentialsSpec) DeepCopy() *NamespaceCredentialsSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceCredentialsStatus) DeepCopyInto(out *NamespaceCredentialsStatus) { *out = *in + if in.TokenSHA != nil { + in, out := &in.TokenSHA, &out.TokenSHA + *out = new(string) + **out = **in + } if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions *out = make([]metav1.Condition, len(*in)) diff --git a/controller/config/crd/bases/deployments.plural.sh_namespacecredentials.yaml b/controller/config/crd/bases/deployments.plural.sh_namespacecredentials.yaml index 183d603405..18317f5c5d 100644 --- a/controller/config/crd/bases/deployments.plural.sh_namespacecredentials.yaml +++ b/controller/config/crd/bases/deployments.plural.sh_namespacecredentials.yaml @@ -138,6 +138,9 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + tokenSHA: + description: TokenSHA contains SHA of last token seen. + type: string type: object required: - spec diff --git a/controller/internal/client/console.go b/controller/internal/client/console.go index 598b1a8751..495261d153 100644 --- a/controller/internal/client/console.go +++ b/controller/internal/client/console.go @@ -5,9 +5,8 @@ import ( "net/http" console "github.com/pluralsh/console-client-go" - "github.com/pluralsh/console/controller/internal/credentials" - "github.com/pluralsh/console/controller/api/v1alpha1" + "github.com/pluralsh/console/controller/internal/credentials" ) type authedTransport struct { diff --git a/controller/internal/controller/cluster_controller.go b/controller/internal/controller/cluster_controller.go index f22fa56eaa..de418e452e 100644 --- a/controller/internal/controller/cluster_controller.go +++ b/controller/internal/controller/cluster_controller.go @@ -42,7 +42,8 @@ type ClusterReconciler struct { // SetupWithManager sets up the controller with the Manager. func (r *ClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.ClusterList))). // Reconcile objects on credentials change. For(&v1alpha1.Cluster{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) } @@ -76,7 +77,7 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req reconcile.Request // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(cluster.SetCondition, nc, err) + credentials.SyncCredentialsInfo(cluster, cluster.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(cluster.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) diff --git a/controller/internal/controller/cluster_controller_test.go b/controller/internal/controller/cluster_controller_test.go index 0b7b923b40..ad14d5dcba 100644 --- a/controller/internal/controller/cluster_controller_test.go +++ b/controller/internal/controller/cluster_controller_test.go @@ -176,7 +176,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), @@ -229,7 +229,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), @@ -283,7 +283,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), @@ -327,7 +327,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), @@ -384,7 +384,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), @@ -432,7 +432,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), @@ -482,7 +482,7 @@ var _ = Describe("Cluster Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), diff --git a/controller/internal/controller/clusterrestore_controller.go b/controller/internal/controller/clusterrestore_controller.go index 227318ea8e..f16914bc7a 100644 --- a/controller/internal/controller/clusterrestore_controller.go +++ b/controller/internal/controller/clusterrestore_controller.go @@ -76,7 +76,7 @@ func (r *ClusterRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(restore.SetCondition, nc, err) + credentials.SyncCredentialsInfo(restore, restore.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(restore.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -149,7 +149,8 @@ func (r *ClusterRestoreReconciler) sync(ctx context.Context, restore *v1alpha1.C // SetupWithManager sets up the controller with the Manager. func (r *ClusterRestoreReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.ClusterRestoreList))). // Reconcile objects on credentials change. For(&v1alpha1.ClusterRestore{}). Complete(r) } diff --git a/controller/internal/controller/clusterrestore_controller_test.go b/controller/internal/controller/clusterrestore_controller_test.go index 4b7bd33076..84258419b9 100644 --- a/controller/internal/controller/clusterrestore_controller_test.go +++ b/controller/internal/controller/clusterrestore_controller_test.go @@ -95,7 +95,7 @@ var _ = Describe("Cluster Restore Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), diff --git a/controller/internal/controller/clusterrestoretrigger_controller.go b/controller/internal/controller/clusterrestoretrigger_controller.go index 3e7ba78162..f5ad8183ed 100644 --- a/controller/internal/controller/clusterrestoretrigger_controller.go +++ b/controller/internal/controller/clusterrestoretrigger_controller.go @@ -94,7 +94,7 @@ func (r *ClusterRestoreTriggerReconciler) Reconcile(ctx context.Context, req ctr // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(trigger.SetCondition, nc, err) + credentials.SyncCredentialsInfo(trigger, trigger.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(trigger.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -149,7 +149,8 @@ func (r *ClusterRestoreTriggerReconciler) isAlreadyExists(ctx context.Context, t // SetupWithManager sets up the controller with the Manager. func (r *ClusterRestoreTriggerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.ClusterRestoreTriggerList))). // Reconcile objects on credentials change. For(&v1alpha1.ClusterRestoreTrigger{}). Complete(r) } diff --git a/controller/internal/controller/customstackrun_controller.go b/controller/internal/controller/customstackrun_controller.go index 8d656abfb0..91925b0624 100644 --- a/controller/internal/controller/customstackrun_controller.go +++ b/controller/internal/controller/customstackrun_controller.go @@ -79,7 +79,7 @@ func (r *CustomStackRunReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(stack.SetCondition, nc, err) + credentials.SyncCredentialsInfo(stack, stack.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(stack.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -141,7 +141,8 @@ func (r *CustomStackRunReconciler) Reconcile(ctx context.Context, req ctrl.Reque // SetupWithManager sets up the controller with the Manager. func (r *CustomStackRunReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.CustomStackRunList))). // Reconcile objects on credentials change. For(&v1alpha1.CustomStackRun{}). Complete(r) } diff --git a/controller/internal/controller/customstackrun_test.go b/controller/internal/controller/customstackrun_test.go index c9ff11279c..8debe4766f 100644 --- a/controller/internal/controller/customstackrun_test.go +++ b/controller/internal/controller/customstackrun_test.go @@ -133,7 +133,7 @@ var _ = Describe("Custom Stack Run Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), @@ -188,7 +188,7 @@ var _ = Describe("Custom Stack Run Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), diff --git a/controller/internal/controller/deploymentsettings_controller.go b/controller/internal/controller/deploymentsettings_controller.go index 8190a8e1ae..3ce6493c4e 100644 --- a/controller/internal/controller/deploymentsettings_controller.go +++ b/controller/internal/controller/deploymentsettings_controller.go @@ -86,7 +86,7 @@ func (r *DeploymentSettingsReconciler) Reconcile(ctx context.Context, req ctrl.R // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(settings.SetCondition, nc, err) + credentials.SyncCredentialsInfo(settings, settings.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(settings.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -131,7 +131,8 @@ func (r *DeploymentSettingsReconciler) Reconcile(ctx context.Context, req ctrl.R // SetupWithManager sets up the controller with the Manager. func (r *DeploymentSettingsReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.DeploymentSettingsList))). // Reconcile objects on credentials change. For(&v1alpha1.DeploymentSettings{}). Complete(r) } diff --git a/controller/internal/controller/deploymentsettings_test.go b/controller/internal/controller/deploymentsettings_test.go index 550c2eb9ba..aa6b4a78eb 100644 --- a/controller/internal/controller/deploymentsettings_test.go +++ b/controller/internal/controller/deploymentsettings_test.go @@ -91,7 +91,7 @@ var _ = Describe("DeploymentSettings Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), diff --git a/controller/internal/controller/globalservice_controller.go b/controller/internal/controller/globalservice_controller.go index 436b55ee73..574521dd3b 100644 --- a/controller/internal/controller/globalservice_controller.go +++ b/controller/internal/controller/globalservice_controller.go @@ -21,7 +21,11 @@ import ( "fmt" console "github.com/pluralsh/console-client-go" + "github.com/pluralsh/console/controller/api/v1alpha1" + consoleclient "github.com/pluralsh/console/controller/internal/client" "github.com/pluralsh/console/controller/internal/credentials" + "github.com/pluralsh/console/controller/internal/errors" + "github.com/pluralsh/console/controller/internal/utils" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -32,11 +36,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" - - "github.com/pluralsh/console/controller/api/v1alpha1" - consoleclient "github.com/pluralsh/console/controller/internal/client" - "github.com/pluralsh/console/controller/internal/errors" - "github.com/pluralsh/console/controller/internal/utils" ) const ( @@ -81,7 +80,7 @@ func (r *GlobalServiceReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(globalService.SetCondition, nc, err) + credentials.SyncCredentialsInfo(globalService, globalService.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(globalService.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -275,7 +274,8 @@ func (r *GlobalServiceReconciler) handleDelete(ctx context.Context, service *v1a // SetupWithManager sets up the controller with the Manager. func (r *GlobalServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.GlobalServiceList))). // Reconcile objects on credentials change. For(&v1alpha1.GlobalService{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Owns(&v1alpha1.ServiceDeployment{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) diff --git a/controller/internal/controller/globalservice_test.go b/controller/internal/controller/globalservice_test.go index d322e85444..2fe4ac250a 100644 --- a/controller/internal/controller/globalservice_test.go +++ b/controller/internal/controller/globalservice_test.go @@ -147,7 +147,7 @@ var _ = Describe("Global Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.SynchronizedConditionType.String(), diff --git a/controller/internal/controller/infrastructurestack_controller.go b/controller/internal/controller/infrastructurestack_controller.go index 3f3ba4d550..898566ac45 100644 --- a/controller/internal/controller/infrastructurestack_controller.go +++ b/controller/internal/controller/infrastructurestack_controller.go @@ -20,11 +20,9 @@ import ( "context" "fmt" + console "github.com/pluralsh/console-client-go" "github.com/pluralsh/console/controller/internal/cache" "github.com/pluralsh/console/controller/internal/credentials" - "sigs.k8s.io/controller-runtime/pkg/controller" - - console "github.com/pluralsh/console-client-go" "github.com/pluralsh/polly/algorithms" "github.com/samber/lo" corev1 "k8s.io/api/core/v1" @@ -34,6 +32,7 @@ import ( "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" @@ -85,7 +84,7 @@ func (r *InfrastructureStackReconciler) Reconcile(ctx context.Context, req ctrl. // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(stack.SetCondition, nc, err) + credentials.SyncCredentialsInfo(stack, stack.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(stack.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -192,7 +191,8 @@ func (r *InfrastructureStackReconciler) Reconcile(ctx context.Context, req ctrl. // SetupWithManager sets up the controller with the Manager. func (r *InfrastructureStackReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.InfrastructureStackList))). // Reconcile objects on credentials change. For(&v1alpha1.InfrastructureStack{}). Complete(r) } diff --git a/controller/internal/controller/infrastructurestack_test.go b/controller/internal/controller/infrastructurestack_test.go index d3c4570b5a..65e9212570 100644 --- a/controller/internal/controller/infrastructurestack_test.go +++ b/controller/internal/controller/infrastructurestack_test.go @@ -173,7 +173,7 @@ var _ = Describe("Infrastructure Stack Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), @@ -227,7 +227,7 @@ var _ = Describe("Infrastructure Stack Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), diff --git a/controller/internal/controller/managednamespace_controller.go b/controller/internal/controller/managednamespace_controller.go index 5da607bf57..8655ee44fb 100644 --- a/controller/internal/controller/managednamespace_controller.go +++ b/controller/internal/controller/managednamespace_controller.go @@ -81,7 +81,7 @@ func (r *ManagedNamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Req // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(managedNamespace.SetCondition, nc, err) + credentials.SyncCredentialsInfo(managedNamespace, managedNamespace.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(managedNamespace.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -144,7 +144,8 @@ func (r *ManagedNamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Req // SetupWithManager sets up the controller with the Manager. func (r *ManagedNamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.ManagedNamespaceList))). // Reconcile objects on credentials change. For(&deploymentsv1alpha1.ManagedNamespace{}). Complete(r) } diff --git a/controller/internal/controller/managenamespace_test.go b/controller/internal/controller/managenamespace_test.go index cfef297184..fa44ffb2ea 100644 --- a/controller/internal/controller/managenamespace_test.go +++ b/controller/internal/controller/managenamespace_test.go @@ -103,7 +103,7 @@ var _ = Describe("ManagedNamespace Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), diff --git a/controller/internal/controller/namespace_credentials_controller.go b/controller/internal/controller/namespace_credentials_controller.go index bac4f200cf..85791cb820 100644 --- a/controller/internal/controller/namespace_credentials_controller.go +++ b/controller/internal/controller/namespace_credentials_controller.go @@ -5,6 +5,8 @@ import ( "fmt" "github.com/pluralsh/console/controller/internal/credentials" + "github.com/samber/lo" + corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" @@ -72,7 +74,9 @@ func (r *NamespaceCredentialsReconciler) Reconcile(ctx context.Context, req reco } // Try to add namespace credentials to cache. - if err := r.CredentialsCache.AddNamespaceCredentials(nc); err != nil { + token, err := r.CredentialsCache.AddNamespaceCredentials(nc) + nc.Status.TokenSHA = lo.ToPtr(utils.HashString(token)) + if err != nil { utils.MarkFalse(nc.SetCondition, v1alpha1.SynchronizedConditionType, v1alpha1.SynchronizedConditionReasonError, err.Error()) return requeue, nil } @@ -87,5 +91,6 @@ func (r *NamespaceCredentialsReconciler) SetupWithManager(mgr ctrl.Manager) erro mgr.GetLogger().Info("Starting reconciler", "reconciler", "namespacecredentials_reconciler") return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.NamespaceCredentials{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Owns(&corev1.Secret{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). Complete(r) } diff --git a/controller/internal/controller/notificationrouter_controller.go b/controller/internal/controller/notificationrouter_controller.go index e42e091f1b..7fe55b0186 100644 --- a/controller/internal/controller/notificationrouter_controller.go +++ b/controller/internal/controller/notificationrouter_controller.go @@ -79,7 +79,7 @@ func (r *NotificationRouterReconciler) Reconcile(ctx context.Context, req ctrl.R // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(notificationRouter.SetCondition, nc, err) + credentials.SyncCredentialsInfo(notificationRouter, notificationRouter.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(notificationRouter.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -237,7 +237,8 @@ func (r *NotificationRouterReconciler) getPipelineID(ctx context.Context, objRef // SetupWithManager sets up the controller with the Manager. func (r *NotificationRouterReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.NotificationRouterList))). // Reconcile objects on credentials change. For(&v1alpha1.NotificationRouter{}). Complete(r) } diff --git a/controller/internal/controller/notificationrouter_test.go b/controller/internal/controller/notificationrouter_test.go index 7d9e15437a..3444ac711e 100644 --- a/controller/internal/controller/notificationrouter_test.go +++ b/controller/internal/controller/notificationrouter_test.go @@ -75,7 +75,7 @@ var _ = Describe("NotificationRouter Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), diff --git a/controller/internal/controller/notificationsink_controller.go b/controller/internal/controller/notificationsink_controller.go index 6a03e06b50..60ed136e8a 100644 --- a/controller/internal/controller/notificationsink_controller.go +++ b/controller/internal/controller/notificationsink_controller.go @@ -79,7 +79,7 @@ func (r *NotificationSinkReconciler) Reconcile(ctx context.Context, req ctrl.Req // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(notificationSink.SetCondition, nc, err) + credentials.SyncCredentialsInfo(notificationSink, notificationSink.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(notificationSink.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -232,7 +232,8 @@ func (r *NotificationSinkReconciler) handleDelete(ctx context.Context, notificat // SetupWithManager sets up the controller with the Manager. func (r *NotificationSinkReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.NotificationSinkList))). // Reconcile objects on credentials change. For(&v1alpha1.NotificationSink{}). Complete(r) } diff --git a/controller/internal/controller/notificationsink_test.go b/controller/internal/controller/notificationsink_test.go index 97f6e45d33..93879a6d88 100644 --- a/controller/internal/controller/notificationsink_test.go +++ b/controller/internal/controller/notificationsink_test.go @@ -76,7 +76,7 @@ var _ = Describe("NotificationSink Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadonlyConditionType.String(), diff --git a/controller/internal/controller/pipeline_controller.go b/controller/internal/controller/pipeline_controller.go index d96cad91bc..06b132da23 100644 --- a/controller/internal/controller/pipeline_controller.go +++ b/controller/internal/controller/pipeline_controller.go @@ -82,7 +82,7 @@ func (r *PipelineReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(pipeline.SetCondition, nc, err) + credentials.SyncCredentialsInfo(pipeline, pipeline.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(pipeline.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -202,7 +202,8 @@ func (r *PipelineReconciler) sync(ctx context.Context, pipeline *v1alpha1.Pipeli // SetupWithManager sets up the controller with the Manager. func (r *PipelineReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.PipelineList))). // Reconcile objects on credentials change. For(&v1alpha1.Pipeline{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) } diff --git a/controller/internal/controller/pipeline_controller_test.go b/controller/internal/controller/pipeline_controller_test.go index dd30204487..d43ea0c9f7 100644 --- a/controller/internal/controller/pipeline_controller_test.go +++ b/controller/internal/controller/pipeline_controller_test.go @@ -251,7 +251,7 @@ var _ = Describe("Pipeline Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.SynchronizedConditionType.String(), @@ -295,7 +295,7 @@ var _ = Describe("Pipeline Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.SynchronizedConditionType.String(), diff --git a/controller/internal/controller/pipelinecontext_controller.go b/controller/internal/controller/pipelinecontext_controller.go index 4fdfa02dce..e0643df5a3 100644 --- a/controller/internal/controller/pipelinecontext_controller.go +++ b/controller/internal/controller/pipelinecontext_controller.go @@ -96,7 +96,7 @@ func (r *PipelineContextReconciler) Reconcile(ctx context.Context, req ctrl.Requ // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(pipelineContext.SetCondition, nc, err) + credentials.SyncCredentialsInfo(pipelineContext, pipelineContext.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(pipelineContext.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -138,7 +138,8 @@ func (r *PipelineContextReconciler) Reconcile(ctx context.Context, req ctrl.Requ // SetupWithManager sets up the controller with the Manager. func (r *PipelineContextReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.PipelineContextList))). // Reconcile objects on credentials change. For(&deploymentsv1alpha1.PipelineContext{}). Complete(r) } diff --git a/controller/internal/controller/pipelinecontext_controller_test.go b/controller/internal/controller/pipelinecontext_controller_test.go index ab3196303b..6ea96ece9a 100644 --- a/controller/internal/controller/pipelinecontext_controller_test.go +++ b/controller/internal/controller/pipelinecontext_controller_test.go @@ -92,7 +92,7 @@ var _ = Describe("Context Pipeline Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.SynchronizedConditionType.String(), diff --git a/controller/internal/controller/prautomationtrigger_controller.go b/controller/internal/controller/prautomationtrigger_controller.go index b8cc4eacca..b182502edd 100644 --- a/controller/internal/controller/prautomationtrigger_controller.go +++ b/controller/internal/controller/prautomationtrigger_controller.go @@ -93,7 +93,7 @@ func (r *PrAutomationTriggerReconciler) Reconcile(ctx context.Context, req ctrl. // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(trigger.SetCondition, nc, err) + credentials.SyncCredentialsInfo(trigger, trigger.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(trigger.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -131,7 +131,8 @@ func isAlreadyExists(trigger *v1alpha1.PrAutomationTrigger) bool { // SetupWithManager sets up the controller with the Manager. func (r *PrAutomationTriggerReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.PrAutomationTriggerList))). // Reconcile objects on credentials change. For(&v1alpha1.PrAutomationTrigger{}). Complete(r) } diff --git a/controller/internal/controller/service_account_controller.go b/controller/internal/controller/service_account_controller.go index 16f7089599..b51bae6a1e 100644 --- a/controller/internal/controller/service_account_controller.go +++ b/controller/internal/controller/service_account_controller.go @@ -89,8 +89,17 @@ func (r *ServiceAccountReconciler) Reconcile(ctx context.Context, req reconcile. return ctrl.Result{}, err } - // Mark token as not ready if found any changes in the resource - if changed { + // Check if secret with token exists + hasTokenSecret := false + if sa.Spec.TokenSecretRef != nil { + secret, _ := utils.GetSecret(ctx, r.Client, sa.Spec.TokenSecretRef) + if secret != nil { + _, hasTokenSecret = secret.Data[credentials.CredentialsSecretTokenKey] + } + } + + // Mark token as not ready if found any changes in the resource or if secret doesn't exist + if changed || !hasTokenSecret { utils.MarkCondition(sa.SetCondition, v1alpha1.ReadyTokenConditionType, v1.ConditionFalse, v1alpha1.ReadyTokenConditionReasonError, "token not synchronized yet") } diff --git a/controller/internal/controller/servicedeployment_controller.go b/controller/internal/controller/servicedeployment_controller.go index 8c67058006..05fe3cc16f 100644 --- a/controller/internal/controller/servicedeployment_controller.go +++ b/controller/internal/controller/servicedeployment_controller.go @@ -5,13 +5,11 @@ import ( "fmt" "sort" - "github.com/pluralsh/console/controller/internal/cache" - "github.com/pluralsh/console/controller/internal/credentials" - "sigs.k8s.io/controller-runtime/pkg/controller" - console "github.com/pluralsh/console-client-go" "github.com/pluralsh/console/controller/api/v1alpha1" + "github.com/pluralsh/console/controller/internal/cache" consoleclient "github.com/pluralsh/console/controller/internal/client" + "github.com/pluralsh/console/controller/internal/credentials" "github.com/pluralsh/console/controller/internal/errors" "github.com/pluralsh/console/controller/internal/utils" "github.com/pluralsh/polly/algorithms" @@ -24,6 +22,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" @@ -70,7 +69,7 @@ func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (_ // Switch to namespace credentials if configured. This has to be done before sending any request to the console. nc, err := r.ConsoleClient.UseCredentials(req.Namespace, r.CredentialsCache) - utils.MarkCredentialsCondition(service.SetCondition, nc, err) + credentials.SyncCredentialsInfo(service, service.SetCondition, nc, err) if err != nil { logger.Error(err, "failed to use namespace credentials", "namespaceCredentials", nc, "namespacedName", req.NamespacedName) utils.MarkCondition(service.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, fmt.Sprintf("failed to use %s namespace credentials: %s", nc, err.Error())) @@ -546,7 +545,8 @@ func isServiceReady(components []v1alpha1.ServiceComponent) bool { // SetupWithManager sets up the controller with the Manager. func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Hard requirement for current namespace credentials implementation. + WithOptions(controller.Options{MaxConcurrentReconciles: 1}). // Requirement for credentials implementation. + Watches(&v1alpha1.NamespaceCredentials{}, credentials.OnCredentialsChange(r.Client, new(v1alpha1.ServiceDeploymentList))). // Reconcile objects on credentials change. For(&v1alpha1.ServiceDeployment{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Owns(&corev1.Secret{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). Owns(&corev1.ConfigMap{}, builder.WithPredicates(predicate.ResourceVersionChangedPredicate{})). diff --git a/controller/internal/controller/servicedeployment_controller_test.go b/controller/internal/controller/servicedeployment_controller_test.go index c8b199cde1..c83db96ef5 100644 --- a/controller/internal/controller/servicedeployment_controller_test.go +++ b/controller/internal/controller/servicedeployment_controller_test.go @@ -132,7 +132,7 @@ var _ = Describe("Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), @@ -192,7 +192,7 @@ var _ = Describe("Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), @@ -258,7 +258,7 @@ var _ = Describe("Service Controller", Ordered, func() { Type: v1alpha1.NamespacedCredentialsConditionType.String(), Status: metav1.ConditionFalse, Reason: v1alpha1.NamespacedCredentialsReasonDefault.String(), - Message: "using default credentials", + Message: v1alpha1.NamespacedCredentialsConditionMessage.String(), }, { Type: v1alpha1.ReadyConditionType.String(), diff --git a/controller/internal/credentials/credentials.go b/controller/internal/credentials/cache.go similarity index 88% rename from controller/internal/credentials/credentials.go rename to controller/internal/credentials/cache.go index 3f91c6bc30..e291a11c4b 100644 --- a/controller/internal/credentials/credentials.go +++ b/controller/internal/credentials/cache.go @@ -16,13 +16,14 @@ import ( const ( CredentialsSecretTokenKey = "token" + DefaultCredentialsKey = "" ) type NamespaceCredentialsCache interface { Init() error Wipe() Reset() error - AddNamespaceCredentials(nc *v1alpha1.NamespaceCredentials) error + AddNamespaceCredentials(nc *v1alpha1.NamespaceCredentials) (string, error) RemoveNamespaceCredentials(nc *v1alpha1.NamespaceCredentials) GetNamespaceCredentials(namespace string) NamespaceCredentials } @@ -37,6 +38,7 @@ func NewNamespaceCredentialsCache(defaultConsoleToken string, scheme *runtime.Sc cache: cmap.New[NamespaceCredentials](), ctx: context.Background(), client: c, + scheme: scheme, defaultConsoleToken: defaultConsoleToken, } @@ -51,6 +53,7 @@ type namespaceCredentialsCache struct { cache cmap.ConcurrentMap[string, NamespaceCredentials] ctx context.Context client client.Client + scheme *runtime.Scheme defaultConsoleToken string } @@ -62,7 +65,7 @@ func (in *namespaceCredentialsCache) Init() error { for _, nc := range list.Items { // Ignore errors during init. These are used in the controller to add them to NamespaceCredentials conditions. - _ = in.AddNamespaceCredentials(&nc) + _, _ = in.AddNamespaceCredentials(&nc) } return nil @@ -79,7 +82,7 @@ func (in *namespaceCredentialsCache) Reset() error { // AddNamespaceCredentials registers custom namespace credentials. // Even if errors occur while reading token, namespaces will be registered as ones using custom credentials. -func (in *namespaceCredentialsCache) AddNamespaceCredentials(namespaceCredentials *v1alpha1.NamespaceCredentials) error { +func (in *namespaceCredentialsCache) AddNamespaceCredentials(namespaceCredentials *v1alpha1.NamespaceCredentials) (string, error) { token, err := in.getNamespaceCredentialsToken(namespaceCredentials) for _, namespace := range namespaceCredentials.Spec.Namespaces { nc, ok := in.cache.Get(namespace) @@ -94,7 +97,7 @@ func (in *namespaceCredentialsCache) AddNamespaceCredentials(namespaceCredential Err: err, }) } - return err + return token, err } func (in *namespaceCredentialsCache) getNamespaceCredentialsToken(nc *v1alpha1.NamespaceCredentials) (string, error) { @@ -103,6 +106,10 @@ func (in *namespaceCredentialsCache) getNamespaceCredentialsToken(nc *v1alpha1.N return "", fmt.Errorf("failed to get secret: %s", err) } + if err := tryAddOwnerRef(in.ctx, in.client, nc, secret, in.scheme); err != nil { + return "", err + } + token, ok := secret.Data[CredentialsSecretTokenKey] if !ok { return "", fmt.Errorf("did not found %s data in a secret", CredentialsSecretTokenKey) @@ -122,7 +129,7 @@ func (in *namespaceCredentialsCache) GetNamespaceCredentials(namespace string) N return nc } - return NamespaceCredentials{Token: in.defaultConsoleToken} + return NamespaceCredentials{Token: in.defaultConsoleToken, NamespaceCredentials: DefaultCredentialsKey} } type NamespaceCredentials struct { diff --git a/controller/internal/credentials/fake.go b/controller/internal/credentials/fake.go index 029e0590f0..7bbfb808c1 100644 --- a/controller/internal/credentials/fake.go +++ b/controller/internal/credentials/fake.go @@ -4,10 +4,10 @@ import ( "context" cmap "github.com/orcaman/concurrent-map/v2" - k8sclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client" ) -func FakeNamespaceCredentialsCache(client k8sclient.Client) NamespaceCredentialsCache { +func FakeNamespaceCredentialsCache(client client.Client) NamespaceCredentialsCache { return &namespaceCredentialsCache{ cache: cmap.New[NamespaceCredentials](), ctx: context.Background(), diff --git a/controller/internal/credentials/kubernetes.go b/controller/internal/credentials/kubernetes.go new file mode 100644 index 0000000000..ac2e19b2a5 --- /dev/null +++ b/controller/internal/credentials/kubernetes.go @@ -0,0 +1,106 @@ +package credentials + +import ( + "context" + "fmt" + "reflect" + + "github.com/pluralsh/console/controller/api/v1alpha1" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + namespacedCredentialsAnnotation = "deployments.plural.sh/namespaced-credentials" +) + +func OnCredentialsChange[T client.ObjectList](c client.Client, list T) handler.EventHandler { + return handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, creds client.Object) []reconcile.Request { + _ = c.List(ctx, list) + items, _ := meta.ExtractList(list) + requests := make([]reconcile.Request, 0, len(items)) + for _, item := range items { + o := item.(client.Object) + if hasCredentialsAnnotation(o.GetAnnotations(), creds.GetName()) { + requests = append(requests, reconcile.Request{NamespacedName: types.NamespacedName{Name: o.GetName(), Namespace: o.GetNamespace()}}) + } + } + return requests + }) +} + +func hasCredentialsAnnotation(annotations map[string]string, creds string) bool { + annotation, ok := annotations[namespacedCredentialsAnnotation] + return ok && annotation == creds +} + +func SyncCredentialsInfo(object client.Object, conditionSetter func(condition metav1.Condition), creds string, err error) { + syncCredentialsAnnotation(object, creds) + syncCredentialsCondition(conditionSetter, creds, err) +} + +func syncCredentialsAnnotation(obj client.Object, creds string) { + annotations := obj.GetAnnotations() + + if creds != DefaultCredentialsKey { + annotations[namespacedCredentialsAnnotation] = creds + } else { + delete(annotations, namespacedCredentialsAnnotation) + } + + obj.SetAnnotations(annotations) +} + +func syncCredentialsCondition(conditionSetter func(condition metav1.Condition), creds string, err error) { + condition := metav1.Condition{Type: v1alpha1.NamespacedCredentialsConditionType.String()} + + if creds != DefaultCredentialsKey { + condition.Reason = v1alpha1.NamespacedCredentialsReason.String() + condition.Status = metav1.ConditionTrue + condition.Message = fmt.Sprintf("Using %s credentials", creds) + } else { + condition.Reason = v1alpha1.NamespacedCredentialsReasonDefault.String() + condition.Status = metav1.ConditionFalse + condition.Message = v1alpha1.NamespacedCredentialsConditionMessage.String() + } + + if err != nil { + condition.Message += fmt.Sprintf(", got error: %s", err.Error()) + } + + conditionSetter(condition) +} + +func tryAddOwnerRef(ctx context.Context, c client.Client, owner client.Object, object client.Object, scheme *runtime.Scheme) error { + key := client.ObjectKeyFromObject(object) + + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + if err := c.Get(ctx, key, object); err != nil { + return err + } + + if owner.GetDeletionTimestamp() != nil || object.GetDeletionTimestamp() != nil { + return nil + } + + original := object.DeepCopyObject().(client.Object) + + err := controllerutil.SetOwnerReference(owner, object, scheme) + if err != nil { + return err + } + + if reflect.DeepEqual(original.GetOwnerReferences(), object.GetOwnerReferences()) { + return nil + } + + return c.Patch(ctx, object, client.MergeFromWithOptions(original, client.MergeFromWithOptimisticLock{})) + }) +} diff --git a/controller/internal/utils/kubernetes.go b/controller/internal/utils/kubernetes.go index d62c67eaa6..b58f5e6b7f 100644 --- a/controller/internal/utils/kubernetes.go +++ b/controller/internal/utils/kubernetes.go @@ -259,26 +259,6 @@ func MarkFalse(set func(metav1.Condition), conditionType v1alpha1.ConditionType, MarkCondition(set, conditionType, metav1.ConditionFalse, conditionReason, message, messageArgs...) } -func MarkCredentialsCondition(set func(condition metav1.Condition), namespacedCredentials string, err error) { - condition := metav1.Condition{Type: v1alpha1.NamespacedCredentialsConditionType.String()} - - if namespacedCredentials != "" { - condition.Reason = v1alpha1.NamespacedCredentialsReason.String() - condition.Status = metav1.ConditionTrue - condition.Message = fmt.Sprintf("using %s credentials", namespacedCredentials) - } else { - condition.Reason = v1alpha1.NamespacedCredentialsReasonDefault.String() - condition.Status = metav1.ConditionFalse - condition.Message = "using default credentials" - } - - if err != nil { - condition.Message += fmt.Sprintf(", got error: %s", err.Error()) - } - - set(condition) -} - func SyncCondition(set func(condition metav1.Condition), conditionType, status, reason, message *string) { condition := metav1.Condition{} diff --git a/controller/internal/utils/sha.go b/controller/internal/utils/sha.go index 481b5de255..7f73198277 100644 --- a/controller/internal/utils/sha.go +++ b/controller/internal/utils/sha.go @@ -6,11 +6,16 @@ import ( "encoding/json" ) -func HashObject(any interface{}) (string, error) { - out, err := json.Marshal(any) +func HashObject(a any) (string, error) { + out, err := json.Marshal(a) if err != nil { return "", err } sha := sha256.Sum256(out) return base32.StdEncoding.EncodeToString(sha[:]), nil } + +func HashString(s string) string { + sha := sha256.Sum256([]byte(s)) + return base32.StdEncoding.EncodeToString(sha[:]) +} diff --git a/plural/helm/console/crds/deployments.plural.sh_namespacecredentials.yaml b/plural/helm/console/crds/deployments.plural.sh_namespacecredentials.yaml index 183d603405..18317f5c5d 100644 --- a/plural/helm/console/crds/deployments.plural.sh_namespacecredentials.yaml +++ b/plural/helm/console/crds/deployments.plural.sh_namespacecredentials.yaml @@ -138,6 +138,9 @@ spec: x-kubernetes-list-map-keys: - type x-kubernetes-list-type: map + tokenSHA: + description: TokenSHA contains SHA of last token seen. + type: string type: object required: - spec