Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c9eb6ac
Implement new config-file first configuration approach.
Apr 27, 2026
47bfdc7
Make central's exposure a pointer
May 5, 2026
b75d4d1
Defaults for GKE & OpenShift
May 5, 2026
dd047d3
More test cases
May 5, 2026
9abbbcb
Update internal/deployer/config.go
mclasmeier May 6, 2026
827cfb8
Renamed receiver
May 6, 2026
24b62d7
Improved code comment
May 6, 2026
5033748
Merge branch 'main' into backup/mc/new-config-2
mclasmeier May 6, 2026
1e732a5
Unrelated, linter demanded it
May 6, 2026
95f43b4
Prevent nil dereference, e.g. when cluster defaults do not provide a …
May 6, 2026
0c57da3
Merge remote-tracking branch 'refs/remotes/origin/backup/mc/new-confi…
May 6, 2026
ad3e62c
Fix cluster type in test
May 6, 2026
1e34f97
Refactor runDeploy
May 6, 2026
9e1d58d
Reorganized config.go
May 7, 2026
23c117d
Add mergo dependency
May 7, 2026
bf5441b
Add DeepCopy method for deployer.Config
May 7, 2026
5e76b90
Let getDefaultsForClusterType return deployer.Config, not unstructured
May 7, 2026
83ef45e
Rewrite ApplyClusterDefaults using mergo
May 7, 2026
27e463c
Generalize test case, using the whole deployer.Config not just hand-p…
May 7, 2026
badabdb
Move checking of resourceProfile into its own test
May 7, 2026
0e90f04
Move config pre defaulting to test cases
May 7, 2026
d681edf
New test case verifying that defaults won't overwrite explicitly set …
May 7, 2026
f154aa4
Added omitempty
May 7, 2026
0b6d495
Merge branch 'main' into backup/mc/new-config-2
May 7, 2026
d6fc8db
Added log message
May 7, 2026
26eb1dd
go mod tidy
May 7, 2026
5fb1130
Accidentally left out during manual merge
May 7, 2026
2ac8465
Merge branch 'main' into backup/mc/new-config-2
May 7, 2026
9d862ea
Reorganize flag parsing setup.
May 11, 2026
5bcd644
Merge branch 'main' into backup/mc/new-config-2
May 11, 2026
40a0a16
Claude review feedback
May 11, 2026
ae51d6d
Merge branch 'main' into backup/mc/new-config-2
May 11, 2026
df863d1
Remove comments
May 11, 2026
0577890
Merge branch 'main' into backup/mc/new-config-2
May 12, 2026
a995af0
Added another retry error condition
May 12, 2026
563992b
Fix accidental logic inversion
May 12, 2026
5680d58
Merge branch 'main' into backup/mc/new-config-2
May 12, 2026
20676d0
Added comments for clarity
May 13, 2026
e9babce
Merge branch 'main' into backup/mc/new-config-2
May 13, 2026
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
477 changes: 316 additions & 161 deletions cmd/deploy.go

Large diffs are not rendered by default.

207 changes: 207 additions & 0 deletions cmd/deploy_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package main

