diff --git a/cmd/deploy.go b/cmd/deploy.go index 87d7caa..72a3567 100644 --- a/cmd/deploy.go +++ b/cmd/deploy.go @@ -347,14 +347,18 @@ func runDeploy(cmd *cobra.Command, args []string) error { } func configureConfig(log *logger.Logger, components component.Component, deploySettings *deployer.Config) error { - clusterType := env.GetCurrentClusterType() - log.Dimf("Detected cluster type: %v", clusterType) - defaults, err := clusterdefaults.ApplyClusterDefaults(clusterType, deploySettings) + if deploySettings.Roxie.ClusterType == types.ClusterTypeUnknown { + clusterType := env.GetAutoDetectedClusterType() + log.Dimf("Detected cluster type: %v", clusterType) + deploySettings.Roxie.ClusterType = clusterType + } + clusterType := deploySettings.Roxie.ClusterType + defaults, err := clusterdefaults.ApplyClusterDefaults(deploySettings) if err != nil { - return fmt.Errorf("applying defaults for cluster type %v: %w", clusterType, err) + return err } if verbose { - log.Dimf("Applying the following defaults based on detected cluster type %v:", clusterType) + log.Dimf("Applying the following defaults based on cluster type %v:", clusterType) helpers.LogMultilineYaml(log, defaults) } @@ -409,6 +413,8 @@ func deployValidate(components component.Component, deploySettings *deployer.Con return errors.New("running without a controlling terminal requires --envrc to be set") } + clusterType := deploySettings.Roxie.ClusterType + if env.RunningInRoxieContainer { // For running containerized we have specific requirements. if deploySettings.Central.PortForwardingEnabled() { @@ -419,7 +425,7 @@ func deployValidate(components component.Component, deploySettings *deployer.Con } // On infra OpenShift we already get image pull secrets for Quay automatically. - if clusterType := env.GetCurrentClusterType(); clusterType != types.ClusterTypeInfraOpenShift4 { + if clusterType.NeedsPullSecrets() { if os.Getenv("REGISTRY_USERNAME") == "" || os.Getenv("REGISTRY_PASSWORD") == "" { return fmt.Errorf("containerized mode requires REGISTRY_USERNAME and REGISTRY_PASSWORD environment variables for clusters of type %s", clusterType) } @@ -437,9 +443,8 @@ func deployValidate(components component.Component, deploySettings *deployer.Con if deploySettings.Operator.DeployViaOlm { return errors.New("using Konflux images while deploying operator via OLM is not supported") } - clusterType := env.GetCurrentClusterType() if !clusterType.IsOpenShift() { - return fmt.Errorf("--konflux flag is only supported on OpenShift 4 clusters (current cluster type: %s)", clusterType.String()) + return fmt.Errorf("--konflux flag is only supported on OpenShift 4 clusters (current cluster type: %s)", clusterType) } } diff --git a/cmd/env.go b/cmd/env.go index 6ad05bb..0e59264 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -32,7 +32,7 @@ func runEnv(cmd *cobra.Command, args []string) error { fmt.Printf("Kube config: %s\n", os.Getenv("KUBECONFIG")) fmt.Printf("Running in roxie container: %v\n", env.RunningInRoxieContainer) fmt.Printf("Current Context: %s\n", env.GetCurrentContext()) - fmt.Printf("Cluster Type: %s\n", env.GetCurrentClusterType().String()) + fmt.Printf("Cluster Type: %s\n", env.GetAutoDetectedClusterType().String()) return nil } diff --git a/internal/clusterdefaults/clusterdefaults.go b/internal/clusterdefaults/clusterdefaults.go index 588c8e9..739a981 100644 --- a/internal/clusterdefaults/clusterdefaults.go +++ b/internal/clusterdefaults/clusterdefaults.go @@ -13,12 +13,12 @@ import ( // provided deployer.Config. // Returns *just* the assembled defaults for the given cluster type for logging purposes. func ApplyClusterDefaults( - clusterType types.ClusterType, config *deployer.Config, ) (*deployer.Config, error) { if config == nil { panic("applying cluster defaults to nil config") } + clusterType := config.Roxie.ClusterType defaults := getDefaultsForClusterType(clusterType) if defaults == nil { return nil, nil @@ -27,11 +27,11 @@ func ApplyClusterDefaults( // Make a copy. defaultsCopy, err := defaults.DeepCopy() if err != nil { - return nil, fmt.Errorf("deep-copying cluster defaults: %w", err) + return nil, fmt.Errorf("deep-copying cluster defaults for cluster type %s: %w", clusterType, err) } if err := mergo.Merge(config, defaultsCopy, mergo.WithoutDereference); err != nil { - return nil, fmt.Errorf("merging-in cluster defaults: %w", err) + return nil, fmt.Errorf("merging-in cluster defaults for cluster type %s: %w", clusterType, err) } return defaultsCopy, nil diff --git a/internal/clusterdefaults/clusterdefaults_test.go b/internal/clusterdefaults/clusterdefaults_test.go index f0dab56..78b59e1 100644 --- a/internal/clusterdefaults/clusterdefaults_test.go +++ b/internal/clusterdefaults/clusterdefaults_test.go @@ -113,7 +113,8 @@ func TestClusterDefaults(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := tt.config - _, err := ApplyClusterDefaults(tt.clusterType, &config) + config.Roxie.ClusterType = tt.clusterType + _, err := ApplyClusterDefaults(&config) require.NoError(t, err) if tt.wantConfig.Central.Exposure == nil { diff --git a/internal/deployer/config.go b/internal/deployer/config.go index 907fc4b..b4fbeab 100644 --- a/internal/deployer/config.go +++ b/internal/deployer/config.go @@ -44,9 +44,10 @@ func (c *Config) DeepCopy() (*Config, error) { // RoxieConfig holds roxie-level settings such as version and feature flags. type RoxieConfig struct { - Version string `yaml:"version,omitempty"` - KonfluxImages bool `yaml:"konfluxImages,omitempty"` - FeatureFlags map[string]bool `yaml:"featureFlags,omitempty"` + Version string `yaml:"version,omitempty"` + KonfluxImages bool `yaml:"konfluxImages,omitempty"` + FeatureFlags map[string]bool `yaml:"featureFlags,omitempty"` + ClusterType types.ClusterType `yaml:"clusterType,omitempty"` } // NewRoxieConfig returns a RoxieConfig with initialized defaults. diff --git a/internal/deployer/deploy_via_operator.go b/internal/deployer/deploy_via_operator.go index f97a923..44b0a47 100644 --- a/internal/deployer/deploy_via_operator.go +++ b/internal/deployer/deploy_via_operator.go @@ -119,7 +119,7 @@ func (d *Deployer) ensureOperatorDeployed(ctx context.Context) error { func (d *Deployer) deployCentralOperator(ctx context.Context) error { d.logger.Info("🚀 Deploying Central via Operator...") - needPullSecrets := env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 + needPullSecrets := d.config.Roxie.ClusterType.NeedsPullSecrets() if err := d.prepareNamespace(ctx, d.config.Central.Namespace, needPullSecrets); err != nil { return fmt.Errorf("failed to prepare namespace: %w", err) } @@ -655,7 +655,7 @@ func (d *Deployer) configureCentralEndpoint(ctx context.Context) error { func (d *Deployer) deploySecuredClusterOperator(ctx context.Context) error { d.logger.Info("🚀 Deploying SecuredCluster via Operator...") - needPullSecrets := env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 + needPullSecrets := d.config.Roxie.ClusterType.NeedsPullSecrets() if err := d.prepareNamespace(ctx, d.config.SecuredCluster.Namespace, needPullSecrets); err != nil { return fmt.Errorf("failed to prepare namespace: %w", err) } diff --git a/internal/deployer/deployer.go b/internal/deployer/deployer.go index 9aec79d..44565b8 100644 --- a/internal/deployer/deployer.go +++ b/internal/deployer/deployer.go @@ -298,7 +298,7 @@ func (d *Deployer) stopDetachedPortForward() { // Deploy deploys the specified components to the cluster. func (d *Deployer) Deploy(ctx context.Context, components component.Component) error { // Prepare and verify credentials early to fail fast. - if env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 { + if d.config.Roxie.ClusterType.NeedsPullSecrets() { if err := d.prepareCredentials(); err != nil { return fmt.Errorf("failed to prepare credentials: %w", err) } @@ -778,7 +778,7 @@ func (d *Deployer) PrintCentralDeploymentSummary() { // Deployment details log.Info(cyan.Sprint("│") + createRow("Component", component)) - log.Info(cyan.Sprint("│") + createRow("Cluster Type", env.GetCurrentClusterType().String())) + log.Info(cyan.Sprint("│") + createRow("Cluster Type", d.config.Roxie.ClusterType.String())) log.Info(cyan.Sprint("│") + createRow("Main Tag", mainImageTag)) log.Info(cyan.Sprint("│") + createRow("Kubernetes Context", kubeContext)) @@ -957,7 +957,7 @@ func (d *Deployer) PrintSecuredClusterDeploymentSummary() { // Deployment details log.Info(cyan.Sprint("│") + createRow("Component", component)) - log.Info(cyan.Sprint("│") + createRow("Cluster Type", env.GetCurrentClusterType().String())) + log.Info(cyan.Sprint("│") + createRow("Cluster Type", d.config.Roxie.ClusterType.String())) log.Info(cyan.Sprint("│") + createRow("Main Tag", mainImageTag)) log.Info(cyan.Sprint("│") + createRow("Kubernetes Context", kubeContext)) diff --git a/internal/deployer/operator.go b/internal/deployer/operator.go index 8f451c4..c10b4c3 100644 --- a/internal/deployer/operator.go +++ b/internal/deployer/operator.go @@ -14,10 +14,8 @@ import ( "gopkg.in/yaml.v3" - "github.com/stackrox/roxie/internal/env" "github.com/stackrox/roxie/internal/k8s" "github.com/stackrox/roxie/internal/ocihelper" - "github.com/stackrox/roxie/internal/types" ) const ( @@ -202,7 +200,7 @@ func (d *Deployer) getOperatorBundleImage() string { // ensureKonfluxImageRewriting configures image rewriting for Konflux images func (d *Deployer) ensureKonfluxImageRewriting(ctx context.Context) error { - if !env.GetCurrentClusterType().IsOpenShift() { + if !d.config.Roxie.ClusterType.IsOpenShift() { return errors.New("image rewriting for Konflux is only supported on OpenShift4 clusters") } @@ -290,7 +288,7 @@ func (d *Deployer) applyImageContentSourcePolicy(ctx context.Context) error { // removeKonfluxImageRewriting removes the ImageContentSourcePolicy for Konflux images if it exists func (d *Deployer) removeKonfluxImageRewriting(ctx context.Context) error { - if !env.GetCurrentClusterType().IsOpenShift() { + if !d.config.Roxie.ClusterType.IsOpenShift() { return nil } @@ -320,7 +318,7 @@ func (d *Deployer) deployOperatorFromCSV(ctx context.Context, bundleDir string) } serviceAccountName := deploymentSpec["service_account"].(string) - d.useOperatorPullSecrets = d.config.Roxie.KonfluxImages && env.GetCurrentClusterType() != types.ClusterTypeInfraOpenShift4 + d.useOperatorPullSecrets = d.config.Roxie.KonfluxImages && d.config.Roxie.ClusterType.NeedsPullSecrets() d.logger.Info("📋 Operator deployment plan:") d.logger.Dim(fmt.Sprintf(" • Namespace: %s", operatorNamespace)) diff --git a/internal/env/env.go b/internal/env/env.go index 9d85814..9b4c93c 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -26,7 +26,7 @@ var ( var ( // currentClusterType holds the detected cluster type for the current kubectl context - // This is lazily populated on first access via GetCurrentClusterType() + // This is lazily populated on first access via GetAutoDetectedClusterType() currentClusterType types.ClusterType // currentContext holds the name of the current kubectl context @@ -78,8 +78,8 @@ func ensureInitialized(log *logger.Logger) error { return nil } -// GetCurrentClusterType returns the current cluster type, initializing if needed -func GetCurrentClusterType() types.ClusterType { +// GetAutoDetectedClusterType returns the current cluster type, initializing if needed +func GetAutoDetectedClusterType() types.ClusterType { return currentClusterType } diff --git a/internal/env/env_integration_test.go b/internal/env/env_integration_test.go index 6a5677b..8a7ed5d 100644 --- a/internal/env/env_integration_test.go +++ b/internal/env/env_integration_test.go @@ -16,7 +16,7 @@ func TestDetectClusterType_Integration(t *testing.T) { // This test uses the current kubectl context // The result will depend on the active cluster - clusterType := GetCurrentClusterType() + clusterType := GetAutoDetectedClusterType() t.Logf("Detected cluster type: %s", clusterType) diff --git a/internal/env/env_test.go b/internal/env/env_test.go index 8e0fed7..b9ccdb7 100644 --- a/internal/env/env_test.go +++ b/internal/env/env_test.go @@ -291,7 +291,7 @@ func TestClusterTypeString(t *testing.T) { { name: "types.ClusterTypeInfraGKE", clusterType: types.ClusterTypeInfraGKE, - want: "GKE", + want: "GKE (infra)", }, { name: "InfraOpenShift4", diff --git a/internal/types/cluster_type.go b/internal/types/cluster_type.go index 47146a2..dfdaa0c 100644 --- a/internal/types/cluster_type.go +++ b/internal/types/cluster_type.go @@ -1,25 +1,27 @@ package types +import "fmt" + // ClusterType represents different types of Kubernetes clusters -type ClusterType int +type ClusterType string const ( // ClusterTypeUnknown represents an unidentified cluster type - ClusterTypeUnknown ClusterType = iota + ClusterTypeUnknown ClusterType = "" // ClusterTypeInfraGKE represents a GKE cluster created via Infra. - ClusterTypeInfraGKE + ClusterTypeInfraGKE ClusterType = "InfraGKE" // ClusterTypeInfraOpenShift4 represents an OpenShift 4 cluster - ClusterTypeInfraOpenShift4 + ClusterTypeInfraOpenShift4 ClusterType = "InfraOpenShift4" // Generic OpenShift4 cluster (e.g. for prow CI) - ClusterTypeOpenShift4 + ClusterTypeOpenShift4 ClusterType = "OpenShift4" // ClusterTypeKind represents a Kind (Kubernetes in Docker) cluster - ClusterTypeKind + ClusterTypeKind ClusterType = "Kind" // ClusterTypeMinikube represents a Minikube cluster - ClusterTypeMinikube + ClusterTypeMinikube ClusterType = "Minikube" // ClusterTypeK3s represents a K3s cluster - ClusterTypeK3s + ClusterTypeK3s ClusterType = "K3s" // ClusterTypeCRC represents a CRC (CodeReady Containers) cluster - ClusterTypeCRC + ClusterTypeCRC ClusterType = "CRC" ) func (ct ClusterType) IsOpenShift() bool { @@ -30,21 +32,13 @@ func (ct ClusterType) IsOpenShift() bool { func (ct ClusterType) String() string { switch ct { case ClusterTypeInfraGKE: - return "GKE" + return "GKE (infra)" case ClusterTypeInfraOpenShift4: return "OpenShift4 (infra)" - case ClusterTypeOpenShift4: - return "OpenShift4" - case ClusterTypeKind: - return "Kind" - case ClusterTypeMinikube: - return "minikube" - case ClusterTypeK3s: - return "k3s" - case ClusterTypeCRC: - return "crc" - default: + case ClusterTypeUnknown: return "Unknown" + default: + return string(ct) } } @@ -59,3 +53,24 @@ func AllClusterTypes() []ClusterType { ClusterTypeOpenShift4, } } + +func (ct *ClusterType) UnmarshalYAML(unmarshal func(any) error) error { + var s string + if err := unmarshal(&s); err != nil { + return err + } + + var sAsClusterType = ClusterType(s) + + for _, valid := range AllClusterTypes() { + if sAsClusterType == valid { + *ct = valid + return nil + } + } + return fmt.Errorf("unknown cluster type identifier: %q", s) +} + +func (ct ClusterType) NeedsPullSecrets() bool { + return ct != ClusterTypeInfraOpenShift4 +} diff --git a/internal/types/cluster_type_test.go b/internal/types/cluster_type_test.go new file mode 100644 index 0000000..b97ff6b --- /dev/null +++ b/internal/types/cluster_type_test.go @@ -0,0 +1,74 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestClusterTypeMarshalYAML(t *testing.T) { + tests := []struct { + clusterType ClusterType + expected string + }{ + {ClusterTypeInfraGKE, "InfraGKE"}, + {ClusterTypeInfraOpenShift4, "InfraOpenShift4"}, + {ClusterTypeOpenShift4, "OpenShift4"}, + {ClusterTypeKind, "Kind"}, + {ClusterTypeMinikube, "Minikube"}, + {ClusterTypeK3s, "K3s"}, + {ClusterTypeCRC, "CRC"}, + } + for _, tt := range tests { + t.Run(tt.expected, func(t *testing.T) { + out, err := yaml.Marshal(tt.clusterType) + require.NoError(t, err) + assert.Equal(t, tt.expected+"\n", string(out)) + }) + } +} + +func TestClusterTypeUnmarshalYAML(t *testing.T) { + tests := []struct { + input string + expected ClusterType + }{ + {"InfraGKE", ClusterTypeInfraGKE}, + {"InfraOpenShift4", ClusterTypeInfraOpenShift4}, + {"OpenShift4", ClusterTypeOpenShift4}, + {"Kind", ClusterTypeKind}, + {"Minikube", ClusterTypeMinikube}, + {"K3s", ClusterTypeK3s}, + {"CRC", ClusterTypeCRC}, + {"", ClusterTypeUnknown}, + } + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + var ct ClusterType + err := yaml.Unmarshal([]byte(tt.input), &ct) + require.NoError(t, err) + assert.Equal(t, tt.expected, ct) + }) + } +} + +func TestClusterTypeUnmarshalYAML_Invalid(t *testing.T) { + var ct ClusterType + err := yaml.Unmarshal([]byte("bogus"), &ct) + assert.ErrorContains(t, err, "unknown cluster type identifier") +} + +func TestClusterTypeRoundTrip(t *testing.T) { + for _, ct := range AllClusterTypes() { + t.Run(ct.String(), func(t *testing.T) { + out, err := yaml.Marshal(ct) + require.NoError(t, err) + + var parsed ClusterType + require.NoError(t, yaml.Unmarshal(out, &parsed)) + assert.Equal(t, ct, parsed) + }) + } +}