Skip to content

Commit

Permalink
Move the server setup to a separate container
Browse files Browse the repository at this point in the history
In the kubernetes world, running the setup as an exec is really dirty as
we can't have it in an operator or helm chart. This commits benefits
from the setup script not needing systemd to run as PID1 to move the
setup in a separate container.
  • Loading branch information
cbosdo committed Dec 3, 2024
1 parent d766b7c commit ab115c0
Show file tree
Hide file tree
Showing 10 changed files with 580 additions and 326 deletions.
2 changes: 1 addition & 1 deletion mgradm/cmd/inspect/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func kuberneteInspect(
}

// Get the SCC credentials secret if existing
pullSecret, err := kubernetes.GetSCCSecret(namespace, &types.SCCCredentials{}, kubernetes.ServerApp)
pullSecret, err := kubernetes.GetRegistrySecret(namespace, &types.SCCCredentials{}, kubernetes.ServerApp)
if err != nil {
return err
}
Expand Down
173 changes: 150 additions & 23 deletions mgradm/cmd/install/podman/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package podman

import (
"errors"
"fmt"
"os/exec"
"strconv"
"strings"

"github.com/rs/zerolog"
Expand All @@ -16,6 +18,7 @@ import (
"github.com/uyuni-project/uyuni-tools/mgradm/shared/hub"
"github.com/uyuni-project/uyuni-tools/mgradm/shared/podman"
"github.com/uyuni-project/uyuni-tools/mgradm/shared/saline"
"github.com/uyuni-project/uyuni-tools/mgradm/shared/templates"
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/l10n"
Expand Down Expand Up @@ -91,35 +94,23 @@ func installForPodman(
return err
}

cnx := shared.NewConnection("podman", shared_podman.ServerContainerName, "")
if err := waitForSystemStart(systemd, cnx, preparedImage, flags); err != nil {
return utils.Errorf(err, L("cannot wait for system start"))
if err := shared_podman.SetupNetwork(false); err != nil {
return utils.Errorf(err, L("cannot setup network"))
}

caPassword := flags.Installation.SSL.Password
if flags.Installation.SSL.UseExisting() {
// We need to have a password for the generated CA, even though it will be thrown away after install
caPassword = "dummy"
}
log.Info().Msg(L("Run setup command in the container"))

env := map[string]string{
"CERT_O": flags.Installation.SSL.Org,
"CERT_OU": flags.Installation.SSL.OU,
"CERT_CITY": flags.Installation.SSL.City,
"CERT_STATE": flags.Installation.SSL.State,
"CERT_COUNTRY": flags.Installation.SSL.Country,
"CERT_EMAIL": flags.Installation.SSL.Email,
"CERT_CNAMES": strings.Join(append([]string{fqdn}, flags.Installation.SSL.Cnames...), ","),
"CERT_PASS": caPassword,
if err := runSetup(preparedImage, &flags.ServerFlags, fqdn); err != nil {
return err
}

log.Info().Msg(L("Run setup command in the container"))
cnx := shared.NewConnection("podman", shared_podman.ServerContainerName, "")
if err := waitForSystemStart(systemd, cnx, preparedImage, flags); err != nil {
return utils.Errorf(err, L("cannot wait for system start"))
}

if err := adm_utils.RunSetup(cnx, &flags.ServerFlags, fqdn, env); err != nil {
if stopErr := systemd.StopService(shared_podman.ServerService); stopErr != nil {
log.Error().Msgf(L("Failed to stop service: %v"), stopErr)
}
return err
if err := cnx.CopyCaCertificate(fqdn); err != nil {
return utils.Errorf(err, L("failed to add SSL CA certificate to host trusted certificates"))
}

if path, err := exec.LookPath("uyuni-payg-extract-data"); err == nil {
Expand Down Expand Up @@ -173,3 +164,139 @@ func installForPodman(
}
return nil
}

// runSetup execute the setup.
func runSetup(image string, flags *adm_utils.ServerFlags, fqdn string) error {
localHostValues := []string{
"localhost",
"127.0.0.1",
"::1",
fqdn,
}

localDB := utils.Contains(localHostValues, flags.Installation.DB.Host)

dbHost := flags.Installation.DB.Host
reportdbHost := flags.Installation.ReportDB.Host

if localDB {
dbHost = "localhost"
if reportdbHost == "" {
reportdbHost = "localhost"
}
}

caPassword := flags.Installation.SSL.Password
if flags.Installation.SSL.UseExisting() {
// We need to have a password for the generated CA, even though it will be thrown away after install
caPassword = "dummy"
}

// TODO Share the env variables preparation with Kubernetes?
env := map[string]string{
"UYUNI_FQDN": fqdn,
"MANAGER_USER": flags.Installation.DB.User,
"MANAGER_PASS": flags.Installation.DB.Password,
"ADMIN_USER": flags.Installation.Admin.Login,
"ADMIN_PASS": flags.Installation.Admin.Password,
"MANAGER_ADMIN_EMAIL": flags.Installation.Email,
"MANAGER_MAIL_FROM": flags.Installation.EmailFrom,
"MANAGER_ENABLE_TFTP": boolToString(flags.Installation.Tftp),
"LOCAL_DB": boolToString(localDB),
"MANAGER_DB_NAME": flags.Installation.DB.Name,
"MANAGER_DB_HOST": dbHost,
"MANAGER_DB_PORT": strconv.Itoa(flags.Installation.DB.Port),
"MANAGER_DB_PROTOCOL": flags.Installation.DB.Protocol,
"REPORT_DB_NAME": flags.Installation.ReportDB.Name,
"REPORT_DB_HOST": reportdbHost,
"REPORT_DB_PORT": strconv.Itoa(flags.Installation.ReportDB.Port),
"REPORT_DB_USER": flags.Installation.ReportDB.User,
"REPORT_DB_PASS": flags.Installation.ReportDB.Password,
"EXTERNALDB_ADMIN_USER": flags.Installation.DB.Admin.User,
"EXTERNALDB_ADMIN_PASS": flags.Installation.DB.Admin.Password,
"EXTERNALDB_PROVIDER": flags.Installation.DB.Provider,
"ISS_PARENT": flags.Installation.IssParent,
"ACTIVATE_SLP": "N", // Deprecated, will be removed soon
"SCC_USER": flags.Installation.SCC.User,
"SCC_PASS": flags.Installation.SCC.Password,
"CERT_O": flags.Installation.SSL.Org,
"CERT_OU": flags.Installation.SSL.OU,
"CERT_CITY": flags.Installation.SSL.City,
"CERT_STATE": flags.Installation.SSL.State,
"CERT_COUNTRY": flags.Installation.SSL.Country,
"CERT_EMAIL": flags.Installation.SSL.Email,
"CERT_CNAMES": strings.Join(append([]string{fqdn}, flags.Installation.SSL.Cnames...), ","),
"CERT_PASS": caPassword,
}

if flags.Mirror != "" {
env["MIRROR_PATH"] = "/mirror"
}

envNames := []string{}
envValues := []string{}
for key, value := range env {
envNames = append(envNames, "-e", key)
envValues = append(envValues, fmt.Sprintf("%s=%s", key, value))
}

command := []string{
"run",
"--rm",
"--shm-size=0",
"--shm-size-systemd=0",
"--name", "uyuni-setup",
"--network", shared_podman.UyuniNetwork,
"-e", "TZ=" + flags.Installation.TZ,
}
for _, volume := range utils.ServerVolumeMounts {
command = append(command, "-v", fmt.Sprintf("%s:%s:z", volume.Name, volume.MountPath))
}
command = append(command, envNames...)
command = append(command, image)

script, err := generateSetupScript(&flags.Installation)
if err != nil {
return err
}
command = append(command, "/usr/bin/sh", "-c", script)

if _, err := newRunner("podman", command...).Env(envValues).StdMapping().Exec(); err != nil {
return utils.Errorf(err, L("server setup failed"))
}

log.Info().Msgf(L("Server set up, login on https://%[1]s with %[2]s user"), fqdn, flags.Installation.Admin.Login)
return nil
}

var newRunner = utils.NewRunner

// generateSetupScript creates a temporary folder with the setup script to execute in the container.
// The script exports all the needed environment variables and calls uyuni's mgr-setup.
func generateSetupScript(flags *adm_utils.InstallationFlags) (string, error) {
// TODO Share with kubernetes implementation
template := templates.MgrSetupScriptTemplateData{
DebugJava: flags.Debug.Java,
OrgName: flags.Organization,
AdminLogin: "$ADMIN_USER",
AdminPassword: "$ADMIN_PASS",
AdminFirstName: flags.Admin.FirstName,
AdminLastName: flags.Admin.LastName,
AdminEmail: flags.Admin.Email,
NoSSL: false,
}

// Prepare the script
scriptBuilder := new(strings.Builder)
if err := template.Render(scriptBuilder); err != nil {
return "", utils.Errorf(err, L("failed to render setup script"))
}
return scriptBuilder.String(), nil
}

func boolToString(value bool) string {
if value {
return "Y"
}
return "N"
}
2 changes: 1 addition & 1 deletion mgradm/cmd/migrate/kubernetes/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func migrateToKubernetes(
}

// Create a secret using SCC credentials if any are provided
pullSecret, err := shared_kubernetes.GetSCCSecret(
pullSecret, err := shared_kubernetes.GetRegistrySecret(
flags.Kubernetes.Uyuni.Namespace, &flags.Installation.SCC, shared_kubernetes.ServerApp,
)
if err != nil {
Expand Down
17 changes: 14 additions & 3 deletions mgradm/shared/kubernetes/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
package kubernetes

import (
"strings"

"github.com/rs/zerolog"
"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
core "k8s.io/api/core/v1"
Expand All @@ -19,12 +22,20 @@ const (
DBSecret = "db-credentials"
// ReportdbSecret is the name of the report database credentials secret.
ReportdbSecret = "reportdb-credentials"
SCCSecret = "scc-credentials"
secretUsername = "username"
secretPassword = "password"
)

// CreateDBSecret creates a secret containing the DB credentials.
func CreateDBSecret(namespace string, name string, user string, password string) error {
// CreateBasicAuthSecret creates a secret of type basic-auth.
func CreateBasicAuthSecret(namespace string, name string, user string, password string) error {
// Check if the secret is already existing
out, err := runCmdOutput(zerolog.DebugLevel, "kubectl", "get", "-n", namespace, "secret", name, "-o", "name")
if err == nil && strings.TrimSpace(string(out)) != "" {
return nil
}

// Create the secret
secret := core.Secret{
TypeMeta: meta.TypeMeta{APIVersion: "v1", Kind: "Secret"},
ObjectMeta: meta.ObjectMeta{
Expand All @@ -40,5 +51,5 @@ func CreateDBSecret(namespace string, name string, user string, password string)
Type: core.SecretTypeBasicAuth,
}

return kubernetes.Apply([]runtime.Object{&secret}, L("failed to create the database secret"))
return kubernetes.Apply([]runtime.Object{&secret}, L("failed to create the secret"))
}
Loading

0 comments on commit ab115c0

Please sign in to comment.