diff --git a/deploy/operator-local.yaml b/deploy/operator-local.yaml index 8c0ac8961..856598217 100644 --- a/deploy/operator-local.yaml +++ b/deploy/operator-local.yaml @@ -51,6 +51,8 @@ spec: value: quay.io/eclipse/che-plugin-registry:7.12.0 - name: IMAGE_default_devfile_registry value: quay.io/eclipse/che-devfile-registry:7.12.0 + - name: IMAGE_default_che_tls_secrets_creation_job + value: quay.io/eclipse/che-tls-secret-creator:alpine-3029769 - name: IMAGE_default_pvc_jobs value: registry.access.redhat.com/ubi8-minimal:8.1-409 - name: IMAGE_default_postgres diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 0da6de286..86ff9ec18 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -50,6 +50,8 @@ spec: value: quay.io/eclipse/che-plugin-registry:7.12.0 - name: IMAGE_default_devfile_registry value: quay.io/eclipse/che-devfile-registry:7.12.0 + - name: IMAGE_default_che_tls_secrets_creation_job + value: quay.io/eclipse/che-tls-secret-creator:alpine-3029769 - name: IMAGE_default_pvc_jobs value: registry.access.redhat.com/ubi8-minimal:8.1-409 - name: IMAGE_default_postgres diff --git a/deploy/role.yaml b/deploy/role.yaml index b0610578e..3acaeb64d 100644 --- a/deploy/role.yaml +++ b/deploy/role.yaml @@ -20,6 +20,12 @@ rules: - ingresses verbs: - '*' +- apiGroups: + - batch + resources: + - jobs + verbs: + - '*' - apiGroups: - route.openshift.io resources: diff --git a/pkg/controller/che/che_controller.go b/pkg/controller/che/che_controller.go index 578a29649..98289ae67 100644 --- a/pkg/controller/che/che_controller.go +++ b/pkg/controller/che/che_controller.go @@ -273,6 +273,26 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } + // Handle Che TLS certificates on Kubernetes like infrastructures + if instance.Spec.Server.TlsSupport && instance.Spec.Server.SelfSignedCert && !isOpenShift { + // Ensure TLS configuration is correct + if err := CheckAndUpdateTLSConfiguration(instance, clusterAPI); err != nil { + instance, _ = r.GetCR(request) + return reconcile.Result{Requeue: true, RequeueAfter: time.Second * 1}, err + } + + // Create TLS secrets if needed + result, err := HandleCheTLSSecrets(instance, clusterAPI) + if result.Requeue || result.RequeueAfter > 0 { + if err != nil { + logrus.Error(err) + } + if !tests { + return result, err + } + } + } + // Get custom ConfigMap // if it exists, add the data into CustomCheProperties customConfigMap := &corev1.ConfigMap{} @@ -401,44 +421,97 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e // create service accounts: // che is the one which token is used to create workspace objects // che-workspace is SA used by plugins like exec and terminal with limited privileges - cheServiceAccount := deploy.NewServiceAccount(instance, "che") - if err := r.CreateServiceAccount(instance, cheServiceAccount); err != nil { - return reconcile.Result{}, err + cheSA, err := deploy.SyncServiceAccountToCluster(instance, "che", clusterAPI) + if cheSA == nil { + logrus.Info("Waiting on service account 'che' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } - workspaceServiceAccount := deploy.NewServiceAccount(instance, "che-workspace") - if err := r.CreateServiceAccount(instance, workspaceServiceAccount); err != nil { - return reconcile.Result{}, err + + cheWorkspaceSA, err := deploy.SyncServiceAccountToCluster(instance, "che-workspace", clusterAPI) + if cheWorkspaceSA == nil { + logrus.Info("Waiting on service account 'che-workspace' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } + // create exec and view roles for CheCluster server and workspaces - execRole := deploy.NewRole(instance, "exec", []string{"pods/exec"}, []string{"*"}) - if err := r.CreateNewRole(instance, execRole); err != nil { - return reconcile.Result{}, err + role, err := deploy.SyncRoleToCluster(instance, "exec", []string{"pods/exec"}, []string{"*"}, clusterAPI) + if role == nil { + logrus.Info("Waiting on role 'exec' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } - viewRole := deploy.NewRole(instance, "view", []string{"pods"}, []string{"list"}) - if err := r.CreateNewRole(instance, viewRole); err != nil { - return reconcile.Result{}, err + + viewRole, err := deploy.SyncRoleToCluster(instance, "view", []string{"pods"}, []string{"list"}, clusterAPI) + if viewRole == nil { + logrus.Info("Waiting on role 'view' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } - // create RoleBindings for created (and existing ClusterRole) roles and service accounts - cheRoleBinding := deploy.NewRoleBinding(instance, "che", cheServiceAccount.Name, "edit", "ClusterRole") - if err := r.CreateNewRoleBinding(instance, cheRoleBinding); err != nil { - return reconcile.Result{}, err + + cheRoleBinding, err := deploy.SyncRoleBindingToCluster(instance, "che", "che", "edit", "ClusterRole", clusterAPI) + if cheRoleBinding == nil { + logrus.Info("Waiting on role binding 'che' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } - execRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-exec", workspaceServiceAccount.Name, execRole.Name, "Role") - if err = r.CreateNewRoleBinding(instance, execRoleBinding); err != nil { - return reconcile.Result{}, err + + cheWSExecRoleBinding, err := deploy.SyncRoleBindingToCluster(instance, "che-workspace-exec", "che-workspace", "exec", "Role", clusterAPI) + if cheWSExecRoleBinding == nil { + logrus.Info("Waiting on role binding 'che-workspace-exec' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } - viewRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-view", workspaceServiceAccount.Name, viewRole.Name, "Role") - if err := r.CreateNewRoleBinding(instance, viewRoleBinding); err != nil { - return reconcile.Result{}, err + + cheWSViewRoleBinding, err := deploy.SyncRoleBindingToCluster(instance, "che-workspace-view", "che-workspace", "view", "Role", clusterAPI) + if cheWSViewRoleBinding == nil { + logrus.Info("Waiting on role binding 'che-workspace-view' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } // If the user specified an additional cluster role to use for the Che workspace, create a role binding for it // Use a role binding instead of a cluster role binding to keep the additional access scoped to the workspace's namespace workspaceClusterRole := instance.Spec.Server.CheWorkspaceClusterRole if workspaceClusterRole != "" { - customRoleBinding := deploy.NewRoleBinding(instance, "che-workspace-custom", workspaceServiceAccount.Name, workspaceClusterRole, "ClusterRole") - if err = r.CreateNewRoleBinding(instance, customRoleBinding); err != nil { - return reconcile.Result{}, err + cheWSCustomRoleBinding, err := deploy.SyncRoleBindingToCluster(instance, "che-workspace-custom", "view", workspaceClusterRole, "ClusterRole", clusterAPI) + if cheWSCustomRoleBinding == nil { + logrus.Info("Waiting on role binding 'che-workspace-custom' to be created") + if err != nil { + logrus.Error(err) + } + if !tests { + return reconcile.Result{RequeueAfter: time.Second}, err + } } } @@ -782,10 +855,6 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } } guessedDevfileRegistryURL := protocol + "://" + host - - if err != nil { - return reconcile.Result{}, err - } if devfileRegistryURL == "" { devfileRegistryURL = guessedDevfileRegistryURL } @@ -942,9 +1011,6 @@ func (r *ReconcileChe) Reconcile(request reconcile.Request) (reconcile.Result, e } guessedPluginRegistryURL := protocol + "://" + host - if err != nil { - return reconcile.Result{}, err - } guessedPluginRegistryURL += "/v3" if pluginRegistryURL == "" { pluginRegistryURL = guessedPluginRegistryURL diff --git a/pkg/controller/che/create.go b/pkg/controller/che/create.go index 6a8fbe009..5219d1458 100644 --- a/pkg/controller/che/create.go +++ b/pkg/controller/che/create.go @@ -21,54 +21,12 @@ import ( oauth "github.com/openshift/api/oauth/v1" "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" - rbac "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -func (r *ReconcileChe) CreateServiceAccount(cr *orgv1.CheCluster, serviceAccount *corev1.ServiceAccount) error { - if err := controllerutil.SetControllerReference(cr, serviceAccount, r.scheme); err != nil { - return err - } - serviceAccountFound := &corev1.ServiceAccount{} - err := r.client.Get(context.TODO(), types.NamespacedName{Name: serviceAccount.Name, Namespace: serviceAccount.Namespace}, serviceAccountFound) - if err != nil && errors.IsNotFound(err) { - logrus.Infof("Creating a new object: %s, name: %s", serviceAccount.Kind, serviceAccount.Name) - err = r.client.Create(context.TODO(), serviceAccount) - if err != nil { - logrus.Errorf("Failed to create %s %s: %s", serviceAccount.Name, serviceAccount.Kind, err) - return err - } - return nil - } else if err != nil { - return err - } - return nil -} - -func (r *ReconcileChe) CreateNewRole(instance *orgv1.CheCluster, role *rbac.Role) error { - if err := controllerutil.SetControllerReference(instance, role, r.scheme); err != nil { - return err - } - roleFound := &rbac.Role{} - err := r.client.Get(context.TODO(), types.NamespacedName{Name: role.Name, Namespace: role.Namespace}, roleFound) - if err != nil && errors.IsNotFound(err) { - logrus.Infof("Creating a new object: %s, name: %s", role.Kind, role.Name) - err = r.client.Create(context.TODO(), role) - if err != nil { - logrus.Errorf("Failed to create %s %s: %s", role.Name, role.Kind, err) - return err - } - return nil - } else if err != nil { - logrus.Errorf("An error occurred: %s", err) - return err - } - return nil -} - func (r *ReconcileChe) CreateNewSecret(instance *orgv1.CheCluster, secret *corev1.Secret) error { if err := controllerutil.SetControllerReference(instance, secret, r.scheme); err != nil { logrus.Errorf("An error occurred: %s", err) @@ -111,27 +69,6 @@ func (r *ReconcileChe) CreateNewOauthClient(instance *orgv1.CheCluster, oAuthCli return nil } -func (r *ReconcileChe) CreateNewRoleBinding(instance *orgv1.CheCluster, roleBinding *rbac.RoleBinding) error { - if err := controllerutil.SetControllerReference(instance, roleBinding, r.scheme); err != nil { - return err - } - roleBindingFound := &rbac.RoleBinding{} - err := r.client.Get(context.TODO(), types.NamespacedName{Name: roleBinding.Name, Namespace: roleBinding.Namespace}, roleBindingFound) - if err != nil && errors.IsNotFound(err) { - logrus.Infof("Creating a new object: %s, name: %s", roleBinding.Kind, roleBinding.Name) - err = r.client.Create(context.TODO(), roleBinding) - if err != nil { - logrus.Errorf("Failed to create %s %s: %s", roleBinding.Name, roleBinding.Kind, err) - return err - } - return nil - } else if err != nil { - logrus.Errorf("An error occurred: %s", err) - return err - } - return nil -} - func (r *ReconcileChe) CreateIdentityProviderItems(instance *orgv1.CheCluster, request reconcile.Request, cheFlavor string, keycloakDeploymentName string, isOpenShift4 bool) (err error) { tests := r.tests oAuthClientName := instance.Spec.Auth.OAuthClientName diff --git a/pkg/controller/che/tls-secrets.go b/pkg/controller/che/tls-secrets.go new file mode 100644 index 000000000..5e0324f04 --- /dev/null +++ b/pkg/controller/che/tls-secrets.go @@ -0,0 +1,262 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// +package che + +import ( + "context" + "time" + + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" + "github.com/eclipse/che-operator/pkg/deploy" + "github.com/sirupsen/logrus" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" +) + +const ( + CheTLSJobServiceAccountName = "che-tls-job-service-account" + CheTLSJobRoleName = "che-tls-job-role" + CheTLSJobRoleBindingName = "che-tls-job-role-binding" + CheTLSJobName = "che-tls-job" + CheTlsJobComponentName = "che-create-tls-secret-job" + CheTLSSelfSignedCertificateSecretName = "self-signed-certificate" +) + +// HandleCheTLSSecrets handles TLS secrets required for Che deployment +func HandleCheTLSSecrets(checluster *orgv1.CheCluster, clusterAPI deploy.ClusterAPI) (reconcile.Result, error) { + cheTLSSecretName := checluster.Spec.K8s.TlsSecretName + + // ===== Check Che TLS certificate ===== // + cheTLSSecret := &corev1.Secret{} + err := clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: checluster.Namespace, Name: cheTLSSecretName}, cheTLSSecret) + if err != nil { + if !errors.IsNotFound(err) { + // Error reading secret info + logrus.Errorf("Error getting Che TLS secert \"%s\": %v", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + + // Che TLS secret doesn't exist, generate a new one + + // Remove Che CA certificate secret if any + cheCASelfSignedCertificateSecret := &corev1.Secret{} + err = clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: checluster.Namespace, Name: CheTLSSelfSignedCertificateSecretName}, cheCASelfSignedCertificateSecret) + if err != nil { + if !errors.IsNotFound(err) { + // Error reading secret info + logrus.Errorf("Error getting Che self-signed certificate secert \"%s\": %v", CheTLSSelfSignedCertificateSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + // Che CA certificate doesn't exists (that's expected at this point), do nothing + } else { + // Remove Che CA secret because Che TLS secret is missing (they should be generated together). + if err = clusterAPI.Client.Delete(context.TODO(), cheCASelfSignedCertificateSecret); err != nil { + logrus.Errorf("Error deleting Che self-signed certificate secret \"%s\": %v", CheTLSSelfSignedCertificateSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + } + + // Prepare permissions for the certificate generation job + sa, err := deploy.SyncServiceAccountToCluster(checluster, CheTLSJobServiceAccountName, clusterAPI) + if sa == nil { + return reconcile.Result{RequeueAfter: time.Second}, err + } + + role, err := deploy.SyncRoleToCluster(checluster, CheTLSJobRoleName, []string{"secrets"}, []string{"create"}, clusterAPI) + if role == nil { + return reconcile.Result{RequeueAfter: time.Second}, err + } + + roleBiding, err := deploy.SyncRoleBindingToCluster(checluster, CheTLSJobRoleBindingName, CheTLSJobServiceAccountName, CheTLSJobRoleName, "Role", clusterAPI) + if roleBiding == nil { + return reconcile.Result{RequeueAfter: time.Second}, err + } + + cheTLSSecretsCreationJobImage := deploy.DefaultCheTLSSecretsCreationJobImage() + jobEnvVars := map[string]string{ + "DOMAIN": checluster.Spec.K8s.IngressDomain, + "CHE_NAMESPACE": checluster.Namespace, + "CHE_SERVER_TLS_SECRET_NAME": cheTLSSecretName, + "CHE_CA_CERTIFICATE_SECRET_NAME": CheTLSSelfSignedCertificateSecretName, + } + job, err := deploy.SyncJobToCluster(checluster, CheTLSJobName, CheTlsJobComponentName, cheTLSSecretsCreationJobImage, CheTLSJobServiceAccountName, jobEnvVars, clusterAPI) + if err != nil { + logrus.Error(err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + if job == nil || job.Status.Succeeded == 0 { + logrus.Infof("Waiting on job '%s' to be finished", CheTLSJobName) + return reconcile.Result{RequeueAfter: time.Second}, err + } + } + + // cleanup job + job := &batchv1.Job{} + err = clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: CheTLSJobName, Namespace: checluster.Namespace}, job) + if err != nil && !errors.IsNotFound(err) { + // Failed to get the job + return reconcile.Result{RequeueAfter: time.Second}, err + } + if err == nil { + // The job object is present + if job.Status.Succeeded > 0 { + logrus.Infof("Import public part of Eclipse Che self-signed CA certificvate from \"%s\" secret into your browser.", CheTLSSelfSignedCertificateSecretName) + deleteJob(job, checluster, clusterAPI) + } else if job.Status.Failed > 0 { + // The job failed, but the certificate is present, shouldn't happen + deleteJob(job, checluster, clusterAPI) + return reconcile.Result{}, nil + } + // Job hasn't reported finished status yet, wait more + return reconcile.Result{RequeueAfter: time.Second}, nil + } + + // Che TLS certificate exists, check for required data fields + if !isCheTLSSecretValid(cheTLSSecret) { + // The secret is invalid because required field(s) missing. + logrus.Infof("Che TLS secret \"%s\" is invalid. Recreating...", cheTLSSecretName) + // Delete old invalid secret + if err = clusterAPI.Client.Delete(context.TODO(), cheTLSSecret); err != nil { + logrus.Errorf("Error deleting Che TLS secret \"%s\": %v", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + // Recreate the secret + return reconcile.Result{RequeueAfter: time.Second}, err + } + + // Check owner reference + if cheTLSSecret.ObjectMeta.OwnerReferences == nil { + // Set owner Che cluster as Che TLS secret owner + if err := controllerutil.SetControllerReference(checluster, cheTLSSecret, clusterAPI.Scheme); err != nil { + logrus.Errorf("Failed to set owner for Che TLS secret \"%s\". Error: %s", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + if err := clusterAPI.Client.Update(context.TODO(), cheTLSSecret); err != nil { + logrus.Errorf("Failed to update owner for Che TLS secret \"%s\". Error: %s", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + } + + // ===== Check Che CA certificate ===== // + + cheTLSSelfSignedCertificateSecret := &corev1.Secret{} + err = clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: checluster.Namespace, Name: CheTLSSelfSignedCertificateSecretName}, cheTLSSelfSignedCertificateSecret) + if err != nil { + if !errors.IsNotFound(err) { + // Error reading Che self-signed secret info + logrus.Errorf("Error getting Che self-signed certificate secert \"%s\": %v", CheTLSSelfSignedCertificateSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + // Che CA self-signed cetificate secret doesn't exist. + // Such situation could happen between reconcile loops, when CA cert is deleted. + // However the certificates should be created together, + // so it is mandatory to remove Che TLS secret now and recreate the pair. + cheTLSSecret = &corev1.Secret{} + err = clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Namespace: checluster.Namespace, Name: cheTLSSecretName}, cheTLSSecret) + if err != nil { // No need to check for not found error as the secret already exists at this point + logrus.Errorf("Error getting Che TLS secert \"%s\": %v", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + if err = clusterAPI.Client.Delete(context.TODO(), cheTLSSecret); err != nil { + logrus.Errorf("Error deleting Che TLS secret \"%s\": %v", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + // Invalid secrets cleaned up, recreate them now + return reconcile.Result{RequeueAfter: time.Second}, err + } + + // Che CA self-signed certificate secret exists, check for required data fields + if !isCheCASecretValid(cheTLSSelfSignedCertificateSecret) { + logrus.Infof("Che self-signed certificate secret \"%s\" is invalid. Recrating...", CheTLSSelfSignedCertificateSecretName) + // Che CA self-signed certificate secret is invalid, delete it + if err = clusterAPI.Client.Delete(context.TODO(), cheTLSSelfSignedCertificateSecret); err != nil { + logrus.Errorf("Error deleting Che self-signed certificate secret \"%s\": %v", CheTLSSelfSignedCertificateSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + // Also delete Che TLS as the certificates should be created together + // Here it is not mandatory to check Che TLS secret existence as it is handled above + if err = clusterAPI.Client.Delete(context.TODO(), cheTLSSecret); err != nil { + logrus.Errorf("Error deleting Che TLS secret \"%s\": %v", cheTLSSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + // Regenerate Che TLS certicates and recreate secrets + return reconcile.Result{RequeueAfter: time.Second}, nil + } + + // Check owner reference + if cheTLSSelfSignedCertificateSecret.ObjectMeta.OwnerReferences == nil { + // Set owner Che cluster as Che TLS secret owner + if err := controllerutil.SetControllerReference(checluster, cheTLSSelfSignedCertificateSecret, clusterAPI.Scheme); err != nil { + logrus.Errorf("Failed to set owner for Che self-signed certificate secret \"%s\". Error: %s", CheTLSSelfSignedCertificateSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + if err := clusterAPI.Client.Update(context.TODO(), cheTLSSelfSignedCertificateSecret); err != nil { + logrus.Errorf("Failed to update owner for Che self-signed certificate secret \"%s\". Error: %s", CheTLSSelfSignedCertificateSecretName, err) + return reconcile.Result{RequeueAfter: time.Second}, err + } + } + + // Both secrets are ok, go further in reconcile loop + return reconcile.Result{}, nil +} + +func isCheTLSSecretValid(cheTLSSecret *corev1.Secret) bool { + if data, exists := cheTLSSecret.Data["tls.key"]; !exists || len(data) == 0 { + return false + } + if data, exists := cheTLSSecret.Data["tls.crt"]; !exists || len(data) == 0 { + return false + } + return true +} + +func isCheCASecretValid(cheCASelfSignedCertificateSecret *corev1.Secret) bool { + if data, exists := cheCASelfSignedCertificateSecret.Data["ca.crt"]; !exists || len(data) == 0 { + return false + } + return true +} + +// CheckAndUpdateTLSConfiguration validates TLS configuration and precreated objects if any +// If configuration is wrong it will guess most common use cases and will make changes in Che CR accordingly to the assumption. +func CheckAndUpdateTLSConfiguration(checluster *orgv1.CheCluster, clusterAPI deploy.ClusterAPI) error { + if checluster.Spec.K8s.TlsSecretName == "" { + checluster.Spec.K8s.TlsSecretName = "che-tls" + if err := deploy.UpdateCheCRSpec(checluster, "TlsSecretName", checluster.Spec.K8s.TlsSecretName, clusterAPI); err != nil { + return err + } + } + + return nil +} + +func deleteJob(job *batchv1.Job, checluster *orgv1.CheCluster, clusterAPI deploy.ClusterAPI) { + names := k8sclient.GetPodsByComponent(CheTlsJobComponentName, checluster.Namespace) + for _, podName := range names { + pod := &corev1.Pod{} + err := clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: checluster.Namespace}, pod) + if err == nil { + // Delete pod (for some reasons pod isn't removed when job is removed) + if err = clusterAPI.Client.Delete(context.TODO(), pod); err != nil { + logrus.Errorf("Error deleting pod: '%s', error: %v", podName, err) + } + } + } + + if err := clusterAPI.Client.Delete(context.TODO(), job); err != nil { + logrus.Errorf("Error deleting job: '%s', error: %v", CheTLSJobName, err) + } +} diff --git a/pkg/deploy/defaults.go b/pkg/deploy/defaults.go index 1d50bb111..7eed029c5 100644 --- a/pkg/deploy/defaults.go +++ b/pkg/deploy/defaults.go @@ -27,13 +27,14 @@ import ( ) var ( - defaultCheServerImage string - defaultCheVersion string - defaultPluginRegistryImage string - defaultDevfileRegistryImage string - defaultPvcJobsImage string - defaultPostgresImage string - defaultKeycloakImage string + defaultCheServerImage string + defaultCheVersion string + defaultPluginRegistryImage string + defaultDevfileRegistryImage string + defaultCheTLSSecretsCreationJobImage string + defaultPvcJobsImage string + defaultPostgresImage string + defaultKeycloakImage string defaultCheWorkspacePluginBrokerMetadataImage string defaultCheWorkspacePluginBrokerArtifactsImage string @@ -114,6 +115,7 @@ func InitDefaultsFromEnv() { defaultCheServerImage = getDefaultFromEnv("IMAGE_default_che_server") defaultPluginRegistryImage = getDefaultFromEnv("IMAGE_default_plugin_registry") defaultDevfileRegistryImage = getDefaultFromEnv("IMAGE_default_devfile_registry") + defaultCheTLSSecretsCreationJobImage = getDefaultFromEnv("IMAGE_default_che_tls_secrets_creation_job") defaultPvcJobsImage = getDefaultFromEnv("IMAGE_default_pvc_jobs") defaultPostgresImage = getDefaultFromEnv("IMAGE_default_postgres") defaultKeycloakImage = getDefaultFromEnv("IMAGE_default_keycloak") @@ -133,6 +135,7 @@ func InitDefaultsFromFile(defaultsPath string) { defaultCheServerImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_che_server") defaultPluginRegistryImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_plugin_registry") defaultDevfileRegistryImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_devfile_registry") + defaultCheTLSSecretsCreationJobImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_che_tls_secrets_creation_job") defaultPvcJobsImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_pvc_jobs") defaultPostgresImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_postgres") defaultKeycloakImage = util.GetDeploymentEnv(operatorDeployment, "IMAGE_default_keycloak") @@ -195,6 +198,10 @@ func DefaultCheServerImage(cr *orgv1.CheCluster) string { return patchDefaultImageName(cr, defaultCheServerImage) } +func DefaultCheTLSSecretsCreationJobImage() string { + return defaultCheTLSSecretsCreationJobImage +} + func DefaultPvcJobsImage(cr *orgv1.CheCluster) string { return patchDefaultImageName(cr, defaultPvcJobsImage) } diff --git a/pkg/deploy/defaults_test.go b/pkg/deploy/defaults_test.go index 3d2d0bea5..6689870aa 100644 --- a/pkg/deploy/defaults_test.go +++ b/pkg/deploy/defaults_test.go @@ -20,16 +20,17 @@ import ( ) const ( - cheVersionTest = "7.12.0" - cheServerImageTest = "quay.io/eclipse/che-server:7.12.0" - pluginRegistryImageTest = "quay.io/eclipse/che-plugin-registry:7.12.0" - devfileRegistryImageTest = "quay.io/eclipse/che-devfile-registry:7.12.0" - pvcJobsImageTest = "registry.access.redhat.com/ubi8-minimal:8.1-409" - postgresImageTest = "centos/postgresql-96-centos7:9.6" - keycloakImageTest = "quay.io/eclipse/che-keycloak:7.12.0" - brokerMetadataTest = "quay.io/eclipse/che-plugin-metadata-broker:v3.1.2" - brokerArtifactsTest = "quay.io/eclipse/che-plugin-artifacts-broker:v3.1.2" - jwtProxyTest = "quay.io/eclipse/che-jwtproxy:fd94e60" + cheVersionTest = "7.12.0" + cheServerImageTest = "quay.io/eclipse/che-server:7.12.0" + pluginRegistryImageTest = "quay.io/eclipse/che-plugin-registry:7.12.0" + devfileRegistryImageTest = "quay.io/eclipse/che-devfile-registry:7.12.0" + cheTLSSecretsCeationJobImageTest = "quay.io/eclipse/che-tls-secrets-creation-job:7.12.0" + pvcJobsImageTest = "registry.access.redhat.com/ubi8-minimal:8.1-409" + postgresImageTest = "centos/postgresql-96-centos7:9.6" + keycloakImageTest = "quay.io/eclipse/che-keycloak:7.12.0" + brokerMetadataTest = "quay.io/eclipse/che-plugin-metadata-broker:v3.1.2" + brokerArtifactsTest = "quay.io/eclipse/che-plugin-artifacts-broker:v3.1.2" + jwtProxyTest = "quay.io/eclipse/che-jwtproxy:fd94e60" ) func init() { @@ -37,6 +38,7 @@ func init() { os.Setenv("IMAGE_default_che_server", cheServerImageTest) os.Setenv("IMAGE_default_plugin_registry", pluginRegistryImageTest) os.Setenv("IMAGE_default_devfile_registry", devfileRegistryImageTest) + os.Setenv("IMAGE_default_che_tls_secrets_creation_job", cheTLSSecretsCeationJobImageTest) os.Setenv("IMAGE_default_pvc_jobs", pvcJobsImageTest) os.Setenv("IMAGE_default_postgres", postgresImageTest) os.Setenv("IMAGE_default_keycloak", keycloakImageTest) diff --git a/pkg/deploy/job.go b/pkg/deploy/job.go new file mode 100644 index 000000000..c23be3672 --- /dev/null +++ b/pkg/deploy/job.go @@ -0,0 +1,174 @@ +// +// Copyright (c) 2020 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// +package deploy + +import ( + "context" + "fmt" + "reflect" + + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" + "github.com/eclipse/che-operator/pkg/util" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sirupsen/logrus" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var ( + jobDiffOpts = cmp.Options{ + cmpopts.IgnoreFields(batchv1.Job{}, "TypeMeta", "ObjectMeta", "Status"), + cmpopts.IgnoreFields(batchv1.JobSpec{}, "Selector", "TTLSecondsAfterFinished"), + cmpopts.IgnoreFields(v1.PodTemplateSpec{}, "ObjectMeta"), + cmpopts.IgnoreFields(corev1.Container{}, "TerminationMessagePath", "TerminationMessagePolicy"), + cmpopts.IgnoreFields(corev1.PodSpec{}, "DNSPolicy", "SchedulerName", "SecurityContext"), + cmp.Comparer(func(x, y []corev1.EnvVar) bool { + xMap := make(map[string]string) + yMap := make(map[string]string) + for _, env := range x { + xMap[env.Name] = env.Value + } + for _, env := range y { + yMap[env.Name] = env.Value + } + return reflect.DeepEqual(xMap, yMap) + }), + } +) + +func SyncJobToCluster( + checluster *orgv1.CheCluster, + name string, + component string, + image string, + serviceAccountName string, + env map[string]string, + clusterAPI ClusterAPI) (*batchv1.Job, error) { + + specJob, err := getSpecJob(checluster, name, component, image, serviceAccountName, env, clusterAPI) + if err != nil { + return nil, err + } + + clusterJob, err := getClusterJob(specJob.Name, specJob.Namespace, clusterAPI) + if err != nil { + return nil, err + } + + if clusterJob == nil { + logrus.Infof("Creating a new object: %s, name %s", specJob.Kind, specJob.Name) + err := clusterAPI.Client.Create(context.TODO(), specJob) + return nil, err + } + + diff := cmp.Diff(clusterJob, specJob, jobDiffOpts) + if len(diff) > 0 { + logrus.Infof("Updating existed object: %s, name: %s", clusterJob.Kind, clusterJob.Name) + fmt.Printf("Difference:\n%s", diff) + + if err := clusterAPI.Client.Delete(context.TODO(), clusterJob); err != nil { + return nil, err + } + + err := clusterAPI.Client.Create(context.TODO(), specJob) + return nil, err + } + + return clusterJob, nil +} + +// GetSpecJob creates new job configuration by given parameters. +func getSpecJob( + checluster *orgv1.CheCluster, + name string, + component string, + image string, + serviceAccountName string, + env map[string]string, + clusterAPI ClusterAPI) (*batchv1.Job, error) { + labels := GetLabels(checluster, util.GetValue(checluster.Spec.Server.CheFlavor, DefaultCheFlavor)) + labels["component"] = component + + backoffLimit := int32(3) + parallelism := int32(1) + comletions := int32(1) + terminationGracePeriodSeconds := int64(30) + ttlSecondsAfterFinished := int32(30) + pullPolicy := corev1.PullPolicy(util.GetValue(string(checluster.Spec.Server.CheImagePullPolicy), "IfNotPresent")) + + var jobEnvVars []corev1.EnvVar + for envVarName, envVarValue := range env { + jobEnvVars = append(jobEnvVars, corev1.EnvVar{Name: envVarName, Value: envVarValue}) + } + + job := &batchv1.Job{ + TypeMeta: metav1.TypeMeta{ + Kind: "Job", + APIVersion: batchv1.SchemeGroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: checluster.Namespace, + Labels: labels, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: labels, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: serviceAccountName, + DeprecatedServiceAccount: serviceAccountName, + RestartPolicy: "Never", + TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, + Containers: []corev1.Container{ + { + Name: name + "-job-container", + Image: image, + ImagePullPolicy: pullPolicy, + Env: jobEnvVars, + }, + }, + }, + }, + TTLSecondsAfterFinished: &ttlSecondsAfterFinished, + Parallelism: ¶llelism, + BackoffLimit: &backoffLimit, + Completions: &comletions, + }, + } + + if err := controllerutil.SetControllerReference(checluster, job, clusterAPI.Scheme); err != nil { + return nil, err + } + + return job, nil +} + +// GetClusterJob gets and returns specified job +func getClusterJob(name string, namespace string, clusterAPI ClusterAPI) (*batchv1.Job, error) { + job := &batchv1.Job{} + err := clusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, job) + if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return job, nil +} diff --git a/pkg/deploy/role.go b/pkg/deploy/role.go index 6c4c3e0b4..7cd881558 100644 --- a/pkg/deploy/role.go +++ b/pkg/deploy/role.go @@ -12,22 +12,71 @@ package deploy import ( + "context" + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/eclipse/che-operator/pkg/util" + "github.com/sirupsen/logrus" rbac "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func NewRole(cr *orgv1.CheCluster, name string, resources []string, verbs []string) *rbac.Role { - labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)) - return &rbac.Role{ +func SyncRoleToCluster( + checluster *orgv1.CheCluster, + name string, + resources []string, + verbs []string, + clusterAPI ClusterAPI) (*rbac.Role, error) { + + specRole, err := getSpecRole(checluster, name, resources, verbs, clusterAPI) + if err != nil { + return nil, err + } + + clusterRole, err := getClusterRole(specRole.Name, specRole.Namespace, clusterAPI.Client) + if err != nil { + return nil, err + } + + if clusterRole == nil { + logrus.Infof("Creating a new object: %s, name %s", specRole.Kind, specRole.Name) + err := clusterAPI.Client.Create(context.TODO(), specRole) + return nil, err + } + + return clusterRole, nil +} + +func getClusterRole(name string, namespace string, client runtimeClient.Client) (*rbac.Role, error) { + role := &rbac.Role{} + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := client.Get(context.TODO(), namespacedName, role) + if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return role, nil +} + +func getSpecRole(checluster *orgv1.CheCluster, name string, resources []string, verbs []string, clusterAPI ClusterAPI) (*rbac.Role, error) { + labels := GetLabels(checluster, util.GetValue(checluster.Spec.Server.CheFlavor, DefaultCheFlavor)) + role := &rbac.Role{ TypeMeta: metav1.TypeMeta{ Kind: "Role", APIVersion: rbac.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: cr.Namespace, + Namespace: checluster.Namespace, Labels: labels, }, Rules: []rbac.PolicyRule{ @@ -40,7 +89,11 @@ func NewRole(cr *orgv1.CheCluster, name string, resources []string, verbs []stri }, }, } + + err := controllerutil.SetControllerReference(checluster, role, clusterAPI.Scheme) + if err != nil { + return nil, err + } + + return role, nil } - - - diff --git a/pkg/deploy/rolebinding.go b/pkg/deploy/rolebinding.go index 9bedf764f..5383fe66e 100644 --- a/pkg/deploy/rolebinding.go +++ b/pkg/deploy/rolebinding.go @@ -12,29 +12,86 @@ package deploy import ( + "context" + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/eclipse/che-operator/pkg/util" + "github.com/sirupsen/logrus" rbac "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func NewRoleBinding(cr *orgv1.CheCluster, name string, serviceAccountName string, roleName string, roleKind string) *rbac.RoleBinding { - labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)) - return &rbac.RoleBinding{ +func SyncRoleBindingToCluster( + checluster *orgv1.CheCluster, + name string, + serviceAccountName string, + roleName string, + roleKind string, + clusterAPI ClusterAPI) (*rbac.RoleBinding, error) { + + specRB, err := getSpecRoleBinding(checluster, name, serviceAccountName, roleName, roleKind, clusterAPI) + if err != nil { + return nil, err + } + + clusterRB, err := getClusterRoleBiding(specRB.Name, specRB.Namespace, clusterAPI.Client) + if err != nil { + return nil, err + } + + if clusterRB == nil { + logrus.Infof("Creating a new object: %s, name %s", specRB.Kind, specRB.Name) + err := clusterAPI.Client.Create(context.TODO(), specRB) + return nil, err + } + + return clusterRB, nil +} + +func getClusterRoleBiding(name string, namespace string, client runtimeClient.Client) (*rbac.RoleBinding, error) { + roleBinding := &rbac.RoleBinding{} + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := client.Get(context.TODO(), namespacedName, roleBinding) + if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return roleBinding, nil +} + +func getSpecRoleBinding( + checluster *orgv1.CheCluster, + name string, + serviceAccountName string, + roleName string, + roleKind string, + clusterAPI ClusterAPI) (*rbac.RoleBinding, error) { + + labels := GetLabels(checluster, util.GetValue(checluster.Spec.Server.CheFlavor, DefaultCheFlavor)) + roleBinding := &rbac.RoleBinding{ TypeMeta: metav1.TypeMeta{ Kind: "RoleBinding", APIVersion: rbac.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: cr.Namespace, + Namespace: checluster.Namespace, Labels: labels, }, Subjects: []rbac.Subject{ { Kind: rbac.ServiceAccountKind, Name: serviceAccountName, - Namespace: cr.Namespace, + Namespace: checluster.Namespace, }, }, RoleRef: rbac.RoleRef{ @@ -43,5 +100,11 @@ func NewRoleBinding(cr *orgv1.CheCluster, name string, serviceAccountName string APIGroup: "rbac.authorization.k8s.io", }, } -} + err := controllerutil.SetControllerReference(checluster, roleBinding, clusterAPI.Scheme) + if err != nil { + return nil, err + } + + return roleBinding, nil +} diff --git a/pkg/deploy/service_account.go b/pkg/deploy/service_account.go index 28cdc7b0b..00a0d243b 100644 --- a/pkg/deploy/service_account.go +++ b/pkg/deploy/service_account.go @@ -12,23 +12,73 @@ package deploy import ( + "context" + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" "github.com/eclipse/che-operator/pkg/util" + "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + runtimeClient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" ) -func NewServiceAccount(cr *orgv1.CheCluster, name string) *corev1.ServiceAccount { - labels := GetLabels(cr, util.GetValue(cr.Spec.Server.CheFlavor, DefaultCheFlavor)) - return &corev1.ServiceAccount{ +func SyncServiceAccountToCluster(checluster *orgv1.CheCluster, name string, clusterAPI ClusterAPI) (*corev1.ServiceAccount, error) { + specSA, err := getSpecServiceAccount(checluster, name, clusterAPI) + if err != nil { + return nil, err + } + + clusterSA, err := getClusterServiceAccount(specSA.Name, specSA.Namespace, clusterAPI.Client) + if err != nil { + return nil, err + } + + if clusterSA == nil { + logrus.Infof("Creating a new object: %s, name %s", specSA.Kind, specSA.Name) + err := clusterAPI.Client.Create(context.TODO(), specSA) + return nil, err + } + + return clusterSA, nil +} + +func getClusterServiceAccount(name string, namespace string, client runtimeClient.Client) (*corev1.ServiceAccount, error) { + serviceAccount := &corev1.ServiceAccount{} + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + err := client.Get(context.TODO(), namespacedName, serviceAccount) + if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } + return nil, err + } + return serviceAccount, nil +} + +func getSpecServiceAccount(checluster *orgv1.CheCluster, name string, clusterAPI ClusterAPI) (*corev1.ServiceAccount, error) { + labels := GetLabels(checluster, util.GetValue(checluster.Spec.Server.CheFlavor, DefaultCheFlavor)) + serviceAccount := &corev1.ServiceAccount{ TypeMeta: metav1.TypeMeta{ Kind: "ServiceAccount", APIVersion: "v1", }, ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: cr.Namespace, + Namespace: checluster.Namespace, Labels: labels, }, } + + err := controllerutil.SetControllerReference(checluster, serviceAccount, clusterAPI.Scheme) + if err != nil { + return nil, err + } + + return serviceAccount, nil } diff --git a/pkg/deploy/update.go b/pkg/deploy/update.go new file mode 100644 index 000000000..e8dbbfb4a --- /dev/null +++ b/pkg/deploy/update.go @@ -0,0 +1,30 @@ +// +// Copyright (c) 2012-2019 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// +package deploy + +import ( + "context" + + orgv1 "github.com/eclipse/che-operator/pkg/apis/org/v1" + "github.com/sirupsen/logrus" +) + +func UpdateCheCRSpec(instance *orgv1.CheCluster, updatedField string, value string, clusterAPI ClusterAPI) (err error) { + logrus.Infof("Updating %s CR with %s: %s", instance.Name, updatedField, value) + err = clusterAPI.Client.Update(context.TODO(), instance) + if err != nil { + logrus.Errorf("Failed to update %s CR: %s", instance.Name, err) + return err + } + logrus.Infof("Custom resource %s updated", instance.Name) + return nil +} diff --git a/pkg/util/k8s_helpers.go b/pkg/util/k8s_helpers.go index df2487065..d18af4931 100644 --- a/pkg/util/k8s_helpers.go +++ b/pkg/util/k8s_helpers.go @@ -109,7 +109,7 @@ func (cl *k8s) GetDeploymentPod(name string, ns string) (podName string, err err podList, _ := api.Pods(ns).List(listOptions) podListItems := podList.Items if len(podListItems) == 0 { - logrus.Errorf("Failed to find pod to exec into. List of pods: %v", podListItems) + logrus.Errorf("Failed to find pod for component %s. List of pods: %v", name, podListItems) return "", err } // expecting only one pod to be there so, taking the first one @@ -118,6 +118,20 @@ func (cl *k8s) GetDeploymentPod(name string, ns string) (podName string, err err return podName, nil } +func (cl *k8s) GetPodsByComponent(name string, ns string) []string { + names := []string{} + api := cl.clientset.CoreV1() + listOptions := metav1.ListOptions{ + LabelSelector: "component=" + name, + } + podList, _ := api.Pods(ns).List(listOptions) + for _, pod := range podList.Items { + names = append(names, pod.Name) + } + + return names +} + // Reads 'user' and 'password' from the given secret func (cl *k8s) ReadSecret(name string, ns string) (user string, password string, err error) { secret, err := cl.clientset.CoreV1().Secrets(ns).Get(name, metav1.GetOptions{})