import (
"os"
"path/filepath"
"reflect"
"testing"
"time"

"github.com/stackrox/roxie/internal/deployer"
"github.com/stackrox/roxie/internal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewDeployCmd_Flags(t *testing.T) {
configFilePath := filepath.Join(t.TempDir(), "config.yaml")

tests := []struct {
name string
config string
args []string
assert func(t *testing.T, cfg deployer.Config)
}{
{
name: "exposure loadbalancer",
args: []string{"--exposure", "loadbalancer"},
assert: func(t *testing.T, cfg deployer.Config) {
require.NotNil(t, cfg.Central.Exposure, "Central.Exposure should be set")
assert.Equal(t, types.ExposureLoadBalancer, *cfg.Central.Exposure, "Central.Exposure mismatch")
},
},
{
name: "resources small",
args: []string{"--resources", "small"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, types.ResourceProfileSmall, cfg.Central.ResourceProfile, "Central.ResourceProfile mismatch")
assert.Equal(t, types.ResourceProfileSmall, cfg.SecuredCluster.ResourceProfile, "SecuredCluster.ResourceProfile mismatch")
},
},
{
name: "tag short flag",
args: []string{"-t", "4.7.0"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, "4.7.0", cfg.Roxie.Version, "Roxie.Version mismatch")
},
},
{
name: "tag long flag",
args: []string{"--tag", "4.7.0"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, "4.7.0", cfg.Roxie.Version, "Roxie.Version mismatch")
},
},
{
name: "port-forwarding enabled",
args: []string{"--port-forwarding"},
assert: func(t *testing.T, cfg deployer.Config) {
require.NotNil(t, cfg.Central.PortForwarding, "Central.PortForwarding should be set")
assert.True(t, *cfg.Central.PortForwarding, "Central.PortForwarding mismatch")
},
},
{
name: "port-forwarding disabled",
args: []string{"--port-forwarding=false"},
assert: func(t *testing.T, cfg deployer.Config) {
require.NotNil(t, cfg.Central.PortForwarding, "Central.PortForwarding should be set")
assert.False(t, *cfg.Central.PortForwarding, "Central.PortForwarding mismatch")
},
},
{
name: "single-namespace",
args: []string{"--single-namespace"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, "stackrox", cfg.Central.Namespace, "Central.Namespace mismatch")
assert.Equal(t, "stackrox", cfg.SecuredCluster.Namespace, "SecuredCluster.Namespace mismatch")
},
},
{
name: "central-wait",
args: []string{"--central-wait", "10m"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, 10*time.Minute, cfg.Central.DeployTimeout, "Central.DeployTimeout mismatch")
},
},
{
name: "secured-cluster-wait",
args: []string{"--secured-cluster-wait", "7m"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, 7*time.Minute, cfg.SecuredCluster.DeployTimeout, "SecuredCluster.DeployTimeout mismatch")
},
},
{
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")
},
},
{
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")
},
},
{
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")
},
},
{
name: "olm",
args: []string{"--olm"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.True(t, cfg.Operator.DeployViaOlm, "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")
},
},
{
name: "multiple flags combined",
args: []string{"--tag", "4.7.0", "--exposure", "loadbalancer", "--early-readiness", "--resources", "small"},
assert: func(t *testing.T, cfg deployer.Config) {
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.Equal(t, types.ResourceProfileSmall, cfg.Central.ResourceProfile, "Central.ResourceProfile mismatch")
},
},
{
name: "config file can be used",
config: `
roxie:
version: 1.2.3
securedCluster:
spec:
foo: bar
`,
args: []string{"--config", configFilePath},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, "1.2.3", cfg.Roxie.Version, "Roxie.Version mismatch")
assert.True(t,
reflect.DeepEqual(cfg.SecuredCluster.Spec,
map[string]interface{}{
"foo": "bar",
}),
"SecuredCluster.Spec mismatch",
)
},
},

{
name: "flags can override earlier specified config file",
config: `
central:
resourceProfile: small
portForwarding: true
`,
args: []string{"--config", configFilePath, "--port-forwarding=false", "--resources=medium"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, types.ResourceProfileMedium, cfg.Central.ResourceProfile, "Central.ResourceProfile mismatch")
require.NotNil(t, cfg.Central.PortForwarding, "Central.PortForwarding should be set")
assert.False(t, *cfg.Central.PortForwarding, "Central.PortForwarding mismatch")
},
},

{
name: "set expressions can be used",
args: []string{"--set", "roxie.version=0.99.1", "--set", "central.deployTimeout=4m", "--set", "securedCluster.spec.clusterName=foo"},
assert: func(t *testing.T, cfg deployer.Config) {
assert.Equal(t, "0.99.1", cfg.Roxie.Version, "version mismatch")
assert.Equal(t, 4*time.Minute, cfg.Central.DeployTimeout, "Central.DeployTimeout mismatch")
assert.Equal(t,
map[string]interface{}{
"clusterName": "foo",
},
cfg.SecuredCluster.Spec,
"SecuredCluster.Spec mismatch",
)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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)
})
}
}
96 changes: 96 additions & 0 deletions cmd/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

