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!!!
openSUSE Build Service is sponsored by