Skip to content

Commit b0bd68d

Browse files
authored
Merge branch 'main' into dependabot/go_modules/github.com/google/go-containerregistry-0.21.6
2 parents 21f080a + db76406 commit b0bd68d

20 files changed

Lines changed: 823 additions & 91 deletions

.github/workflows/build-roxie-image.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
uses: docker/setup-buildx-action@v4
3737

3838
- name: Log in to Quay.io
39-
uses: docker/login-action@v3
39+
uses: docker/login-action@v4
4040
with:
4141
registry: ${{ env.REGISTRY }}
4242
username: ${{ secrets.REGISTRY_USERNAME }}

.github/workflows/code-quality.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616

1717
steps:
1818
- name: Checkout code
19-
uses: actions/checkout@v4
19+
uses: actions/checkout@v6
2020

2121
- name: Set up Go
2222
uses: actions/setup-go@v6

.github/workflows/docker-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ jobs:
3737
uses: docker/setup-buildx-action@v4
3838

3939
- name: Log in to Quay.io
40-
uses: docker/login-action@v3
40+
uses: docker/login-action@v4
4141
with:
4242
registry: ${{ env.REGISTRY }}
4343
username: ${{ secrets.REGISTRY_USERNAME }}

.github/workflows/e2e-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ jobs:
4242
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
4343

