From 87baa7d560d5e1d3e771bf4734b3ef89f86151d6 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Tue, 29 Sep 2026 13:40:56 +0200 Subject: [PATCH 1/2] Support AKS clusters --- internal/clusterdefaults/clusterdefaults.go | 4 +- .../clusterdefaults/clusterdefaults_test.go | 30 ++++++++ internal/env/env.go | 18 ++++- internal/env/env_test.go | 69 ++++++++++++++++--- internal/types/cluster_type.go | 16 +++++ internal/types/cluster_type_test.go | 4 ++ 6 files changed, 126 insertions(+), 15 deletions(-) diff --git a/internal/clusterdefaults/clusterdefaults.go b/internal/clusterdefaults/clusterdefaults.go index bda3f59..fff83df 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(): return types.ResourceProfileMedium default: diff --git a/internal/clusterdefaults/clusterdefaults_test.go b/internal/clusterdefaults/clusterdefaults_test.go index 3607da5..9f2b38b 100644 --- a/internal/clusterdefaults/clusterdefaults_test.go +++ b/internal/clusterdefaults/clusterdefaults_test.go @@ -102,6 +102,26 @@ 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: "cluster does not override existing values", clusterType: types.ClusterTypeInfraGKE, @@ -182,6 +202,16 @@ 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: "unknown cluster type", clusterType: types.ClusterTypeUnknown, diff --git a/internal/env/env.go b/internal/env/env.go index 8e01145..6f63a25 100644 --- a/internal/env/env.go +++ b/internal/env/env.go @@ -73,7 +73,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 +136,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 +157,14 @@ 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 + } + // 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..7a5e800 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,49 @@ 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_InfraOpenShift4(t *testing.T) { config := KubeConfig{ CurrentContext: "admin", @@ -61,7 +98,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 +119,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 +135,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 +153,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 +171,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 +184,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 +202,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 +233,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 +363,16 @@ 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: "ClusterTypeUnknown", clusterType: types.ClusterTypeUnknown, @@ -428,7 +475,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..4f3063b 100644 --- a/internal/types/cluster_type.go +++ b/internal/types/cluster_type.go @@ -24,6 +24,10 @@ 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" ) func (ct ClusterType) IsGKE() bool { @@ -34,6 +38,10 @@ func (ct ClusterType) IsOpenShift() bool { return ct == ClusterTypeInfraOpenShift4 || ct == ClusterTypeOpenShift4 } +func (ct ClusterType) IsAKS() bool { + return ct == ClusterTypeInfraAKS || ct == ClusterTypeAKS +} + // String returns the string representation of a ClusterType func (ct ClusterType) String() string { switch ct { @@ -41,6 +49,8 @@ func (ct ClusterType) String() string { return "GKE (infra)" case ClusterTypeInfraOpenShift4: return "OpenShift4 (infra)" + case ClusterTypeInfraAKS: + return "AKS (infra)" default: return string(ct) } @@ -57,6 +67,8 @@ func AllClusterTypes() []ClusterType { ClusterTypeCRC, ClusterTypeInfraOpenShift4, ClusterTypeOpenShift4, + ClusterTypeAKS, + ClusterTypeInfraAKS, } } @@ -90,3 +102,7 @@ func (ct ClusterType) IsLocal() bool { } return false } + +func (ct ClusterType) SupportsLoadBalancer() bool { + return ct.IsGKE() || ct.IsOpenShift() || ct.IsAKS() +} diff --git a/internal/types/cluster_type_test.go b/internal/types/cluster_type_test.go index 40d623b..8d90ed0 100644 --- a/internal/types/cluster_type_test.go +++ b/internal/types/cluster_type_test.go @@ -21,6 +21,8 @@ func TestClusterTypeMarshalYAML(t *testing.T) { {ClusterTypeMinikube, "Minikube"}, {ClusterTypeK3s, "K3s"}, {ClusterTypeCRC, "CRC"}, + {ClusterTypeAKS, "AKS"}, + {ClusterTypeInfraAKS, "InfraAKS"}, } for _, tt := range tests { t.Run(tt.expected, func(t *testing.T) { @@ -45,6 +47,8 @@ func TestClusterTypeUnmarshalYAML(t *testing.T) { {"K3s", ClusterTypeK3s}, {"CRC", ClusterTypeCRC}, {"Unknown", ClusterTypeUnknown}, + {"AKS", ClusterTypeAKS}, + {"InfraAKS", ClusterTypeInfraAKS}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) { From de267ef4042466bd34ecda8435774e04ffacc787 Mon Sep 17 00:00:00 2001 From: Moritz Clasmeier Date: Tue, 29 Sep 2026 14:17:35 +0200 Subject: [PATCH 2/2] EKS suport (untested) --- internal/clusterdefaults/clusterdefaults.go | 2 +- .../clusterdefaults/clusterdefaults_test.go | 30 +++++++++++++++ internal/env/env.go | 10 +++++ internal/env/env_test.go | 38 +++++++++++++++++++ internal/types/cluster_type.go | 14 ++++++- internal/types/cluster_type_test.go | 4 ++ 6 files changed, 96 insertions(+), 2 deletions(-) diff --git a/internal/clusterdefaults/clusterdefaults.go b/internal/clusterdefaults/clusterdefaults.go index fff83df..3d40388 100644 --- a/internal/clusterdefaults/clusterdefaults.go +++ b/internal/clusterdefaults/clusterdefaults.go @@ -72,7 +72,7 @@ func ResolveAutoResourceProfile(clusterType types.ClusterType) types.ResourcePro case clusterType.IsLocal(): return types.ResourceProfileSmall - case clusterType.IsGKE() || clusterType.IsOpenShift() || clusterType.IsAKS(): + 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 9f2b38b..9030330 100644 --- a/internal/clusterdefaults/clusterdefaults_test.go +++ b/internal/clusterdefaults/clusterdefaults_test.go @@ -122,6 +122,26 @@ func TestClusterDefaults(t *testing.T) { }, }, }, + { + 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, @@ -212,6 +232,16 @@ func TestResolveAutoResourceProfile(t *testing.T) { 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 6f63a25..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 @@ -165,6 +167,14 @@ func DetectClusterType(log *logger.Logger, config KubeConfig, apiResources []str 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 7a5e800..342bcd9 100644 --- a/internal/env/env_test.go +++ b/internal/env/env_test.go @@ -81,6 +81,34 @@ func TestDetectClusterType_AKS_NoClusters(t *testing.T) { 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", @@ -373,6 +401,16 @@ func TestClusterTypeString(t *testing.T) { 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, diff --git a/internal/types/cluster_type.go b/internal/types/cluster_type.go index 4f3063b..0baa0dd 100644 --- a/internal/types/cluster_type.go +++ b/internal/types/cluster_type.go @@ -28,6 +28,10 @@ const ( 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 { @@ -42,6 +46,10 @@ 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 { @@ -51,6 +59,8 @@ func (ct ClusterType) String() string { return "OpenShift4 (infra)" case ClusterTypeInfraAKS: return "AKS (infra)" + case ClusterTypeInfraEKS: + return "EKS (infra)" default: return string(ct) } @@ -69,6 +79,8 @@ func AllClusterTypes() []ClusterType { ClusterTypeOpenShift4, ClusterTypeAKS, ClusterTypeInfraAKS, + ClusterTypeEKS, + ClusterTypeInfraEKS, } } @@ -104,5 +116,5 @@ func (ct ClusterType) IsLocal() bool { } func (ct ClusterType) SupportsLoadBalancer() bool { - return ct.IsGKE() || ct.IsOpenShift() || ct.IsAKS() + 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 8d90ed0..c7b96e5 100644 --- a/internal/types/cluster_type_test.go +++ b/internal/types/cluster_type_test.go @@ -23,6 +23,8 @@ func TestClusterTypeMarshalYAML(t *testing.T) { {ClusterTypeCRC, "CRC"}, {ClusterTypeAKS, "AKS"}, {ClusterTypeInfraAKS, "InfraAKS"}, + {ClusterTypeEKS, "EKS"}, + {ClusterTypeInfraEKS, "InfraEKS"}, } for _, tt := range tests { t.Run(tt.expected, func(t *testing.T) { @@ -49,6 +51,8 @@ func TestClusterTypeUnmarshalYAML(t *testing.T) { {"Unknown", ClusterTypeUnknown}, {"AKS", ClusterTypeAKS}, {"InfraAKS", ClusterTypeInfraAKS}, + {"EKS", ClusterTypeEKS}, + {"InfraEKS", ClusterTypeInfraEKS}, } for _, tt := range tests { t.Run(tt.input, func(t *testing.T) {