From 202c4247d1bf24827ced401d6847994b5ad9a41b Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:27:09 +0200 Subject: [PATCH 01/13] Turn --config into a top-level flag --- cmd/deploy.go | 21 --------------------- cmd/main.go | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index 8dad765..3101375 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -95,27 +95,6 @@ this flag can be used to tell roxie how to pre-load images for the current clust }), ) - registerFlag(cmd, settings, "config", "Path to YAML config file", - withShortName("c"), - withApplyFn("filename", func(config *deployer.Config, filename string) error { - if filename == "-" { - filename = "/dev/stdin" - } - data, err := os.ReadFile(filename) - if err != nil { - return fmt.Errorf("failed to read config file %q: %w", filename, err) - } - var configFromFile deployer.Config - if err := yaml.Unmarshal(data, &configFromFile); err != nil { - return fmt.Errorf("failed to unmarshal config file %q: %w", filename, err) - } - if err := mergo.Merge(config, configFromFile, mergo.WithOverride, mergo.WithoutDereference); err != nil { - return fmt.Errorf("merging config file %q into deployer Config: %w", filename, err) - } - return nil - }), - ) - registerFlag(cmd, settings, "exposure", "Central exposure backend (loadbalancer, none)", withApplyFn("exposure", func(config *deployer.Config, val string) error { var exposure types.Exposure diff --git a/cmd/main.go b/cmd/main.go index ffe30dd..8f1c67f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -76,6 +76,27 @@ func init() { rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Do not actually modify cluster") rootCmd.PersistentFlags().BoolVar(&skipUserConfig, "skip-user-config", false, fmt.Sprintf("Skips reading of user's configuration (%s)", paths.UserConfigPathString())) + registerFlag(rootCmd, &deploySettingsFromArgs, "config", "Path to YAML config file", + withShortName("c"), + withApplyFn("filename", func(config *deployer.Config, filename string) error { + if filename == "-" { + filename = "/dev/stdin" + } + data, err := os.ReadFile(filename) + if err != nil { + return fmt.Errorf("failed to read config file %q: %w", filename, err) + } + var configFromFile deployer.Config + if err := yaml.Unmarshal(data, &configFromFile); err != nil { + return fmt.Errorf("failed to unmarshal config file %q: %w", filename, err) + } + if err := mergo.Merge(config, configFromFile, mergo.WithOverride, mergo.WithoutDereference); err != nil { + return fmt.Errorf("merging config file %q into deployer Config: %w", filename, err) + } + return nil + }), + ) + rootCmd.AddCommand(newDeployCmd(&deploySettingsFromArgs)) rootCmd.AddCommand(newTeardownCmd(&deploySettingsFromArgs)) rootCmd.AddCommand(newShellCmd()) From 7bf34850e4ecaf2ddd38a15b06d3cdc0bb58afc0 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:28:30 +0200 Subject: [PATCH 02/13] Add assembleConfigForCommand helper --- cmd/config.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 cmd/config.go diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000..2107c34 --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + + "dario.cat/mergo" + "github.com/stackrox/roxie/internal/deployer" +) + +func assembleConfigForCommand(configFromArgs deployer.Config, skipUserConfig bool) (deployer.Config, error) { + // Start with default configuration. + config := deployer.DefaultConfig() + + // Apply user config on top (overriding defaults). + if !skipUserConfig { + if err := tryApplyUserDefaults(globalLogger, &config); err != nil { + return deployer.Config{}, fmt.Errorf("applying user config: %w", err) + } + } + + // Apply changes from arg parsing. + if err := mergo.Merge(&config, &configFromArgs, mergo.WithOverride, mergo.WithoutDereference); err != nil { + return deployer.Config{}, fmt.Errorf("applying config patches from command line argument: %w", err) + } + + return config, nil +} From 3b8d8c5b7a2bf545628cd4a05b28d8573de47806 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:31:46 +0200 Subject: [PATCH 03/13] Use assembleConfigForCommand helper. --- cmd/deploy.go | 16 +++------------- cmd/teardown.go | 17 +++-------------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index 3101375..660212f 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -243,19 +243,9 @@ func runDeploy(cmd *cobra.Command, args []string) error { return err } - // Start with default configuration. - deploySettings := deployer.DefaultConfig() - - // Apply user config on top (overriding defaults). - if !skipUserConfig { - if err := tryApplyUserDefaults(globalLogger, &deploySettings); err != nil { - return fmt.Errorf("applying user config: %w", err) - } - } - - // Apply changes from arg parsing. - if err := mergo.Merge(&deploySettings, &deploySettingsFromArgs, mergo.WithOverride, mergo.WithoutDereference); err != nil { - return fmt.Errorf("applying config patches from command line argument: %w", err) + deploySettings, err := assembleConfigForCommand(deploySettingsFromArgs, skipUserConfig) + if err != nil { + return err } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) diff --git a/cmd/teardown.go b/cmd/teardown.go index 9064475..68cbcaf 100644 --- a/cmd/teardown.go +++ b/cmd/teardown.go @@ -5,7 +5,6 @@ import ( "fmt" "time" - "dario.cat/mergo" "github.com/spf13/cobra" "github.com/stackrox/roxie/internal/component" "github.com/stackrox/roxie/internal/deployer" @@ -56,19 +55,9 @@ func runTeardown(cmd *cobra.Command, args []string) error { return nil } - // Start with default configuration. - deploySettings := deployer.DefaultConfig() - - // Apply user config on top (overriding defaults). - if !skipUserConfig { - if err := tryApplyUserDefaults(globalLogger, &deploySettings); err != nil { - return fmt.Errorf("applying user config: %w", err) - } - } - - // Apply changes from arg parsing. - if err := mergo.Merge(&deploySettings, &deploySettingsFromArgs, mergo.WithOverride, mergo.WithoutDereference); err != nil { - return fmt.Errorf("applying config patches from command line argument: %w", err) + deploySettings, err := assembleConfigForCommand(deploySettingsFromArgs, skipUserConfig) + if err != nil { + return err } d, err := deployer.New(log) From 680513b29812bff4d13aae5ea7a7a8ffe43a7be2 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:34:21 +0200 Subject: [PATCH 04/13] Disable stdin input for spawned command --- cmd/subshell.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/subshell.go b/cmd/subshell.go index 19462dc..aaaa23b 100644 --- a/cmd/subshell.go +++ b/cmd/subshell.go @@ -138,8 +138,7 @@ backend https_back configFile.Close() cmd := exec.Command("haproxy", "-f", configPath) - // TODO(#91): What about stdin? We probably should prevent haproxy from reading from - // the terminal just in case... + cmd.Stdin = nil cmd.Stdout = nil cmd.Stderr = nil From d084f77ad5649554714634883487bab6ef4670de Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:38:32 +0200 Subject: [PATCH 05/13] Replace boolean indicator for HAProxy with port integer --- internal/types/central_deployment_info.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/types/central_deployment_info.go b/internal/types/central_deployment_info.go index c04e0f5..fac4181 100644 --- a/internal/types/central_deployment_info.go +++ b/internal/types/central_deployment_info.go @@ -2,11 +2,11 @@ package types // CentralDeploymentInfo holds the state of a Central deployment. type CentralDeploymentInfo struct { - Endpoint string - Username string - Password string - KubeContext string - Exposure Exposure - CACertFile string - HAProxyStarted bool + Endpoint string + Username string + Password string + KubeContext string + Exposure Exposure + CACertFile string + HAProxyPort int // If positive, HAProxy was started and listens on that port. } From 9e45700aeed31a784419719aa4a610b545b03c18 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 16:14:11 +0200 Subject: [PATCH 06/13] HAProxy configuration rendering --- internal/haproxy/config.go | 32 ++++++++++++++++++++++++++++++++ internal/haproxy/config_test.go | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 internal/haproxy/config.go create mode 100644 internal/haproxy/config_test.go diff --git a/internal/haproxy/config.go b/internal/haproxy/config.go new file mode 100644 index 0000000..d20eb00 --- /dev/null +++ b/internal/haproxy/config.go @@ -0,0 +1,32 @@ +package haproxy + +import ( + "fmt" + "strings" + + "github.com/stackrox/roxie/internal/deployer" +) + +func RenderConfig(haproxyConfig deployer.HAProxyConfig, endpoint, caCertFile string) string { + return strings.NewReplacer( + "${ENDPOINT}", endpoint, + "${CA_CERT_FILE}", caCertFile, + "${BIND_PORT}", fmt.Sprintf("%d", haproxyConfig.BindPort), + ).Replace(`global + log /dev/null local0 + +defaults + log global + mode http + timeout connect 5s + timeout client 30s + timeout server 30s + +frontend http_front + bind *:${BIND_PORT} + default_backend https_back + +backend https_back + server srv1 ${ENDPOINT} ssl verify required ca-file ${CA_CERT_FILE} +`) +} diff --git a/internal/haproxy/config_test.go b/internal/haproxy/config_test.go new file mode 100644 index 0000000..0e46127 --- /dev/null +++ b/internal/haproxy/config_test.go @@ -0,0 +1,32 @@ +package haproxy + +import ( + "strings" + "testing" + + "github.com/stackrox/roxie/internal/deployer" + "github.com/stretchr/testify/assert" +) + +func TestRenderConfig(t *testing.T) { + cfg := deployer.HAProxyConfig{BindPort: 8080} + result := RenderConfig(cfg, "central.acs:443", "/tmp/ca.pem") + + assert.Contains(t, result, "bind *:8080") + assert.Contains(t, result, "server srv1 central.acs:443 ssl verify required ca-file /tmp/ca.pem") +} + +func TestRenderConfig_CustomPort(t *testing.T) { + cfg := deployer.HAProxyConfig{BindPort: 9090} + result := RenderConfig(cfg, "central.acs:443", "/tmp/ca.pem") + + assert.Contains(t, result, "bind *:9090") + assert.NotContains(t, result, "8080") +} + +func TestRenderConfig_NoUnresolvedPlaceholders(t *testing.T) { + cfg := deployer.HAProxyConfig{BindPort: 8080} + result := RenderConfig(cfg, "central.acs:443", "/tmp/ca.pem") + + assert.False(t, strings.Contains(result, "${"), "config should not contain unresolved placeholders") +} From d040ce0f093db3982d1a38d11e36446bd195bf1a Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:36:58 +0200 Subject: [PATCH 07/13] Parameterize startHAProxy with RoxieConfig --- cmd/subshell.go | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/cmd/subshell.go b/cmd/subshell.go index aaaa23b..6a28c39 100644 --- a/cmd/subshell.go +++ b/cmd/subshell.go @@ -10,11 +10,13 @@ import ( "github.com/fatih/color" "github.com/stackrox/roxie/internal/deployer" "github.com/stackrox/roxie/internal/env" + "github.com/stackrox/roxie/internal/haproxy" "github.com/stackrox/roxie/internal/logger" "github.com/stackrox/roxie/internal/roxieenv" "github.com/stackrox/roxie/internal/types" ) + // spawnSubshellForDeployerEnv assembles the roxie environment from a Deployer and invokes an interactive subshell. func spawnSubshellForDeployerEnv(d *deployer.Deployer, log *logger.Logger) error { return runCommandOrSubshell(d.GetCentralDeploymentInfo(), log, nil) @@ -105,32 +107,16 @@ func resolveShellPath() string { return "/bin/bash" } -func startHAProxy(endpoint, caCertFile string, log *logger.Logger) (*exec.Cmd, string, error) { +func startHAProxy(roxieConfig deployer.RoxieConfig, centralDeploymentInfo *types.CentralDeploymentInfo) (*exec.Cmd, string, error) { configFile, err := os.CreateTemp("", "roxie-haproxy-*.cfg") if err != nil { return nil, "", fmt.Errorf("failed to create temp config: %w", err) } configPath := configFile.Name() - haproxyConfig := fmt.Sprintf(`global - log /dev/null local0 - -defaults - log global - mode http - timeout connect 5s - timeout client 30s - timeout server 30s - -frontend http_front - bind *:8080 # TODO(#91): this should probably be configurable? - default_backend https_back + haproxyCfg := haproxy.RenderConfig(roxieConfig.HAProxy, centralDeploymentInfo.Endpoint, centralDeploymentInfo.CACertFile) -backend https_back - server srv1 %s ssl verify required ca-file %s -`, endpoint, caCertFile) - - if _, err := configFile.WriteString(haproxyConfig); err != nil { + if _, err := configFile.WriteString(haproxyCfg); err != nil { configFile.Close() os.Remove(configPath) return nil, "", fmt.Errorf("failed to write haproxy config: %w", err) @@ -147,6 +133,8 @@ backend https_back return nil, "", fmt.Errorf("failed to start haproxy: %w", err) } + centralDeploymentInfo.HAProxyPort = roxieConfig.HAProxy.BindPort + return cmd, configPath, nil } From f09b5ace07b754c98cec4fd9190233664d0293c2 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:37:57 +0200 Subject: [PATCH 08/13] New helper tryStartHAProxy --- cmd/subshell.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/cmd/subshell.go b/cmd/subshell.go index 6a28c39..181f00a 100644 --- a/cmd/subshell.go +++ b/cmd/subshell.go @@ -90,6 +90,35 @@ func runCommandOrSubshell(centralDeploymentInfo types.CentralDeploymentInfo, log return nil } +func tryStartHAProxy( + log *logger.Logger, + roxieConfig deployer.RoxieConfig, + centralDeploymentInfo *types.CentralDeploymentInfo) (func(), error) { + + if !isHAProxyAvailable() { + log.Dim("No HAProxy available, skipping") + return nil, nil + } + + if centralDeploymentInfo.Endpoint == "" { + log.Warning("No Central endpoint available, skipping") + return nil, nil + } + if centralDeploymentInfo.CACertFile == "" { + log.Warning("No Central CA Cert available, skipping") + return nil, nil + } + + haproxyCmd, haproxyConfigPath, err := startHAProxy(roxieConfig, centralDeploymentInfo) + if err != nil { + return nil, err + } + + return func() { + cleanupHAProxy(haproxyCmd, haproxyConfigPath) + }, nil +} + func subShellMode(args []string) bool { return len(args) == 0 } From 58f4eeb93d9b2293c802809cbaf480de4f78b33b Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:40:04 +0200 Subject: [PATCH 09/13] Let spawnSubshellForDeployerEnv and runCommandOrSubshell propagate RoxieConfig --- cmd/deploy.go | 2 +- cmd/shell.go | 6 +++--- cmd/subshell.go | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/deploy.go b/cmd/deploy.go index 660212f..f839dc9 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -365,7 +365,7 @@ func runDeploy(cmd *cobra.Command, args []string) error { } if components.IncludesCentral() && envrc == "" { - if err := spawnSubshellForDeployerEnv(d, log); err != nil { + if err := spawnSubshellForDeployerEnv(deploySettings.Roxie, d, log); err != nil { return fmt.Errorf("failed to spawn subshell: %w", err) } } diff --git a/cmd/shell.go b/cmd/shell.go index 695b45d..69de909 100644 --- a/cmd/shell.go +++ b/cmd/shell.go @@ -33,7 +33,7 @@ Examples: roxie shell -- roxctl central whoami roxie shell -- bash -c 'echo $ROX_ENDPOINT'`, Run: func(cmd *cobra.Command, args []string) { - err := runShell(cmd, args) + err := runShell(args) if err != nil { if exitErr, ok := errors.AsType[*exec.ExitError](err); ok { // Propagate exit error from the child process. @@ -51,7 +51,7 @@ Examples: return cmd } -func runShell(cmd *cobra.Command, args []string) error { +func runShell(args []string) error { log := logger.New() if err := env.Initialize(log); err != nil { return err @@ -84,5 +84,5 @@ func runShell(cmd *cobra.Command, args []string) error { return fmt.Errorf("extracting central deployment info from manifest: %w", err) } - return runCommandOrSubshell(centralDeploymentInfo, log, args) + return runCommandOrSubshell(m.Config.Roxie, centralDeploymentInfo, log, args) } diff --git a/cmd/subshell.go b/cmd/subshell.go index 181f00a..73e6626 100644 --- a/cmd/subshell.go +++ b/cmd/subshell.go @@ -18,14 +18,14 @@ import ( // spawnSubshellForDeployerEnv assembles the roxie environment from a Deployer and invokes an interactive subshell. -func spawnSubshellForDeployerEnv(d *deployer.Deployer, log *logger.Logger) error { - return runCommandOrSubshell(d.GetCentralDeploymentInfo(), log, nil) +func spawnSubshellForDeployerEnv(roxieConfig deployer.RoxieConfig, d *deployer.Deployer, log *logger.Logger) error { + return runCommandOrSubshell(roxieConfig, d.GetCentralDeploymentInfo(), log, nil) } // runCommandOrSubshell spawns an interactive subshell or runs the provided command using the given // central deployment info. // It handles HAProxy setup, prints the connection banner, and manages shell lifecycle. -func runCommandOrSubshell(centralDeploymentInfo types.CentralDeploymentInfo, log *logger.Logger, args []string) error { +func runCommandOrSubshell(roxieConfig deployer.RoxieConfig, centralDeploymentInfo types.CentralDeploymentInfo, log *logger.Logger, args []string) error { cmdEnv := os.Environ() for name, val := range roxieenv.AssembleRoxieEnvironment(centralDeploymentInfo).Export() { cmdEnv = append(cmdEnv, fmt.Sprintf("%s=%s", name, val)) From 4cc883eb0471b5f0a58ed8ca5ef71bc7509e659f Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:41:38 +0200 Subject: [PATCH 10/13] Use tryStartHAProxy helper --- cmd/subshell.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/cmd/subshell.go b/cmd/subshell.go index 73e6626..e873232 100644 --- a/cmd/subshell.go +++ b/cmd/subshell.go @@ -33,17 +33,12 @@ func runCommandOrSubshell(roxieConfig deployer.RoxieConfig, centralDeploymentInf cmdEnv = append(cmdEnv, "ROXIE_SHELL=1") cmdEnv = append(cmdEnv, fmt.Sprintf("name=acs@%s", centralDeploymentInfo.KubeContext)) - haproxyAvailable := isHAProxyAvailable() - - if haproxyAvailable && centralDeploymentInfo.Endpoint != "" && centralDeploymentInfo.CACertFile != "" { - haproxyCmd, haproxyConfigPath, err := startHAProxy(centralDeploymentInfo.Endpoint, centralDeploymentInfo.CACertFile, log) - if err != nil { - log.Warningf("Failed to start HAProxy: %v", err) - } else { - cmdEnv = append(cmdEnv, "ROXIE_HAPROXY_CFG_FILE="+haproxyConfigPath) - centralDeploymentInfo.HAProxyStarted = true - defer cleanupHAProxy(haproxyCmd, haproxyConfigPath) - } + cleanupFunc, err := tryStartHAProxy(log, roxieConfig, ¢ralDeploymentInfo) + if err != nil { + log.Warningf("Failed to start HAProxy: %v", err) + } + if cleanupFunc != nil { + defer cleanupFunc() } var cmd *exec.Cmd @@ -62,7 +57,7 @@ func runCommandOrSubshell(roxieConfig deployer.RoxieConfig, centralDeploymentInf cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Run() + err = cmd.Run() if subShellMode(args) { cyan := color.New(color.FgCyan, color.Bold) From 9c3bc2f994749f894eabf706a86c00278655818d Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:42:31 +0200 Subject: [PATCH 11/13] Add HAProxyConfig to RoxieConfig --- internal/deployer/config.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/deployer/config.go b/internal/deployer/config.go index ea0bd82..fa8fd5a 100644 --- a/internal/deployer/config.go +++ b/internal/deployer/config.go @@ -21,7 +21,7 @@ type Config struct { // DefaultConfig returns a Config populated with default values. func DefaultConfig() Config { return Config{ - Roxie: NewRoxieConfig(), + Roxie: DefaultRoxieConfig(), Central: DefaultCentralConfig(), SecuredCluster: DefaultSecuredClusterConfig(), } @@ -57,6 +57,7 @@ type RoxieConfig struct { KonfluxImages *bool `yaml:"konfluxImages,omitempty"` FeatureFlags map[string]bool `yaml:"featureFlags,omitempty"` ClusterType types.ClusterType `yaml:"clusterType,omitempty"` + HAProxy HAProxyConfig `yaml:"haProxy,omitempty"` } func (c *RoxieConfig) KonfluxImagesSet() bool { @@ -135,6 +136,12 @@ func NewCentralConfig() CentralConfig { } } +func DefaultRoxieConfig() RoxieConfig { + cfg := NewRoxieConfig() + cfg.HAProxy.BindPort = 8080 + return cfg +} + // DefaultCentralConfig returns a CentralConfig with sensible defaults. func DefaultCentralConfig() CentralConfig { cfg := NewCentralConfig() @@ -356,3 +363,7 @@ func (s *SecuredClusterConfig) CustomResource() (map[string]interface{}, error) } return cr, nil } + +type HAProxyConfig struct { + BindPort int `yaml:"bindPort"` +} From 9af61d07115fd42b56e4b37a236f5a63967dfe89 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 10:43:20 +0200 Subject: [PATCH 12/13] Adjust logging --- cmd/subshell.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/subshell.go b/cmd/subshell.go index e873232..973719b 100644 --- a/cmd/subshell.go +++ b/cmd/subshell.go @@ -198,12 +198,12 @@ func printBanner(centralDeploymentInfo types.CentralDeploymentInfo) { cyan.Println("[roxie] * roxcurl /v1/clusters") cyan.Println("[roxie]") - if centralDeploymentInfo.HAProxyStarted { - cyan.Println("[roxie] Central UI: http://localhost:8080 (username: admin, password: see $ROX_ADMIN_PASSWORD)") + if port := centralDeploymentInfo.HAProxyPort; port > 0 { + cyan.Printf("[roxie] Central UI: http://localhost:%d (username: admin, password: see $ROX_ADMIN_PASSWORD)\n", port) } else if centralDeploymentInfo.Exposure != types.ExposureNone { cyan.Printf("[roxie] Central UI: https://%s", centralDeploymentInfo.Endpoint) } else if !env.RunningInRoxieContainer { - cyan.Println("[roxie] Note: Installing haproxy enables automatic HTTP access to Central at http://localhost:8080") + cyan.Println("[roxie] Note: Installing haproxy enables automatic HTTP access to Central through a forwarded local port") } cyan.Println("") From 301552de0a820519282b05b06f068fae4dc330b4 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Mon, 13 Jul 2026 16:01:45 +0200 Subject: [PATCH 13/13] Fix unit tests cmd/flags.go, cmd/main.go: Enable flag inheritance for --config, so that it is available on subcommands. cmd/deploy_test.go: Extract subcommand from root command so that they related a parent/child and therefore parsing of `--config` works as expected. --- cmd/deploy_test.go | 9 +++++---- cmd/flags.go | 13 ++++++++++++- cmd/main.go | 1 + 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cmd/deploy_test.go b/cmd/deploy_test.go index 71c6a6d..71c63a7 100644 --- a/cmd/deploy_test.go +++ b/cmd/deploy_test.go @@ -278,10 +278,11 @@ central: if tt.config != "" { require.NoError(t, os.WriteFile(configFilePath, []byte(tt.config), 0o644)) } - cfg := deployer.NewConfig() - cmd := newDeployCmd(&cfg) - require.NoError(t, cmd.ParseFlags(tt.args)) - tt.assert(t, cfg) + deploySettingsFromArgs = deployer.NewConfig() // Need to reset this global after each test. + deployCmd, _, err := rootCmd.Find([]string{"deploy"}) + require.NoError(t, err) + require.NoError(t, deployCmd.ParseFlags(tt.args)) + tt.assert(t, deploySettingsFromArgs) }) } } diff --git a/cmd/flags.go b/cmd/flags.go index ede5d81..fe4fce8 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -16,6 +16,7 @@ type cliFlag struct { applyFn func(config *deployer.Config, val string) error noOptDefVal string description string + persistent bool } type flagOpt func(opts *cliFlag) @@ -80,6 +81,12 @@ func withShortName(shortName string) flagOpt { } } +func withPersistent() flagOpt { + return func(opts *cliFlag) { + opts.persistent = true + } +} + func registerFlag(cmd *cobra.Command, settings *deployer.Config, longName string, description string, flagOpts ...flagOpt) { f := cliFlag{ config: settings, @@ -89,7 +96,11 @@ func registerFlag(cmd *cobra.Command, settings *deployer.Config, longName string for _, applyOpt := range flagOpts { applyOpt(&f) } - flag := cmd.Flags().VarPF(&f, f.longName, f.shortName, f.description) + flagSet := cmd.Flags() + if f.persistent { + flagSet = cmd.PersistentFlags() + } + flag := flagSet.VarPF(&f, f.longName, f.shortName, f.description) if f.noOptDefVal != "" { flag.NoOptDefVal = f.noOptDefVal } diff --git a/cmd/main.go b/cmd/main.go index 8f1c67f..bf83dcb 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -77,6 +77,7 @@ func init() { rootCmd.PersistentFlags().BoolVar(&skipUserConfig, "skip-user-config", false, fmt.Sprintf("Skips reading of user's configuration (%s)", paths.UserConfigPathString())) registerFlag(rootCmd, &deploySettingsFromArgs, "config", "Path to YAML config file", + withPersistent(), withShortName("c"), withApplyFn("filename", func(config *deployer.Config, filename string) error { if filename == "-" {