Skip to content
46 changes: 29 additions & 17 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ import (
"github.com/stackrox/roxie/internal/stackroxversions"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/utils/ptr"
)

var (
const (
sharedNamespace = "stackrox"
)

Expand All @@ -55,40 +54,40 @@ Examples:
registerFlag(cmd, settings, "olm", "Deploy operator via OLM (requires OLM installed)",
withNoOptDefVal("true"),
withApplyFnBool(func(config *deployer.Config, val bool) error {
config.Operator.DeployViaOlm = val
config.Operator.DeployViaOlm = new(val)
return nil
}),
)

registerFlag(cmd, settings, "konflux", "Use Konflux images",
withNoOptDefVal("true"),
withApplyFnBool(func(config *deployer.Config, val bool) error {
config.Roxie.KonfluxImages = val
config.Roxie.KonfluxImages = new(val)
return nil
}),
)

registerFlag(cmd, settings, "deploy-operator", "Whether to deploy and manage the operator",
withNoOptDefVal("true"),
withApplyFnBool(func(config *deployer.Config, val bool) error {
config.Operator.SkipDeployment = !val
config.Operator.SkipDeployment = new(!val)
return nil
}),
)

registerFlag(cmd, settings, "port-forwarding", "Enable localhost port-forward for Central",
withNoOptDefVal("true"),
withApplyFnBool(func(config *deployer.Config, val bool) error {
config.Central.PortForwarding = ptr.To(val)
config.Central.PortForwarding = new(val)
return nil
}),
)

registerFlag(cmd, settings, "pause-reconciliation", "Pause reconciliation after deployment",
withNoOptDefVal("true"),
withApplyFnBool(func(config *deployer.Config, val bool) error {
config.Central.PauseReconciliation = val
config.SecuredCluster.PauseReconciliation = val
config.Central.PauseReconciliation = new(val)
config.SecuredCluster.PauseReconciliation = new(val)
return nil
}),
)
Expand Down Expand Up @@ -120,7 +119,7 @@ Examples:
if err := yaml.Unmarshal([]byte(val), &exposure); err != nil {
return err
}
config.Central.Exposure = ptr.To(exposure)
config.Central.Exposure = new(exposure)
return nil
}),
)
Expand Down Expand Up @@ -228,8 +227,8 @@ Examples:
registerFlag(cmd, settings, "early-readiness", "Only wait for essential workloads (central/sensor) to be ready",
withNoOptDefVal("true"),
withApplyFnBool(func(config *deployer.Config, val bool) error {
config.Central.EarlyReadiness = val
config.SecuredCluster.EarlyReadiness = val
config.Central.EarlyReadiness = new(val)
config.SecuredCluster.EarlyReadiness = new(val)
return nil
}),
)
Expand All @@ -246,7 +245,7 @@ Examples:
}

