From 30037f4c39a935a42433b9acdd8935bde81891d6 Mon Sep 17 00:00:00 2001 From: Michael Strassberger Date: Wed, 15 Jan 2025 17:54:07 +0100 Subject: [PATCH] Add ServiceAccountService Add a new ServiceAccount service to manage service accounts in Kubernetes. * **service/k8s/serviceaccount.go** - Define the `ServiceAccount` interface with methods to manage service accounts. - Implement the `ServiceAccountService` struct that satisfies the `ServiceAccount` interface. - Add methods to create, update, get, and delete service accounts. * **operator/redisfailover/service/generator.go** - If no user provided service account is used generate a new ServiceAccount via the new ServiceAccountService --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/Saremox/redis-operator?shareId=XXXX-XXXX-XXXX-XXXX). --- operator/redisfailover/service/generator.go | 14 +++ service/k8s/k8s.go | 3 + service/k8s/serviceaccount.go | 99 +++++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 service/k8s/serviceaccount.go diff --git a/operator/redisfailover/service/generator.go b/operator/redisfailover/service/generator.go index 93bc0bfce..522242945 100644 --- a/operator/redisfailover/service/generator.go +++ b/operator/redisfailover/service/generator.go @@ -519,6 +519,11 @@ func generateSentinelDeployment(rf *redisfailoverv1.RedisFailover, labels map[st volumeMounts := getSentinelVolumeMounts(rf) volumes := getSentinelVolumes(rf, configMapName) + if rf.Spec.Sentinel.ServiceAccountName == "" { + sa := generateSentinelServiceAccount(rf) + rf.Spec.Sentinel.ServiceAccountName = sa.Name + } + sd := &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -1201,3 +1206,12 @@ func envExists(env []corev1.EnvVar, name string) bool { } return false } + +func generateSentinelServiceAccount(rf *redisfailoverv1.RedisFailover) *corev1.ServiceAccount { + return &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: rf.Name, + Namespace: rf.Namespace, + }, + } +} diff --git a/service/k8s/k8s.go b/service/k8s/k8s.go index 53d060d9c..231582d99 100644 --- a/service/k8s/k8s.go +++ b/service/k8s/k8s.go @@ -20,6 +20,7 @@ type Services interface { RBAC Deployment StatefulSet + ServiceAccount } type services struct { @@ -32,6 +33,7 @@ type services struct { RBAC Deployment StatefulSet + ServiceAccount } // New returns a new Kubernetes service. @@ -46,5 +48,6 @@ func New(kubecli kubernetes.Interface, crdcli redisfailoverclientset.Interface, RBAC: NewRBACService(kubecli, logger, metricsRecorder), Deployment: NewDeploymentService(kubecli, logger, metricsRecorder), StatefulSet: NewStatefulSetService(kubecli, logger, metricsRecorder), + ServiceAccount: NewServiceAccountService(kubecli, logger, metricsRecorder), } } diff --git a/service/k8s/serviceaccount.go b/service/k8s/serviceaccount.go new file mode 100644 index 000000000..0a4c004e4 --- /dev/null +++ b/service/k8s/serviceaccount.go @@ -0,0 +1,99 @@ +package k8s + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/saremox/redis-operator/log" + "github.com/saremox/redis-operator/metrics" +) + +// ServiceAccount the ServiceAccount service that knows how to interact with k8s to manage them +type ServiceAccount interface { + GetServiceAccount(namespace string, name string) (*corev1.ServiceAccount, error) + CreateServiceAccount(namespace string, serviceAccount *corev1.ServiceAccount) error + UpdateServiceAccount(namespace string, serviceAccount *corev1.ServiceAccount) error + CreateOrUpdateServiceAccount(namespace string, serviceAccount *corev1.ServiceAccount) error + DeleteServiceAccount(namespace string, name string) error + ListServiceAccounts(namespace string) (*corev1.ServiceAccountList, error) +} + +// ServiceAccountService is the serviceAccount service implementation using API calls to kubernetes. +type ServiceAccountService struct { + kubeClient kubernetes.Interface + logger log.Logger + metricsRecorder metrics.Recorder +} + +// NewServiceAccountService returns a new ServiceAccount KubeService. +func NewServiceAccountService(kubeClient kubernetes.Interface, logger log.Logger, metricsRecorder metrics.Recorder) *ServiceAccountService { + logger = logger.With("service", "k8s.serviceAccount") + return &ServiceAccountService{ + kubeClient: kubeClient, + logger: logger, + metricsRecorder: metricsRecorder, + } +} + +func (s *ServiceAccountService) GetServiceAccount(namespace string, name string) (*corev1.ServiceAccount, error) { + serviceAccount, err := s.kubeClient.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{}) + recordMetrics(namespace, "ServiceAccount", name, "GET", err, s.metricsRecorder) + if err != nil { + return nil, err + } + return serviceAccount, err +} + +func (s *ServiceAccountService) CreateServiceAccount(namespace string, serviceAccount *corev1.ServiceAccount) error { + _, err := s.kubeClient.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), serviceAccount, metav1.CreateOptions{}) + recordMetrics(namespace, "ServiceAccount", serviceAccount.GetName(), "CREATE", err, s.metricsRecorder) + if err != nil { + return err + } + s.logger.WithField("namespace", namespace).WithField("serviceAccountName", serviceAccount.Name).Debugf("serviceAccount created") + return nil +} + +func (s *ServiceAccountService) UpdateServiceAccount(namespace string, serviceAccount *corev1.ServiceAccount) error { + _, err := s.kubeClient.CoreV1().ServiceAccounts(namespace).Update(context.TODO(), serviceAccount, metav1.UpdateOptions{}) + recordMetrics(namespace, "ServiceAccount", serviceAccount.GetName(), "UPDATE", err, s.metricsRecorder) + if err != nil { + return err + } + s.logger.WithField("namespace", namespace).WithField("serviceAccountName", serviceAccount.Name).Debugf("serviceAccount updated") + return nil +} + +func (s *ServiceAccountService) CreateOrUpdateServiceAccount(namespace string, serviceAccount *corev1.ServiceAccount) error { + storedServiceAccount, err := s.GetServiceAccount(namespace, serviceAccount.Name) + if err != nil { + // If no resource we need to create. + if errors.IsNotFound(err) { + return s.CreateServiceAccount(namespace, serviceAccount) + } + return err + } + + // Already exists, need to Update. + // Set the correct resource version to ensure we are on the latest version. This way the only valid + // namespace is our spec(https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#concurrency-control-and-consistency), + // we will replace the current namespace state. + serviceAccount.ResourceVersion = storedServiceAccount.ResourceVersion + return s.UpdateServiceAccount(namespace, serviceAccount) +} + +func (s *ServiceAccountService) DeleteServiceAccount(namespace string, name string) error { + err := s.kubeClient.CoreV1().ServiceAccounts(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) + recordMetrics(namespace, "ServiceAccount", name, "DELETE", err, s.metricsRecorder) + return err +} + +func (s *ServiceAccountService) ListServiceAccounts(namespace string) (*corev1.ServiceAccountList, error) { + serviceAccountList, err := s.kubeClient.CoreV1().ServiceAccounts(namespace).List(context.TODO(), metav1.ListOptions{}) + recordMetrics(namespace, "ServiceAccount", metrics.NOT_APPLICABLE, "LIST", err, s.metricsRecorder) + return serviceAccountList, err +}