diff --git a/pkg/kubeconfig/extensions.go b/pkg/kubeconfig/extensions.go index 16bb0468..781e768f 100644 --- a/pkg/kubeconfig/extensions.go +++ b/pkg/kubeconfig/extensions.go @@ -19,6 +19,10 @@ package kubeconfig import ( "encoding/json" "fmt" + tenancyv1alpha1 "github.com/kubestellar/kubeflex/api/v1alpha1" + "github.com/kubestellar/kubeflex/cmd/kflex/common" + "github.com/kubestellar/kubeflex/pkg/client" + "k8s.io/client-go/tools/clientcmd" "time" corev1 "k8s.io/api/core/v1" @@ -234,12 +238,32 @@ func CheckHostingClusterContextName(kconf clientcmdapi.Config) string { } } -func VerifyControlPlaneOnHostingCluster(kconf clientcmdapi.Config, ctxName string) string { - // TODO: implement actual control plane verification logic - return DiagnosisStatusOK +func VerifyControlPlaneOnHostingCluster(cp common.CP, ctxName string) string { + c, err := client.GetClient(cp.Kubeconfig) + if err != nil { + return DiagnosisStatusCritical + } + + var controlPlanes tenancyv1alpha1.ControlPlaneList + if err := c.List(cp.Ctx, &controlPlanes); err != nil { + return DiagnosisStatusCritical + } + + for _, controlPlane := range controlPlanes.Items { + if controlPlane.Name == ctxName { + return DiagnosisStatusOK + } + } + + return DiagnosisStatusMissing } -func CheckContextScopeKubeflexExtensionSet(kconf clientcmdapi.Config, ctxName string) string { +func CheckContextScopeKubeflexExtensionSet(cp common.CP, ctxName string) string { + kconf, err := clientcmd.LoadFromFile(cp.Kubeconfig) + if err != nil { + return DiagnosisStatusCritical + } + ctx, ok := kconf.Contexts[ctxName] if !ok { return DiagnosisStatusMissing // Context not found @@ -269,10 +293,37 @@ func CheckContextScopeKubeflexExtensionSet(kconf clientcmdapi.Config, ctxName st return DiagnosisStatusWarning } - status := VerifyControlPlaneOnHostingCluster(kconf, ctxName) + status := VerifyControlPlaneOnHostingCluster(cp, ctxName) if status != DiagnosisStatusOK { return status } return DiagnosisStatusOK } + +func CountKubeflexControlPlaneContexts(kconf clientcmdapi.Config) int { + count := 0 + + for _, ctx := range kconf.Contexts { + ext := ctx.Extensions + if ext == nil { + continue + } + + runtimeObj, ok := ext[ExtensionKubeflexKey] + if !ok { + continue + } + + var extData RuntimeKubeflexExtension + if err := ConvertRuntimeObjectToRuntimeExtension(runtimeObj, &extData); err != nil { + continue + } + + if cpName, ok := extData.Data[ExtensionControlPlaneName]; ok && cpName != "" { + count++ + } + } + + return count +} diff --git a/pkg/kubeconfig/extensions_test.go b/pkg/kubeconfig/extensions_test.go index ba899649..a8687a87 100644 --- a/pkg/kubeconfig/extensions_test.go +++ b/pkg/kubeconfig/extensions_test.go @@ -2,6 +2,9 @@ package kubeconfig import ( "fmt" + "github.com/kubestellar/kubeflex/cmd/kflex/common" + "k8s.io/client-go/tools/clientcmd" + "os" "testing" "k8s.io/apimachinery/pkg/runtime" @@ -206,6 +209,27 @@ func TestCheckHostingClusterContextNameSingle(t *testing.T) { } } +func writeKubeconfigToTempFile(t *testing.T, kconf *api.Config) string { + data, err := clientcmd.Write(*kconf) + if err != nil { + t.Fatalf("failed to serialize kubeconfig: %v", err) + } + + tmpfile, err := os.CreateTemp("", "kubeconfig-*.yaml") + if err != nil { + t.Fatalf("failed to create temp file: %v", err) + } + + if _, err := tmpfile.Write(data); err != nil { + t.Fatalf("failed to write kubeconfig: %v", err) + } + if err := tmpfile.Close(); err != nil { + t.Fatalf("failed to close temp file: %v", err) + } + + return tmpfile.Name() +} + func TestCheckContextScopeKubeflexExtensionSetNoKubeflexExtensions(t *testing.T) { kconf := api.NewConfig() kconf.Clusters["cluster1"] = &api.Cluster{Server: "https://example.com:6443"} @@ -216,7 +240,11 @@ func TestCheckContextScopeKubeflexExtensionSetNoKubeflexExtensions(t *testing.T) } kconf.CurrentContext = "ctx1" - result := CheckContextScopeKubeflexExtensionSet(*kconf, "ctx1") + tmpFile := writeKubeconfigToTempFile(t, kconf) + defer os.Remove(tmpFile) + + cp := common.NewCP(tmpFile) + result := CheckContextScopeKubeflexExtensionSet(cp, "ctx1") if result != DiagnosisStatusMissing { t.Errorf("Expected %s, got %s", DiagnosisStatusMissing, result) } @@ -237,7 +265,11 @@ func TestCheckContextScopeKubeflexExtensionSetNoData(t *testing.T) { } kconf.CurrentContext = "ctx1" - result := CheckContextScopeKubeflexExtensionSet(*kconf, "ctx1") + tmpFile := writeKubeconfigToTempFile(t, kconf) + defer os.Remove(tmpFile) + + cp := common.NewCP(tmpFile) + result := CheckContextScopeKubeflexExtensionSet(cp, "ctx1") if result != DiagnosisStatusCritical { t.Errorf("Expected %s, got %s", DiagnosisStatusCritical, result) } @@ -258,8 +290,33 @@ func TestCheckContextScopeKubeflexExtensionSetPartialData(t *testing.T) { } kconf.CurrentContext = "ctx1" - result := CheckContextScopeKubeflexExtensionSet(*kconf, "ctx1") + tmpFile := writeKubeconfigToTempFile(t, kconf) + defer os.Remove(tmpFile) + + cp := common.NewCP(tmpFile) + result := CheckContextScopeKubeflexExtensionSet(cp, "ctx1") if result != DiagnosisStatusWarning { t.Errorf("Expected %s, got %s", DiagnosisStatusWarning, result) } } + +func TestCountKubeflexControlPlaneContextsZero(t *testing.T) { + kconf := api.NewConfig() + kconf.Clusters["cluster1"] = &api.Cluster{Server: "https://example.com:6443"} + kconf.AuthInfos["user1"] = &api.AuthInfo{Token: "token"} + + ext := NewRuntimeKubeflexExtension() + ext.Data[ExtensionContextsIsHostingCluster] = "true" + + kconf.Contexts["ctx1"] = &api.Context{ + Cluster: "cluster1", + AuthInfo: "user1", + Extensions: map[string]runtime.Object{ExtensionKubeflexKey: ext}, + } + + expected := 0 + got := CountKubeflexControlPlaneContexts(*kconf) + if got != expected { + t.Errorf("Expected %d control plane context(s), got %d", expected, got) + } +}