diff --git a/internal/clusterdefaults/clusterdefaults.go b/internal/clusterdefaults/clusterdefaults.go index bda3f59..3d40388 100644 --- a/internal/clusterdefaults/clusterdefaults.go +++ b/internal/clusterdefaults/clusterdefaults.go @@ -53,7 +53,7 @@ func getDefaultsForClusterType(clusterType types.ClusterType) *deployer.Config { }, } - case clusterType.IsGKE() || clusterType.IsOpenShift(): + case clusterType.SupportsLoadBalancer(): return &deployer.Config{ Central: deployer.CentralConfig{ Exposure: ptr.To(types.ExposureLoadBalancer), @@ -72,7 +72,7 @@ func ResolveAutoResourceProfile(clusterType types.ClusterType) types.ResourcePro case clusterType.IsLocal(): return types.ResourceProfileSmall - case clusterType.IsGKE() || clusterType.IsOpenShift(): + case clusterType.IsGKE() || clusterType.IsOpenShift() || clusterType.IsAKS() || clusterType.IsEKS(): return types.ResourceProfileMedium default: diff --git a/internal/clusterdefaults/clusterdefaults_test.go b/internal/clusterdefaults/clusterdefaults_test.go index 3607da5..9030330 100644 --- a/internal/clusterdefaults/clusterdefaults_test.go +++ b/internal/clusterdefaults/clusterdefaults_test.go @@ -102,6 +102,46 @@ func TestClusterDefaults(t *testing.T) { }, }, }, + { + name: "aks cluster", + clusterType: types.ClusterTypeAKS, + wantConfig: deployer.Config{ + Central: deployer.CentralConfig{ + Exposure: new(types.ExposureLoadBalancer), + PortForwarding: new(false), + }, + }, + }, + { + name: "infra aks cluster", + clusterType: types.ClusterTypeInfraAKS, + wantConfig: deployer.Config{ + Central: deployer.CentralConfig{ + Exposure: new(types.ExposureLoadBalancer), + PortForwarding: new(false), + }, + }, + }, + { + name: "eks cluster", + clusterType: types.ClusterTypeEKS, + wantConfig: deployer.Config{ + Central: deployer.CentralConfig{ + Exposure: new(types.ExposureLoadBalancer), + PortForwarding: new(false), + }, + }, + }, + { + name: "infra eks cluster", + clusterType: types.ClusterTypeInfraEKS, + wantConfig: deployer.Config{ + Central: deployer.CentralConfig{ + Exposure: new(types.ExposureLoadBalancer), + PortForwarding: new(false), + }, + }, + }, { name: "cluster does not override existing values", clusterType: types.ClusterTypeInfraGKE, @@ -182,6 +222,26 @@ func TestResolveAutoResourceProfile(t *testing.T) { clusterType: types.ClusterTypeInfraOpenShift4, want: types.ResourceProfileMedium, }, + { + name: "aks cluster", + clusterType: types.ClusterTypeAKS, + want: types.ResourceProfileMedium, + }, + { + name: "infra aks cluster", + clusterType: types.ClusterTypeInfraAKS, + want: types.ResourceProfileMedium, + }, + { + name: "eks cluster", + clusterType: types.ClusterTypeEKS, + want: types.ResourceProfileMedium, + }, + { + name: "infra eks cluster", + clusterType: types.ClusterTypeInfraEKS, + want: types.ResourceProfileMedium, + }, { name: "unknown cluster type", clusterType: types.ClusterTypeUnknown, diff --git a/internal/env/env.go b/internal/env/env.go index 8e01145..c8f34e7 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -19,6 +19,8 @@ import ( "golang.org/x/term" ) +const infraAWSAccountID = "051999192406" + var ( RunningInRoxieContainer bool RunningInteractively bool @@ -73,7 +75,7 @@ func ensureInitialized(log *logger.Logger) error { if err != nil { return err } - currentClusterType = DetectClusterType(kubeConfig, apiResources) + currentClusterType = DetectClusterType(log, kubeConfig, apiResources) initialized = true } return nil @@ -136,7 +138,13 @@ func Initialize(log *logger.Logger) error { // DetectClusterType implements the cluster type detection logic // This function is pure and testable - it doesn't invoke kubectl itself -func DetectClusterType(config KubeConfig, apiResources []string) types.ClusterType { +func DetectClusterType(log *logger.Logger, config KubeConfig, apiResources []string) types.ClusterType { + serverURL := getServerURL(config) + parsedURL, err := url.Parse(serverURL) + if err != nil && log != nil { + log.Warningf("Failed to parse cluster server URL %q: %v", serverURL, err) + } + if config.CurrentContext == "" { return types.ClusterTypeUnknown } @@ -151,6 +159,22 @@ func DetectClusterType(config KubeConfig, apiResources []string) types.ClusterTy return types.ClusterTypeGKE } + // AKS clusters have server hostnames ending in .azmk8s.io + if parsedURL != nil && strings.HasSuffix(parsedURL.Hostname(), ".azmk8s.io") { + if strings.Contains(parsedURL.Hostname(), "srox-temp-dev") { + return types.ClusterTypeInfraAKS + } + return types.ClusterTypeAKS + } + + // EKS clusters have server hostnames ending in .eks.amazonaws.com + if parsedURL != nil && strings.HasSuffix(parsedURL.Hostname(), ".eks.amazonaws.com") { + if strings.Contains(config.CurrentContext, ":"+infraAWSAccountID+":") { + return types.ClusterTypeInfraEKS + } + return types.ClusterTypeEKS + } + // Minikube clusters typically have context name "minikube". if contextLower == "minikube" || strings.HasPrefix(contextLower, "minikube-") { return types.ClusterTypeMinikube diff --git a/internal/env/env_test.go b/internal/env/env_test.go index 04ad89b..342bcd9 100644 --- a/internal/env/env_test.go +++ b/internal/env/env_test.go @@ -20,7 +20,7 @@ func TestDetectClusterType_InfraGKE(t *testing.T) { } apiResources := []string{"pods", "services", "deployments"} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeInfraGKE { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeInfraGKE) } @@ -38,12 +38,77 @@ func TestDetectClusterType_InfraGKE_ExactMatch(t *testing.T) { } apiResources := []string{"pods", "services"} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeInfraGKE { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeInfraGKE) } } +func TestDetectClusterType_AKS(t *testing.T) { + config := KubeConfig{ + CurrentContext: "my-aks-cluster", + Clusters: []KubeCluster{ + { + Name: "my-aks-cluster", + Server: "https://my-clust-my-rg-abc123.hcp.westus2.azmk8s.io:443", + }, + }, + } + result := DetectClusterType(nil, config, []string{"pods", "services"}) + assert.Equal(t, types.ClusterTypeAKS, result) +} + +func TestDetectClusterType_InfraAKS(t *testing.T) { + config := KubeConfig{ + CurrentContext: "mc-09-29-top-good-queen", + Clusters: []KubeCluster{ + { + Name: "mc-09-29-top-good-queen", + Server: "https://mc-09-29-t-srox-temp-dev-te-3fe608-0gsrb8e2.hcp.eastus.azmk8s.io:443", + }, + }, + } + result := DetectClusterType(nil, config, []string{"pods", "services"}) + assert.Equal(t, types.ClusterTypeInfraAKS, result) +} + +func TestDetectClusterType_AKS_NoClusters(t *testing.T) { + config := KubeConfig{ + CurrentContext: "my-aks-cluster", + Clusters: []KubeCluster{}, + } + result := DetectClusterType(nil, config, []string{"pods"}) + assert.Equal(t, types.ClusterTypeUnknown, result) +} + +func TestDetectClusterType_EKS(t *testing.T) { + config := KubeConfig{ + CurrentContext: "arn:aws:eks:eu-west-1:123456789012:cluster/my-cluster", + Clusters: []KubeCluster{ + { + Name: "arn:aws:eks:eu-west-1:123456789012:cluster/my-cluster", + Server: "https://ABCDEF1234567890.gr7.eu-west-1.eks.amazonaws.com", + }, + }, + } + result := DetectClusterType(nil, config, []string{"pods", "services"}) + assert.Equal(t, types.ClusterTypeEKS, result) +} + +func TestDetectClusterType_InfraEKS(t *testing.T) { + config := KubeConfig{ + CurrentContext: "arn:aws:eks:us-west-2:051999192406:cluster/mc-09-29-guide-sign-plus", + Clusters: []KubeCluster{ + { + Name: "arn:aws:eks:us-west-2:051999192406:cluster/mc-09-29-guide-sign-plus", + Server: "https://69D5BA7BBF406A1E387C6A0BC009795C.gr7.us-west-2.eks.amazonaws.com", + }, + }, + } + result := DetectClusterType(nil, config, []string{"pods", "services"}) + assert.Equal(t, types.ClusterTypeInfraEKS, result) +} + func TestDetectClusterType_InfraOpenShift4(t *testing.T) { config := KubeConfig{ CurrentContext: "admin", @@ -61,7 +126,7 @@ func TestDetectClusterType_InfraOpenShift4(t *testing.T) { "clusteroperators.config.openshift.io", } - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) assert.Equal(t, types.ClusterTypeInfraOpenShift4, result) } @@ -82,7 +147,7 @@ func TestDetectClusterType_OpenShift4(t *testing.T) { "clusteroperators.config.openshift.io", } - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) assert.Equal(t, types.ClusterTypeOpenShift4, result) } @@ -98,7 +163,7 @@ func TestDetectClusterType_OpenShift4_NoAPIResources(t *testing.T) { } apiResources := []string{"pods", "services"} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeUnknown { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeUnknown) } @@ -116,7 +181,7 @@ func TestDetectClusterType_Kind(t *testing.T) { } apiResources := []string{"pods", "services"} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeKind { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeKind) } @@ -134,7 +199,7 @@ func TestDetectClusterType_Kind_CaseInsensitive(t *testing.T) { } apiResources := []string{"pods"} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeKind { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeKind) } @@ -147,7 +212,7 @@ func TestDetectClusterType_EmptyContext(t *testing.T) { } apiResources := []string{} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeUnknown { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeUnknown) } @@ -165,7 +230,7 @@ func TestDetectClusterType_Minikube(t *testing.T) { } apiResources := []string{"pods", "services"} - result := DetectClusterType(config, apiResources) + result := DetectClusterType(nil, config, apiResources) if result != types.ClusterTypeMinikube { t.Errorf("DetectClusterType() = %v (%s), want %v", result, result.String(), types.ClusterTypeMinikube) } @@ -196,7 +261,7 @@ func TestDetectClusterType_GKE_DifferentProject(t *testing.T) { }, }, } - result := DetectClusterType(config, []string{"pods"}) + result := DetectClusterType(nil, config, []string{"pods"}) assert.Equal(t, types.ClusterTypeGKE, result) }) } @@ -326,6 +391,26 @@ func TestClusterTypeString(t *testing.T) { clusterType: types.ClusterTypeKind, want: "Kind", }, + { + name: "AKS", + clusterType: types.ClusterTypeAKS, + want: "AKS", + }, + { + name: "InfraAKS", + clusterType: types.ClusterTypeInfraAKS, + want: "AKS (infra)", + }, + { + name: "EKS", + clusterType: types.ClusterTypeEKS, + want: "EKS", + }, + { + name: "InfraEKS", + clusterType: types.ClusterTypeInfraEKS, + want: "EKS (infra)", + }, { name: "ClusterTypeUnknown", clusterType: types.ClusterTypeUnknown, @@ -428,7 +513,7 @@ func TestDefaultDetector_Detect(t *testing.T) { kubeConfig := KubeConfig{ CurrentContext: tt.kubeContext, } - got := DetectClusterType(kubeConfig, nil) + got := DetectClusterType(nil, kubeConfig, nil) if got != tt.want { t.Errorf("Detect(%q) = %v, want %v", tt.kubeContext, got, tt.want) } diff --git a/internal/types/cluster_type.go b/internal/types/cluster_type.go index de8d71b..0baa0dd 100644 --- a/internal/types/cluster_type.go +++ b/internal/types/cluster_type.go @@ -24,6 +24,14 @@ const ( ClusterTypeK3s ClusterType = "K3s" // ClusterTypeCRC represents a CRC (CodeReady Containers) cluster ClusterTypeCRC ClusterType = "CRC" + // ClusterTypeAKS represents a generic AKS (Azure Kubernetes Service) cluster. + ClusterTypeAKS ClusterType = "AKS" + // ClusterTypeInfraAKS represents an AKS cluster created via Infra. + ClusterTypeInfraAKS ClusterType = "InfraAKS" + // ClusterTypeEKS represents a generic EKS (Amazon Elastic Kubernetes Service) cluster. + ClusterTypeEKS ClusterType = "EKS" + // ClusterTypeInfraEKS represents an EKS cluster created via Infra. + ClusterTypeInfraEKS ClusterType = "InfraEKS" ) func (ct ClusterType) IsGKE() bool { @@ -34,6 +42,14 @@ func (ct ClusterType) IsOpenShift() bool { return ct == ClusterTypeInfraOpenShift4 || ct == ClusterTypeOpenShift4 } +func (ct ClusterType) IsAKS() bool { + return ct == ClusterTypeInfraAKS || ct == ClusterTypeAKS +} + +func (ct ClusterType) IsEKS() bool { + return ct == ClusterTypeInfraEKS || ct == ClusterTypeEKS +} + // String returns the string representation of a ClusterType func (ct ClusterType) String() string { switch ct { @@ -41,6 +57,10 @@ func (ct ClusterType) String() string { return "GKE (infra)" case ClusterTypeInfraOpenShift4: return "OpenShift4 (infra)" + case ClusterTypeInfraAKS: + return "AKS (infra)" + case ClusterTypeInfraEKS: + return "EKS (infra)" default: return string(ct) } @@ -57,6 +77,10 @@ func AllClusterTypes() []ClusterType { ClusterTypeCRC, ClusterTypeInfraOpenShift4, ClusterTypeOpenShift4, + ClusterTypeAKS, + ClusterTypeInfraAKS, + ClusterTypeEKS, + ClusterTypeInfraEKS, } } @@ -90,3 +114,7 @@ func (ct ClusterType) IsLocal() bool { } return false } + +func (ct ClusterType) SupportsLoadBalancer() bool { + return ct.IsGKE() || ct.IsOpenShift() || ct.IsAKS() || ct.IsEKS() +} diff --git a/internal/types/cluster_type_test.go b/internal/types/cluster_type_test.go index 40d623b..c7b96e5 100644 --- a/internal/types/cluster_type_test.go +++ b/internal/types/cluster_type_test.go @@ -21,6 +21,10 @@ func TestClusterTypeMarshalYAML(t *testing.T) { {ClusterTypeMinikube, "Minikube"}, {ClusterTypeK3s, "K3s"}, {ClusterTypeCRC, "CRC"}, + {ClusterTypeAKS, "AKS"}, + {ClusterTypeInfraAKS, "InfraAKS"}, + {ClusterTypeEKS, "EKS"}, + {ClusterTypeInfraEKS, "InfraEKS"}, } for _, tt := range tests { t.Run(tt.expected, func(t *testing.T) { @@ -45,6 +49,10 @@ func TestClusterTypeUnmarshalYAML(t *testing.T) { {"K3s", ClusterTypeK3s}, {"CRC", ClusterTypeCRC}, {"Unknown", ClusterTypeUnknown}, + {"AKS", ClusterTypeAKS}, + {"InfraAKS", ClusterTypeInfraAKS}, + {"EKS", ClusterTypeEKS}, + {"InfraEKS", ClusterTypeInfraEKS}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) {