import (
"time"

"github.com/spf13/cobra"
"github.com/stackrox/roxie/internal/deployer"
"gopkg.in/yaml.v3"
)

type cliFlag struct {
config *deployer.Config
longName string
shortName string
flagType string
applyFn func(config *deployer.Config, val string) error
noOptDefVal string
description string
}

type flagOpt func(opts *cliFlag)

func (f *cliFlag) Set(val string) error {
return f.applyFn(f.config, val)
}

func (f *cliFlag) String() string {
return "" // Not sure what to return here.
}

func (f *cliFlag) Type() string {
return f.flagType
}

func withApplyFnBool(boolApplyFn func(config *deployer.Config, val bool) error) flagOpt {
return func(opts *cliFlag) {
opts.flagType = "bool"
opts.applyFn = func(config *deployer.Config, val string) error {
var valParsed bool
if err := yaml.Unmarshal([]byte(val), &valParsed); err != nil {
return err
}
return boolApplyFn(config, valParsed)
}
}
}

func withApplyFn(flagType string, stringApplyFn func(config *deployer.Config, val string) error) flagOpt {
return func(opts *cliFlag) {
opts.flagType = flagType
opts.applyFn = func(config *deployer.Config, val string) error {
return stringApplyFn(config, val)
}
}
}

func withApplyFnDuration(durationApplyFn func(config *deployer.Config, duration time.Duration) error) flagOpt {
return func(opts *cliFlag) {
opts.flagType = "duration"
opts.applyFn = func(config *deployer.Config, val string) error {
var duration time.Duration
duration, err := time.ParseDuration(val)
if err != nil {
return err
}
return durationApplyFn(config, duration)
}
}
}

func withNoOptDefVal(defVal string) flagOpt {
return func(opts *cliFlag) {
opts.noOptDefVal = defVal
}
}

func withShortName(shortName string) flagOpt {
return func(opts *cliFlag) {
opts.shortName = shortName
}
}

func registerFlag(cmd *cobra.Command, settings *deployer.Config, longName string, description string, flagOpts ...flagOpt) {
f := cliFlag{
config: settings,
longName: longName,
description: description,
}
for _, applyOpt := range flagOpts {
applyOpt(&f)
}
flag := cmd.Flags().VarPF(&f, f.longName, f.shortName, f.description)
if f.noOptDefVal != "" {
flag.NoOptDefVal = f.noOptDefVal
}
}
32 changes: 11 additions & 21 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,18 @@ import (

"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/stackrox/roxie/internal/deployer"
)

var (
// Global flags
verbose bool
earlyReadiness bool
olm bool
konflux bool
deployOperator bool
portForwarding bool
pauseReconciliation bool
overrideFile string
overrideSetExpressions []string
exposure string
resources string
shell string
envrc string
singleNamespace bool
tag string
featureFlags []string
centralWait string
securedClusterWait string
verbose bool
shell string
envrc string
dryRun bool

// We need this set up before command line flags are parsed.
deploySettings = deployer.NewConfig()
)

func main() {
Expand All @@ -48,9 +38,9 @@ Red Hat Advanced Cluster Security (ACS) on any Kubernetes/OpenShift cluster.`,

func init() {
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose output (show CRs)")
rootCmd.PersistentFlags().BoolVar(&earlyReadiness, "early-readiness", true, "Only wait for essential workloads (central/sensor) to be ready")
rootCmd.AddCommand(newDeployCmd())
rootCmd.AddCommand(newTeardownCmd())
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Do not actually modify cluster")
rootCmd.AddCommand(newDeployCmd(&deploySettings))
rootCmd.AddCommand(newTeardownCmd(&deploySettings))
rootCmd.AddCommand(newVersionCmd())
rootCmd.AddCommand(newEnvCmd())
rootCmd.AddCommand(newLogsCmd())
Expand Down
Loading
Loading