Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 7 additions & 5 deletions internal/env/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}

Expand All @@ -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 {
Expand Down
74 changes: 48 additions & 26 deletions internal/env/env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a case with the CRC server hostname but only Kubernetes API resources? That would protect the OpenShift-preset requirement if the detection order changes in the future.


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",
Expand Down Expand Up @@ -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,
},
}
Expand Down
10 changes: 7 additions & 3 deletions internal/types/cluster_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -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()
}
75 changes: 75 additions & 0 deletions internal/types/cluster_type_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading