From b20b6c7a58318b8b73892e3a4965bd5474fbbd6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Bosdonnat?= Date: Wed, 2 Oct 2024 13:12:17 +0200 Subject: [PATCH] Merge kubernetes install/upgrade/migrate into one entry point --- mgradm/cmd/install/kubernetes/utils.go | 184 +--------- mgradm/cmd/migrate/kubernetes/migrationJob.go | 14 + mgradm/cmd/migrate/kubernetes/ssl.go | 51 --- mgradm/cmd/migrate/kubernetes/utils.go | 179 ++-------- mgradm/shared/kubernetes/certificates.go | 61 +--- mgradm/shared/kubernetes/deployment.go | 12 + mgradm/shared/kubernetes/inspect.go | 44 +++ mgradm/shared/kubernetes/install.go | 2 +- mgradm/shared/kubernetes/install_new.go | 314 ++++++++++++++++++ mgradm/shared/ssl/ssl.go | 4 +- mgradm/shared/utils/cmd_utils.go | 7 +- shared/kubernetes/deploy.go | 38 +++ shared/kubernetes/deploy_test.go | 58 ++++ shared/kubernetes/kubernetes.go | 10 +- shared/kubernetes/pvc.go | 13 + shared/kubernetes/pvc_test.go | 35 ++ shared/kubernetes/utils.go | 3 +- shared/utils/inspector.go | 16 + shared/utils/volumes.go | 15 +- 19 files changed, 624 insertions(+), 436 deletions(-) delete mode 100644 mgradm/cmd/migrate/kubernetes/ssl.go create mode 100644 mgradm/shared/kubernetes/inspect.go create mode 100644 mgradm/shared/kubernetes/install_new.go create mode 100644 shared/kubernetes/deploy.go create mode 100644 shared/kubernetes/deploy_test.go create mode 100644 shared/kubernetes/pvc_test.go diff --git a/mgradm/cmd/install/kubernetes/utils.go b/mgradm/cmd/install/kubernetes/utils.go index 78ccd5b88..dc967eeb9 100644 --- a/mgradm/cmd/install/kubernetes/utils.go +++ b/mgradm/cmd/install/kubernetes/utils.go @@ -7,18 +7,9 @@ package kubernetes import ( - "errors" - "os/exec" - - "github.com/rs/zerolog/log" "github.com/spf13/cobra" "github.com/uyuni-project/uyuni-tools/mgradm/shared/kubernetes" - adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils" - "github.com/uyuni-project/uyuni-tools/shared" - shared_kubernetes "github.com/uyuni-project/uyuni-tools/shared/kubernetes" - . "github.com/uyuni-project/uyuni-tools/shared/l10n" "github.com/uyuni-project/uyuni-tools/shared/types" - shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils" ) func installForKubernetes( @@ -27,179 +18,6 @@ func installForKubernetes( cmd *cobra.Command, args []string, ) error { - if _, err := exec.LookPath("kubectl"); err != nil { - return errors.New(L("install kubectl before running this command")) - } - flags.Installation.CheckParameters(cmd, "kubectl") - - fqdn := args[0] - if err := shared_utils.IsValidFQDN(fqdn); err != nil { - return err - } - - namespace := flags.Helm.Uyuni.Namespace - // Create the namespace if not present - if err := kubernetes.CreateNamespace(namespace); err != nil { - return err - } - - // TODO Is there a Hub API deployment? - - // TODO If there is already a server deployment, inspect it - - // TODO Run sanity checks for upgrade - - // TODO Get the fqdn from the inspected data if possible. Ignore difference witth input value for now. - - // TODO Scale down server deployment if present to upgrade the DB - - serverImage, err := shared_utils.ComputeImage(flags.Image.Registry, shared_utils.DefaultTag, flags.Image) - if err != nil { - return shared_utils.Errorf(err, L("failed to compute image URL")) - } - - mounts := kubernetes.GetServerMounts() - mounts = kubernetes.TuneMounts(mounts, &flags.Volumes) - - // TODO Only create PVCs if needed - if err := shared_kubernetes.CreatePersistentVolumeClaims(namespace, mounts); err != nil { - return err - } - - // TODO Run the DB Upgrade job if needed - - // TODO Run DB finalization job - - // TODO Run Post upgrade job - - // Extract some data from the cluster to guess how to configure Uyuni. - clusterInfos, err := shared_kubernetes.CheckCluster() - if err != nil { - return err - } - - // Install the traefik / nginx config on the node - // This will never be done in an operator. - needsHub := flags.HubXmlrpc.Replicas > 0 - if err := kubernetes.DeployNodeConfig( - namespace, clusterInfos, needsHub, flags.Installation.Debug.Java, - ); err != nil { - return err - } - - // Deploy the SSL CA and server certificates - var caIssuer string - sslFlags := flags.Installation.Ssl - if sslFlags.UseExisting() { - if err := kubernetes.DeployExistingCertificate(flags.Helm.Uyuni.Namespace, &sslFlags); err != nil { - return err - } - } else { - issuer, err := kubernetes.DeployGeneratedCa( - &flags.Helm, &sslFlags, clusterInfos.GetKubeconfig(), fqdn, flags.Image.PullPolicy, - ) - - if err != nil { - return shared_utils.Errorf(err, L("cannot deploy certificate")) - } - caIssuer = issuer - } - - // Create the Ingress routes before the deployments as those are triggering - // the creation of the uyuni-cert secret from cert-manager. - if err := kubernetes.CreateIngress(namespace, fqdn, caIssuer, clusterInfos.Ingress); err != nil { - return err - } - - // Wait for uyuni-cert secret to be ready - shared_kubernetes.WaitForSecret(namespace, kubernetes.CertSecretName) - - // Create a secret using SCC credentials if any are provided - pullSecret, err := shared_kubernetes.GetSccSecret(flags.Helm.Uyuni.Namespace, &flags.Installation.Scc) - if err != nil { - return err - } - - // Start the server - if err := kubernetes.CreateServerDeployment( - namespace, serverImage, flags.Image.PullPolicy, flags.Installation.TZ, flags.Installation.Debug.Java, - flags.Volumes.Mirror, pullSecret, - ); err != nil { - return err - } - - // Create the services - if err := kubernetes.CreateServices(namespace, flags.Installation.Debug.Java); err != nil { - return err - } - - if clusterInfos.Ingress == "traefik" { - // Create the Traefik routes - if err := kubernetes.CreateTraefikRoutes(namespace, needsHub, flags.Installation.Debug.Java); err != nil { - return err - } - } - - // Wait for the server deployment to have a running pod before trying to set it up. - if err := shared_kubernetes.WaitForRunningDeployment(namespace, kubernetes.ServerDeployName); err != nil { - return err - } - - // TODO Run the setup only if it hasn't be done before: this is a one-off task. - // Run the setup. This runs an exec into the running deployment. - // TODO Ideally we would need a job running at an earlier stage to persist the logs in a kubernetes-friendly way. - cnx := shared.NewConnection("kubectl", "", shared_kubernetes.ServerFilter) - - if err := adm_utils.RunSetup( - cnx, &flags.ServerFlags, fqdn, map[string]string{"NO_SSL": "Y"}, - ); err != nil { - if stopErr := shared_kubernetes.Stop(namespace, shared_kubernetes.ServerApp); stopErr != nil { - log.Error().Msgf(L("Failed to stop service: %v"), stopErr) - } - return err - } - - // Store the DB credentials in a secret. - dbFlags := flags.Installation.DB - if err := kubernetes.CreateDBSecret(namespace, kubernetes.DBSecret, dbFlags.User, dbFlags.Password); err != nil { - return err - } - - deploymentsStarting := []string{} - - // Start the Coco Deployments if requested. - if flags.Coco.Replicas > 0 { - cocoImage, err := shared_utils.ComputeImage(flags.Image.Registry, flags.Image.Tag, flags.Coco.Image) - if err != nil { - return err - } - if err := kubernetes.StartCocoDeployment( - namespace, cocoImage, flags.Image.PullPolicy, flags.Coco.Replicas, - dbFlags.Port, dbFlags.Name, - ); err != nil { - return err - } - deploymentsStarting = append(deploymentsStarting, kubernetes.CocoDeployName) - } - - // In an operator mind, the user would just change the custom resource to enable the feature. - if needsHub { - // Install Hub API deployment, service - hubAPIImage, err := shared_utils.ComputeImage(flags.Image.Registry, flags.Image.Tag, flags.HubXmlrpc.Image) - if err != nil { - return err - } - if err := kubernetes.InstallHubAPI(namespace, hubAPIImage, flags.Image.PullPolicy); err != nil { - return err - } - deploymentsStarting = append(deploymentsStarting, kubernetes.HubAPIDeployName) - } - - // Wait for all the other deployments to be ready - if err := shared_kubernetes.WaitForDeployments(namespace, deploymentsStarting...); err != nil { - return err - } - - return nil + return kubernetes.Reconcile(flags, args[0]) } diff --git a/mgradm/cmd/migrate/kubernetes/migrationJob.go b/mgradm/cmd/migrate/kubernetes/migrationJob.go index ba9f254f8..de6b81b77 100644 --- a/mgradm/cmd/migrate/kubernetes/migrationJob.go +++ b/mgradm/cmd/migrate/kubernetes/migrationJob.go @@ -99,5 +99,19 @@ func getMigrationJob( job.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts job.Spec.Template.Spec.Volumes = volumes + initScript := `cp -a /etc/systemd/system/multi-user.target.wants/. /mnt/etc-systemd-multi` + + job.Spec.Template.Spec.InitContainers = []core.Container{ + { + Name: "init-volumes", + Image: image, + ImagePullPolicy: kubernetes.GetPullPolicy(pullPolicy), + Command: []string{"sh", "-c", initScript}, + VolumeMounts: []core.VolumeMount{ + {Name: "etc-systemd-multi", MountPath: "/mnt/etc-systemd-multi"}, + }, + }, + } + return job, nil } diff --git a/mgradm/cmd/migrate/kubernetes/ssl.go b/mgradm/cmd/migrate/kubernetes/ssl.go deleted file mode 100644 index 6b032d93f..000000000 --- a/mgradm/cmd/migrate/kubernetes/ssl.go +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-FileCopyrightText: 2024 SUSE LLC -// -// SPDX-License-Identifier: Apache-2.0 - -//go:build !nok8s - -package kubernetes - -import ( - "os" - "path" - - "github.com/uyuni-project/uyuni-tools/mgradm/shared/kubernetes" - "github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl" - adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils" - . "github.com/uyuni-project/uyuni-tools/shared/l10n" - "github.com/uyuni-project/uyuni-tools/shared/utils" -) - -func installExistingCertificate(namespace string, extractedData *MigrationData) error { - // Store the certificates and key to file to load them - tmpDir, cleaner, err := utils.TempDir() - if err != nil { - return err - } - defer cleaner() - - caCrtPath := path.Join(tmpDir, "ca.crt") - if err := os.WriteFile(caCrtPath, []byte(extractedData.CaCert), 0700); err != nil { - return utils.Errorf(err, L("failed to create temporary ca.crt file")) - } - - srvCrtPath := path.Join(tmpDir, "srv.crt") - if err := os.WriteFile(srvCrtPath, []byte(extractedData.ServerCert), 0700); err != nil { - return utils.Errorf(err, L("failed to create temporary srv.crt file")) - } - - srvKeyPath := path.Join(tmpDir, "srv.key") - if err := os.WriteFile(srvKeyPath, []byte(extractedData.ServerKey), 0700); err != nil { - return utils.Errorf(err, L("failed to create temporary srv.key file")) - } - - sslFlags := adm_utils.SslCertFlags{ - Ca: ssl.CaChain{Root: caCrtPath}, - Server: ssl.SslPair{ - Key: srvKeyPath, - Cert: srvCrtPath, - }, - } - return kubernetes.DeployExistingCertificate(namespace, &sslFlags) -} diff --git a/mgradm/cmd/migrate/kubernetes/utils.go b/mgradm/cmd/migrate/kubernetes/utils.go index b912ec0ab..9d3e75447 100644 --- a/mgradm/cmd/migrate/kubernetes/utils.go +++ b/mgradm/cmd/migrate/kubernetes/utils.go @@ -7,12 +7,11 @@ package kubernetes import ( - "encoding/base64" - "fmt" + "os" + "path" "github.com/spf13/cobra" "github.com/uyuni-project/uyuni-tools/mgradm/shared/kubernetes" - "github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl" shared_kubernetes "github.com/uyuni-project/uyuni-tools/shared/kubernetes" . "github.com/uyuni-project/uyuni-tools/shared/l10n" "github.com/uyuni-project/uyuni-tools/shared/types" @@ -28,10 +27,6 @@ func migrateToKubernetes( args []string, ) error { namespace := flags.Helm.Uyuni.Namespace - // Create the namespace if not present - if err := kubernetes.CreateNamespace(namespace); err != nil { - return err - } // Check the for the required SSH key and configuration if err := checkSSH(namespace, &flags.SSH); err != nil { @@ -82,169 +77,57 @@ func migrateToKubernetes( return err } - oldPgVersion := extractedData.Data.CurrentPgVersion - newPgVersion := extractedData.Data.ImagePgVersion - - // Run the DB Migration job if needed - if oldPgVersion < newPgVersion { - if err := kubernetes.StartDBUpgradeJob( - namespace, flags.Image.Registry, flags.Image, flags.DBUpgradeImage, - oldPgVersion, newPgVersion, - ); err != nil { - return err - } - - // Wait for ever for the job to finish: the duration of this job depends on the amount of data to upgrade - if err := shared_kubernetes.WaitForJob(namespace, kubernetes.DBUpgradeJobName, -1); err != nil { - return err - } - } else if oldPgVersion > newPgVersion { - return fmt.Errorf( - L("downgrading database from PostgreSQL %[1]d to %[2]d is not supported"), oldPgVersion, newPgVersion) - } - - // Run the DB Finalization job - schemaUpdateRequired := oldPgVersion != newPgVersion - if err := kubernetes.StartDBFinalizeJob( - namespace, serverImage, flags.Image.PullPolicy, schemaUpdateRequired, true, - ); err != nil { - return err - } - - // Wait for ever for the job to finish: the duration of this job depends on the amount of data to reindex - if err := shared_kubernetes.WaitForJob(namespace, kubernetes.DBFinalizeJobName, -1); err != nil { - return err - } - - // Run the Post Upgrade job - if err := kubernetes.StartPostUpgradeJob(namespace, serverImage, flags.Image.PullPolicy); err != nil { - return err - } - - if err := shared_kubernetes.WaitForJob(namespace, kubernetes.PostUpgradeJobName, 60); err != nil { - return err + flags.Installation.TZ = extractedData.Data.Timezone + flags.Installation.Debug.Java = extractedData.Data.Debug + if extractedData.Data.HasHubXmlrpcAPI { + flags.HubXmlrpc.Replicas = 1 + flags.HubXmlrpc.IsChanged = true } + flags.Installation.DB.User = extractedData.Data.DBUser + flags.Installation.DB.Password = extractedData.Data.DBPassword + // TODO Are those two really needed in migration? + flags.Installation.DB.Name = extractedData.Data.DBName + flags.Installation.DB.Port = extractedData.Data.DBPort - // Extract some data from the cluster to guess how to configure Uyuni. - clusterInfos, err := shared_kubernetes.CheckCluster() + sslDir, cleaner, err := utils.TempDir() if err != nil { return err } + defer cleaner() - // Install the traefik / nginx config on the node - // This will never be done in an operator. - needsHub := flags.HubXmlrpc.Replicas > 0 - if err := kubernetes.DeployNodeConfig(namespace, clusterInfos, needsHub, extractedData.Data.Debug); err != nil { + // Extract the SSL data as files and pass them as arguments to share code with installation. + if err := writeToFile( + extractedData.CaCert, path.Join(sslDir, "ca.crt"), &flags.Installation.Ssl.Ca.Root, + ); err != nil { return err } - // Deploy the SSL CA and server certificates - var caIssuer string + // The CA key shouldn't be stored as a temporary file. if extractedData.CaKey != "" { - // cert-manager is not required for 3rd party certificates, only if we have the CA key. - // Note that in an operator we won't be able to install cert-manager and just wait for it to be installed. - kubeconfig := clusterInfos.GetKubeconfig() - - if err := kubernetes.InstallCertManager(&flags.Helm, kubeconfig, flags.Image.PullPolicy); err != nil { - return utils.Errorf(err, L("cannot install cert manager")) - } - - // Convert CA to RSA to use in a Kubernetes TLS secret. - // In an operator we would have to fail now if there is no SSL password as we cannot prompt it. - ca := ssl.SslPair{ - Key: base64.StdEncoding.EncodeToString( - ssl.GetRsaKey(extractedData.CaKey, flags.Installation.Ssl.Password), - ), - Cert: base64.StdEncoding.EncodeToString(ssl.StripTextFromCertificate(extractedData.CaCert)), - } - - // Install the cert-manager issuers - if _, err := kubernetes.DeployReusedCa(namespace, &ca); err != nil { - return err - } - caIssuer = kubernetes.CaIssuerName - } else { - // Most likely a 3rd party certificate: cert-manager is not needed in this case - if err := installExistingCertificate(namespace, extractedData); err != nil { - return err - } + flags.Installation.Ssl.Ca.Key = extractedData.CaKey } - // Create the Ingress routes before the deployments as those are triggering - // the creation of the uyuni-cert secret from cert-manager. - if err := kubernetes.CreateIngress(namespace, fqdn, caIssuer, clusterInfos.Ingress); err != nil { - return err - } - - // Wait for uyuni-cert secret to be ready - shared_kubernetes.WaitForSecret(namespace, kubernetes.CertSecretName) - - // Create a secret using SCC credentials if any are provided - pullSecret, err := shared_kubernetes.GetSccSecret(flags.Helm.Uyuni.Namespace, &flags.Installation.Scc) - if err != nil { - return err - } - - deploymentsStarting := []string{kubernetes.ServerDeployName} - // Start the server - if err := kubernetes.CreateServerDeployment( - namespace, serverImage, flags.Image.PullPolicy, extractedData.Data.Timezone, extractedData.Data.Debug, - flags.Volumes.Mirror, pullSecret, + if err := writeToFile( + extractedData.ServerCert, path.Join(sslDir, "srv.crt"), &flags.Installation.Ssl.Server.Cert, ); err != nil { return err } - // Create the services - if err := kubernetes.CreateServices(namespace, extractedData.Data.Debug); err != nil { - return err - } - - if clusterInfos.Ingress == "traefik" { - // Create the Traefik routes - if err := kubernetes.CreateTraefikRoutes(namespace, needsHub, extractedData.Data.Debug); err != nil { - return err - } - } - - // Store the extracted DB credentials in a secret. - if err := kubernetes.CreateDBSecret( - namespace, kubernetes.DBSecret, extractedData.Data.DBUser, extractedData.Data.DBPassword, + if err := writeToFile( + extractedData.ServerKey, path.Join(sslDir, "srv.key"), &flags.Installation.Ssl.Server.Key, ); err != nil { return err } - // Start the Coco Deployments if requested. - if flags.Coco.Replicas > 0 { - cocoImage, err := utils.ComputeImage(flags.Image.Registry, flags.Image.Tag, flags.Coco.Image) - if err != nil { - return err - } - if err := kubernetes.StartCocoDeployment( - namespace, cocoImage, flags.Image.PullPolicy, flags.Coco.Replicas, - extractedData.Data.DBPort, extractedData.Data.DBName, - ); err != nil { - return err - } - deploymentsStarting = append(deploymentsStarting, kubernetes.CocoDeployName) - } + return kubernetes.Reconcile(flags, fqdn) +} - // In an operator mind, the user would just change the custom resource to enable the feature. - if extractedData.Data.HasHubXmlrpcAPI { - // Install Hub API deployment, service - hubAPIImage, err := utils.ComputeImage(flags.Image.Registry, flags.Image.Tag, flags.HubXmlrpc.Image) - if err != nil { - return err - } - if err := kubernetes.InstallHubAPI(namespace, hubAPIImage, flags.Image.PullPolicy); err != nil { - return err +func writeToFile(content string, file string, flag *string) error { + if content != "" { + if err := os.WriteFile(file, []byte(content), 0600); err != nil { + return utils.Errorf(err, L("failed to write certificate to %s"), file) } - deploymentsStarting = append(deploymentsStarting, kubernetes.HubAPIDeployName) - } - - // Wait for all the deployments to be ready - if err := shared_kubernetes.WaitForDeployments(namespace, deploymentsStarting...); err != nil { - return err + *flag = file } - return nil } diff --git a/mgradm/shared/kubernetes/certificates.go b/mgradm/shared/kubernetes/certificates.go index 2b7aa1a37..8b684368b 100644 --- a/mgradm/shared/kubernetes/certificates.go +++ b/mgradm/shared/kubernetes/certificates.go @@ -7,9 +7,9 @@ package kubernetes import ( "encoding/base64" "errors" - "fmt" "os" "path/filepath" + "strings" "time" "github.com/rs/zerolog" @@ -29,12 +29,6 @@ import ( // CaIssuerName is the name of the CA issuer deployed if cert-manager is used. const CaIssuerName = "uyuni-ca-issuer" -// Helm annotation to add in order to use cert-manager's uyuni CA issuer, in JSON format. -var ingressCertManagerAnnotation string = fmt.Sprintf( - "ingressSslAnnotations={\"cert-manager.io/issuer\": \"%s\"}", - CaIssuerName, -) - // DeployExistingCertificate execute a deploy of an existing certificate. func DeployExistingCertificate(namespace string, sslFlags *cmd_utils.SslCertFlags) error { // Deploy the SSL Certificate secret and CA configmap @@ -97,47 +91,26 @@ func DeployReusedCa(namespace string, ca *ssl.SslPair) ([]string, error) { log.Fatal().Err(err).Msg(L("Failed to create issuer")) } - // Wait for issuer to be ready - if err := waitForIssuer(namespace, CaIssuerName); err != nil { - return nil, err - } else { - helmArgs = append(helmArgs, "--set-json", ingressCertManagerAnnotation) - } - - // Copy the CA cert into uyuni-ca config map as the container shouldn't have the CA secret - if err := createCaConfig(namespace, []byte(ca.Cert)); err != nil { - return nil, err - } - return helmArgs, nil } // DeployGenerateCa deploys a new SSL CA using cert-manager. func DeployGeneratedCa( - helmFlags *cmd_utils.HelmFlags, + namespace string, sslFlags *cmd_utils.SslCertFlags, - kubeconfig string, fqdn string, - imagePullPolicy string, -) (string, error) { - const issuer = "uyuni-ca-issuer" - - // Install cert-manager if needed - if err := InstallCertManager(helmFlags, kubeconfig, imagePullPolicy); err != nil { - return "", utils.Errorf(err, L("cannot install cert manager")) - } - +) error { log.Info().Msg(L("Creating SSL certificate issuer")) tempDir, err := os.MkdirTemp("", "mgradm-*") if err != nil { - return "", utils.Errorf(err, L("failed to create temporary directory")) + return utils.Errorf(err, L("failed to create temporary directory")) } defer os.RemoveAll(tempDir) issuerPath := filepath.Join(tempDir, "issuer.yaml") issuerData := templates.GeneratedCaIssuerTemplateData{ - Namespace: helmFlags.Uyuni.Namespace, + Namespace: namespace, Country: sslFlags.Country, State: sslFlags.State, City: sslFlags.City, @@ -148,25 +121,15 @@ func DeployGeneratedCa( } if err = utils.WriteTemplateToFile(issuerData, issuerPath, 0500, true); err != nil { - return "", utils.Errorf(err, L("failed to generate issuer definition")) + return utils.Errorf(err, L("failed to generate issuer definition")) } err = utils.RunCmd("kubectl", "apply", "-f", issuerPath) if err != nil { - return "", utils.Errorf(err, L("Failed to create issuer")) - } - - // Wait for issuer to be ready - if err := waitForIssuer(helmFlags.Uyuni.Namespace, "uyuni-ca-issuer"); err != nil { - return "", err - } - - // Extract the CA cert into uyuni-ca config map as the container shouldn't have the CA secret - if err := extractCaCertToConfig(helmFlags.Uyuni.Namespace); err != nil { - return "", err + return utils.Errorf(err, L("Failed to create issuer")) } - return issuer, nil + return nil } // Wait for issuer to be ready. @@ -274,3 +237,11 @@ func createCaConfig(namespace string, ca []byte) error { } return kubernetes.Apply([]runtime.Object{&configMap}, L("failed to create the SSH migration ConfigMap")) } + +// HasIssuer returns true if the issuer is defined. +// +// False will be returned in case of errors or if the issuer resource doesn't exist on the cluster. +func HasIssuer(namespace string, name string) bool { + out, err := runCmdOutput(zerolog.DebugLevel, "kubectl", "get", "issuer", "-n", namespace, name, "-o", "name") + return err == nil && strings.TrimSpace(string(out)) != "" +} diff --git a/mgradm/shared/kubernetes/deployment.go b/mgradm/shared/kubernetes/deployment.go index b35432b16..d1a0809b9 100644 --- a/mgradm/shared/kubernetes/deployment.go +++ b/mgradm/shared/kubernetes/deployment.go @@ -309,3 +309,15 @@ func getRunningServerImage(namespace string) string { } return strings.TrimSpace(string(out)) } + +// neverSetup checks if the server container has already been setup setup. +func neverSetup(namespace string, image string, pullPolicy string) bool { + out, err := kubernetes.RunPodLogs(namespace, "ran-setup-check", image, pullPolicy, + []types.VolumeMount{utils.RootVolumeMount}, + "ls", "-1a", "/root/", + ) + if err != nil { + return false + } + return !strings.Contains(string(out), ".MANAGER_SETUP_COMPLETE") +} diff --git a/mgradm/shared/kubernetes/inspect.go b/mgradm/shared/kubernetes/inspect.go new file mode 100644 index 000000000..a5dacec4e --- /dev/null +++ b/mgradm/shared/kubernetes/inspect.go @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package kubernetes + +import ( + "github.com/uyuni-project/uyuni-tools/shared/kubernetes" + . "github.com/uyuni-project/uyuni-tools/shared/l10n" + "github.com/uyuni-project/uyuni-tools/shared/types" + "github.com/uyuni-project/uyuni-tools/shared/utils" +) + +func inspectServer(namespace string, image string, pullPolicy string) (*utils.ServerInspectData, error) { + podName := "uyuni-image-inspector" + + tempDir, cleaner, err := utils.TempDir() + if err != nil { + return nil, err + } + defer cleaner() + inspector := utils.NewServerInspector(tempDir) + // We need the inspector to write to the pod's logs instead of a file + inspector.DataPath = "/dev/stdout" + script, err := inspector.GenerateScriptString() + if err != nil { + return nil, err + } + + out, err := kubernetes.RunPodLogs( + namespace, podName, image, pullPolicy, []types.VolumeMount{utils.EtcRhnVolumeMount, utils.VarPgsqlVolumeMount}, + "sh", "-c", script, + ) + if err != nil { + return nil, err + } + + // Parse the data + inspectedData, err := utils.ReadInspectDataString[utils.ServerInspectData]([]byte(out)) + if err != nil { + return nil, utils.Errorf(err, L("failed to parse the inspected data")) + } + return inspectedData, nil +} diff --git a/mgradm/shared/kubernetes/install.go b/mgradm/shared/kubernetes/install.go index 3394b6c4e..e59d0189f 100644 --- a/mgradm/shared/kubernetes/install.go +++ b/mgradm/shared/kubernetes/install.go @@ -178,7 +178,7 @@ func Upgrade( var runningData *utils.ServerInspectData if runningImage := getRunningServerImage(namespace); runningImage != "" { - runningData, err = kubernetes.InspectKubernetes(namespace, runningImage, "Never") + runningData, err = inspectServer(namespace, runningImage, "Never") if err != nil { return err } diff --git a/mgradm/shared/kubernetes/install_new.go b/mgradm/shared/kubernetes/install_new.go new file mode 100644 index 000000000..ac512f704 --- /dev/null +++ b/mgradm/shared/kubernetes/install_new.go @@ -0,0 +1,314 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package kubernetes + +import ( + "encoding/base64" + "errors" + "fmt" + "os" + "os/exec" + + "github.com/rs/zerolog/log" + "github.com/uyuni-project/uyuni-tools/mgradm/shared/ssl" + adm_utils "github.com/uyuni-project/uyuni-tools/mgradm/shared/utils" + "github.com/uyuni-project/uyuni-tools/shared" + "github.com/uyuni-project/uyuni-tools/shared/kubernetes" + . "github.com/uyuni-project/uyuni-tools/shared/l10n" + "github.com/uyuni-project/uyuni-tools/shared/utils" +) + +// Reconcile upgrades, migrate or install the server. +func Reconcile(flags *KubernetesServerFlags, fqdn string) error { + if _, err := exec.LookPath("kubectl"); err != nil { + return errors.New(L("install kubectl before running this command")) + } + + if err := utils.IsValidFQDN(fqdn); err != nil { + return err + } + + namespace := flags.Helm.Uyuni.Namespace + // Create the namespace if not present + if err := CreateNamespace(namespace); err != nil { + return err + } + + serverImage, err := utils.ComputeImage(flags.Image.Registry, utils.DefaultTag, flags.Image) + if err != nil { + return utils.Errorf(err, L("failed to compute image URL")) + } + + // TODO Is there a Hub API deployment? + + cnx := shared.NewConnection("kubectl", "", kubernetes.ServerFilter) + + // Do we have an existing deployment to upgrade? + // This can be freshly synchronized data from a migration or a running instance to upgrade. + hasDatabase := kubernetes.HasVolume(namespace, "var-pgsql") + var inspectedData utils.ServerInspectData + if hasDatabase { + // Inspect the image and the existing volumes + data, err := inspectServer(namespace, serverImage, flags.Image.PullPolicy) + if err != nil { + return err + } + inspectedData = *data + + // Do we have a running server deploy? which version is it? + // If there is no deployment / image, don't check the uyuni / SUMA upgrades + var runningData *utils.ServerInspectData + if runningImage := getRunningServerImage(namespace); runningImage != "" { + runningData, err = inspectServer(namespace, runningImage, "Never") + if err != nil { + return err + } + } + + // Run sanity checks for upgrade + if err := adm_utils.SanityCheck(runningData, &inspectedData, serverImage); err != nil { + return err + } + + // Get the fqdn from the inspected data if possible. Ignore difference with input value for now. + fqdn = inspectedData.Fqdn + + if inspectedData.CurrentPgVersion != inspectedData.ImagePgVersion { + hasDeployment := kubernetes.HasDeployment(namespace, kubernetes.ServerFilter) + if hasDeployment { + // Scale down server deployment if present to upgrade the DB + if err := kubernetes.ReplicasTo(namespace, kubernetes.ServerApp, 0); err != nil { + return utils.Errorf(err, L("cannot set replica to 0")) + } + } + } + } + + mounts := GetServerMounts() + mounts = TuneMounts(mounts, &flags.Volumes) + + if err := kubernetes.CreatePersistentVolumeClaims(namespace, mounts); err != nil { + return err + } + + if hasDatabase { + oldPgVersion := inspectedData.CurrentPgVersion + newPgVersion := inspectedData.ImagePgVersion + + // Run the DB Upgrade job if needed + if oldPgVersion < newPgVersion { + if err := StartDBUpgradeJob( + namespace, flags.Image.Registry, flags.Image, flags.DBUpgradeImage, + oldPgVersion, newPgVersion, + ); err != nil { + return err + } + + // Wait for ever for the job to finish: the duration of this job depends on the amount of data to upgrade + if err := kubernetes.WaitForJob(namespace, DBUpgradeJobName, -1); err != nil { + return err + } + } else if oldPgVersion > newPgVersion { + return fmt.Errorf( + L("downgrading database from PostgreSQL %[1]d to %[2]d is not supported"), oldPgVersion, newPgVersion) + } + + // Run DB finalization job + schemaUpdateRequired := oldPgVersion != newPgVersion + if err := StartDBFinalizeJob( + namespace, serverImage, flags.Image.PullPolicy, schemaUpdateRequired, true, + ); err != nil { + return err + } + + // Wait for ever for the job to finish: the duration of this job depends on the amount of data to reindex + if err := kubernetes.WaitForJob(namespace, DBFinalizeJobName, -1); err != nil { + return err + } + + // Run the Post Upgrade job + if err := StartPostUpgradeJob(namespace, serverImage, flags.Image.PullPolicy); err != nil { + return err + } + + if err := kubernetes.WaitForJob(namespace, PostUpgradeJobName, 60); err != nil { + return err + } + } + + // Extract some data from the cluster to guess how to configure Uyuni. + clusterInfos, err := kubernetes.CheckCluster() + if err != nil { + return err + } + + if replicas := kubernetes.GetReplicas(namespace, ServerDeployName); replicas > 0 && !flags.HubXmlrpc.IsChanged { + // Upgrade: detect the number of existing hub xmlrpc replicas + flags.HubXmlrpc.Replicas = replicas + } + needsHub := flags.HubXmlrpc.Replicas > 0 + + // Install the traefik / nginx config on the node + // This will never be done in an operator. + if err := DeployNodeConfig(namespace, clusterInfos, needsHub, flags.Installation.Debug.Java); err != nil { + return err + } + + // Deploy the SSL CA and server certificates + var caIssuer string + if flags.Installation.Ssl.UseExisting() { + if err := DeployExistingCertificate(flags.Helm.Uyuni.Namespace, &flags.Installation.Ssl); err != nil { + return err + } + } else if !HasIssuer(namespace, CaIssuerName) { + // TODO Skip for upgrade + + // cert-manager is not required for 3rd party certificates, only if we have the CA key. + // Note that in an operator we won't be able to install cert-manager and just wait for it to be installed. + kubeconfig := clusterInfos.GetKubeconfig() + + if err := InstallCertManager(&flags.Helm, kubeconfig, flags.Image.PullPolicy); err != nil { + return utils.Errorf(err, L("cannot install cert manager")) + } + + if flags.Installation.Ssl.UseMigratedCa() { + // Convert CA to RSA to use in a Kubernetes TLS secret. + // In an operator we would have to fail now if there is no SSL password as we cannot prompt it. + rootCA, err := os.ReadFile(flags.Installation.Ssl.Ca.Root) + if err != nil { + return utils.Errorf(err, L("failed to read Root CA file")) + } + ca := ssl.SslPair{ + Key: base64.StdEncoding.EncodeToString( + ssl.GetRsaKey(flags.Installation.Ssl.Ca.Key, flags.Installation.Ssl.Password), + ), + Cert: base64.StdEncoding.EncodeToString(ssl.StripTextFromCertificate(string(rootCA))), + } + + // Install the cert-manager issuers + if _, err := DeployReusedCa(namespace, &ca); err != nil { + return err + } + } else { + if err := DeployGeneratedCa(flags.Helm.Uyuni.Namespace, &flags.Installation.Ssl, fqdn); err != nil { + return err + } + } + + // Wait for issuer to be ready + if err := waitForIssuer(flags.Helm.Uyuni.Namespace, CaIssuerName); err != nil { + return err + } + + // Extract the CA cert into uyuni-ca config map as the container shouldn't have the CA secret + if err := extractCaCertToConfig(flags.Helm.Uyuni.Namespace); err != nil { + return err + } + caIssuer = CaIssuerName + } + + // Create the Ingress routes before the deployments as those are triggering + // the creation of the uyuni-cert secret from cert-manager. + if err := CreateIngress(namespace, fqdn, caIssuer, clusterInfos.Ingress); err != nil { + return err + } + + // Wait for uyuni-cert secret to be ready + kubernetes.WaitForSecret(namespace, CertSecretName) + + // Create a secret using SCC credentials if any are provided + pullSecret, err := kubernetes.GetSccSecret(flags.Helm.Uyuni.Namespace, &flags.Installation.Scc) + if err != nil { + return err + } + + // Start the server + if err := CreateServerDeployment( + namespace, serverImage, flags.Image.PullPolicy, flags.Installation.TZ, flags.Installation.Debug.Java, + flags.Volumes.Mirror, pullSecret, + ); err != nil { + return err + } + + // Create the services + if err := CreateServices(namespace, flags.Installation.Debug.Java); err != nil { + return err + } + + if clusterInfos.Ingress == "traefik" { + // Create the Traefik routes + if err := CreateTraefikRoutes(namespace, needsHub, flags.Installation.Debug.Java); err != nil { + return err + } + } + + // Wait for the server deployment to have a running pod before trying to set it up. + if err := kubernetes.WaitForRunningDeployment(namespace, ServerDeployName); err != nil { + return err + } + + // Run the setup only if it hasn't be done before: this is a one-off task. + // TODO Ideally we would need a job running at an earlier stage to persist the logs in a kubernetes-friendly way. + if neverSetup(namespace, serverImage, flags.Image.PullPolicy) { + if err := adm_utils.RunSetup( + cnx, &flags.ServerFlags, fqdn, map[string]string{"NO_SSL": "Y"}, + ); err != nil { + if stopErr := kubernetes.Stop(namespace, kubernetes.ServerApp); stopErr != nil { + log.Error().Msgf(L("Failed to stop service: %v"), stopErr) + } + return err + } + } + + // Store the DB credentials in a secret. + if flags.Installation.DB.User != "" && flags.Installation.DB.Password != "" { + if err := CreateDBSecret( + namespace, DBSecret, flags.Installation.DB.User, flags.Installation.DB.Password, + ); err != nil { + return err + } + } + + deploymentsStarting := []string{} + + // Start the Coco Deployments if requested. + if replicas := kubernetes.GetReplicas(namespace, CocoDeployName); replicas != 0 && !flags.Coco.IsChanged { + // Upgrade: detect the number of running coco replicas + flags.Coco.Replicas = replicas + } + if flags.Coco.Replicas > 0 { + cocoImage, err := utils.ComputeImage(flags.Image.Registry, flags.Image.Tag, flags.Coco.Image) + if err != nil { + return err + } + if err := StartCocoDeployment( + namespace, cocoImage, flags.Image.PullPolicy, flags.Coco.Replicas, + flags.Installation.DB.Port, flags.Installation.DB.Name, + ); err != nil { + return err + } + deploymentsStarting = append(deploymentsStarting, CocoDeployName) + } + + // In an operator mind, the user would just change the custom resource to enable the feature. + if needsHub { + // Install Hub API deployment, service + hubAPIImage, err := utils.ComputeImage(flags.Image.Registry, flags.Image.Tag, flags.HubXmlrpc.Image) + if err != nil { + return err + } + if err := InstallHubAPI(namespace, hubAPIImage, flags.Image.PullPolicy); err != nil { + return err + } + deploymentsStarting = append(deploymentsStarting, HubAPIDeployName) + } + + // Wait for all the other deployments to be ready + if err := kubernetes.WaitForDeployments(namespace, deploymentsStarting...); err != nil { + return err + } + + return nil +} diff --git a/mgradm/shared/ssl/ssl.go b/mgradm/shared/ssl/ssl.go index a335ec26b..b08807cf9 100644 --- a/mgradm/shared/ssl/ssl.go +++ b/mgradm/shared/ssl/ssl.go @@ -20,7 +20,9 @@ import ( // CaChain is a type to store CA Chain. type CaChain struct { - Root string + Root string + // Key is the CA key file in the case of a migration of a self-generate CA. + Key string Intermediate []string } diff --git a/mgradm/shared/utils/cmd_utils.go b/mgradm/shared/utils/cmd_utils.go index 8c27542bf..c99eb1b63 100644 --- a/mgradm/shared/utils/cmd_utils.go +++ b/mgradm/shared/utils/cmd_utils.go @@ -18,7 +18,12 @@ var defaultImage = path.Join(utils.DefaultRegistry, "server") // UseExisting return true if existing SSL Cert can be used. func (f *SslCertFlags) UseExisting() bool { - return f.Server.Cert != "" && f.Server.Key != "" && f.Ca.Root != "" + return f.Server.Cert != "" && f.Server.Key != "" && f.Ca.Root != "" && f.Ca.Key == "" +} + +// UseMigratedCa returns true if a migrated CA and key can be used. +func (f *SslCertFlags) UseMigratedCa() bool { + return f.Ca.Root != "" && f.Ca.Key != "" } // CheckParameters checks that all the required flags are passed if using 3rd party certificates. diff --git a/shared/kubernetes/deploy.go b/shared/kubernetes/deploy.go new file mode 100644 index 000000000..a69489942 --- /dev/null +++ b/shared/kubernetes/deploy.go @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package kubernetes + +import ( + "strconv" + "strings" + + "github.com/rs/zerolog" +) + +// HasDeployment returns true when a deployment matching the kubectl get filter is existing in the namespace. +func HasDeployment(namespace string, filter string) bool { + out, err := runCmdOutput(zerolog.DebugLevel, "kubectl", "get", "deploy", "-n", namespace, filter, "-o", "name") + if err == nil && strings.TrimSpace(string(out)) != "" { + return true + } + return false +} + +// GetReplicas return the number of replicas of a deployment. +// +// If no such deployment exists, 0 will be returned as if there was a deployment scaled down to 0. +func GetReplicas(namespace string, name string) int { + out, err := runCmdOutput(zerolog.DebugLevel, + "kubectl", "get", "deploy", "-n", namespace, name, "-o", "jsonpath={.status.replicas}", + ) + if err != nil { + return 0 + } + replicas, err := strconv.Atoi(strings.TrimSpace(string(out))) + if err != nil { + return 0 + } + return replicas +} diff --git a/shared/kubernetes/deploy_test.go b/shared/kubernetes/deploy_test.go new file mode 100644 index 000000000..8e925b2ea --- /dev/null +++ b/shared/kubernetes/deploy_test.go @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package kubernetes + +import ( + "errors" + "fmt" + "testing" + + "github.com/rs/zerolog" + "github.com/uyuni-project/uyuni-tools/shared/testutils" +) + +func TestHasDeployment(t *testing.T) { + type dataType struct { + out string + err error + expected bool + } + + data := []dataType{ + {"deployment.apps/traefik\n", nil, true}, + {"\n", nil, false}, + {"Some error", errors.New("Some error"), false}, + } + + for i, test := range data { + runCmdOutput = func(_ zerolog.Level, _ string, _ ...string) ([]byte, error) { + return []byte(test.out), test.err + } + testutils.AssertEquals(t, fmt.Sprintf("test %d: unexpected result", i+1), test.expected, + HasDeployment("kube-system", "-lapp.kubernetes.io/name=traefik"), + ) + } +} + +func TestGetReplicas(t *testing.T) { + type dataType struct { + out string + err error + expected int + } + data := []dataType{ + {"2\n", nil, 2}, + {"no such deploy\n", errors.New("No such deploy"), 0}, + {"invalid output\n", nil, 0}, + } + + for i, test := range data { + runCmdOutput = func(_ zerolog.Level, _ string, _ ...string) ([]byte, error) { + return []byte(test.out), test.err + } + testutils.AssertEquals(t, fmt.Sprintf("test %d: unexpected result", i+1), + test.expected, GetReplicas("uyuni", "uyuni-hub-api")) + } +} diff --git a/shared/kubernetes/kubernetes.go b/shared/kubernetes/kubernetes.go index 1e0832b8f..b04388ffc 100644 --- a/shared/kubernetes/kubernetes.go +++ b/shared/kubernetes/kubernetes.go @@ -203,8 +203,16 @@ func AddSccSecret(helmArgs []string, namespace string, scc *types.SCCCredentials // GetSccSecret creates a secret holding the SCC credentials and returns the secret name. func GetSccSecret(namespace string, scc *types.SCCCredentials) (string, error) { + const secretName = "scc-credentials" + + // Return the existing secret if any. + out, err := runCmdOutput(zerolog.DebugLevel, "kubectl", "get", "-n", namespace, "secret", secretName, "-o", "name") + if err == nil && strings.TrimSpace(string(out)) != "" { + return secretName, nil + } + + // Create the secret if SCC user and password are passed. if scc.User != "" && scc.Password != "" { - secretName := "scc-credentials" if err := createDockerSecret( namespace, secretName, "registry.suse.com", scc.User, scc.Password, ); err != nil { diff --git a/shared/kubernetes/pvc.go b/shared/kubernetes/pvc.go index 5f520264b..d20d2fddc 100644 --- a/shared/kubernetes/pvc.go +++ b/shared/kubernetes/pvc.go @@ -244,3 +244,16 @@ func CreateVolumes(mounts []types.VolumeMount) []core.Volume { return volumes } + +var runCmdOutput = utils.RunCmdOutput + +// HasVolume returns true if the pvcName persistent volume claim is bound. +func HasVolume(namespace string, pvcName string) bool { + out, err := runCmdOutput( + zerolog.DebugLevel, "kubectl", "get", "pvc", "-n", namespace, pvcName, "-o", "jsonpath={.status.phase}", + ) + if err != nil { + return false + } + return strings.TrimSpace(string(out)) == "Bound" +} diff --git a/shared/kubernetes/pvc_test.go b/shared/kubernetes/pvc_test.go new file mode 100644 index 000000000..da947c124 --- /dev/null +++ b/shared/kubernetes/pvc_test.go @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2024 SUSE LLC +// +// SPDX-License-Identifier: Apache-2.0 + +package kubernetes + +import ( + "errors" + "fmt" + "testing" + + "github.com/rs/zerolog" + "github.com/uyuni-project/uyuni-tools/shared/testutils" +) + +func TestHasVolume(t *testing.T) { + type dataType struct { + err error + out string + expected bool + } + data := []dataType{ + {nil, "Bound\n", true}, + {nil, "Pending\n", false}, + {errors.New("PVC not found"), "", false}, + } + + for i, test := range data { + runCmdOutput = func(_ zerolog.Level, _ string, _ ...string) ([]byte, error) { + return []byte(test.out), test.err + } + actual := HasVolume("myns", "thepvc") + testutils.AssertEquals(t, fmt.Sprintf("test %d: unexpected output", i), test.expected, actual) + } +} diff --git a/shared/kubernetes/utils.go b/shared/kubernetes/utils.go index 878a45a56..00b083f68 100644 --- a/shared/kubernetes/utils.go +++ b/shared/kubernetes/utils.go @@ -269,9 +269,8 @@ func GetDeploymentStatus(namespace string, name string) (*DeploymentStatus, erro // ReplicasTo set the replica for an app to the given value. // Scale the number of replicas of the server. func ReplicasTo(namespace string, app string, replica uint) error { - args := []string{"scale", "-n", namespace, "deploy", app, "--replicas"} + args := []string{"scale", "-n", namespace, "deploy", app, "--replicas", strconv.FormatUint(uint64(replica), 10)} log.Debug().Msgf("Setting replicas for pod in %s to %d", app, replica) - args = append(args, fmt.Sprint(replica), "-n", namespace) _, err := utils.RunCmdOutput(zerolog.DebugLevel, "kubectl", args...) if err != nil { diff --git a/shared/utils/inspector.go b/shared/utils/inspector.go index c446466a7..78b21599e 100644 --- a/shared/utils/inspector.go +++ b/shared/utils/inspector.go @@ -8,6 +8,7 @@ import ( "bytes" "os" "path" + "strings" "github.com/rs/zerolog/log" "github.com/spf13/viper" @@ -46,6 +47,21 @@ type BaseInspector struct { Values []types.InspectData } +// GenerateScriptString creates the inspector script and returns it as a string. +func (i *BaseInspector) GenerateScriptString() (string, error) { + data := templates.InspectTemplateData{ + Param: i.Values, + OutputFile: i.GetDataPath(), + } + + scriptBuilder := new(strings.Builder) + if err := data.Render(scriptBuilder); err != nil { + return "", err + } + + return scriptBuilder.String(), nil +} + // GenerateScript is a common implementation for all inspectors. func (i *BaseInspector) GenerateScript() error { log.Debug().Msgf("Generating inspect script in %s", i.GetScriptPath()) diff --git a/shared/utils/volumes.go b/shared/utils/volumes.go index c0aefe242..161e153f6 100644 --- a/shared/utils/volumes.go +++ b/shared/utils/volumes.go @@ -6,11 +6,20 @@ package utils import "github.com/uyuni-project/uyuni-tools/shared/types" +// EtcRhnVolumeMount defines the /etc/rhn volume mount. +var EtcRhnVolumeMount = types.VolumeMount{MountPath: "/etc/rhn", Name: "etc-rhn", Size: "1Mi"} + +// VarPgsqlVolumeMount defines the /var/lib/pgsql volume mount. +var VarPgsqlVolumeMount = types.VolumeMount{MountPath: "/var/lib/pgsql", Name: "var-pgsql", Size: "50Gi"} + +// RootVolumeMount defines the /root volume mount. +var RootVolumeMount = types.VolumeMount{MountPath: "/root", Name: "root", Size: "1Mi"} + // PgsqlRequiredVolumeMounts represents volumes mount used by PostgreSQL. var PgsqlRequiredVolumeMounts = []types.VolumeMount{ {MountPath: "/etc/pki/tls", Name: "etc-tls", Size: "1Mi"}, - {MountPath: "/var/lib/pgsql", Name: "var-pgsql", Size: "50Gi"}, - {MountPath: "/etc/rhn", Name: "etc-rhn", Size: "1Mi"}, + VarPgsqlVolumeMount, + EtcRhnVolumeMount, {MountPath: "/etc/pki/spacewalk-tls", Name: "tls-key"}, } @@ -75,7 +84,7 @@ var ServerVolumeMounts = append([]types.VolumeMount{ {MountPath: "/srv/pillar", Name: "srv-pillar", Size: "10Mi"}, {MountPath: "/srv/susemanager", Name: "srv-susemanager", Size: "1Mi"}, {MountPath: "/srv/spacewalk", Name: "srv-spacewalk", Size: "10Mi"}, - {MountPath: "/root", Name: "root", Size: "1Mi"}, + RootVolumeMount, {MountPath: "/etc/pki/trust/anchors/", Name: "ca-cert"}, }, etcAndPgsqlVolumeMounts[:]...)