diff --git a/README.md b/README.md index fd2b6a3..edaa531 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,34 @@ Similarly, the deployment(s) can be torn down using: ./bin/roxie teardown [ ] ``` +### Multi-cluster deployments + +roxie supports hub + spoke architectures where Central and SecuredCluster run on separate clusters. + +1. Deploy Central on the hub cluster: +```bash +./roxie deploy central -t 4.9.2 +``` + +2. Create a config file for the spoke cluster, pointing at the Central endpoint (printed during step 1): +```yaml +# spoke-config.yaml +securedCluster: + spec: + centralEndpoint: ":443" +``` + +3. Switch kubectl context to the spoke cluster and deploy SecuredCluster: +```bash +ROX_ADMIN_PASSWORD= \ +ROX_CA_CERT_FILE= \ +./roxie deploy secured-cluster -t 4.9.2 -c spoke-config.yaml +``` + +> **Tip:** If deploying from the roxie subshell, `ROX_ADMIN_PASSWORD` and `ROX_CA_CERT_FILE` are +> already set. For automation, consider using `--envrc ` on the Central deploy to write the +> environment to a file instead of spawning a subshell. + ## Development Enter the dev shell: diff --git a/internal/deployer/central_endpoint_test.go b/internal/deployer/central_endpoint_test.go new file mode 100644 index 0000000..14e98f6 --- /dev/null +++ b/internal/deployer/central_endpoint_test.go @@ -0,0 +1,61 @@ +package deployer + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +func TestConfigureSpec_CentralEndpoint(t *testing.T) { + tests := []struct { + name string + spec map[string]interface{} + centralNamespace string + expected string + }{ + { + name: "sets internal endpoint when not provided", + spec: map[string]interface{}{}, + centralNamespace: "acs-central", + expected: "central.acs-central.svc:443", + }, + { + name: "sets internal endpoint with custom namespace", + spec: map[string]interface{}{}, + centralNamespace: "stackrox", + expected: "central.stackrox.svc:443", + }, + { + name: "preserves user-provided endpoint", + spec: map[string]interface{}{"centralEndpoint": "central.example.com:443"}, + centralNamespace: "acs-central", + expected: "central.example.com:443", + }, + { + name: "user-provided endpoint takes precedence over internal default", + spec: map[string]interface{}{"centralEndpoint": "10.0.0.1:443"}, + centralNamespace: "stackrox", + expected: "10.0.0.1:443", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sc := &SecuredClusterConfig{ + Spec: tt.spec, + } + roxie := NewRoxieConfig() + central := &CentralConfig{Namespace: tt.centralNamespace} + + err := sc.ConfigureSpec(&roxie, central) + require.NoError(t, err, "ConfigureSpec failed") + + got, found, err := unstructured.NestedString(sc.Spec, "centralEndpoint") + require.NoError(t, err, "failed to get centralEndpoint from spec") + require.True(t, found, "centralEndpoint not found in spec") + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/internal/deployer/config.go b/internal/deployer/config.go index 1a6b2ca..2135cb2 100644 --- a/internal/deployer/config.go +++ b/internal/deployer/config.go @@ -234,10 +234,8 @@ func (s *SecuredClusterConfig) ConfigureSpec(roxieConfig *RoxieConfig, centralCo return err } - if err := helpers.DeepMerge(s.Spec, map[string]interface{}{ - "centralEndpoint": internalCentralEndpoint(centralConfig.Namespace), - }); err != nil { - return err + if _, exists := s.Spec["centralEndpoint"]; !exists { + s.Spec["centralEndpoint"] = internalCentralEndpoint(centralConfig.Namespace) } return nil diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 2c602e2..2bf1844 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -995,6 +995,10 @@ func (d *Deployer) PrintSecuredClusterDeploymentSummary() { log.Info(cyan.Sprint("│") + createRow("OLM", "Yes")) } + if ep, ok := d.config.SecuredCluster.Spec["centralEndpoint"].(string); ok && ep != internalCentralEndpoint(d.config.Central.Namespace) { + log.Info(cyan.Sprint("│") + createRow("Central Endpoint", ep)) + } + log.Info(cyan.Sprint("└" + strings.Repeat("─", boxWidth) + "┘")) log.Info("") }