File mgrpxy.obscpio of Package uyuni-tools
07070100000000000081a400000000000000000000000168ed21dd00000446000000000000000000000000000000000000001a00000000mgrpxy/cmd/cache/clear.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package cache
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type cacheClearFlags struct {
}
// NewClearCmd creates the command to clear the cache.
func NewClearCmd(globalFlags *types.GlobalFlags) *cobra.Command {
var clearCmd = &cobra.Command{
Use: "clear",
Short: L("Clear the cache"),
Long: L("Clear the cache"),
RunE: func(cmd *cobra.Command, args []string) error {
var flags cacheClearFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, clearCmd)
},
}
return clearCmd
}
func clearCmd(globalFlags *types.GlobalFlags, flags *cacheClearFlags, cmd *cobra.Command, args []string) error {
fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanCacheClear, kubernetesCacheClear)
if err != nil {
return err
}
return fn(globalFlags, flags, cmd, args)
}
07070100000001000081a400000000000000000000000168ed21dd00000436000000000000000000000000000000000000001f00000000mgrpxy/cmd/cache/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package cache
import (
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func kubernetesCacheClear(
_ *types.GlobalFlags,
_ *cacheClearFlags,
_ *cobra.Command,
_ []string,
) error {
cnx := shared.NewConnection("kubectl", "squid", kubernetes.ProxyFilter)
namespace, err := cnx.GetNamespace("")
if err != nil {
return utils.Errorf(err, L("failed retrieving namespace"))
}
if _, err := cnx.Exec("find", "/var/cache/squid", "-mindepth", "1", "-delete"); err != nil {
return utils.Errorf(err, L("failed to remove cached data"))
}
if _, err := cnx.Exec("squid", "-z", "--foreground"); err != nil {
return utils.Errorf(err, L("failed to re-create the cache directories"))
}
return kubernetes.Restart(namespace, kubernetes.ProxyApp)
}
07070100000002000081a400000000000000000000000168ed21dd000003cd000000000000000000000000000000000000001b00000000mgrpxy/cmd/cache/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package cache
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
var systemd podman.Systemd = podman.NewSystemd()
func podmanCacheClear(
_ *types.GlobalFlags,
_ *cacheClearFlags,
_ *cobra.Command,
_ []string,
) error {
cnx := shared.NewConnection("podman", "uyuni-proxy-squid", "")
if _, err := cnx.Exec("sh", "-c", "rm -rf /var/cache/squid/*"); err != nil {
return utils.Errorf(err, L("failed to remove cached data"))
}
if _, err := cnx.Exec("sh", "-c", "squid -z --foreground"); err != nil {
return utils.Errorf(err, L("failed to re-create the cache directories"))
}
return systemd.RestartService(podman.ProxyService)
}
07070100000003000081a400000000000000000000000168ed21dd00000294000000000000000000000000000000000000001a00000000mgrpxy/cmd/cache/squid.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package cache
import (
"github.com/spf13/cobra"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// NewCommand entry command for managing cache.
// Setup for subcommand to clear (the cache).
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
var cacheCmd = &cobra.Command{
Use: "cache",
Short: L("Manage proxy cache"),
Long: L("Manage proxy cache"),
Run: func(cmd *cobra.Command, _ []string) {
_ = cmd.Help()
},
}
cacheCmd.AddCommand(NewClearCmd(globalFlags))
return cacheCmd
}
07070100000004000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001100000000mgrpxy/cmd/cache07070100000005000081a400000000000000000000000168ed21dd00000c9b000000000000000000000000000000000000001200000000mgrpxy/cmd/cmd.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package cmd
import (
"os"
"path"
"strings"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/cache"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/install"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/logs"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/restart"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/start"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/status"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/stop"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/uninstall"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/upgrade"
"github.com/uyuni-project/uyuni-tools/shared/completion"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
// NewUyuniproxyCommand returns a new cobra.Command implementing the root command for mgrpxy.
func NewUyuniproxyCommand() (*cobra.Command, error) {
globalFlags := &types.GlobalFlags{}
name := path.Base(os.Args[0])
rootCmd := &cobra.Command{
Use: name,
Short: L("Uyuni proxy administration tool"),
Long: L("Tool to help administering Uyuni proxies in containers"),
Version: utils.Version,
SilenceUsage: true, // Don't show usage help on errors
}
rootCmd.AddGroup(&cobra.Group{
ID: "deploy",
Title: L("Server Deployment:"),
})
rootCmd.AddGroup(&cobra.Group{
ID: "management",
Title: L("Server Management:"),
})
rootCmd.AddGroup(&cobra.Group{
ID: "tool",
Title: L("Administrator tools:"),
})
rootCmd.SetUsageTemplate(utils.GetLocalizedUsageTemplate())
rootCmd.PersistentPreRun = func(cmd *cobra.Command, _ []string) {
// do not log if running the completion cmd as the output is redirected to create a file to source
if cmd.Name() != "completion" && cmd.Name() != "__complete" {
utils.LogInit(true)
utils.SetLogLevel(globalFlags.LogLevel)
log.Info().Msgf(L("Starting %s"), strings.Join(os.Args, " "))
log.Info().Msgf(L("Use of this software implies acceptance of the End User License Agreement."))
}
}
rootCmd.PersistentFlags().StringVarP(&globalFlags.ConfigPath, "config", "c", "", L("configuration file path"))
utils.AddLogLevelFlags(rootCmd, &globalFlags.LogLevel)
installCmd := install.NewCommand(globalFlags)
rootCmd.AddCommand(installCmd)
uninstallCmd := uninstall.NewCommand(globalFlags)
rootCmd.AddCommand(uninstallCmd)
rootCmd.AddCommand(completion.NewCommand(globalFlags))
rootCmd.AddCommand(cache.NewCommand(globalFlags))
rootCmd.AddCommand(status.NewCommand(globalFlags))
rootCmd.AddCommand(start.NewCommand(globalFlags))
rootCmd.AddCommand(stop.NewCommand(globalFlags))
rootCmd.AddCommand(restart.NewCommand(globalFlags))
rootCmd.AddCommand(upgrade.NewCommand(globalFlags))
rootCmd.AddCommand(logs.NewCommand(globalFlags))
if supportCommand := support.NewCommand(globalFlags); supportCommand != nil {
rootCmd.AddCommand(supportCommand)
}
rootCmd.AddCommand(utils.GetConfigHelpCommand())
return rootCmd, nil
}
07070100000006000081a400000000000000000000000168ed21dd0000032f000000000000000000000000000000000000001e00000000mgrpxy/cmd/install/install.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package install
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/install/kubernetes"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/install/podman"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// NewCommand install a new proxy from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
installCmd := &cobra.Command{
Use: "install [fqdn]",
GroupID: "deploy",
Short: L("Install a new proxy from scratch"),
Long: L("Install a new proxy from scratch"),
}
installCmd.AddCommand(podman.NewCommand(globalFlags))
installCmd.AddCommand(kubernetes.NewCommand(globalFlags))
return installCmd
}
07070100000007000081a400000000000000000000000168ed21dd0000068e000000000000000000000000000000000000002c00000000mgrpxy/cmd/install/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
pxy_utils "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type kubernetesProxyInstallFlags struct {
pxy_utils.ProxyImageFlags `mapstructure:",squash"`
Helm kubernetes.HelmFlags
SCC types.SCCCredentials
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[kubernetesProxyInstallFlags]) *cobra.Command {
cmd := &cobra.Command{
Use: "kubernetes [path/to/config.tar.gz]",
Short: L("Install a new proxy on a running kubernetes cluster"),
Long: L(`Install a new proxy on a running kubernetes cluster.
It only takes the path to the configuration tarball generated by the server
as parameter.
The install kubernetes command assumes kubectl is installed locally.
NOTE: for now installing on a remote kubernetes cluster is not supported!
`),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var flags kubernetesProxyInstallFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
pxy_utils.AddImageFlags(cmd)
pxy_utils.AddSCCFlag(cmd)
kubernetes.AddHelmFlags(cmd)
return cmd
}
// NewCommand install a new proxy on a running kubernetes cluster.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, installForKubernetes)
}
07070100000008000081a400000000000000000000000168ed21dd0000048d000000000000000000000000000000000000003100000000mgrpxy/cmd/install/kubernetes/kubernetes_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/testutils/flagstests"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"config.tar.gz",
}
args = append(args, flagstests.ImageProxyFlagsTestArgs...)
args = append(args, flagstests.ProxyHelmFlagsTestArgs...)
args = append(args, flagstests.SCCFlagTestArgs...)
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *kubernetesProxyInstallFlags,
_ *cobra.Command, _ []string,
) error {
flagstests.AssertProxyImageFlags(t, &flags.ProxyImageFlags)
flagstests.AssertProxyHelmFlags(t, &flags.Helm)
flagstests.AssertSCCFlag(t, &flags.SCC)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000009000081a400000000000000000000000168ed21dd00000876000000000000000000000000000000000000002700000000mgrpxy/cmd/install/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"errors"
"fmt"
"os/exec"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
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(_ *types.GlobalFlags,
flags *kubernetesProxyInstallFlags, _ *cobra.Command, args []string,
) error {
for _, binary := range []string{"kubectl", "helm"} {
if _, err := exec.LookPath(binary); err != nil {
return fmt.Errorf(L("install %s before running this command"), binary)
}
}
// Unpack the tarball
configPath := utils.GetConfigPath(args)
tmpDir, cleaner, err := shared_utils.TempDir()
if err != nil {
return err
}
defer cleaner()
if err := shared_utils.ExtractTarGz(configPath, tmpDir); err != nil {
return errors.New(L("failed to extract configuration"))
}
// Check the kubernetes cluster setup
clusterInfos, err := shared_kubernetes.CheckCluster()
if err != nil {
return err
}
// If installing on k3s, install the traefik helm config in manifests
isK3s := clusterInfos.IsK3s()
IsRke2 := clusterInfos.IsRke2()
ports := shared_utils.GetProxyPorts()
if isK3s {
err = shared_kubernetes.InstallK3sTraefikConfig(ports)
} else if IsRke2 {
err = shared_kubernetes.InstallRke2NginxConfig(ports, flags.Helm.Proxy.Namespace)
}
if err != nil {
return err
}
helmArgs := []string{"--set", "ingress=" + clusterInfos.Ingress}
helmArgs, err = shared_kubernetes.AddSCCSecret(
helmArgs, flags.Helm.Proxy.Namespace, &flags.SCC, shared_kubernetes.ProxyApp,
)
if err != nil {
return err
}
// Install the uyuni proxy helm chart
if err := kubernetes.Deploy(
&flags.ProxyImageFlags, &flags.Helm, tmpDir, clusterInfos.GetKubeconfig(), helmArgs...,
); err != nil {
return shared_utils.Errorf(err, L("cannot deploy proxy helm chart"))
}
return nil
}
0707010000000a000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001e00000000mgrpxy/cmd/install/kubernetes0707010000000b000081a400000000000000000000000168ed21dd000005e3000000000000000000000000000000000000002400000000mgrpxy/cmd/install/podman/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)
func newCmd(globalFlags *types.GlobalFlags, run shared_utils.CommandFunc[podman.PodmanProxyFlags]) *cobra.Command {
podmanCmd := &cobra.Command{
Use: "podman [path/to/config.tar.gz]",
Short: L("Install a new proxy on podman"),
Long: L(`Install a new proxy on podman
It only takes the path to the configuration tarball generated by the server
as parameter.
The install podman command assumes podman is installed locally.
NOTE: for now installing on a remote podman is not supported!
`),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var flags podman.PodmanProxyFlags
return shared_utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
utils.AddSCCFlag(podmanCmd)
utils.AddImageFlags(podmanCmd)
shared_podman.AddPodmanArgFlag(podmanCmd)
return podmanCmd
}
// NewCommand install a new proxy on podman from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, installForPodman)
}
0707010000000c000081a400000000000000000000000168ed21dd000004f8000000000000000000000000000000000000002900000000mgrpxy/cmd/install/podman/podman_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/testutils/flagstests"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"config.tar.gz",
}
args = append(args, flagstests.ImageProxyFlagsTestArgs...)
args = append(args, flagstests.PodmanFlagsTestArgs...)
args = append(args, flagstests.SCCFlagTestArgs...)
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *podman.PodmanProxyFlags, _ *cobra.Command, _ []string) error {
flagstests.AssertProxyImageFlags(t, &flags.ProxyImageFlags)
flagstests.AssertPodmanInstallFlags(t, &flags.Podman)
flagstests.AssertSCCFlag(t, &flags.SCC)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
t.Logf("flags: %s", strings.Join(args, " "))
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
0707010000000d000081a400000000000000000000000168ed21dd00000af8000000000000000000000000000000000000002300000000mgrpxy/cmd/install/podman/utils.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"errors"
"os/exec"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)
var systemd shared_podman.Systemd = shared_podman.NewSystemd()
// Start the proxy services.
func startPod() error {
ret := systemd.IsServiceRunning(shared_podman.ProxyService)
if ret {
return systemd.RestartService(shared_podman.ProxyService)
}
return systemd.EnableService(shared_podman.ProxyService)
}
func installForPodman(
_ *types.GlobalFlags,
flags *podman.PodmanProxyFlags,
_ *cobra.Command,
args []string,
) error {
if _, err := exec.LookPath("podman"); err != nil {
return errors.New(L("install podman before running this command"))
}
configPath := utils.GetConfigPath(args)
if err := podman.UnpackConfig(configPath); err != nil {
return shared_utils.Errorf(err, L("failed to retrieve proxy config files"))
}
hostData, err := shared_podman.InspectHost()
if err != nil {
return err
}
// If we previously created systemid secret, remove it
shared_podman.DeleteSecret(podman.SystemIDSecret, false)
// Check if we are a salt minion registered to SMLM and if so, try to get up to date systemid
if hostData.HasSaltMinion {
if err := podman.GetSystemID(); err != nil {
log.Warn().Err(err).Msg(L("Unable to fetch up to date systemid, using one from the provided configuration file"))
}
}
authFile, cleaner, err := shared_podman.PodmanLogin(hostData, flags.SCC)
if err != nil {
return shared_utils.Errorf(err, L("failed to login to registry.suse.com"))
}
defer cleaner()
httpdImage, err := podman.GetContainerImage(authFile, &flags.ProxyImageFlags, "httpd")
if err != nil {
return err
}
saltBrokerImage, err := podman.GetContainerImage(authFile, &flags.ProxyImageFlags, "salt-broker")
if err != nil {
return err
}
squidImage, err := podman.GetContainerImage(authFile, &flags.ProxyImageFlags, "squid")
if err != nil {
return err
}
sshImage, err := podman.GetContainerImage(authFile, &flags.ProxyImageFlags, "ssh")
if err != nil {
return err
}
tftpdImage, err := podman.GetContainerImage(authFile, &flags.ProxyImageFlags, "tftpd")
if err != nil {
return err
}
// Setup the systemd service configuration options
err = podman.GenerateSystemdService(systemd, httpdImage, saltBrokerImage, squidImage, sshImage, tftpdImage, flags)
if err != nil {
return err
}
return startPod()
}
0707010000000e000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001a00000000mgrpxy/cmd/install/podman0707010000000f000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001300000000mgrpxy/cmd/install07070100000010000081a400000000000000000000000168ed21dd00000755000000000000000000000000000000000000001e00000000mgrpxy/cmd/logs/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package logs
import (
"fmt"
"time"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func kubernetesLogs(
_ *types.GlobalFlags,
flags *logsFlags,
_ *cobra.Command,
args []string,
) error {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
podName, err := cnx.GetPodName()
if err != nil {
return utils.Errorf(err, L("failed to find proxy pod"))
}
namespace, errNamespace := cnx.GetNamespace("")
if errNamespace != nil {
return utils.Errorf(err, L("failed to find proxy deployment namespace"))
}
commandArgs := []string{"logs", "-n", namespace}
if flags.Follow {
commandArgs = append(commandArgs, "-f")
}
if flags.Tail != -1 {
commandArgs = append(commandArgs, "--tail="+fmt.Sprintf("%d", flags.Tail))
}
if flags.Timestamps {
commandArgs = append(commandArgs, "--timestamps")
}
if flags.Since != "" {
if isRFC3339(flags.Since) {
commandArgs = append(commandArgs, fmt.Sprintf("--since-time=%s", flags.Since))
} else {
commandArgs = append(commandArgs, fmt.Sprintf("--since=%s", flags.Since))
}
}
if len(flags.Containers) == 0 {
commandArgs = append(commandArgs, podName, "--all-containers")
} else if len(flags.Containers) == 1 {
commandArgs = append(commandArgs, flags.Containers[0], "--all-containers")
} else {
commandArgs = append(commandArgs, args...)
}
return utils.RunCmdStdMapping(zerolog.DebugLevel, "kubectl", commandArgs...)
}
func isRFC3339(timestamp string) bool {
_, err := time.Parse(time.RFC3339, timestamp)
return err == nil
}
07070100000011000081a400000000000000000000000168ed21dd00001242000000000000000000000000000000000000001800000000mgrpxy/cmd/logs/logs.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package logs
import (
"os/exec"
"strings"
"github.com/rs/zerolog/log"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type logsFlags struct {
Containers []string
Follow bool
Timestamps bool
Tail int
Since string
}
var systemd podman.Systemd = podman.NewSystemd()
// NewCommand to get the logs of the server.
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[logsFlags]) *cobra.Command {
var flags logsFlags
cmd := &cobra.Command{
Use: "logs [pod [container] | container...]",
Short: L("Get the proxy logs"),
Long: L(`Get the proxy logs
The command automatically detects installed backend and displays the logs for containers managed by Kubernetes or Podman
However, you can specify the pod and/or container names to get the logs for specific container(s).
See examples for more details.`),
Example: ` Log all relevant containers (Podman and Kubernetes)
$ mgrpxy logs
Log all relevant containers in the specified pod (Kubernetes)
$ mgrpxy logs uyuni-proxy-pod
Log the specified container in the specified pod (Kubernetes)
$ mgrpxy logs uyuni-proxy-pod httpd
Log the specified containers (Podman)
$ mgrpxy logs logs uyuni-proxy-httpd uyuni-proxy-ssh`,
RunE: func(cmd *cobra.Command, args []string) error {
flags.Containers = cmd.Flags().Args()
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
ValidArgsFunction: getContainerNames,
}
cmd.Flags().BoolP("follow", "f", false, L("specify if logs should be followed"))
cmd.Flags().BoolP("timestamps", "t", false, L("show timestamps in the log outputs"))
cmd.Flags().Int("tail", -1, L("number of lines to show from the end of the logs"))
cmd.Flags().Lookup("tail").NoOptDefVal = "-1"
cmd.Flags().String("since", "",
L(`show logs since a specific time or duration.
Supports Go duration strings and RFC3339 format (e.g. 3h, 2023-01-02T15:04:05)`),
)
cmd.SetUsageTemplate(cmd.UsageTemplate())
return cmd
}
// NewCommand to get the logs of the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, logs)
}
func logs(globalFlags *types.GlobalFlags, flags *logsFlags, cmd *cobra.Command, args []string) error {
fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanLogs, kubernetesLogs)
if err != nil {
return err
}
return fn(globalFlags, flags, cmd, args)
}
func getContainerNames(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
var names []string
if systemd.HasService(podman.ProxyService) {
names = getNames(exec.Command("podman", "ps", "--format", "{{.Names}}"), "\n", "uyuni")
} else if utils.IsInstalled("kubectl") && utils.IsInstalled("helm") {
if len(args) == 0 {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
podName, err := cnx.GetPodName()
if err != nil {
log.Fatal().Err(err)
}
return []string{podName}, cobra.ShellCompDirectiveNoFileComp
} else if len(args) == 1 {
names = getNames(
exec.Command("kubectl", "get", "pod", args[0], "-o", "jsonpath={.spec.containers[*].name}"),
" ", "",
)
} else {
// kubernetes log only accepts either 1 container name or the --all-containers flag.
return names, cobra.ShellCompDirectiveNoFileComp
}
}
return minus(names, args), cobra.ShellCompDirectiveNoFileComp
}
// retrieves pod/container retrieve command and parses its names for auto completion.
func getNames(cmd *exec.Cmd, cmdResultSeparator string, namesPrefix string) []string {
out, err := cmd.Output()
if err != nil {
return nil
}
names := strings.Split(strings.TrimSpace(string(out)), cmdResultSeparator)
if namesPrefix == "" {
return names
}
var filteredNames []string
for _, name := range names {
if strings.HasPrefix(name, namesPrefix) {
filteredNames = append(filteredNames, name)
}
}
return filteredNames
}
// Returns the elements of a left slice minus the elements of the right slice.
func minus(left []string, right []string) []string {
rightMap := make(map[string]bool)
for _, elementRight := range right {
rightMap[elementRight] = true
}
var result []string
for _, elementLeft := range left {
if !rightMap[elementLeft] {
result = append(result, elementLeft)
}
}
return result
}
07070100000012000081a400000000000000000000000168ed21dd000004c0000000000000000000000000000000000000001d00000000mgrpxy/cmd/logs/logs_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package logs
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--follow",
"--timestamps",
"--tail=20",
"--since", "3h",
"container1", "container2",
}
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *logsFlags,
_ *cobra.Command, _ []string,
) error {
testutils.AssertTrue(t, "Error parsing --follow", flags.Follow)
testutils.AssertTrue(t, "Error parsing --timestamps", flags.Timestamps)
testutils.AssertEquals(t, "Error parsing --tail", 20, flags.Tail)
testutils.AssertEquals(t, "Error parsing --since", "3h", flags.Since)
testutils.AssertEquals(t, "Error parsing containers", []string{"container1", "container2"}, flags.Containers)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000013000081a400000000000000000000000168ed21dd00000420000000000000000000000000000000000000001a00000000mgrpxy/cmd/logs/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package logs
import (
"fmt"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func podmanLogs(
_ *types.GlobalFlags,
flags *logsFlags,
_ *cobra.Command,
args []string,
) error {
commandArgs := []string{"logs"}
if flags.Follow {
commandArgs = append(commandArgs, "-f")
}
if flags.Tail != -1 {
commandArgs = append(commandArgs, "--tail="+fmt.Sprintf("%d", flags.Tail))
}
if flags.Timestamps {
commandArgs = append(commandArgs, "--timestamps")
}
if flags.Since != "" {
commandArgs = append(commandArgs, fmt.Sprintf("--since=%s", flags.Since))
}
if len(flags.Containers) == 0 {
commandArgs = append(commandArgs, podman.ProxyContainerNames...)
} else {
commandArgs = append(commandArgs, args...)
}
return utils.RunCmdStdMapping(zerolog.DebugLevel, "podman", commandArgs...)
}
07070100000014000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001000000000mgrpxy/cmd/logs07070100000015000081a400000000000000000000000168ed21dd000002f9000000000000000000000000000000000000002100000000mgrpxy/cmd/restart/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package restart
import (
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func kubernetesRestart(
_ *types.GlobalFlags,
_ *restartFlags,
_ *cobra.Command,
_ []string,
) error {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
namespace, err := cnx.GetNamespace("")
if err != nil {
return utils.Errorf(err, L("failed retrieving namespace"))
}
return kubernetes.Restart(namespace, kubernetes.ProxyApp)
}
07070100000016000081a400000000000000000000000168ed21dd000001c5000000000000000000000000000000000000001d00000000mgrpxy/cmd/restart/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package restart
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
var systemd podman.Systemd = podman.NewSystemd()
func podmanRestart(
_ *types.GlobalFlags,
_ *restartFlags,
_ *cobra.Command,
_ []string,
) error {
return systemd.RestartService(podman.ProxyService)
}
07070100000017000081a400000000000000000000000168ed21dd00000557000000000000000000000000000000000000001e00000000mgrpxy/cmd/restart/restart.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package restart
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type restartFlags struct {
Backend string
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[restartFlags]) *cobra.Command {
restartCmd := &cobra.Command{
Use: "restart",
GroupID: "management",
Short: L("Restart the proxy"),
Long: L("Restart the proxy"),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags restartFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
restartCmd.SetUsageTemplate(restartCmd.UsageTemplate())
utils.AddBackendFlag(restartCmd)
return restartCmd
}
// NewCommand to restart server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, restart)
}
func restart(globalFlags *types.GlobalFlags, flags *restartFlags, cmd *cobra.Command, args []string) error {
fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanRestart, kubernetesRestart)
if err != nil {
return err
}
return fn(globalFlags, flags, cmd, args)
}
07070100000018000081a400000000000000000000000168ed21dd00000349000000000000000000000000000000000000002300000000mgrpxy/cmd/restart/restart_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package restart
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--backend", "kubectl",
}
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *restartFlags,
_ *cobra.Command, _ []string,
) error {
testutils.AssertEquals(t, "Error parsing --backend", "kubectl", flags.Backend)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000019000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001300000000mgrpxy/cmd/restart0707010000001a000081a400000000000000000000000168ed21dd000002f1000000000000000000000000000000000000001f00000000mgrpxy/cmd/start/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package start
import (
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func kubernetesStart(
_ *types.GlobalFlags,
_ *startFlags,
_ *cobra.Command,
_ []string,
) error {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
namespace, err := cnx.GetNamespace("")
if err != nil {
return utils.Errorf(err, L("failed retrieving namespace"))
}
return kubernetes.Start(namespace, kubernetes.ProxyApp)
}
0707010000001b000081a400000000000000000000000168ed21dd000001bd000000000000000000000000000000000000001b00000000mgrpxy/cmd/start/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package start
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
var systemd podman.Systemd = podman.NewSystemd()
func podmanStart(
_ *types.GlobalFlags,
_ *startFlags,
_ *cobra.Command,
_ []string,
) error {
return systemd.StartService(podman.ProxyService)
}
0707010000001c000081a400000000000000000000000168ed21dd00000535000000000000000000000000000000000000001a00000000mgrpxy/cmd/start/start.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package start
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type startFlags struct {
Backend string
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[startFlags]) *cobra.Command {
startCmd := &cobra.Command{
Use: "start",
GroupID: "management",
Short: L("Start the proxy"),
Long: L("Start the proxy"),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags startFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
startCmd.SetUsageTemplate(startCmd.UsageTemplate())
utils.AddBackendFlag(startCmd)
return startCmd
}
// NewCommand starts the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, start)
}
func start(globalFlags *types.GlobalFlags, flags *startFlags, cmd *cobra.Command, args []string) error {
fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanStart, kubernetesStart)
if err != nil {
return err
}
return fn(globalFlags, flags, cmd, args)
}
0707010000001d000081a400000000000000000000000168ed21dd00000340000000000000000000000000000000000000001f00000000mgrpxy/cmd/start/start_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package start
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--backend", "kubectl",
}
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *startFlags, _ *cobra.Command, _ []string) error {
testutils.AssertEquals(t, "Error parsing --backend", "kubectl", flags.Backend)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
0707010000001e000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001100000000mgrpxy/cmd/start0707010000001f000081a400000000000000000000000168ed21dd000004c5000000000000000000000000000000000000002000000000mgrpxy/cmd/status/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package status
import (
"errors"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func kubernetesStatus(
_ *types.GlobalFlags,
_ *statusFlags,
_ *cobra.Command,
_ []string,
) error {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
namespace, err := cnx.GetNamespace("")
if err != nil {
return err
}
// Is the pod running? Do we have all the replicas?
status, err := kubernetes.GetDeploymentStatus(namespace, kubernetes.ProxyApp)
if err != nil {
return utils.Errorf(err, L("failed to get deployment status"))
}
if status.Replicas != status.ReadyReplicas {
log.Warn().Msgf(L("Some replicas are not ready: %[1]d / %[2]d"), status.ReadyReplicas, status.Replicas)
}
if status.AvailableReplicas == 0 {
return errors.New(L("the pod is not running"))
}
log.Info().Msg(L("Proxy containers up and running"))
return nil
}
07070100000020000081a400000000000000000000000168ed21dd000003d0000000000000000000000000000000000000001c00000000mgrpxy/cmd/status/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package status
import (
"errors"
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
. "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 podmanStatus(
_ *types.GlobalFlags,
_ *statusFlags,
_ *cobra.Command,
_ []string,
) error {
var returnErr error
services := []string{"httpd", "salt-broker", "squid", "ssh", "tftpd", "pod"}
for _, service := range services {
serviceName := fmt.Sprintf("uyuni-proxy-%s", service)
if err := utils.RunCmdStdMapping(zerolog.DebugLevel, "systemctl", "status", "--no-pager", serviceName); err != nil {
log.Error().Err(err).Msgf(L("Failed to get status of the %s service"), serviceName)
returnErr = errors.New(L("failed to get the status of at least one service"))
}
}
return returnErr
}
07070100000021000081a400000000000000000000000168ed21dd000005c5000000000000000000000000000000000000001c00000000mgrpxy/cmd/status/status.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package status
import (
"errors"
"github.com/spf13/cobra"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
var systemd podman.Systemd = podman.NewSystemd()
type statusFlags struct {
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[statusFlags]) *cobra.Command {
cmd := &cobra.Command{
Use: "status",
GroupID: "management",
Short: L("Get the proxy status"),
Long: L("Get the proxy status"),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags statusFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
cmd.SetUsageTemplate(cmd.UsageTemplate())
return cmd
}
// NewCommand to get the status of the server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, status)
}
func status(globalFlags *types.GlobalFlags, flags *statusFlags, cmd *cobra.Command, args []string) error {
if systemd.HasService(podman.ProxyService) {
return podmanStatus(globalFlags, flags, cmd, args)
}
if utils.IsInstalled("kubectl") && utils.IsInstalled("helm") {
return kubernetesStatus(globalFlags, flags, cmd, args)
}
return errors.New(L("no installed proxy detected"))
}
07070100000022000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001200000000mgrpxy/cmd/status07070100000023000081a400000000000000000000000168ed21dd000002ed000000000000000000000000000000000000001e00000000mgrpxy/cmd/stop/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package stop
import (
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func kubernetesStop(
_ *types.GlobalFlags,
_ *stopFlags,
_ *cobra.Command,
_ []string,
) error {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
namespace, err := cnx.GetNamespace("")
if err != nil {
return utils.Errorf(err, L("failed retrieving namespace"))
}
return kubernetes.Stop(namespace, kubernetes.ProxyApp)
}
07070100000024000081a400000000000000000000000168ed21dd000001b9000000000000000000000000000000000000001a00000000mgrpxy/cmd/stop/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package stop
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
var systemd podman.Systemd = podman.NewSystemd()
func podmanStop(
_ *types.GlobalFlags,
_ *stopFlags,
_ *cobra.Command,
_ []string,
) error {
return systemd.StopService(podman.ProxyService)
}
07070100000025000081a400000000000000000000000168ed21dd00000522000000000000000000000000000000000000001800000000mgrpxy/cmd/stop/stop.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package stop
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type stopFlags struct {
Backend string
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[stopFlags]) *cobra.Command {
stopCmd := &cobra.Command{
Use: "stop",
GroupID: "management",
Short: L("Stop the proxy"),
Long: L("Stop the proxy"),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags stopFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
stopCmd.SetUsageTemplate(stopCmd.UsageTemplate())
utils.AddBackendFlag(stopCmd)
return stopCmd
}
// NewCommand to stop server.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, stop)
}
func stop(globalFlags *types.GlobalFlags, flags *stopFlags, cmd *cobra.Command, args []string) error {
fn, err := shared.ChooseProxyPodmanOrKubernetes(cmd.Flags(), podmanStop, kubernetesStop)
if err != nil {
return err
}
return fn(globalFlags, flags, cmd, args)
}
07070100000026000081a400000000000000000000000168ed21dd00000343000000000000000000000000000000000000001d00000000mgrpxy/cmd/stop/stop_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package stop
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--backend", "kubectl",
}
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *stopFlags,
_ *cobra.Command, _ []string,
) error {
testutils.AssertEquals(t, "Error parsing --backend", "kubectl", flags.Backend)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000027000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001000000000mgrpxy/cmd/stop07070100000028000081a400000000000000000000000168ed21dd00000479000000000000000000000000000000000000002400000000mgrpxy/cmd/support/config/config.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package config
import (
"github.com/spf13/cobra"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type configFlags struct {
Output string
Backend string
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[configFlags]) *cobra.Command {
configCmd := &cobra.Command{
Use: "config",
Short: L("Extract configuration and logs"),
Long: L(`Extract the host or cluster configuration and logs as well as those from
the containers for support to help debugging.`),
RunE: func(cmd *cobra.Command, args []string) error {
var flags configFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
configCmd.Flags().StringP("output", "o", ".", L("path where to extract the data"))
utils.AddBackendFlag(configCmd)
return configCmd
}
// NewCommand is the command for creates supportconfig.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, extract)
}
07070100000029000081a400000000000000000000000168ed21dd000003cb000000000000000000000000000000000000002900000000mgrpxy/cmd/support/config/config_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package config
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--output", "path/to/output.tar.gz",
"--backend", "kubectl",
}
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *configFlags,
_ *cobra.Command, _ []string,
) error {
testutils.AssertEquals(t, "Error parsing --output", "path/to/output.tar.gz", flags.Output)
testutils.AssertEquals(t, "Error parsing --backend", "kubectl", flags.Backend)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
0707010000002a000081a400000000000000000000000168ed21dd0000051a000000000000000000000000000000000000002700000000mgrpxy/cmd/support/config/extractor.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package config
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared"
"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
var systemd podman.Systemd = podman.NewSystemd()
func extract(_ *types.GlobalFlags, flags *configFlags, _ *cobra.Command, _ []string) error {
// Copy the generated file locally
tmpDir, cleaner, err := utils.TempDir()
if err != nil {
return err
}
defer cleaner()
var fileList []string
if systemd.HasService(podman.ProxyService) {
fileList, err = podman.RunSupportConfigOnPodmanHost(systemd, tmpDir)
}
if utils.IsInstalled("kubectl") && utils.IsInstalled("helm") {
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
var namespace string
namespace, err = cnx.GetNamespace("")
if err != nil {
return err
}
fileList, err = kubernetes.RunSupportConfigOnKubernetesHost(tmpDir, namespace, kubernetes.ProxyFilter)
}
if err != nil {
return err
}
if err := utils.CreateSupportConfigTarball(flags.Output, fileList); err != nil {
return err
}
return nil
}
0707010000002b000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001a00000000mgrpxy/cmd/support/config0707010000002c000081a400000000000000000000000168ed21dd00000688000000000000000000000000000000000000003000000000mgrpxy/cmd/support/ptf/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package kubernetes
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
pxy_utils "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
type kubernetesPTFFlags struct {
UpgradeFlags kubernetes.KubernetesProxyUpgradeFlags `mapstructure:",squash"`
}
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[kubernetesPTFFlags]) *cobra.Command {
kubernetesCmd := &cobra.Command{
Use: "kubernetes",
Short: L("Install a PTF or Test package on a kubernetes cluster"),
Long: L(`Install a PTR of Test package on a kubernetes cluster
The support ptf command assumes the following:
* kubectl and helm are installed locally
* a working kubectl configuration should be set to connect to the cluster to deploy to
The helm values file will be overridden with the values from the command parameters or configuration.
NOTE: installing on a remote cluster is not supported yet!
`),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var flags kubernetesPTFFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
pxy_utils.AddImageFlags(kubernetesCmd)
kubernetes.AddHelmFlags(kubernetesCmd)
return kubernetesCmd
}
// NewCommand for kubernetes installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, ptfForKubernetes)
}
0707010000002d000081a400000000000000000000000168ed21dd0000043a000000000000000000000000000000000000003500000000mgrpxy/cmd/support/ptf/kubernetes/kubernetes_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package kubernetes
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/testutils/flagstests"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{}
args = append(args, flagstests.ImageProxyFlagsTestArgs...)
args = append(args, flagstests.ProxyHelmFlagsTestArgs...)
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *kubernetesPTFFlags,
_ *cobra.Command, _ []string,
) error {
flagstests.AssertProxyImageFlags(t, &flags.UpgradeFlags.ProxyImageFlags)
flagstests.AssertProxyHelmFlags(t, &flags.UpgradeFlags.Helm)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
0707010000002e000081a400000000000000000000000168ed21dd000001c7000000000000000000000000000000000000002b00000000mgrpxy/cmd/support/ptf/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package kubernetes
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func ptfForKubernetes(_ *types.GlobalFlags,
flags *kubernetesPTFFlags,
cmd *cobra.Command,
args []string,
) error {
return kubernetes.Upgrade(&flags.UpgradeFlags, cmd, args)
}
0707010000002f000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000002200000000mgrpxy/cmd/support/ptf/kubernetes07070100000030000081a400000000000000000000000168ed21dd00000146000000000000000000000000000000000000002000000000mgrpxy/cmd/support/ptf/noptf.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build !ptf
package ptf
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// NewCommand is the command for creates supportptf.
func NewCommand(_ *types.GlobalFlags) *cobra.Command {
return nil
}
07070100000031000081a400000000000000000000000168ed21dd0000065b000000000000000000000000000000000000002800000000mgrpxy/cmd/support/ptf/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package podman
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "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"
)
type podmanPTFFlags struct {
UpgradeFlags podman.PodmanProxyFlags `mapstructure:",squash"`
PTFId string `mapstructure:"ptf"`
TestID string `mapstructure:"test"`
CustomerID string `mapstructure:"user"`
}
func newCmd(globalFlags *types.GlobalFlags, run shared_utils.CommandFunc[podmanPTFFlags]) *cobra.Command {
var flags podmanPTFFlags
podmanCmd := &cobra.Command{
Use: "podman",
Short: L("Install a PTF or Test package on podman"),
Long: L(`Install a PTF or Test package on podman
The support ptf podman command assumes podman is installed locally and
the host machine is registered to SCC.
NOTE: for now installing on a remote podman is not supported!
`),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return shared_utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
utils.AddSCCFlag(podmanCmd)
utils.AddImageFlags(podmanCmd)
shared_utils.AddPTFFlag(podmanCmd)
return podmanCmd
}
// NewCommand for podman installation.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, ptfForPodman)
}
07070100000032000081a400000000000000000000000168ed21dd00000550000000000000000000000000000000000000002d00000000mgrpxy/cmd/support/ptf/podman/podman_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package podman
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/testutils/flagstests"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--ptf", "ptf123",
"--test", "test123",
"--user", "sccuser",
}
args = append(args, flagstests.SCCFlagTestArgs...)
args = append(args, flagstests.ImageProxyFlagsTestArgs...)
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *podmanPTFFlags,
_ *cobra.Command, _ []string,
) error {
flagstests.AssertSCCFlag(t, &flags.UpgradeFlags.SCC)
flagstests.AssertProxyImageFlags(t, &flags.UpgradeFlags.ProxyImageFlags)
testutils.AssertEquals(t, "Error parsing --ptf", "ptf123", flags.PTFId)
testutils.AssertEquals(t, "Error parsing --test", "test123", flags.TestID)
testutils.AssertEquals(t, "Error parsing --user", "sccuser", flags.CustomerID)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000033000081a400000000000000000000000168ed21dd00000f17000000000000000000000000000000000000002700000000mgrpxy/cmd/support/ptf/podman/utils.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package podman
import (
"errors"
"os/exec"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
podman_shared "github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
var systemd podman_shared.Systemd = podman_shared.NewSystemd()
func ptfForPodman(
globalFlags *types.GlobalFlags,
flags *podmanPTFFlags,
cmd *cobra.Command,
args []string,
) error {
if _, err := exec.LookPath("podman"); err != nil {
return errors.New(L("install podman before running this command"))
}
if err := updateParameters(flags); err != nil {
return err
}
if err := systemd.StopService(podman_shared.ProxyService); err != nil {
return err
}
hostData, err := podman_shared.InspectHost()
if err != nil {
return err
}
authFile, cleaner, err := podman_shared.PodmanLogin(hostData, flags.UpgradeFlags.SCC)
if err != nil {
return utils.Errorf(err, L("failed to login to registry.suse.com"))
}
defer cleaner()
httpdImage, err := podman_shared.PrepareImage(authFile, flags.UpgradeFlags.Httpd.Name, flags.UpgradeFlags.PullPolicy, true)
if err != nil {
log.Warn().Msgf(L("cannot find httpd image: it will no be upgraded"))
}
saltBrokerImage, err := podman_shared.PrepareImage(authFile, flags.UpgradeFlags.SaltBroker.Name, flags.UpgradeFlags.PullPolicy, true)
if err != nil {
log.Warn().Msgf(L("cannot find salt-broker image: it will no be upgraded"))
}
squidImage, err := podman_shared.PrepareImage(authFile, flags.UpgradeFlags.Squid.Name, flags.UpgradeFlags.PullPolicy, true)
if err != nil {
log.Warn().Msgf(L("cannot find squid image: it will no be upgraded"))
}
sshImage, err := podman_shared.PrepareImage(authFile, flags.UpgradeFlags.SSH.Name, flags.UpgradeFlags.PullPolicy, true)
if err != nil {
log.Warn().Msgf(L("cannot find ssh image: it will no be upgraded"))
}
tftpdImage, err := podman_shared.PrepareImage(authFile, flags.UpgradeFlags.Tftpd.Name, flags.UpgradeFlags.PullPolicy, true)
if err != nil {
log.Warn().Msgf(L("cannot find tftpd image: it will no be upgraded"))
}
// Setup the systemd service configuration options
err = podman.GenerateSystemdService(systemd, httpdImage, saltBrokerImage, squidImage, sshImage, tftpdImage, &flags.UpgradeFlags)
if err != nil {
return err
}
return podman.StartPod(systemd)
}
func updateParameters(flags *podmanPTFFlags) error {
if flags.TestID != "" && flags.PTFId != "" {
return errors.New(L("ptf and test flags cannot be set simultaneously "))
}
if flags.TestID == "" && flags.PTFId == "" {
return errors.New(L("ptf and test flags cannot be empty simultaneously "))
}
if flags.CustomerID == "" {
return errors.New(L("user flag cannot be empty"))
}
suffix := "ptf"
projectID := flags.PTFId
if flags.TestID != "" {
suffix = "test"
projectID = flags.TestID
}
proxyImages := []struct {
serviceName string
imageFlag *types.ImageFlags
}{
{"httpd", &flags.UpgradeFlags.Httpd},
{"ssh", &flags.UpgradeFlags.SSH},
{"tftpd", &flags.UpgradeFlags.Tftpd},
{"salt-broker", &flags.UpgradeFlags.SaltBroker},
{"squid", &flags.UpgradeFlags.Squid},
}
// Process each pxy image
for _, config := range proxyImages {
runningImage, err := podman_shared.GetRunningImage(config.serviceName)
if err != nil {
return err
}
config.imageFlag.Name, err = utils.ComputePTF(flags.UpgradeFlags.ProxyImageFlags.Registry, flags.CustomerID, projectID,
runningImage, suffix)
if err != nil {
return err
}
log.Info().Msgf(L("The %[1]s ptf image computed is: %[2]s"), config.serviceName, config.imageFlag.Name)
}
return nil
}
07070100000034000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001e00000000mgrpxy/cmd/support/ptf/podman07070100000035000081a400000000000000000000000168ed21dd0000030f000000000000000000000000000000000000001e00000000mgrpxy/cmd/support/ptf/ptf.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
//go:build ptf
package ptf
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/ptf/kubernetes"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/ptf/podman"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// NewCommand is the command for creates supportptf.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
ptfCmd := &cobra.Command{
Use: "ptf",
Short: L("Install a PTF"),
}
ptfCmd.AddCommand(podman.NewCommand(globalFlags))
if kubernetesCmd := kubernetes.NewCommand(globalFlags); kubernetesCmd != nil {
ptfCmd.AddCommand(kubernetesCmd)
}
return ptfCmd
}
07070100000036000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001700000000mgrpxy/cmd/support/ptf07070100000037000081a400000000000000000000000168ed21dd00000344000000000000000000000000000000000000001e00000000mgrpxy/cmd/support/support.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package support
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/config"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/support/ptf"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// NewCommand to export supportconfig.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
supportCmd := &cobra.Command{
Use: "support",
GroupID: "tool",
Short: L("Commands for support operations"),
Long: L("Commands for support operations"),
}
supportCmd.AddCommand(config.NewCommand(globalFlags))
if ptfCommand := ptf.NewCommand(globalFlags); ptfCommand != nil {
supportCmd.AddCommand(ptfCommand)
}
return supportCmd
}
07070100000038000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001300000000mgrpxy/cmd/support07070100000039000081a400000000000000000000000168ed21dd000007ff000000000000000000000000000000000000002300000000mgrpxy/cmd/uninstall/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package uninstall
import (
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func uninstallForKubernetes(
_ *types.GlobalFlags,
flags *utils.UninstallFlags,
_ *cobra.Command,
_ []string,
) error {
dryRun := !flags.Force
if flags.Purge.Volumes {
log.Warn().Msg(L("--purge-volumes is ignored on a kubernetes deployment"))
}
if flags.Purge.Images {
log.Warn().Msg(L("--purge-images is ignored on a kubernetes deployment"))
}
clusterInfos, err := kubernetes.CheckCluster()
if err != nil {
return err
}
kubeconfig := clusterInfos.GetKubeconfig()
// TODO Find all the PVs related to the server if we want to delete them
// Uninstall uyuni
cnx := shared.NewConnection("kubectl", "", kubernetes.ProxyFilter)
namespace, err := cnx.GetNamespace("")
if err != nil {
return err
}
if err := kubernetes.HelmUninstall(namespace, kubeconfig, kubernetes.ProxyApp, dryRun); err != nil {
return err
}
// TODO Remove the PVs or wait for their automatic removal if purge is requested
// Also wait if the PVs are dynamic with Delete reclaim policy but the user didn't ask to purge them
// Since some storage plugins don't handle Delete policy, we may need to check for error events to avoid infinite loop
// Remove the K3s Traefik config
if clusterInfos.IsK3s() {
kubernetes.UninstallK3sTraefikConfig(dryRun)
}
// Remove the rke2 nginx config
if clusterInfos.IsRke2() {
kubernetes.UninstallRke2NginxConfig(dryRun)
}
if dryRun {
log.Warn().Msg(L("Nothing has been uninstalled, run with --force to actually uninstall"))
}
log.Warn().Msg(L("Volumes have not been touched. Depending on the storage class used, they may not have been removed"))
return nil
}
0707010000003a000081a400000000000000000000000168ed21dd00000c2d000000000000000000000000000000000000001f00000000mgrpxy/cmd/uninstall/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package uninstall
import (
"os"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
pxy_podman "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
var systemd podman.Systemd = podman.NewSystemd()
func uninstallForPodman(
_ *types.GlobalFlags,
flags *utils.UninstallFlags,
_ *cobra.Command,
_ []string,
) error {
dryRun := !flags.Force
// Get the images from the service configs before they are removed
images := []string{
podman.GetServiceImage("uyuni-proxy-httpd"),
podman.GetServiceImage("uyuni-proxy-salt-broker"),
podman.GetServiceImage("uyuni-proxy-squid"),
podman.GetServiceImage("uyuni-proxy-ssh"),
podman.GetServiceImage("uyuni-proxy-tftpd"),
}
// Uninstall the service
systemd.UninstallService("uyuni-proxy-pod", dryRun)
systemd.UninstallService("uyuni-proxy-httpd", dryRun)
systemd.UninstallService("uyuni-proxy-salt-broker", dryRun)
systemd.UninstallService("uyuni-proxy-squid", dryRun)
systemd.UninstallService("uyuni-proxy-ssh", dryRun)
systemd.UninstallService("uyuni-proxy-tftpd", dryRun)
// Force stop the pod
for _, containerName := range podman.ProxyContainerNames {
podman.DeleteContainer(containerName, dryRun)
}
// Remove the volumes
if flags.Purge.Volumes {
// Merge all proxy containers volumes into a map
volumes := []string{}
for _, volume := range utils.ProxyHttpdVolumes {
volumes = append(volumes, volume.Name)
}
for _, volume := range utils.ProxySquidVolumes {
volumes = append(volumes, volume.Name)
}
for _, volume := range utils.ProxyTftpdVolumes {
volumes = append(volumes, volume.Name)
}
// Delete each volume
for _, volume := range volumes {
if err := podman.DeleteVolume(volume, dryRun); err != nil {
return utils.Errorf(err, L("cannot delete volume %s"), volume)
}
}
log.Info().Msg(L("All volumes removed"))
// Remove config dir
if err := os.RemoveAll("/etc/uyuni/proxy"); err != nil {
log.Warn().Msg(L("Failed to delete /etc/uyuni/proxy folder"))
} else {
log.Info().Msg(L("/etc/uyuni/proxy folder removed"))
}
}
if flags.Purge.Images {
for _, image := range images {
if image != "" {
if err := podman.DeleteImage(image, !flags.Force); err != nil {
return utils.Errorf(err, L("cannot delete image %s"), image)
}
}
}
log.Info().Msg(L("All images have been removed"))
}
podman.DeleteNetwork(dryRun)
if podman.HasSecret(pxy_podman.SystemIDSecret) {
podman.DeleteSecret(pxy_podman.SystemIDSecret, false)
}
err := systemd.ReloadDaemon(dryRun)
if dryRun {
log.Warn().Msg(
L("Nothing has been uninstalled, run with --force and --purge-volumes to actually uninstall and clear data"),
)
} else if !flags.Purge.Volumes {
log.Warn().Msg(L("Data have been kept, use podman volume commands to clear the volumes"))
}
return err
}
0707010000003b000081a400000000000000000000000168ed21dd000005f5000000000000000000000000000000000000002200000000mgrpxy/cmd/uninstall/uninstall.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package uninstall
import (
"github.com/spf13/cobra"
"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/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func newCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[utils.UninstallFlags]) *cobra.Command {
uninstallCmd := &cobra.Command{
Use: "uninstall",
GroupID: "deploy",
Short: L("Uninstall a proxy"),
Long: L(`Uninstall a proxy and optionally the corresponding volumes.
By default it will only print what would be done, use --force to actually remove.`) + kubernetes.UninstallHelp(),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags utils.UninstallFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
utils.AddUninstallFlags(uninstallCmd, true)
return uninstallCmd
}
// NewCommand for uninstall proxy.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, uninstall)
}
func uninstall(
globalFlags *types.GlobalFlags,
flags *utils.UninstallFlags,
cmd *cobra.Command,
args []string,
) error {
fn, err := shared.ChoosePodmanOrKubernetes(cmd.Flags(), uninstallForPodman, uninstallForKubernetes)
if err != nil {
return err
}
return fn(globalFlags, flags, cmd, args)
}
0707010000003c000081a400000000000000000000000168ed21dd0000049c000000000000000000000000000000000000002700000000mgrpxy/cmd/uninstall/uninstall_test.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package uninstall
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
func TestParamsParsing(t *testing.T) {
args := []string{
"--force",
"--purge-volumes",
"--purge-images",
"--backend", "kubectl",
}
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *utils.UninstallFlags,
_ *cobra.Command, _ []string,
) error {
testutils.AssertTrue(t, "Error parsing --force", flags.Force)
testutils.AssertTrue(t, "Error parsing --purge-volumes", flags.Purge.Volumes)
testutils.AssertTrue(t, "Error parsing --purge-images", flags.Purge.Images)
testutils.AssertEquals(t, "Error parsing --backend", "kubectl", flags.Backend)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
0707010000003d000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001500000000mgrpxy/cmd/uninstall0707010000003e000081a400000000000000000000000168ed21dd0000054f000000000000000000000000000000000000002c00000000mgrpxy/cmd/upgrade/kubernetes/kubernetes.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
pxy_utils "github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "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 newCmd(
globalFlags *types.GlobalFlags,
run utils.CommandFunc[kubernetes.KubernetesProxyUpgradeFlags],
) *cobra.Command {
cmd := &cobra.Command{
Use: "kubernetes",
Short: L("Upgrade a proxy on a running kubernetes cluster"),
Long: L(`Upgrade a proxy on a running kubernetes cluster.
The upgrade kubernetes command assumes kubectl is installed locally.
NOTE: for now upgrading on a remote kubernetes cluster is not supported!
`),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags kubernetes.KubernetesProxyUpgradeFlags
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
pxy_utils.AddImageFlags(cmd)
kubernetes.AddHelmFlags(cmd)
return cmd
}
// NewCommand install a new proxy on a running kubernetes cluster.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, upgradeKubernetes)
}
0707010000003f000081a400000000000000000000000168ed21dd00000466000000000000000000000000000000000000003100000000mgrpxy/cmd/upgrade/kubernetes/kubernetes_test.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/testutils/flagstests"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{}
args = append(args, flagstests.ImageProxyFlagsTestArgs...)
args = append(args, flagstests.ProxyHelmFlagsTestArgs...)
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *kubernetes.KubernetesProxyUpgradeFlags,
_ *cobra.Command, _ []string,
) error {
flagstests.AssertProxyImageFlags(t, &flags.ProxyImageFlags)
flagstests.AssertProxyHelmFlags(t, &flags.Helm)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000040000081a400000000000000000000000168ed21dd000001bd000000000000000000000000000000000000002700000000mgrpxy/cmd/upgrade/kubernetes/utils.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/kubernetes"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func upgradeKubernetes(_ *types.GlobalFlags,
flags *kubernetes.KubernetesProxyUpgradeFlags, cmd *cobra.Command, args []string,
) error {
return kubernetes.Upgrade(flags, cmd, args)
}
07070100000041000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001e00000000mgrpxy/cmd/upgrade/kubernetes07070100000042000081a400000000000000000000000168ed21dd00000560000000000000000000000000000000000000002400000000mgrpxy/cmd/upgrade/podman/podman.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)
func newCmd(globalFlags *types.GlobalFlags, run shared_utils.CommandFunc[podman.PodmanProxyFlags]) *cobra.Command {
podmanCmd := &cobra.Command{
Use: "podman",
Short: L("Upgrade a proxy on podman"),
Long: L(`Upgrade a proxy on podman
The upgrade podman command assumes podman is upgraded locally.
NOTE: for now upgrading on a remote podman is not supported!
`),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
var flags podman.PodmanProxyFlags
return shared_utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
},
}
utils.AddSCCFlag(podmanCmd)
utils.AddImageFlags(podmanCmd)
shared_podman.AddPodmanArgFlag(podmanCmd)
return podmanCmd
}
// NewCommand install a new proxy on podman from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
return newCmd(globalFlags, upgradePodman)
}
07070100000043000081a400000000000000000000000168ed21dd000004b0000000000000000000000000000000000000002900000000mgrpxy/cmd/upgrade/podman/podman_test.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"testing"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
"github.com/uyuni-project/uyuni-tools/shared/testutils/flagstests"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
func TestParamsParsing(t *testing.T) {
args := []string{}
args = append(args, flagstests.SCCFlagTestArgs...)
args = append(args, flagstests.ImageProxyFlagsTestArgs...)
args = append(args, flagstests.PodmanFlagsTestArgs...)
// Test function asserting that the args are properly parsed
tester := func(_ *types.GlobalFlags, flags *podman.PodmanProxyFlags,
_ *cobra.Command, _ []string,
) error {
flagstests.AssertSCCFlag(t, &flags.SCC)
flagstests.AssertPodmanInstallFlags(t, &flags.Podman)
flagstests.AssertProxyImageFlags(t, &flags.ProxyImageFlags)
return nil
}
globalFlags := types.GlobalFlags{}
cmd := newCmd(&globalFlags, tester)
testutils.AssertHasAllFlags(t, cmd, args)
cmd.SetArgs(args)
if err := cmd.Execute(); err != nil {
t.Errorf("command failed with error: %s", err)
}
}
07070100000044000081a400000000000000000000000168ed21dd00000246000000000000000000000000000000000000002300000000mgrpxy/cmd/upgrade/podman/utils.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/podman"
shared_podman "github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
var systemd shared_podman.Systemd = shared_podman.NewSystemd()
func upgradePodman(
globalFlags *types.GlobalFlags,
flags *podman.PodmanProxyFlags,
cmd *cobra.Command,
args []string,
) error {
return podman.Upgrade(systemd, globalFlags, flags, cmd, args)
}
07070100000045000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001a00000000mgrpxy/cmd/upgrade/podman07070100000046000081a400000000000000000000000168ed21dd00000306000000000000000000000000000000000000001e00000000mgrpxy/cmd/upgrade/upgrade.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package upgrade
import (
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/upgrade/kubernetes"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd/upgrade/podman"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// NewCommand install a new proxy from scratch.
func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
upgradeCmd := &cobra.Command{
Use: "upgrade",
GroupID: "deploy",
Short: L("Upgrade a proxy"),
Long: L("Upgrade a proxy"),
}
upgradeCmd.AddCommand(podman.NewCommand(globalFlags))
upgradeCmd.AddCommand(kubernetes.NewCommand(globalFlags))
return upgradeCmd
}
07070100000047000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001300000000mgrpxy/cmd/upgrade07070100000048000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000000b00000000mgrpxy/cmd07070100000049000081a400000000000000000000000168ed21dd000002bc000000000000000000000000000000000000000f00000000mgrpxy/main.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package main
import (
"os"
"github.com/chai2010/gettext-go"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/cmd"
l10n_utils "github.com/uyuni-project/uyuni-tools/shared/l10n/utils"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
// Run runs the `mgrpxy` root command.
func Run() error {
gettext.BindLocale(gettext.New("mgrpxy", utils.LocaleRoot, l10n_utils.New(utils.LocaleRoot)))
cobra.EnableCaseInsensitive = true
run, err := cmd.NewUyuniproxyCommand()
if err != nil {
return err
}
return run.Execute()
}
func main() {
if err := Run(); err != nil {
os.Exit(1)
}
}
0707010000004a000081a400000000000000000000000168ed21dd000003c2000000000000000000000000000000000000002000000000mgrpxy/shared/kubernetes/cmd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"fmt"
"github.com/spf13/cobra"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
// HelmFlags it's used for helm chart flags.
type HelmFlags struct {
Proxy types.ChartFlags
}
// AddHelmFlags add helm flags to a command.
func AddHelmFlags(cmd *cobra.Command) {
defaultChart := fmt.Sprintf("oci://%s/proxy-helm", utils.DefaultHelmRegistry)
cmd.Flags().String("helm-proxy-namespace", "default", L("Kubernetes namespace where to install the proxy"))
cmd.Flags().String("helm-proxy-chart", defaultChart, L("URL to the proxy helm chart"))
cmd.Flags().String("helm-proxy-version", "", L("Version of the proxy helm chart"))
cmd.Flags().String("helm-proxy-values", "", L("Path to a values YAML file to use for proxy helm install"))
}
0707010000004b000081a400000000000000000000000168ed21dd000018cb000000000000000000000000000000000000002300000000mgrpxy/shared/kubernetes/deploy.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package kubernetes
import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
"github.com/uyuni-project/uyuni-tools/shared/kubernetes"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)
const helmAppName = "uyuni-proxy"
// KubernetesProxyUpgradeFlags represents the flags for the mgrpxy upgrade kubernetes command.
type KubernetesProxyUpgradeFlags struct {
utils.ProxyImageFlags `mapstructure:",squash"`
Helm HelmFlags
}
// Deploy will deploy proxy in kubernetes.
func Deploy(imageFlags *utils.ProxyImageFlags, helmFlags *HelmFlags, configDir string,
kubeconfig string, helmArgs ...string,
) error {
log.Info().Msg(L("Installing Uyuni proxy"))
helmParams := []string{}
// Pass the user-provided values file
extraValues := helmFlags.Proxy.Values
if extraValues != "" {
helmParams = append(helmParams, "-f", extraValues)
}
if !shared_utils.FileExists(path.Join(configDir, "httpd.yaml")) {
if _, err := getHTTPDYaml(configDir); err != nil {
return err
}
}
helmParams = append(helmParams, "-f", path.Join(configDir, "httpd.yaml"))
if !shared_utils.FileExists(path.Join(configDir, "ssh.yaml")) {
if _, err := getSSHYaml(configDir); err != nil {
return err
}
}
helmParams = append(helmParams, "-f", path.Join(configDir, "ssh.yaml"))
if !shared_utils.FileExists(path.Join(configDir, "config.yaml")) {
if _, err := getConfigYaml(configDir); err != nil {
return err
}
}
helmParams = append(helmParams, "-f", path.Join(configDir, "config.yaml"))
if len(imageFlags.Tuning.Httpd) > 0 {
absPath, err := filepath.Abs(imageFlags.Tuning.Httpd)
if err != nil {
return err
}
helmParams = append(helmParams, "--set-file", "apache_tuning="+absPath)
}
if len(imageFlags.Tuning.Squid) > 0 {
absPath, err := filepath.Abs(imageFlags.Tuning.Squid)
if err != nil {
return err
}
helmParams = append(helmParams, "--set-file", "squid_tuning="+absPath)
}
helmParams = append(helmParams,
"--set", "images.proxy-httpd="+imageFlags.GetContainerImage("httpd"),
"--set", "images.proxy-salt-broker="+imageFlags.GetContainerImage("salt-broker"),
"--set", "images.proxy-squid="+imageFlags.GetContainerImage("squid"),
"--set", "images.proxy-ssh="+imageFlags.GetContainerImage("ssh"),
"--set", "images.proxy-tftpd="+imageFlags.GetContainerImage("tftpd"),
"--set", "repository="+imageFlags.Registry,
"--set", "version="+imageFlags.Tag,
"--set", "pullPolicy="+string(kubernetes.GetPullPolicy(imageFlags.PullPolicy)))
helmParams = append(helmParams, helmArgs...)
// Install the helm chart
if err := kubernetes.HelmUpgrade(kubeconfig, helmFlags.Proxy.Namespace, true, "", helmAppName, helmFlags.Proxy.Chart,
helmFlags.Proxy.Version, helmParams...); err != nil {
return shared_utils.Errorf(err, L("cannot run helm upgrade"))
}
// Wait for the pod to be started
return kubernetes.WaitForDeployments(helmFlags.Proxy.Namespace, helmAppName)
}
func getSSHYaml(directory string) (string, error) {
sshPayload, err := kubernetes.GetSecret("proxy-secret", "-o=jsonpath={.data.ssh\\.yaml}")
if err != nil {
return "", err
}
sshYamlFilename := filepath.Join(directory, "ssh.yaml")
err = os.WriteFile(sshYamlFilename, []byte(sshPayload), 0644)
if err != nil {
return "", shared_utils.Errorf(err, L("failed to write in file %s"), sshYamlFilename)
}
return sshYamlFilename, nil
}
func getHTTPDYaml(directory string) (string, error) {
httpdPayload, err := kubernetes.GetSecret("proxy-secret", "-o=jsonpath={.data.httpd\\.yaml}")
if err != nil {
return "", err
}
httpdYamlFilename := filepath.Join(directory, "httpd.yaml")
err = os.WriteFile(httpdYamlFilename, []byte(httpdPayload), 0644)
if err != nil {
return "", shared_utils.Errorf(err, L("failed to write in file %s"), httpdYamlFilename)
}
return httpdYamlFilename, nil
}
func getConfigYaml(directory string) (string, error) {
configPayload, err := kubernetes.GetConfigMap("proxy-configMap", "-o=jsonpath={.data.config\\.yaml}")
if err != nil {
return "", err
}
configYamlFilename := filepath.Join(directory, "config.yaml")
err = os.WriteFile(configYamlFilename, []byte(configPayload), 0644)
if err != nil {
return "", shared_utils.Errorf(err, L("failed to write in file %s"), configYamlFilename)
}
return configYamlFilename, nil
}
// Upgrade will upgrade the current kubernetes proxy.
func Upgrade(flags *KubernetesProxyUpgradeFlags, _ *cobra.Command, _ []string) error {
for _, binary := range []string{"kubectl", "helm"} {
if _, err := exec.LookPath(binary); err != nil {
return fmt.Errorf(L("install %s before running this command"), binary)
}
}
tmpDir, cleaner, err := shared_utils.TempDir()
if err != nil {
return err
}
defer cleaner()
// Check the kubernetes cluster setup
clusterInfos, err := kubernetes.CheckCluster()
if err != nil {
return err
}
namespace := flags.Helm.Proxy.Namespace
if _, err = kubernetes.GetNode(namespace, kubernetes.ProxyApp); err != nil {
if err := kubernetes.ReplicasTo(namespace, kubernetes.ProxyApp, 1); err != nil {
return err
}
}
err = kubernetes.ReplicasTo(namespace, kubernetes.ProxyApp, 0)
if err != nil {
return err
}
defer func() {
// if something is running, we don't need to set replicas to 1
if _, err = kubernetes.GetNode(namespace, kubernetes.ProxyApp); err != nil {
if err = kubernetes.ReplicasTo(namespace, kubernetes.ProxyApp, 1); err != nil {
log.Error().Err(err).Msg(L("failed to scale replicas to 1"))
}
}
}()
helmArgs := []string{"--set", "ingress=" + clusterInfos.Ingress}
// Get the registry secret name if any
pullSecret, err := kubernetes.GetDeploymentImagePullSecret(namespace, kubernetes.ProxyFilter)
if err != nil {
return err
}
if pullSecret != "" {
helmArgs = append(helmArgs, "--set", "registrySecret="+pullSecret)
}
// Install the uyuni proxy helm chart
if err := Deploy(&flags.ProxyImageFlags, &flags.Helm, tmpDir, clusterInfos.GetKubeconfig(),
helmArgs...,
); err != nil {
return shared_utils.Errorf(err, L("cannot deploy proxy helm chart"))
}
return nil
}
0707010000004c000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001900000000mgrpxy/shared/kubernetes0707010000004d000081a400000000000000000000000168ed21dd00002e57000000000000000000000000000000000000001f00000000mgrpxy/shared/podman/podman.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"time"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/templates"
"github.com/uyuni-project/uyuni-tools/mgrpxy/shared/utils"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/podman"
"github.com/uyuni-project/uyuni-tools/shared/types"
shared_utils "github.com/uyuni-project/uyuni-tools/shared/utils"
)
const (
SystemIDEvent = "suse/systemid/generate"
SystemIDEventResponse = "suse/systemid/generated"
SystemIDSecret = "uyuni-proxy-systemid"
)
var contextRunner = shared_utils.NewRunnerWithContext
var newRunner = shared_utils.NewRunner
// PodmanProxyFlags are the flags used by podman proxy install and upgrade command.
type PodmanProxyFlags struct {
utils.ProxyImageFlags `mapstructure:",squash"`
SCC types.SCCCredentials
Podman podman.PodmanFlags
}
// GenerateSystemdService generates all the systemd files required by proxy.
func GenerateSystemdService(
systemd podman.Systemd,
httpdImage string,
saltBrokerImage string,
squidImage string,
sshImage string,
tftpdImage string,
flags *PodmanProxyFlags,
) error {
err := podman.SetupNetwork(true)
if err != nil {
return shared_utils.Errorf(err, L("cannot setup network"))
}
ipv6Enabled := podman.HasIpv6Enabled(podman.UyuniNetwork)
log.Info().Msg(L("Generating systemd services"))
httpProxyConfig := getHTTPProxyConfig()
ports := []types.PortMap{}
ports = append(ports, shared_utils.ProxyTCPPorts...)
ports = append(ports, shared_utils.ProxyPodmanPorts...)
ports = append(ports, shared_utils.TftpPorts...)
// Pod
dataPod := templates.PodTemplateData{
Ports: ports,
HTTPProxyFile: httpProxyConfig,
Network: podman.UyuniNetwork,
IPV6Enabled: ipv6Enabled,
}
podEnv := fmt.Sprintf(`Environment="PODMAN_EXTRA_ARGS=%s"`, strings.Join(flags.Podman.Args, " "))
if err := generateSystemdFile(dataPod, "pod", "", podEnv); err != nil {
return err
}
// Httpd
volumeOptions := ""
{
dataHttpd := templates.HttpdTemplateData{
Volumes: shared_utils.ProxyHttpdVolumes,
HTTPProxyFile: httpProxyConfig,
}
if podman.HasSecret(SystemIDSecret) {
dataHttpd.SystemIDSecret = SystemIDSecret
}
additionHttpdTuningSettings := ""
if flags.ProxyImageFlags.Tuning.Httpd != "" {
absPath, err := filepath.Abs(flags.ProxyImageFlags.Tuning.Httpd)
if err != nil {
return err
}
additionHttpdTuningSettings = fmt.Sprintf(
`Environment=HTTPD_EXTRA_CONF=-v%s:/etc/apache2/conf.d/apache_tuning.conf:ro%s`,
absPath, volumeOptions,
)
}
if err := generateSystemdFile(dataHttpd, "httpd", httpdImage, additionHttpdTuningSettings); err != nil {
return err
}
}
// Salt broker
{
dataSaltBroker := templates.SaltBrokerTemplateData{
HTTPProxyFile: httpProxyConfig,
}
if err := generateSystemdFile(dataSaltBroker, "salt-broker", saltBrokerImage, ""); err != nil {
return err
}
}
// Squid
{
dataSquid := templates.SquidTemplateData{
Volumes: shared_utils.ProxySquidVolumes,
HTTPProxyFile: httpProxyConfig,
}
additionSquidTuningSettings := ""
if flags.ProxyImageFlags.Tuning.Squid != "" {
absPath, err := filepath.Abs(flags.ProxyImageFlags.Tuning.Squid)
if err != nil {
return err
}
additionSquidTuningSettings = fmt.Sprintf(
`Environment=SQUID_EXTRA_CONF=-v%s:/etc/squid/conf.d/squid_tuning.conf:ro%s`,
absPath, volumeOptions,
)
}
if err := generateSystemdFile(dataSquid, "squid", squidImage, additionSquidTuningSettings); err != nil {
return err
}
}
// SSH
{
dataSSH := templates.SSHTemplateData{
HTTPProxyFile: httpProxyConfig,
}
if err := generateSystemdFile(dataSSH, "ssh", sshImage, ""); err != nil {
return err
}
}
// Tftpd
{
dataTftpd := templates.TFTPDTemplateData{
Volumes: shared_utils.ProxyTftpdVolumes,
HTTPProxyFile: httpProxyConfig,
}
if err := generateSystemdFile(dataTftpd, "tftpd", tftpdImage, ""); err != nil {
return err
}
}
return systemd.ReloadDaemon(false)
}
func generateSystemdFile(template shared_utils.Template, service string, image string, config string) error {
name := fmt.Sprintf("uyuni-proxy-%s.service", service)
const systemdPath = "/etc/systemd/system"
path := path.Join(systemdPath, name)
if err := shared_utils.WriteTemplateToFile(template, path, 0644, true); err != nil {
return shared_utils.Errorf(err, L("failed to generate systemd file '%s'"), path)
}
if image != "" {
configBody := fmt.Sprintf("Environment=UYUNI_IMAGE=%s", image)
if err := podman.GenerateSystemdConfFile("uyuni-proxy-"+service, "generated.conf", configBody, true); err != nil {
return shared_utils.Errorf(err, L("cannot generate systemd conf file"))
}
}
if config != "" {
if err := podman.GenerateSystemdConfFile("uyuni-proxy-"+service, podman.CustomConf, config, false); err != nil {
return shared_utils.Errorf(err, L("cannot generate systemd conf user configuration file"))
}
}
return nil
}
func getHTTPProxyConfig() string {
const httpProxyConfigPath = "/etc/sysconfig/proxy"
// Only SUSE distros seem to have such a file for HTTP proxy settings
if shared_utils.FileExists(httpProxyConfigPath) {
return httpProxyConfigPath
}
return ""
}
// GetContainerImage returns a proxy image URL.
func GetContainerImage(authFile string, flags *utils.ProxyImageFlags, name string) (string, error) {
image := flags.GetContainerImage(name)
preparedImage, err := podman.PrepareImage(authFile, image, flags.PullPolicy, true)
if err != nil {
return "", err
}
return preparedImage, nil
}
// UnpackConfig uncompress the config.tar.gz containing proxy configuration.
func UnpackConfig(configPath string) error {
const proxyConfigDir = "/etc/uyuni/proxy"
// Create dir if it doesn't exist & check perms
if err := os.MkdirAll(proxyConfigDir, 0755); err != nil {
return err
}
if err := checkPermissions(proxyConfigDir, 0005|0050|0500); err != nil {
return err
}
// Extract the tarball, if provided
if configPath != "" {
log.Info().Msgf(L("Setting up proxy with configuration %s"), configPath)
if err := shared_utils.ExtractTarGz(configPath, proxyConfigDir); err != nil {
return shared_utils.Errorf(err, L("failed to extract proxy config from %s file"), configPath)
}
} else {
log.Info().Msg(L("No tarball provided. Will check existing configuration files."))
}
return validateInstallYamlFiles(proxyConfigDir)
}
// checkPermissions checks if a directory or file has a required permissions.
func checkPermissions(path string, requiredMode os.FileMode) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if info.Mode()&requiredMode != requiredMode {
if info.IsDir() {
return fmt.Errorf(L("%s directory has no required permissions. Check your umask settings"), path)
}
return fmt.Errorf(L("%s file has no required permissions. Check your umask settings"), path)
}
return nil
}
// validateYamlFiles validates if the required configuration files.
func validateInstallYamlFiles(dir string) error {
yamlFiles := []string{"httpd.yaml", "ssh.yaml", "config.yaml"}
for _, file := range yamlFiles {
filePath := path.Join(dir, file)
_, err := os.Stat(filePath)
if err != nil {
return fmt.Errorf(L("missing required configuration file: %s"), filePath)
}
if file == "config.yaml" {
if err := checkPermissions(filePath, 0004|0040|0400); err != nil {
return err
}
}
}
return nil
}
// Upgrade will upgrade the proxy podman deploy.
func Upgrade(
systemd podman.Systemd, _ *types.GlobalFlags, flags *PodmanProxyFlags,
_ *cobra.Command, _ []string,
) error {
if _, err := exec.LookPath("podman"); err != nil {
return errors.New(L("install podman before running this command"))
}
if err := systemd.StopService(podman.ProxyService); err != nil {
return err
}
hostData, err := podman.InspectHost()
if err != nil {
return err
}
// Check if we are a salt minion registered to SMLM and if so, try to get up to date systemid
if hostData.HasSaltMinion {
// If we previously created systemid secret, remove it
podman.DeleteSecret(SystemIDSecret, false)
if err := GetSystemID(); err != nil {
log.Warn().Err(err).Msg(L("Unable to fetch up to date systemid, using one from the provided configuration file"))
}
}
authFile, cleaner, err := podman.PodmanLogin(hostData, flags.SCC)
if err != nil {
return shared_utils.Errorf(err, L("failed to login to registry.suse.com"))
}
defer cleaner()
httpdImage, err := GetContainerImage(authFile, &flags.ProxyImageFlags, "httpd")
if err != nil {
log.Warn().Msgf(L("cannot find httpd image: it will no be upgraded"))
}
saltBrokerImage, err := GetContainerImage(authFile, &flags.ProxyImageFlags, "salt-broker")
if err != nil {
log.Warn().Msgf(L("cannot find salt-broker image: it will no be upgraded"))
}
squidImage, err := GetContainerImage(authFile, &flags.ProxyImageFlags, "squid")
if err != nil {
log.Warn().Msgf(L("cannot find squid image: it will no be upgraded"))
}
sshImage, err := GetContainerImage(authFile, &flags.ProxyImageFlags, "ssh")
if err != nil {
log.Warn().Msgf(L("cannot find ssh image: it will no be upgraded"))
}
tftpdImage, err := GetContainerImage(authFile, &flags.ProxyImageFlags, "tftpd")
if err != nil {
log.Warn().Msgf(L("cannot find tftpd image: it will no be upgraded"))
}
// Setup the systemd service configuration options
err = GenerateSystemdService(systemd, httpdImage, saltBrokerImage, squidImage, sshImage, tftpdImage, flags)
if err != nil {
return err
}
return StartPod(systemd)
}
// Start the proxy services.
func StartPod(systemd podman.Systemd) error {
ret := systemd.IsServiceRunning(podman.ProxyService)
if ret {
return systemd.RestartService(podman.ProxyService)
}
return systemd.EnableService(podman.ProxyService)
}
func getSystemIDEvent() ([]byte, error) {
// Start the event listener in the background
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
eventListenerCmd := contextRunner(
ctx,
"venv-salt-call",
"state.event",
"tagmatch="+SystemIDEventResponse,
"count=1",
"--out=quiet",
)
var out bytes.Buffer
log.Debug().Msg("Starting event listener")
if err := eventListenerCmd.Std(&out).Start(); err != nil {
return nil, err
}
// Allow event listener to start
time.Sleep(time.Second)
// Trigger the even
fireEventCmd := newRunner(
"venv-salt-call",
"event.send",
SystemIDEvent,
)
log.Debug().Msg("Asking for up to date systemid")
if _, err := fireEventCmd.Exec(); err != nil {
return nil, err
}
// Wait for the event listener to finish, we are waiting for one event at most 10s
err := eventListenerCmd.Wait()
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
return nil, ctx.Err()
}
return nil, err
}
return out.Bytes(), nil
}
func parseSystemIDEvent(event []byte) (string, error) {
found := bytes.HasPrefix(event, []byte(SystemIDEventResponse))
if !found {
return "", errors.New(L("Not a system id event"))
}
jsonData := map[string]string{}
err := json.Unmarshal(event[len(SystemIDEventResponse):], &jsonData)
if err != nil {
return "", err
}
data, ok := jsonData["data"]
if !ok {
return "", errors.New(L("System id not found in returned event"))
}
return data, nil
}
func GetSystemID() error {
event, err := getSystemIDEvent()
if err != nil {
return err
}
systemid, err := parseSystemIDEvent(event)
if err != nil {
return err
}
log.Trace().Msgf("SystemID: %s", systemid)
return podman.CreateSecret(SystemIDSecret, systemid)
}
0707010000004e000081a400000000000000000000000168ed21dd00000ea9000000000000000000000000000000000000002400000000mgrpxy/shared/podman/podman_test.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package podman
import (
"os"
"path"
"strconv"
"testing"
"github.com/uyuni-project/uyuni-tools/shared/testutils"
)
func TestCheckDirPermissions(t *testing.T) {
tempDir := t.TempDir()
if err := os.MkdirAll(tempDir, 0755); err != nil {
t.Fatal(err)
}
if err := checkPermissions(tempDir, 0005|0050|0500); err != nil {
t.Errorf("Expected no error, got %v", err)
}
}
func TestValidateYamlFiles(t *testing.T) {
tempDir := t.TempDir()
testFiles := []string{"httpd.yaml", "ssh.yaml", "config.yaml"}
for _, file := range testFiles {
filePath := path.Join(tempDir, file)
if _, err := os.Create(filePath); err != nil {
t.Fatalf("Failed to create test file %s: %v", filePath, err)
}
}
// Test: when all files are present and have correct permissions
if err := validateInstallYamlFiles(tempDir); err != nil {
t.Errorf("Expected no error, got %v", err)
}
// Change the permission of config.yaml to 0600 to simulate a permission error
configFilePath := path.Join(tempDir, "config.yaml")
if err := os.Chmod(configFilePath, 0600); err != nil {
t.Fatalf("Failed to change permissions for %s: %v", configFilePath, err)
}
if err := validateInstallYamlFiles(tempDir); err == nil {
t.Errorf("Expected an error due to incorrect permissions on config.yaml, but got none")
}
// Restore the correct permissions for the next test run
if err := os.Chmod(configFilePath, 0644); err != nil {
t.Fatalf("Failed to restore permissions for %s: %v", configFilePath, err)
}
// Test: Missing file scenario, remove one file and expect an error
os.Remove(path.Join(tempDir, "httpd.yaml"))
if err := validateInstallYamlFiles(tempDir); err == nil {
t.Errorf("Expected an error due to missing httpd.yaml, but got none")
}
}
func TestGetSystemID(t *testing.T) {
// event output
systemid := `<?xml version=\"1.0\"?><params><param><value><struct><member><name>username</name>` +
`<value><string>admin</string></value></member><member><name>os_release</name><value><string>6.1</string>` +
`</value></member><member><name>operating_system</name><value><string>SL-Micro</string></value></member>` +
`<member><name>architecture</name><value><string>x86_64-redhat-linux</string></value></member><member>` +
`<name>system_id</name><value><string>ID-1000010001</string></value></member><member><name>type</name><value>` +
`<string>REAL</string></value></member><member><name>fields</name><value><array><data><value>` +
`<string>system_id</string></value><value><string>os_release</string></value><value><string>operating_system` +
`</string></value><value><string>architecture</string></value><value><string>username</string></value><value>` +
`<string>type</string></value></data></array></value></member><member><name>checksum</name><value>` +
`<string>1aaa4427328cfd7fbd613693802e0920d9f1c1ea2b3d31a869ed1ac3fbfe4174</string></value></member></struct>` +
`</value></param></params>`
event := `suse/systemid/generated {"data": "` + systemid + `", "_stamp": "2025-08-04T12:04:29.403745"}`
// create custom runners
contextRunner = testutils.FakeContextRunnerGenerator(event, nil)
newRunner = testutils.FakeRunnerGenerator("", nil)
received, err := getSystemIDEvent()
testutils.AssertNoError(t, "error during obtaining systemid", err)
testutils.AssertEquals(t, "received event differs", []byte(event), received)
receivedSystemid, err := parseSystemIDEvent(received)
testutils.AssertNoError(t, "error during event decoding", err)
// unquote raw string before comparing.
unquotedSystemid, _ := strconv.Unquote(`"` + systemid + `"`)
testutils.AssertEquals(t, "received systemid differs", unquotedSystemid, receivedSystemid)
}
0707010000004f000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001500000000mgrpxy/shared/podman07070100000050000081a400000000000000000000000168ed21dd0000077d000000000000000000000000000000000000002100000000mgrpxy/shared/templates/httpd.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package templates
import (
"io"
"text/template"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
const httpdTemplate = `# uyuni-proxy-httpd.service, generated by mgrpxy
# Use an uyuni-proxy-httpd.service.d/local.conf file to override
[Unit]
Description=Uyuni proxy httpd container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HTTPProxyFile }}
EnvironmentFile={{ .HTTPProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-httpd.pid %t/uyuni-proxy-httpd.ctr-id
ExecStart=/bin/sh -c '/usr/bin/podman run \
--conmon-pidfile %t/uyuni-proxy-httpd.pid \
--cidfile %t/uyuni-proxy-httpd.ctr-id \
--cgroups=no-conmon \
--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
--replace -dt \
-v /etc/uyuni/proxy:/etc/uyuni:ro \
{{- range .Volumes }}
-v {{ .Name }}:{{ .MountPath }} \
{{- end }}
{{- if .SystemIDSecret }}
--secret {{ .SystemIDSecret }},type=mount,mode=0444,target="/etc/sysconfig/rhn/systemid" \
{{- end }}
${HTTPD_EXTRA_CONF} --name uyuni-proxy-httpd \
${UYUNI_IMAGE}'
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-httpd.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-httpd.ctr-id
PIDFile=%t/uyuni-proxy-httpd.pid
TimeoutStopSec=60
Type=forking
[Install]
WantedBy=multi-user.target default.target
`
// HttpdTemplateData represents HTTPD information to create systemd file.
type HttpdTemplateData struct {
Volumes []types.VolumeMount
HTTPProxyFile string
SystemIDSecret string
}
// Render will create the systemd configuration file.
func (data HttpdTemplateData) Render(wr io.Writer) error {
t := template.Must(template.New("service").Parse(httpdTemplate))
return t.Execute(wr, data)
}
07070100000051000081a400000000000000000000000168ed21dd0000087d000000000000000000000000000000000000001f00000000mgrpxy/shared/templates/pod.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package templates
import (
"io"
"text/template"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
const podTemplate = `# uyuni-proxy-pod.service, generated by mgrpxy
[Unit]
Description=Podman uyuni-proxy-pod.service
Wants=network.target
After=network-online.target
Requires=uyuni-proxy-httpd.service
Requires=uyuni-proxy-salt-broker.service
Requires=uyuni-proxy-squid.service
Requires=uyuni-proxy-ssh.service
Requires=uyuni-proxy-tftpd.service
Before=uyuni-proxy-httpd.service
Before=uyuni-proxy-salt-broker.service
Before=uyuni-proxy-squid.service
Before=uyuni-proxy-ssh.service
Before=uyuni-proxy-tftpd.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HTTPProxyFile }}
EnvironmentFile={{ .HTTPProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-pod.pid %t/uyuni-proxy-pod.pod-id
ExecStartPre=/bin/sh -c '/usr/bin/podman pod create --infra-conmon-pidfile %t/uyuni-proxy-pod.pid \
--pod-id-file %t/uyuni-proxy-pod.pod-id --name uyuni-proxy-pod \
--network {{ .Network }} \
{{- range .Ports }}
-p {{ .Exposed }}:{{ .Port }}{{ if .Protocol }}/{{ .Protocol }}{{ end }} \
{{- if $.IPV6Enabled }}
-p [::]:{{ .Exposed }}:{{ .Port }}{{if .Protocol}}/{{ .Protocol }}{{end}} \
{{- end }}
{{- end }}
--replace ${PODMAN_EXTRA_ARGS}'
ExecStart=/usr/bin/podman pod start --pod-id-file %t/uyuni-proxy-pod.pod-id
ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/uyuni-proxy-pod.pod-id -t 10
ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/uyuni-proxy-pod.pod-id
PIDFile=%t/uyuni-proxy-pod.pid
TimeoutStopSec=60
Type=forking
[Install]
WantedBy=multi-user.target default.target
`
// PodTemplateData POD information to create systemd file.
type PodTemplateData struct {
Ports []types.PortMap
HTTPProxyFile string
Network string
IPV6Enabled bool
}
// Render will create the systemd configuration file.
func (data PodTemplateData) Render(wr io.Writer) error {
t := template.Must(template.New("service").Parse(podTemplate))
return t.Execute(wr, data)
}
07070100000052000081a400000000000000000000000168ed21dd0000068f000000000000000000000000000000000000002700000000mgrpxy/shared/templates/salt-broker.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package templates
import (
"io"
"text/template"
)
const saltBrokerTemplate = `# uyuni-proxy-salt-broker.service, generated by mgrpxy
# Use an uyuni-proxy-salt-broker.service.d/local.conf file to override
[Unit]
Description=Uyuni proxy Salt broker container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HTTPProxyFile }}
EnvironmentFile={{ .HTTPProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-salt-broker.pid %t/uyuni-proxy-salt-broker.ctr-id
ExecStart=/bin/sh -c '/usr/bin/podman run \
--conmon-pidfile %t/uyuni-proxy-salt-broker.pid \
--cidfile %t/uyuni-proxy-salt-broker.ctr-id \
--cgroups=no-conmon \
--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
--replace -dt \
-v /etc/uyuni/proxy:/etc/uyuni:ro \
--name uyuni-proxy-salt-broker \
${UYUNI_IMAGE}'
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-salt-broker.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-salt-broker.ctr-id
PIDFile=%t/uyuni-proxy-salt-broker.pid
TimeoutStopSec=60
Type=forking
[Install]
WantedBy=multi-user.target default.target
`
// SaltBrokerTemplateData represents Salt Broker information to create systemd file.
type SaltBrokerTemplateData struct {
HTTPProxyFile string
}
// Render will create the systemd configuration file.
func (data SaltBrokerTemplateData) Render(wr io.Writer) error {
t := template.Must(template.New("service").Parse(saltBrokerTemplate))
return t.Execute(wr, data)
}
07070100000053000081a400000000000000000000000168ed21dd000006d6000000000000000000000000000000000000002100000000mgrpxy/shared/templates/squid.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package templates
import (
"io"
"text/template"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
const squidTemplate = `# uyuni-proxy-squid.service, generated by mgrpxy
# Use an uyuni-proxy-squid.service.d/local.conf file to override
[Unit]
Description=Uyuni proxy squid container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HTTPProxyFile }}
EnvironmentFile={{ .HTTPProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-squid.pid %t/uyuni-proxy-squid.ctr-id
ExecStart=/bin/sh -c '/usr/bin/podman run \
--conmon-pidfile %t/uyuni-proxy-squid.pid \
--cidfile %t/uyuni-proxy-squid.ctr-id \
--cgroups=no-conmon \
--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
--replace -dt \
-v /etc/uyuni/proxy:/etc/uyuni:ro \
{{- range .Volumes }}
-v {{ .Name }}:{{ .MountPath }} \
{{- end }}
${SQUID_EXTRA_CONF} --name uyuni-proxy-squid \
${UYUNI_IMAGE}'
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-squid.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-squid.ctr-id
PIDFile=%t/uyuni-proxy-squid.pid
TimeoutStopSec=60
Type=forking
[Install]
WantedBy=multi-user.target default.target
`
// SquidTemplateData Squid information to create systemd file.
type SquidTemplateData struct {
Volumes []types.VolumeMount
HTTPProxyFile string
}
// Render will create the systemd configuration file.
func (data SquidTemplateData) Render(wr io.Writer) error {
t := template.Must(template.New("service").Parse(squidTemplate))
return t.Execute(wr, data)
}
07070100000054000081a400000000000000000000000168ed21dd00000601000000000000000000000000000000000000001f00000000mgrpxy/shared/templates/ssh.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package templates
import (
"io"
"text/template"
)
const sshTemplate = `# uyuni-proxy-ssh.service, generated by mgrpxy
# Use an uyuni-proxy-ssh.service.d/local.conf file to override
[Unit]
Description=Uyuni proxy ssh container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HTTPProxyFile }}
EnvironmentFile={{ .HTTPProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-ssh.pid %t/uyuni-proxy-ssh.ctr-id
ExecStart=/bin/sh -c '/usr/bin/podman run \
--conmon-pidfile %t/uyuni-proxy-ssh.pid \
--cidfile %t/uyuni-proxy-ssh.ctr-id \
--cgroups=no-conmon \
--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
--replace -dt \
-v /etc/uyuni/proxy:/etc/uyuni:ro \
--name uyuni-proxy-ssh \
${UYUNI_IMAGE}'
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-ssh.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-ssh.ctr-id
PIDFile=%t/uyuni-proxy-ssh.pid
TimeoutStopSec=60
Type=forking
[Install]
WantedBy=multi-user.target default.target
`
// SSHTemplateData SSH information to create systemd file.
type SSHTemplateData struct {
HTTPProxyFile string
}
// Render will create the systemd configuration file.
func (data SSHTemplateData) Render(wr io.Writer) error {
t := template.Must(template.New("service").Parse(sshTemplate))
return t.Execute(wr, data)
}
07070100000055000081a400000000000000000000000168ed21dd000006e6000000000000000000000000000000000000002100000000mgrpxy/shared/templates/tftpd.go// SPDX-FileCopyrightText: 2024 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package templates
import (
"io"
"text/template"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
const tftpdTemplate = `# uyuni-proxy-tftpd.service, generated by mgrpxy
# Use an uyuni-proxy-tftpd.service.d/local.conf file to override
[Unit]
Description=Uyuni proxy tftpd container service
Wants=network.target
After=network-online.target
BindsTo=uyuni-proxy-pod.service
After=uyuni-proxy-pod.service
[Service]
Environment=PODMAN_SYSTEMD_UNIT=%n
{{- if .HTTPProxyFile }}
EnvironmentFile={{ .HTTPProxyFile }}
{{- end }}
Restart=on-failure
ExecStartPre=/bin/rm -f %t/uyuni-proxy-tftpd.pid %t/uyuni-proxy-tftpd.ctr-id
ExecStart=/bin/sh -c '/usr/bin/podman run \
--conmon-pidfile %t/uyuni-proxy-tftpd.pid \
--cidfile %t/uyuni-proxy-tftpd.ctr-id \
--cgroups=no-conmon \
--pod-id-file %t/uyuni-proxy-pod.pod-id -d \
--replace -dt \
-v /etc/uyuni/proxy:/etc/uyuni:ro \
{{- range .Volumes }}
-v {{ .Name }}:{{ .MountPath }} \
{{- end }}
--name uyuni-proxy-tftpd \
${UYUNI_IMAGE}'
ExecStop=/usr/bin/podman stop --ignore --cidfile %t/uyuni-proxy-tftpd.ctr-id -t 10
ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/uyuni-proxy-tftpd.ctr-id
PIDFile=%t/uyuni-proxy-tftpd.pid
TimeoutStopSec=60
Type=forking
[Install]
WantedBy=multi-user.target default.target
`
// TFTPDTemplateData represents information used to create TFTPD systemd configuration file.
type TFTPDTemplateData struct {
Volumes []types.VolumeMount
HTTPProxyFile string
}
// Render will create the TFTPD systemd configuration file.
func (data TFTPDTemplateData) Render(wr io.Writer) error {
t := template.Must(template.New("service").Parse(tftpdTemplate))
return t.Execute(wr, data)
}
07070100000056000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001800000000mgrpxy/shared/templates07070100000057000081a400000000000000000000000168ed21dd0000021e000000000000000000000000000000000000001b00000000mgrpxy/shared/utils/cmd.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"github.com/rs/zerolog/log"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
// GetConfigPath returns the configuration path if exists.
func GetConfigPath(args []string) string {
if len(args) == 0 {
return ""
}
configPath := args[0]
if !utils.FileExists(configPath) {
log.Fatal().Msgf(L("argument is not an existing file: %s"), configPath)
}
return configPath
}
07070100000058000081a400000000000000000000000168ed21dd00000d64000000000000000000000000000000000000001d00000000mgrpxy/shared/utils/flags.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"fmt"
"path"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
"github.com/uyuni-project/uyuni-tools/shared/types"
"github.com/uyuni-project/uyuni-tools/shared/utils"
)
// ProxyImageFlags are the flags used by install proxy command.
type ProxyImageFlags struct {
Registry string `mapstructure:"registry"`
Tag string `mapstructure:"tag"`
PullPolicy string `mapstructure:"pullPolicy"`
Httpd types.ImageFlags `mapstructure:"httpd"`
SaltBroker types.ImageFlags `mapstructure:"saltBroker"`
Squid types.ImageFlags `mapstructure:"squid"`
SSH types.ImageFlags `mapstructure:"ssh"`
Tftpd types.ImageFlags `mapstructure:"tftpd"`
Tuning Tuning `mapstructure:"tuning"`
}
// Tuning are the custom configuration file provide by users.
type Tuning struct {
Httpd string `mapstructure:"httpd"`
Squid string `mapstructure:"squid"`
}
// GetContainerImage gets the full container image name and tag for a container name.
func (f *ProxyImageFlags) GetContainerImage(name string) string {
var containerImage *types.ImageFlags
switch name {
case "httpd":
containerImage = &f.Httpd
case "salt-broker":
containerImage = &f.SaltBroker
case "squid":
containerImage = &f.Squid
case "ssh":
containerImage = &f.SSH
case "tftpd":
containerImage = &f.Tftpd
default:
log.Fatal().Msgf(L("Invalid proxy container name: %s"), name)
}
imageURL, err := utils.ComputeImage(f.Registry, f.Tag, *containerImage)
if err != nil {
log.Fatal().Err(err).Msg(L("failed to compute image URL"))
}
return imageURL
}
// AddSCCFlag add SCC flags to a command.
func AddSCCFlag(cmd *cobra.Command) {
cmd.Flags().String("scc-user", "",
L("SUSE Customer Center username. It will be used to pull images from registry.suse.com"),
)
cmd.Flags().String("scc-password", "",
L("SUSE Customer Center password. It will be used to pull images from registry.suse.com"),
)
_ = utils.AddFlagHelpGroup(cmd, &utils.Group{ID: "scc", Title: L("SUSE Customer Center Flags")})
_ = utils.AddFlagToHelpGroupID(cmd, "scc-user", "scc")
_ = utils.AddFlagToHelpGroupID(cmd, "scc-password", "scc")
}
// AddImageFlags will add the proxy install flags to a command.
func AddImageFlags(cmd *cobra.Command) {
cmd.Flags().String("tag", utils.DefaultTag, L("image tag"))
cmd.Flags().String("registry", utils.DefaultRegistry, L("Specify a registry where to pull the images from"))
utils.AddPullPolicyFlag(cmd)
addContainerImageFlags(cmd, "httpd", "httpd")
addContainerImageFlags(cmd, "saltbroker", "salt-broker")
addContainerImageFlags(cmd, "squid", "squid")
addContainerImageFlags(cmd, "ssh", "ssh")
addContainerImageFlags(cmd, "tftpd", "tftpd")
cmd.Flags().String("tuning-httpd", "", L("HTTPD tuning configuration file"))
cmd.Flags().String("tuning-squid", "", L("Squid tuning configuration file"))
}
func addContainerImageFlags(cmd *cobra.Command, paramName string, imageName string) {
defaultImage := path.Join(utils.DefaultRegistry, "proxy-"+imageName)
cmd.Flags().String(paramName+"-image", defaultImage,
fmt.Sprintf(L("Image for %s container"), imageName))
cmd.Flags().String(paramName+"-tag", "",
fmt.Sprintf(L("Tag for %s container, overrides the global value if set"), imageName))
}
07070100000059000081a400000000000000000000000168ed21dd00000b0e000000000000000000000000000000000000002200000000mgrpxy/shared/utils/flags_test.go// SPDX-FileCopyrightText: 2025 SUSE LLC
//
// SPDX-License-Identifier: Apache-2.0
package utils
import (
"testing"
"github.com/uyuni-project/uyuni-tools/shared/types"
)
// TestGetContainerImage tests GetContainerImage method
// Covering different scenarios: defaults, empty values, and overriding values.
func TestGetContainerImage(t *testing.T) {
tests := []struct {
name string
proxyFlags ProxyImageFlags
expectedResult string
description string
}{
// Defaults and overiding values
{
name: "no image details",
proxyFlags: ProxyImageFlags{
Registry: "default/image",
Tag: "tag",
Httpd: types.ImageFlags{
Name: "",
Tag: "",
},
},
expectedResult: "default/image:tag",
},
{
name: "httpd image details overrule defaults",
proxyFlags: ProxyImageFlags{
Registry: "default/image",
Tag: "tag",
Httpd: types.ImageFlags{
Name: "default/image/proxy-httpd",
Tag: "mytag",
},
},
expectedResult: "default/image/proxy-httpd:mytag",
},
// registry and image name matching
{
name: "httpd image name overrule when contains full registry",
proxyFlags: ProxyImageFlags{
Registry: "default",
Tag: "tag",
Httpd: types.ImageFlags{
Name: "default/image/proxy-httpd",
Tag: "mytag",
},
},
expectedResult: "default/image/proxy-httpd:mytag",
},
{
name: "httpd image name is appended to registry when it does not include registry",
proxyFlags: ProxyImageFlags{
Registry: "default/extra/image",
Tag: "tag",
Httpd: types.ImageFlags{
Name: "default/image/proxy-httpd",
Tag: "mytag",
},
},
expectedResult: "default/extra/image/default/image/proxy-httpd:mytag",
},
// domain usage
{
name: "custom full httpd registry image name",
proxyFlags: ProxyImageFlags{
Registry: "registry.suse.com/suse/some/paths/",
Tag: "1.0.0",
Httpd: types.ImageFlags{
Name: "registry.opensuse.org/uyuni/proxy-httpd",
Tag: "2.0.0",
},
},
expectedResult: "registry.suse.com/suse/some/paths/uyuni/proxy-httpd:2.0.0",
// expectedResult: "registry.opensuse.org/uyuni/proxy-httpd:2.0.0", // this should be the expected result
},
{
name: "httpd with path-only image name",
proxyFlags: ProxyImageFlags{
Registry: "registry.suse.com/uyuni",
Tag: "1.0.0",
Httpd: types.ImageFlags{
Name: "path/to/proxy-httpd",
Tag: "",
},
},
expectedResult: "registry.suse.com/uyuni/path/to/proxy-httpd:1.0.0",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual := tt.proxyFlags.GetContainerImage("httpd")
if actual != tt.expectedResult {
t.Errorf("GetContainerImage('httpd') = %s, expected: %s", actual, tt.expectedResult)
}
})
}
}
0707010000005a000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000001400000000mgrpxy/shared/utils0707010000005b000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000000e00000000mgrpxy/shared0707010000005c000041ed00000000000000000000000168ed21dd00000000000000000000000000000000000000000000000700000000mgrpxy07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000b00000000TRAILER!!!