4444
- name: Log in to Quay.io
45-
uses: docker/login-action@v3
45+
uses: docker/login-action@v4
4646
with:
4747
registry: ${{ env.REGISTRY }}
4848
username: ${{ secrets.REGISTRY_USERNAME }}

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,34 @@ Similarly, the deployment(s) can be torn down using:
8787
./bin/roxie teardown [ <component> ]
8888
```
8989

90+
### Multi-cluster deployments
91+
92+
roxie supports hub + spoke architectures where Central and SecuredCluster run on separate clusters.
93+
94+
1. Deploy Central on the hub cluster:
95+
```bash
96+
./roxie deploy central -t 4.9.2
97+
```
98+
99+
2. Create a config file for the spoke cluster, pointing at the Central endpoint (printed during step 1):
100+
```yaml
101+
# spoke-config.yaml
102+
securedCluster:
103+
spec:
104+
centralEndpoint: "<central-loadbalancer-ip>:443"
105+
```
106+
107+
3. Switch kubectl context to the spoke cluster and deploy SecuredCluster:
108+
```bash
109+
ROX_ADMIN_PASSWORD=<admin-password> \
110+
ROX_CA_CERT_FILE=<path-to-ca-cert> \
111+
./roxie deploy secured-cluster -t 4.9.2 -c spoke-config.yaml
112+
```
113+
114+
> **Tip:** If deploying from the roxie subshell, `ROX_ADMIN_PASSWORD` and `ROX_CA_CERT_FILE` are
115+
> already set. For automation, consider using `--envrc <file>` on the Central deploy to write the
116+
> environment to a file instead of spawning a subshell.
117+
90118
## Development
91119

92120
Enter the dev shell:

cmd/deploy.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/stackrox/roxie/internal/env"
1919
"github.com/stackrox/roxie/internal/helpers"
2020
"github.com/stackrox/roxie/internal/logger"
21+
"github.com/stackrox/roxie/internal/manifest"
22+
"github.com/stackrox/roxie/internal/roxieenv"
2123
"github.com/stackrox/roxie/internal/types"
2224

2325
"github.com/stackrox/roxie/internal/stackroxversions"
@@ -337,8 +339,19 @@ func runDeploy(cmd *cobra.Command, args []string) error {
337339
d.WaitForCentral(5 * time.Minute)
338340
}
339341

342+
if components.IncludesCentral() && !dryRun {
343+
roxieEnv := roxieenv.AssembleRoxieEnvironment(d.GetCentralDeploymentInfo())
344+
m := manifest.RoxieManifest{
345+
RoxieEnvironment: roxieEnv,
346+
Config: deploySettings,
347+
}
348+
if err := manifest.CreateManifestSecretOnCluster(ctx, log, m); err != nil {
349+
log.Warningf("Failed to save roxie manifest: %v", err)
350+
}
351+
}
352+
340353
if components.IncludesCentral() && envrc == "" {
341-
if err := spawnSubshell(d, log); err != nil {
354+
if err := spawnSubshellForDeployerEnv(d, log); err != nil {
342355
return fmt.Errorf("failed to spawn subshell: %w", err)
343356
}
344357
}

cmd/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ func init() {
4141
rootCmd.PersistentFlags().BoolVar(&dryRun, "dry-run", false, "Do not actually modify cluster")
4242
rootCmd.AddCommand(newDeployCmd(&deploySettings))
4343
rootCmd.AddCommand(newTeardownCmd(&deploySettings))
44+
rootCmd.AddCommand(newShellCmd())
4445
rootCmd.AddCommand(newVersionCmd())
4546
rootCmd.AddCommand(newEnvCmd())
4647
rootCmd.AddCommand(newLogsCmd())

cmd/shell.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"os/exec"
9+
"time"
10+
11+
"github.com/spf13/cobra"
12+
"github.com/stackrox/roxie/internal/env"
13+
"github.com/stackrox/roxie/internal/logger"
14+
"github.com/stackrox/roxie/internal/manifest"
15+
)
16+
17+
func newShellCmd() *cobra.Command {
18+
cmd := &cobra.Command{
19+
Use: "shell [-- command [args...]]",
20+
Short: "Open a subshell for an existing ACS Central deployment",
21+
Long: `Open an interactive subshell with ACS environment variables
22+
set for an existing ACS Central deployment.
23+
24+
This command reads the roxie manifest secret from the cluster,
25+
re-fetches the CA certificate, and spawns an interactive subshell
26+
with the environment variables set.
27+
28+
If a command is given after "--", it is executed in the modified environment
29+
instead of spawning a subshell.
30+
31+
Examples:
32+
roxie shell
33+
roxie shell -- roxctl central whoami
34+
roxie shell -- bash -c 'echo $ROX_ENDPOINT'`,
35+
Run: func(cmd *cobra.Command, args []string) {
36+
err := runShell(cmd, args)
37+
if err != nil {
38+
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
39+
// Propagate exit error from the child process.
40+
os.Exit(exitErr.ExitCode())
41+
}
42+
cmd.PrintErrln(err)
43+
os.Exit(1)
44+
}
45+
},
46+
DisableFlagParsing: false,
47+
}
48+
49+
cmd.Flags().StringVar(&shell, "shell", "", "Shell to spawn")
50+
51+
return cmd
52+
}
53+
54+
func runShell(cmd *cobra.Command, args []string) error {
55+
log := logger.New()
56+
if err := env.Initialize(log); err != nil {
57+
return err
58+
}
59+
60+
if os.Getenv("ROXIE_SHELL") != "" {
61+
return errors.New("already in a roxie sub-shell (ROXIE_SHELL environment variable is set), please exit the shell and try again")
62+
}
63+
64+
log.Info("Loading manifest from cluster...")
65+
66+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
67+
defer cancel()
68+
69+
m, err := manifest.LoadManifestSecret(ctx, log)
70+
if err != nil {
71+
return fmt.Errorf("failed to load roxie manifest: %w", err)
72+
}
73+
log.Dim("roxie manifest loaded")
74+
75+
// We need this for the setup of the CA cert.
76+
tempDir, err := os.MkdirTemp("", "roxie-shell-*")
77+
if err != nil {
78+
return fmt.Errorf("failed to create temp dir: %w", err)
79+
}
80+
defer os.RemoveAll(tempDir)
81+
82+
centralDeploymentInfo, err := manifest.ManifestToCentralDeploymentInfo(ctx, log, tempDir, m)
83+
if err != nil {
84+
return fmt.Errorf("extracting central deployment info from manifest: %w", err)
85+
}
86+
87+
return runCommandOrSubshell(centralDeploymentInfo, log, args)
88+
}

cmd/subshell.go

Lines changed: 71 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -11,94 +11,100 @@ import (
1111
"github.com/stackrox/roxie/internal/deployer"
1212
"github.com/stackrox/roxie/internal/env"
1313
"github.com/stackrox/roxie/internal/logger"
14+
"github.com/stackrox/roxie/internal/roxieenv"
1415
"github.com/stackrox/roxie/internal/types"
1516
)
1617

17-
func spawnSubshell(d *deployer.Deployer, log *logger.Logger) error {
18-
shellPath := shell
19-
if shellPath == "" {
20-
shellPath = os.Getenv("ROXIE_USER_SHELL")
21-
}
22-
if shellPath == "" {
23-
shellPath = os.Getenv("SHELL")
24-
}
25-
if shellPath == "" {
26-
shellPath = "/bin/bash"
27-
}
28-
29-
log.Infof("Spawning sub-shell: %s", shellPath)
30-
31-
env := os.Environ()
32-
33-
centralDeploymentInfo := d.GetCentralDeploymentInfo()
34-
35-
if centralDeploymentInfo.Endpoint != "" {
36-
env = append(env, fmt.Sprintf("API_ENDPOINT=%s", centralDeploymentInfo.Endpoint))
37-
env = append(env, fmt.Sprintf("ROX_ENDPOINT=%s", centralDeploymentInfo.Endpoint))
38-
env = append(env, fmt.Sprintf("ROX_BASE_URL=https://%s", centralDeploymentInfo.Endpoint))
39-
}
40-
41-
if centralDeploymentInfo.Password != "" {
42-
env = append(env, fmt.Sprintf("ROX_ADMIN_PASSWORD=%s", centralDeploymentInfo.Password))
43-
}
18+
// spawnSubshellForDeployerEnv assembles the roxie environment from a Deployer and invokes an interactive subshell.
19+
func spawnSubshellForDeployerEnv(d *deployer.Deployer, log *logger.Logger) error {
20+
return runCommandOrSubshell(d.GetCentralDeploymentInfo(), log, nil)
21+
}
4422

45-
if centralDeploymentInfo.CACertFile != "" {
46-
env = append(env, fmt.Sprintf("ROX_CA_CERT_FILE=%s", centralDeploymentInfo.CACertFile))
23+
// runCommandOrSubshell spawns an interactive subshell or runs the provided command using the given
24+
// central deployment info.
25+
// It handles HAProxy setup, prints the connection banner, and manages shell lifecycle.
26+
func runCommandOrSubshell(centralDeploymentInfo types.CentralDeploymentInfo, log *logger.Logger, args []string) error {
27+
cmdEnv := os.Environ()
28+
for name, val := range roxieenv.AssembleRoxieEnvironment(centralDeploymentInfo).Export() {
29+
cmdEnv = append(cmdEnv, fmt.Sprintf("%s=%s", name, val))
4730
}
48-
49-
env = append(env, fmt.Sprintf("ROX_USERNAME=%s", deployer.AdminUsername))
50-
env = append(env, "ROXIE_SHELL=1")
51-
env = append(env, fmt.Sprintf("name=acs@%s", centralDeploymentInfo.KubeContext))
31+
cmdEnv = append(cmdEnv, "ROXIE_SHELL=1")
32+
cmdEnv = append(cmdEnv, fmt.Sprintf("name=acs@%s", centralDeploymentInfo.KubeContext))
5233

5334
haproxyAvailable := isHAProxyAvailable()
5435

55-
var haproxyCmd *exec.Cmd
56-
var haproxyConfigPath string
57-
5836
if haproxyAvailable && centralDeploymentInfo.Endpoint != "" && centralDeploymentInfo.CACertFile != "" {
59-
var err error
60-
haproxyCmd, haproxyConfigPath, err = startHAProxy(centralDeploymentInfo.Endpoint, centralDeploymentInfo.CACertFile, log)
37+
haproxyCmd, haproxyConfigPath, err := startHAProxy(centralDeploymentInfo.Endpoint, centralDeploymentInfo.CACertFile, log)
6138
if err != nil {
6239
log.Warningf("Failed to start HAProxy: %v", err)
6340
} else {
64-
env = append(env, fmt.Sprintf("ROXIE_HAPROXY_CFG_FILE=%s", haproxyConfigPath))
41+
cmdEnv = append(cmdEnv, "ROXIE_HAPROXY_CFG_FILE="+haproxyConfigPath)
6542
centralDeploymentInfo.HAProxyStarted = true
6643
defer cleanupHAProxy(haproxyCmd, haproxyConfigPath)
6744
}
6845
}
6946

70-
printBanner(centralDeploymentInfo)
71-
72-
shellCmd := exec.Command(shellPath, "-i")
73-
shellCmd.Env = env
74-
shellCmd.Stdin = os.Stdin
75-
shellCmd.Stdout = os.Stdout
76-
shellCmd.Stderr = os.Stderr
77-
78-
err := shellCmd.Run()
47+
var cmd *exec.Cmd
7948

80-
// Print exit message
81-
cyan := color.New(color.FgCyan, color.Bold)
82-
cyan.Println("\n[roxie] Exited subshell. You are now back in your original shell.")
83-
cyan.Println("")
84-
85-
// Don't treat shell exit as an error - shells can exit with non-zero status
86-
// for various reasons (like the last command failing) which is normal behavior
87-
if err != nil {
88-
// Check if it's a normal exit (exit code from the shell)
89-
if exitErr, ok := err.(*exec.ExitError); ok {
90-
// Shell exited (could be normal exit or last command failed)
91-
// This is not an error condition for roxie - the subshell worked fine
92-
_ = exitErr // Acknowledge we handled this
93-
return nil
49+
if subShellMode(args) {
50+
shellPath := resolveShellPath()
51+
log.Infof("Spawning sub-shell: %s", shellPath)
52+
printBanner(centralDeploymentInfo)
53+
cmd = exec.Command(shellPath, "-i")
54+
} else {
55+
// args is non-empty.
56+
cmd = exec.Command(args[0], args[1:]...)
57+
}
58+
cmd.Env = cmdEnv
59+
cmd.Stdin = os.Stdin
60+
cmd.Stdout = os.Stdout
61+
cmd.Stderr = os.Stderr
62+
63+
err := cmd.Run()
64+
65+
if subShellMode(args) {
66+
cyan := color.New(color.FgCyan, color.Bold)
67+
cyan.Println("")
68+
cyan.Println("[roxie] Exited subshell. You are now back in your original shell.")
69+
cyan.Println("[roxie] If you accidentally closed the roxie subshell, you can use `roxie shell` to re-open it.")
70+
cyan.Println("")
71+
72+
// Don't treat shell exit as an error - shells can exit with non-zero status
73+
// for various reasons (like the last command failing) which is normal behavior
74+
if err != nil {
75+
// Check if it's a normal exit (exit code from the shell)
76+
if _, ok := err.(*exec.ExitError); ok {
77+
return nil
78+
}
79+
// Only return error if we couldn't even start the shell
80+
return fmt.Errorf("failed to run subshell: %w", err)
81+
}
82+
} else {
83+
if err != nil {
84+
return fmt.Errorf("failed to execute command: %w", err)
9485
}
95-
// Only return error if we couldn't even start the shell
96-
return fmt.Errorf("failed to run subshell: %w", err)
9786
}
9887

9988
return nil
10089
}
10190

91+
func subShellMode(args []string) bool {
92+
return len(args) == 0
93+
}
94+
95+
func resolveShellPath() string {
96+
if shell != "" {
97+
return shell
98+
}
99+
if s := os.Getenv("ROXIE_USER_SHELL"); s != "" {
100+
return s
101+
}
102+
if s := os.Getenv("SHELL"); s != "" {
103+
return s
104+
}
105+
return "/bin/bash"
106+
}
107+
102108
func startHAProxy(endpoint, caCertFile string, log *logger.Logger) (*exec.Cmd, string, error) {
103109
configFile, err := os.CreateTemp("", "roxie-haproxy-*.cfg")
104110
if err != nil {
@@ -171,7 +177,7 @@ func isHAProxyAvailable() bool {
171177
return err == nil
172178
}
173179

174-
func printBanner(centralDeploymentInfo deployer.CentralDeploymentInfo) {
180+
func printBanner(centralDeploymentInfo types.CentralDeploymentInfo) {
175181
cyan := color.New(color.FgCyan, color.Bold)
176182
cyan.Println("\n[roxie] Entering a subshell with ACS environment variables set.")
177183
cyan.Println("[roxie]")

cmd/teardown.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/stackrox/roxie/internal/deployer"
1111
"github.com/stackrox/roxie/internal/env"
1212
"github.com/stackrox/roxie/internal/logger"
13+
"github.com/stackrox/roxie/internal/manifest"
1314
)
1415

1516
func newTeardownCmd(settings *deployer.Config) *cobra.Command {
@@ -70,6 +71,17 @@ func runTeardown(cmd *cobra.Command, args []string) error {
7071
return fmt.Errorf("teardown failed: %w", err)
7172
}
7273

74+
if components.IncludesCentral() {
75+
if err := manifest.DeleteManifestSecret(ctx, log); err != nil {
76+
log.Warningf("Failed to delete roxie manifest: %v", err)
77+
}
78+
}
79+
if components == component.All {
80+
if err := manifest.DeleteRoxieNamespace(ctx, log); err != nil {
81+
log.Warningf("Failed to delete roxie namespace: %v", err)
82+
}
83+
}
84+
7385
log.Success("🎉 Teardown complete!")
7486

7587
return nil

0 commit comments

Comments
 (0)