diff --git a/internal/env/env.go b/internal/env/env.go index 98ae575..2489d39 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -171,6 +171,9 @@ func DetectClusterType(config KubeConfig, apiResources []string) types.ClusterTy // Check for OpenShift 4 clusters. if isOpenShift4(apiResources) { + if isCRC(parsedURL) { + return types.ClusterTypeCRC + } if isInfraOpenShift4(config) { return types.ClusterTypeInfraOpenShift4 } @@ -188,11 +191,6 @@ func DetectClusterType(config KubeConfig, apiResources []string) types.ClusterTy return types.ClusterTypeKind } - // CRC (CodeReady Containers) contexts start with "crc" or contain "-crc-"/"_crc_" as a segment - if strings.HasPrefix(contextLower, "crc") || strings.Contains(contextLower, "-crc-") || strings.Contains(contextLower, "-crc:") { - return types.ClusterTypeCRC - } - return types.ClusterTypeUnknown } @@ -209,6 +207,10 @@ func isInfraOpenShift4(config KubeConfig) bool { return strings.HasSuffix(hostname, ".ocp.infra.rox.systems") } +func isCRC(parsedURL *url.URL) bool { + return parsedURL != nil && strings.EqualFold(parsedURL.Hostname(), "api.crc.testing") +} + // getServerURL retrieves the server URL from the KubeConfig func getServerURL(config KubeConfig) string { if len(config.Clusters) == 0 { diff --git a/internal/env/env_test.go b/internal/env/env_test.go index b03aca2..e642e61 100644 --- a/internal/env/env_test.go +++ b/internal/env/env_test.go @@ -118,6 +118,53 @@ func TestDetectClusterType_OpenShift4(t *testing.T) { assert.Equal(t, types.ClusterTypeOpenShift4, result) } +func TestDetectClusterType_CRC_OpenShiftPreset(t *testing.T) { + // The CRC OpenShift preset is full OpenShift 4 running locally: it serves the + // config.openshift.io API group AND is reachable at api.crc.testing. It must be + // detected as CRC (not OpenShift4), keyed off the server host. A non-CRC server + // host with the same API resources is plain OpenShift4. + openShift4Resources := []string{ + "pods", + "services", + "clusterversions.config.openshift.io", + "clusteroperators.config.openshift.io", + } + + tests := []struct { + name string + kubeContext string + server string + want types.ClusterType + }{ + {name: "crc server host", kubeContext: "crc-admin", server: "https://api.crc.testing:6443", want: types.ClusterTypeCRC}, + {name: "crc server host, arbitrary context", kubeContext: "whatever", server: "https://api.crc.testing:6443", want: types.ClusterTypeCRC}, + // Hostnames are case-insensitive, so an uppercase CRC server host must + // still be detected as CRC. + {name: "crc server host, uppercase", kubeContext: "crc-admin", server: "https://API.CRC.TESTING:6443", want: types.ClusterTypeCRC}, + // A non-CRC server host must be detected as plain OpenShift4, even if the + // context name happens to contain "crc". + {name: "non-crc server, crc-like context", kubeContext: "acrc-cluster", server: "https://api.my-cluster.example.com:6443", want: types.ClusterTypeOpenShift4}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := KubeConfig{ + CurrentContext: tt.kubeContext, + Clusters: []KubeCluster{ + { + Name: tt.kubeContext, + Server: tt.server, + }, + }, + } + + result := DetectClusterType(config, openShift4Resources) + assert.Equal(t, tt.want, result, + "context %q / server %q with OpenShift4 API resources", tt.kubeContext, tt.server) + }) + } +} + func TestDetectClusterType_OpenShift4_NoAPIResources(t *testing.T) { config := KubeConfig{ CurrentContext: "admin", @@ -434,33 +481,8 @@ func TestDefaultDetector_Detect(t *testing.T) { want: types.ClusterTypeKind, }, { - name: "crc cluster with admin context", + name: "crc-like context without server host or api resources is unknown", kubeContext: "crc-admin", - want: types.ClusterTypeCRC, - }, - { - name: "crc cluster with api prefix", - kubeContext: "api-crc-testing:6443", - want: types.ClusterTypeCRC, - }, - { - name: "crc cluster with uppercase", - kubeContext: "CRC-admin", - want: types.ClusterTypeCRC, - }, - { - name: "crc cluster bare name", - kubeContext: "crc", - want: types.ClusterTypeCRC, - }, - { - name: "not crc - incidental substring", - kubeContext: "acrc-cluster", - want: types.ClusterTypeUnknown, - }, - { - name: "not crc - encrypted in name", - kubeContext: "my-encrypted-cluster", want: types.ClusterTypeUnknown, }, } diff --git a/internal/types/cluster_type.go b/internal/types/cluster_type.go index 198d716..cc27d23 100644 --- a/internal/types/cluster_type.go +++ b/internal/types/cluster_type.go @@ -22,7 +22,8 @@ const ( ClusterTypeMinikube ClusterType = "Minikube" // ClusterTypeK3s represents a K3s cluster ClusterTypeK3s ClusterType = "K3s" - // ClusterTypeCRC represents a CRC (CodeReady Containers) cluster + // ClusterTypeCRC represents a CRC (CodeReady Containers) cluster. + // Only CRC's OpenShift preset is supported. ClusterTypeCRC ClusterType = "CRC" // ClusterTypeAKS represents a generic AKS (Azure Kubernetes Service) cluster. ClusterTypeAKS ClusterType = "AKS" @@ -35,7 +36,7 @@ func (ct ClusterType) IsGKE() bool { } func (ct ClusterType) IsOpenShift() bool { - return ct == ClusterTypeInfraOpenShift4 || ct == ClusterTypeOpenShift4 + return ct == ClusterTypeInfraOpenShift4 || ct == ClusterTypeOpenShift4 || ct == ClusterTypeCRC } func (ct ClusterType) IsAKS() bool { @@ -106,5 +107,8 @@ func (ct ClusterType) IsLocal() bool { } func (ct ClusterType) SupportsLoadBalancer() bool { - return ct.IsGKE() || ct.IsOpenShift() || ct.IsAKS() || ct.IsEKS() + if ct.IsOpenShift() { + return ct != ClusterTypeCRC + } + return ct.IsGKE() || ct.IsAKS() || ct.IsEKS() } diff --git a/internal/types/cluster_type_test.go b/internal/types/cluster_type_test.go index f11bbf9..9e67f0e 100644 --- a/internal/types/cluster_type_test.go +++ b/internal/types/cluster_type_test.go @@ -60,6 +60,81 @@ func TestClusterTypeUnmarshalYAML(t *testing.T) { } } +func TestClusterTypeClassification(t *testing.T) { + tests := []struct { + clusterType ClusterType + isOpenShift bool + isLocal bool + supportsLoadBalancer bool + needsDefaultRegistryPullSecrets bool + }{ + { + clusterType: ClusterTypeCRC, + isOpenShift: true, + isLocal: true, + supportsLoadBalancer: false, + needsDefaultRegistryPullSecrets: true, + }, + { + clusterType: ClusterTypeOpenShift4, + isOpenShift: true, + isLocal: false, + supportsLoadBalancer: true, + needsDefaultRegistryPullSecrets: true, + }, + { + clusterType: ClusterTypeInfraOpenShift4, + isOpenShift: true, + isLocal: false, + supportsLoadBalancer: true, + needsDefaultRegistryPullSecrets: false, + }, + { + clusterType: ClusterTypeKind, + isOpenShift: false, + isLocal: true, + supportsLoadBalancer: false, + needsDefaultRegistryPullSecrets: true, + }, + { + clusterType: ClusterTypeMinikube, + isOpenShift: false, + isLocal: true, + supportsLoadBalancer: false, + needsDefaultRegistryPullSecrets: true, + }, + { + clusterType: ClusterTypeGKE, + isOpenShift: false, + isLocal: false, + supportsLoadBalancer: true, + needsDefaultRegistryPullSecrets: true, + }, + { + clusterType: ClusterTypeAKS, + isOpenShift: false, + isLocal: false, + supportsLoadBalancer: true, + needsDefaultRegistryPullSecrets: true, + }, + { + clusterType: ClusterTypeEKS, + isOpenShift: false, + isLocal: false, + supportsLoadBalancer: true, + needsDefaultRegistryPullSecrets: true, + }, + } + for _, tt := range tests { + t.Run(string(tt.clusterType), func(t *testing.T) { + assert.Equal(t, tt.isOpenShift, tt.clusterType.IsOpenShift(), "IsOpenShift") + assert.Equal(t, tt.isLocal, tt.clusterType.IsLocal(), "IsLocal") + assert.Equal(t, tt.supportsLoadBalancer, tt.clusterType.SupportsLoadBalancer(), "SupportsLoadBalancer") + assert.Equal(t, tt.needsDefaultRegistryPullSecrets, tt.clusterType.NeedsDefaultRegistryPullSecrets(), "NeedsDefaultRegistryPullSecrets") + }) + } +} + func TestClusterTypeUnmarshalYAML_Invalid(t *testing.T) { var ct ClusterType err := yaml.Unmarshal([]byte("bogus"), &ct)