func runDeploy(cmd *cobra.Command, args []string) error {
log := logger.New()
log := globalLogger
if !dryRun {
if err := env.Initialize(log); err != nil {
return err
Expand All @@ -264,6 +263,19 @@ 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 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)
}

if deploySettings.Roxie.Version != "" {
log.Dimf("Using main image tag %s", deploySettings.Roxie.Version)
} else {
Expand All @@ -282,7 +294,7 @@ func runDeploy(cmd *cobra.Command, args []string) error {
return err
}

if !deploySettings.Central.EarlyReadiness || !deploySettings.SecuredCluster.EarlyReadiness {
if !deploySettings.Central.EarlyReadinessEnabled() || !deploySettings.SecuredCluster.EarlyReadinessEnabled() {
// Explanation on the versions involved here:
// Deploying StackRox begins with picking a "main image tag" -- this is a version identifier, which cannot be reliably parsed as a semver.
// But there is a derived version from that -- the operator version -- which can be parsed as a semver.
Expand Down Expand Up @@ -411,7 +423,7 @@ func configureConfig(log *logger.Logger, components component.Component, deployS

if !deploySettings.Central.PortForwardingSet() && !deploySettings.Central.ExposureEnabled() {
log.Info("Enabling port-forwarding due to no exposure")
deploySettings.Central.PortForwarding = ptr.To(true)
deploySettings.Central.PortForwarding = new(true)
}

return nil
Expand Down Expand Up @@ -448,12 +460,12 @@ func deployValidate(components component.Component, deploySettings *deployer.Con
}
}

if deploySettings.Operator.SkipDeployment && deploySettings.Operator.DeployViaOlm {
if deploySettings.Operator.SkipDeploymentEnabled() && deploySettings.Operator.DeployViaOlmEnabled() {
return errors.New("skipping operator deployment while also requesting deploying via OLM at the same time does not make sense")
}

if deploySettings.Roxie.KonfluxImages {
if deploySettings.Operator.DeployViaOlm {
if deploySettings.Roxie.KonfluxImagesEnabled() {
if deploySettings.Operator.DeployViaOlmEnabled() {
return errors.New("using Konflux images while deploying operator via OLM is not supported")
}
if !clusterType.IsOpenShift() {
Expand Down
136 changes: 127 additions & 9 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import (
"testing"
"time"

"dario.cat/mergo"
"github.com/stackrox/roxie/internal/deployer"
"github.com/stackrox/roxie/internal/logger"
"github.com/stackrox/roxie/internal/paths"
"github.com/stackrox/roxie/internal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

func TestNewDeployCmd_Flags(t *testing.T) {
Expand Down Expand Up @@ -94,38 +98,38 @@ func TestNewDeployCmd_Flags(t *testing.T) {
name: "early-readiness",
args: []string{"--early-readiness"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.True(t, cfg.Central.EarlyReadiness, "Central.EarlyReadiness mismatch")
assert.True(t, cfg.SecuredCluster.EarlyReadiness, "SecuredCluster.EarlyReadiness mismatch")
assert.True(t, cfg.Central.EarlyReadinessEnabled(), "Central.EarlyReadiness mismatch")
assert.True(t, cfg.SecuredCluster.EarlyReadinessEnabled(), "SecuredCluster.EarlyReadiness mismatch")
},
},
{
name: "disable early-readiness",
args: []string{"--early-readiness=false"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.False(t, cfg.Central.EarlyReadiness, "Central.EarlyReadiness mismatch")
assert.False(t, cfg.SecuredCluster.EarlyReadiness, "SecuredCluster.EarlyReadiness mismatch")
assert.False(t, cfg.Central.EarlyReadinessEnabled(), "Central.EarlyReadiness mismatch")
assert.False(t, cfg.SecuredCluster.EarlyReadinessEnabled(), "SecuredCluster.EarlyReadiness mismatch")
},
},
{
name: "pause-reconciliation",
args: []string{"--pause-reconciliation"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.True(t, cfg.Central.PauseReconciliation, "Central.PauseReconciliation mismatch")
assert.True(t, cfg.SecuredCluster.PauseReconciliation, "SecuredCluster.PauseReconciliation mismatch")
assert.True(t, cfg.Central.PauseReconciliationEnabled(), "Central.PauseReconciliation mismatch")
assert.True(t, cfg.SecuredCluster.PauseReconciliationEnabled(), "SecuredCluster.PauseReconciliation mismatch")
},
},
{
name: "olm",
args: []string{"--olm"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.True(t, cfg.Operator.DeployViaOlm, "Operator.DeployViaOlm mismatch")
assert.True(t, cfg.Operator.DeployViaOlmEnabled(), "Operator.DeployViaOlm mismatch")
},
},
{
name: "disable deploy-operator",
args: []string{"--deploy-operator=false"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.True(t, cfg.Operator.SkipDeployment, "Operator.SkipDeployment mismatch")
assert.True(t, cfg.Operator.SkipDeploymentEnabled(), "Operator.SkipDeployment mismatch")
},
},
{
Expand All @@ -135,7 +139,7 @@ func TestNewDeployCmd_Flags(t *testing.T) {
assert.Equal(t, "4.7.0", cfg.Roxie.Version, "Roxie.Version mismatch")
require.NotNil(t, cfg.Central.Exposure, "Central.Exposure should be set")
assert.Equal(t, types.ExposureLoadBalancer, *cfg.Central.Exposure, "Central.Exposure mismatch")
assert.True(t, cfg.Central.EarlyReadiness, "Central.EarlyReadiness mismatch")
assert.True(t, cfg.Central.EarlyReadinessEnabled(), "Central.EarlyReadiness mismatch")
assert.Equal(t, types.ResourceProfileSmall, cfg.Central.ResourceProfile, "Central.ResourceProfile mismatch")
},
},
Expand Down Expand Up @@ -205,3 +209,117 @@ central:
})
}
}

func TestApplyUserDefaults(t *testing.T) {
log := logger.New()

tests := []struct {
name string
config deployer.Config
user deployer.Config
expected deployer.Config
}{
{
name: "empty user config leaves config unchanged",
config: deployer.Config{
Roxie: deployer.RoxieConfig{Version: "4.5.0"},
Central: deployer.CentralConfig{
Namespace: "custom-namespace",
},
},
expected: deployer.Config{
Roxie: deployer.RoxieConfig{Version: "4.5.0"},
Central: deployer.CentralConfig{
Namespace: "custom-namespace",
},
},
},
{
name: "fills empty fields from user defaults",
config: deployer.Config{},
user: deployer.Config{
Roxie: deployer.RoxieConfig{Version: "4.5.0"},
Operator: deployer.OperatorConfig{DeployViaOlm: new(true)},
},
expected: deployer.Config{
Roxie: deployer.RoxieConfig{Version: "4.5.0"},
Operator: deployer.OperatorConfig{DeployViaOlm: new(true)},
},
},
{
name: "user config overrides any config fields including config defaults",
config: deployer.Config{
Roxie: deployer.RoxieConfig{
Version: "4.9.2",
},
Central: deployer.CentralConfig{
EarlyReadiness: new(true),
},
},
user: deployer.Config{
Roxie: deployer.RoxieConfig{
Version: "4.5.0",
},
Operator: deployer.OperatorConfig{
DeployViaOlm: new(true),
},
Central: deployer.CentralConfig{
Namespace: "custom-namespace",
EarlyReadiness: new(false),
},
},
expected: deployer.Config{
Roxie: deployer.RoxieConfig{
Version: "4.5.0",
},
Operator: deployer.OperatorConfig{
DeployViaOlm: new(true),
},
Central: deployer.CentralConfig{
Namespace: "custom-namespace",
EarlyReadiness: new(false),
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", tmpDir)
t.Setenv("HOME", tmpDir) // For non-Unix systems.

if !reflect.DeepEqual(tt.user, deployer.Config{}) {
configPath, err := paths.UserConfigPath()
require.NoError(t, err)
require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
data, err := yaml.Marshal(tt.user)
require.NoError(t, err)
require.NoError(t, os.WriteFile(configPath, data, 0o644))
}

cfg := deployer.NewConfig()
require.NoError(t, mergo.Merge(&cfg, &tt.config, mergo.WithOverride, mergo.WithoutDereference))
require.NoError(t, tryApplyUserDefaults(log, &cfg))

expected := deployer.NewConfig()
require.NoError(t, mergo.Merge(&expected, &tt.expected, mergo.WithOverride, mergo.WithoutDereference))

assert.True(t, reflect.DeepEqual(expected, cfg), "expected %+v, got %+v", expected, cfg)
})
}

t.Run("returns error on invalid yaml", func(t *testing.T) {
tmpDir := t.TempDir()
t.Setenv("XDG_CONFIG_HOME", tmpDir)
t.Setenv("HOME", tmpDir) // For non-Unix systems.

configPath, err := paths.UserConfigPath()
require.NoError(t, err)
require.NoError(t, os.MkdirAll(filepath.Dir(configPath), 0o755))
require.NoError(t, os.WriteFile(configPath, []byte(`invalid: [yaml`), 0o644))

cfg := deployer.NewConfig()
assert.Error(t, tryApplyUserDefaults(log, &cfg))
})
}
3 changes: 1 addition & 2 deletions cmd/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/spf13/cobra"
"github.com/stackrox/roxie/internal/env"
"github.com/stackrox/roxie/internal/logger"
)

func newEnvCmd() *cobra.Command {
Expand All @@ -22,7 +21,7 @@ func newEnvCmd() *cobra.Command {
}

func runEnv(cmd *cobra.Command, args []string) error {
log := logger.New()
log := globalLogger
if err := env.Initialize(log); err != nil {
return err
}
Expand Down
Loading
Loading