Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 4 additions & 35 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -264,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)
Expand Down Expand Up @@ -396,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)
}
}
Expand Down
9 changes: 5 additions & 4 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
}
Expand Down
13 changes: 12 additions & 1 deletion cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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
}
Expand Down
22 changes: 22 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ 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",
withPersistent(),
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())
Expand Down
6 changes: 3 additions & 3 deletions cmd/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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)
}
89 changes: 50 additions & 39 deletions cmd/subshell.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,35 @@ 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)
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))
}
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, &centralDeploymentInfo)
if err != nil {
log.Warningf("Failed to start HAProxy: %v", err)
}
if cleanupFunc != nil {
defer cleanupFunc()
}

var cmd *exec.Cmd
Expand All @@ -60,7 +57,7 @@ func runCommandOrSubshell(centralDeploymentInfo types.CentralDeploymentInfo, log
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

err := cmd.Run()
err = cmd.Run()

if subShellMode(args) {
cyan := color.New(color.FgCyan, color.Bold)
Expand Down Expand Up @@ -88,6 +85,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
}
Expand All @@ -105,41 +131,24 @@ 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)
}
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

Expand All @@ -148,6 +157,8 @@ backend https_back
return nil, "", fmt.Errorf("failed to start haproxy: %w", err)
}

centralDeploymentInfo.HAProxyPort = roxieConfig.HAProxy.BindPort

return cmd, configPath, nil
}

Expand Down Expand Up @@ -187,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("")
Expand Down
17 changes: 3 additions & 14 deletions cmd/teardown.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading