| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372 |
- /*
- 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 addon
- import (
- "context"
- "fmt"
- "time"
- "github.com/external-secrets/external-secrets-e2e/framework/log"
- appsv1 "k8s.io/api/core/v1"
- rbacv1 "k8s.io/api/rbac/v1"
- "k8s.io/apimachinery/pkg/api/errors"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/wait"
- "k8s.io/client-go/kubernetes"
- )
- const (
- v2Namespace = "external-secrets-system"
- v2ControllerName = "external-secrets-v2"
- v2ProviderName = "kubernetes-provider"
- )
- // ESOV2 is an addon that installs External Secrets Operator V2 with Kubernetes provider.
- type ESOV2 struct {
- config *Config
- kubeClientSet kubernetes.Interface
- }
- // Setup installs ESO V2 controller and Kubernetes provider.
- func (e *ESOV2) Setup(config *Config) error {
- e.config = config
- e.kubeClientSet = config.KubeClientSet
- log.Logf("installing External Secrets Operator V2")
- // Create namespace
- if err := e.createNamespace(); err != nil {
- return fmt.Errorf("failed to create namespace: %w", err)
- }
- // Install CRDs
- if err := e.installCRDs(); err != nil {
- return fmt.Errorf("failed to install CRDs: %w", err)
- }
- // Create RBAC
- if err := e.createRBAC(); err != nil {
- return fmt.Errorf("failed to create RBAC: %w", err)
- }
- // Deploy controller
- if err := e.deployController(); err != nil {
- return fmt.Errorf("failed to deploy controller: %w", err)
- }
- // Deploy Kubernetes provider
- if err := e.deployKubernetesProvider(); err != nil {
- return fmt.Errorf("failed to deploy Kubernetes provider: %w", err)
- }
- // Wait for deployments to be ready
- if err := e.waitForDeployments(); err != nil {
- return fmt.Errorf("failed waiting for deployments: %w", err)
- }
- log.Logf("External Secrets Operator V2 installed successfully")
- return nil
- }
- func (e *ESOV2) createNamespace() error {
- ns := &appsv1.Namespace{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2Namespace,
- },
- }
- _, err := e.kubeClientSet.CoreV1().Namespaces().Create(context.Background(), ns, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- log.Logf("created namespace: %s", v2Namespace)
- return nil
- }
- func (e *ESOV2) installCRDs() error {
- // In a real implementation, this would apply actual CRD manifests
- // For now, we'll assume CRDs are already installed or use the Helm chart
- log.Logf("CRDs installation (assuming pre-installed)")
- return nil
- }
- func (e *ESOV2) createRBAC() error {
- // Create ServiceAccount
- sa := &appsv1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2ControllerName,
- Namespace: v2Namespace,
- },
- }
- _, err := e.kubeClientSet.CoreV1().ServiceAccounts(v2Namespace).Create(context.Background(), sa, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- // Create ClusterRole
- clusterRole := &rbacv1.ClusterRole{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2ControllerName,
- },
- Rules: []rbacv1.PolicyRule{
- {
- APIGroups: []string{"external-secrets.io"},
- Resources: []string{"secretstores", "clustersecretstores", "externalsecrets"},
- Verbs: []string{"get", "list", "watch"},
- },
- {
- APIGroups: []string{"external-secrets.io"},
- Resources: []string{"secretstores/status", "clustersecretstores/status", "externalsecrets/status"},
- Verbs: []string{"get", "patch", "update"},
- },
- {
- APIGroups: []string{""},
- Resources: []string{"secrets"},
- Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"},
- },
- {
- APIGroups: []string{""},
- Resources: []string{"events"},
- Verbs: []string{"create", "patch"},
- },
- },
- }
- _, err = e.kubeClientSet.RbacV1().ClusterRoles().Create(context.Background(), clusterRole, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- // Create ClusterRoleBinding
- clusterRoleBinding := &rbacv1.ClusterRoleBinding{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2ControllerName,
- },
- Subjects: []rbacv1.Subject{
- {
- Kind: "ServiceAccount",
- Name: v2ControllerName,
- Namespace: v2Namespace,
- },
- },
- RoleRef: rbacv1.RoleRef{
- APIGroup: "rbac.authorization.k8s.io",
- Kind: "ClusterRole",
- Name: v2ControllerName,
- },
- }
- _, err = e.kubeClientSet.RbacV1().ClusterRoleBindings().Create(context.Background(), clusterRoleBinding, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- // Create ServiceAccount for provider
- providerSA := &appsv1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2ProviderName,
- Namespace: v2Namespace,
- },
- }
- _, err = e.kubeClientSet.CoreV1().ServiceAccounts(v2Namespace).Create(context.Background(), providerSA, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- // Create ClusterRole for provider
- providerClusterRole := &rbacv1.ClusterRole{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2ProviderName,
- },
- Rules: []rbacv1.PolicyRule{
- {
- APIGroups: []string{""},
- Resources: []string{"secrets"},
- Verbs: []string{"get", "list"},
- },
- },
- }
- _, err = e.kubeClientSet.RbacV1().ClusterRoles().Create(context.Background(), providerClusterRole, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- // Create ClusterRoleBinding for provider
- providerClusterRoleBinding := &rbacv1.ClusterRoleBinding{
- ObjectMeta: metav1.ObjectMeta{
- Name: v2ProviderName,
- },
- Subjects: []rbacv1.Subject{
- {
- Kind: "ServiceAccount",
- Name: v2ProviderName,
- Namespace: v2Namespace,
- },
- },
- RoleRef: rbacv1.RoleRef{
- APIGroup: "rbac.authorization.k8s.io",
- Kind: "ClusterRole",
- Name: v2ProviderName,
- },
- }
- _, err = e.kubeClientSet.RbacV1().ClusterRoleBindings().Create(context.Background(), providerClusterRoleBinding, metav1.CreateOptions{})
- if err != nil && !isAlreadyExists(err) {
- return err
- }
- log.Logf("created RBAC resources")
- return nil
- }
- func (e *ESOV2) deployController() error {
- // This would deploy the actual controller
- // For E2E tests, we assume it's deployed via Helm or manifests
- log.Logf("controller deployment (assuming pre-deployed)")
- return nil
- }
- func (e *ESOV2) deployKubernetesProvider() error {
- // This would deploy the Kubernetes provider
- // For E2E tests, we assume it's deployed via Helm or manifests
- log.Logf("Kubernetes provider deployment (assuming pre-deployed)")
- return nil
- }
- func (e *ESOV2) waitForDeployments() error {
- log.Logf("waiting for deployments to be ready")
- ctx := context.Background()
- // Wait for controller deployment
- err := wait.PollImmediate(5*time.Second, 5*time.Minute, func() (bool, error) {
- deployment, err := e.kubeClientSet.AppsV1().Deployments(v2Namespace).Get(ctx, v2ControllerName, metav1.GetOptions{})
- if err != nil {
- log.Logf("waiting for controller deployment: %v", err)
- return false, nil
- }
- if deployment.Status.ReadyReplicas == deployment.Status.Replicas && deployment.Status.Replicas > 0 {
- log.Logf("controller deployment is ready")
- return true, nil
- }
- log.Logf("controller deployment not ready yet: %d/%d replicas", deployment.Status.ReadyReplicas, deployment.Status.Replicas)
- return false, nil
- })
- if err != nil {
- return fmt.Errorf("controller deployment not ready: %w", err)
- }
- // Wait for provider deployment
- err = wait.PollImmediate(5*time.Second, 5*time.Minute, func() (bool, error) {
- deployment, err := e.kubeClientSet.AppsV1().Deployments(v2Namespace).Get(ctx, v2ProviderName, metav1.GetOptions{})
- if err != nil {
- log.Logf("waiting for provider deployment: %v", err)
- return false, nil
- }
- if deployment.Status.ReadyReplicas == deployment.Status.Replicas && deployment.Status.Replicas > 0 {
- log.Logf("provider deployment is ready")
- return true, nil
- }
- log.Logf("provider deployment not ready yet: %d/%d replicas", deployment.Status.ReadyReplicas, deployment.Status.Replicas)
- return false, nil
- })
- if err != nil {
- return fmt.Errorf("provider deployment not ready: %w", err)
- }
- return nil
- }
- // Logs returns the logs of the ESO V2 components.
- func (e *ESOV2) Logs() error {
- log.Logf("=== Controller Logs ===")
- if err := printPodLogs(e.kubeClientSet, v2Namespace, "app="+v2ControllerName); err != nil {
- log.Logf("failed to get controller logs: %v", err)
- }
- log.Logf("=== Provider Logs ===")
- if err := printPodLogs(e.kubeClientSet, v2Namespace, "app="+v2ProviderName); err != nil {
- log.Logf("failed to get provider logs: %v", err)
- }
- return nil
- }
- // Uninstall removes ESO V2 components.
- func (e *ESOV2) Uninstall() error {
- log.Logf("uninstalling External Secrets Operator V2")
- ctx := context.Background()
- // Delete deployments
- _ = e.kubeClientSet.AppsV1().Deployments(v2Namespace).Delete(ctx, v2ControllerName, metav1.DeleteOptions{})
- _ = e.kubeClientSet.AppsV1().Deployments(v2Namespace).Delete(ctx, v2ProviderName, metav1.DeleteOptions{})
- // Delete RBAC
- _ = e.kubeClientSet.RbacV1().ClusterRoleBindings().Delete(ctx, v2ControllerName, metav1.DeleteOptions{})
- _ = e.kubeClientSet.RbacV1().ClusterRoles().Delete(ctx, v2ControllerName, metav1.DeleteOptions{})
- _ = e.kubeClientSet.RbacV1().ClusterRoleBindings().Delete(ctx, v2ProviderName, metav1.DeleteOptions{})
- _ = e.kubeClientSet.RbacV1().ClusterRoles().Delete(ctx, v2ProviderName, metav1.DeleteOptions{})
- _ = e.kubeClientSet.CoreV1().ServiceAccounts(v2Namespace).Delete(ctx, v2ControllerName, metav1.DeleteOptions{})
- _ = e.kubeClientSet.CoreV1().ServiceAccounts(v2Namespace).Delete(ctx, v2ProviderName, metav1.DeleteOptions{})
- // Delete namespace
- _ = e.kubeClientSet.CoreV1().Namespaces().Delete(ctx, v2Namespace, metav1.DeleteOptions{})
- log.Logf("External Secrets Operator V2 uninstalled")
- return nil
- }
- func isAlreadyExists(err error) bool {
- return err != nil && (err.Error() == "already exists" || errors.IsAlreadyExists(err))
- }
- func printPodLogs(clientset kubernetes.Interface, namespace, labelSelector string) error {
- ctx := context.Background()
- pods, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
- LabelSelector: labelSelector,
- })
- if err != nil {
- return err
- }
- for _, pod := range pods.Items {
- log.Logf("Logs for pod %s:", pod.Name)
- req := clientset.CoreV1().Pods(namespace).GetLogs(pod.Name, &appsv1.PodLogOptions{})
- logs, err := req.Stream(ctx)
- if err != nil {
- log.Logf("failed to get logs: %v", err)
- continue
- }
- defer logs.Close()
- buf := make([]byte, 2048)
- for {
- n, err := logs.Read(buf)
- if n > 0 {
- log.Logf("%s", string(buf[:n]))
- }
- if err != nil {
- break
- }
- }
- }
- return nil
- }
|