diff --git a/cmd/kflex/ctx/ctx.go b/cmd/kflex/ctx/ctx.go index cdcd1c7..4ea95fe 100644 --- a/cmd/kflex/ctx/ctx.go +++ b/cmd/kflex/ctx/ctx.go @@ -50,29 +50,34 @@ func (c *CPCtx) Context(chattyStatus, failIfNone bool) { switch c.CP.Name { case "": - util.PrintStatus("Checking for saved initial context...", done, &wg, chattyStatus) + util.PrintStatus("Checking for saved hosting cluster context...", done, &wg, chattyStatus) time.Sleep(1 * time.Second) done <- true - if kubeconfig.IsInitialConfigSet(kconf) { - util.PrintStatus("Switching to initial context...", done, &wg, chattyStatus) - if err = kubeconfig.SwitchToInitialContext(kconf, false); err != nil { - fmt.Fprintf(os.Stderr, "Error switching kubeconfig to initial context: %s\n", err) + if kubeconfig.IsHostingClusterContextPreferenceSet(kconf) { + util.PrintStatus("Switching to hosting cluster context...", done, &wg, chattyStatus) + if err = kubeconfig.SwitchToHostingClusterContext(kconf, false); err != nil { + fmt.Fprintf(os.Stderr, "Error switching kubeconfig to hosting cluster context: %s\n", err) os.Exit(1) } done <- true } else if failIfNone { - fmt.Fprintln(os.Stderr, "The initial (hosting) context is not known!\n"+ - "You can make it known to kflex by doing `kubectl config use-context $name_of_hosting_context` "+ - "and then either `kflex create ...` or `kflex ctx $some_control_plane_name") - os.Exit(1) + if !c.isCurrentContextHostingClusterContext() { + fmt.Fprintln(os.Stderr, "The hosting cluster context is not known!\n"+ + "You can make it known to kflex by doing `kubectl config use-context $name_of_hosting_context` "+ + "and then either `kflex ctx` or `kflex create ...`") + os.Exit(1) + } + util.PrintStatus("Hosting cluster context not set, setting it to current context", done, &wg, chattyStatus) + kubeconfig.SetHostingClusterContextPreference(kconf) + done <- true } default: ctxName := certs.GenerateContextName(c.Name) util.PrintStatus(fmt.Sprintf("Switching to context %s...", ctxName), done, &wg, chattyStatus) if err = kubeconfig.SwitchContext(kconf, c.Name); err != nil { fmt.Fprintf(os.Stderr, "kubeconfig context %s not found, trying to load from server...\n", err) - if err := c.switchToInitialContextAndWrite(kconf); err != nil { - fmt.Fprintf(os.Stderr, "Error switching back to initial context: %s\n", err) + if err := c.switchToHostingClusterContextAndWrite(kconf); err != nil { + fmt.Fprintf(os.Stderr, "Error switching back to hosting cluster context: %s\n", err) os.Exit(1) } if err = c.loadAndMergeFromServer(kconf); err != nil { @@ -125,9 +130,9 @@ func (c *CPCtx) loadAndMergeFromServer(kconfig *api.Config) error { return nil } -func (c *CPCtx) switchToInitialContextAndWrite(kconf *api.Config) error { - if kubeconfig.IsInitialConfigSet(kconf) { - if err := kubeconfig.SwitchToInitialContext(kconf, false); err != nil { +func (c *CPCtx) switchToHostingClusterContextAndWrite(kconf *api.Config) error { + if kubeconfig.IsHostingClusterContextPreferenceSet(kconf) { + if err := kubeconfig.SwitchToHostingClusterContext(kconf, false); err != nil { return err } if err := kubeconfig.WriteKubeconfig(c.Ctx, kconf); err != nil { @@ -136,3 +141,13 @@ func (c *CPCtx) switchToInitialContextAndWrite(kconf *api.Config) error { } return nil } + +func (c *CPCtx) isCurrentContextHostingClusterContext() bool { + clientsetp, err := kfclient.GetClientSet(c.Kubeconfig) + if err != nil { + fmt.Fprintf(os.Stderr, "Error getting clientset: %s\n", err) + os.Exit(1) + } + clientset := *clientsetp + return util.CheckResourceExists(clientset, "tenancy.kflex.kubestellar.org", "v1alpha1", "controlplanes") +} diff --git a/cmd/kflex/delete/delete.go b/cmd/kflex/delete/delete.go index 5b2121f..14102df 100644 --- a/cmd/kflex/delete/delete.go +++ b/cmd/kflex/delete/delete.go @@ -49,7 +49,7 @@ func (c *CPDelete) Delete(chattyStatus bool) { os.Exit(1) } - if err = kubeconfig.SwitchToInitialContext(kconf, false); err != nil { + if err = kubeconfig.SwitchToHostingClusterContext(kconf, false); err != nil { fmt.Fprintf(os.Stderr, "no initial kubeconfig context was found: %s\n", err) } diff --git a/cmd/kflex/init/init.go b/cmd/kflex/init/init.go index 1d8848b..74d6001 100644 --- a/cmd/kflex/init/init.go +++ b/cmd/kflex/init/init.go @@ -30,6 +30,7 @@ import ( "github.com/kubestellar/kubeflex/pkg/client" "github.com/kubestellar/kubeflex/pkg/helm" + kcfg "github.com/kubestellar/kubeflex/pkg/kubeconfig" "github.com/kubestellar/kubeflex/pkg/util" ) @@ -47,6 +48,14 @@ func Init(ctx context.Context, kubeconfig, version, buildDate string, domain, ex util.PrintStatus(fmt.Sprintf("Kubeflex %s %s", version, buildDate), done, &wg, chattyStatus) done <- true + util.PrintStatus("Setting hosting cluster preference in kubeconfig", done, &wg, chattyStatus) + err = kcfg.SaveHostingClusterContextPreference(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "Error setting hosting cluster context preference: %v\n", err) + os.Exit(1) + } + done <- true + util.PrintStatus("Ensuring kubeflex-system namespace...", done, &wg, chattyStatus) ensureSystemNamespace(kubeconfig, util.SystemNamespace) done <- true diff --git a/cmd/kflex/main.go b/cmd/kflex/main.go index c609df6..b269dc8 100644 --- a/cmd/kflex/main.go +++ b/cmd/kflex/main.go @@ -159,7 +159,7 @@ var deleteCmd = &cobra.Command{ var ctxCmd = &cobra.Command{ Use: "ctx", Short: "Switch kubeconfig context to a control plane instance", - Long: `Running without an argument switches the context back to the initial context, + Long: `Running without an argument switches the context back to the hosting cluster context, while providing the control plane name as argument switches the context to that control plane`, Args: cobra.MaximumNArgs(1), diff --git a/docs/users.md b/docs/users.md index 39dafc9..59ff756 100644 --- a/docs/users.md +++ b/docs/users.md @@ -160,7 +160,7 @@ been stored in an extension in your kubeconfig file (see failure will look like the following. $ kflex create cp1 - ✔ Checking for saved initial context... + ✔ Checking for saved hosting cluster context... ◐ Creating new control plane cp1 of type k8s ...Error creating instance: no matches for kind "ControlPlane" in version "tenancy.kflex.kubestellar.org/v1alpha1" To create a new control plane with name `cp1` using the KubeFlex CLI: @@ -311,8 +311,8 @@ Let's create an OCM control plane: ```shell $ kflex create cp3 --type ocm -✔ Checking for saved initial context... -✔ Switching to initial context... +✔ Checking for saved hosting cluster context... +✔ Switching to hosting cluster context... ✔ Creating new control plane cp3... ✔ Waiting for API server to become ready... ``` @@ -459,7 +459,7 @@ Let's create a vcluster control plane: ```shell $ kflex create cp2 --type vcluster -✔ Checking for saved initial context... +✔ Checking for saved hosting cluster context... ✔ Creating new control plane cp2... ✔ Waiting for API server to become ready... ``` diff --git a/pkg/kubeconfig/config-util.go b/pkg/kubeconfig/config-util.go index bc27766..f5e21f5 100644 --- a/pkg/kubeconfig/config-util.go +++ b/pkg/kubeconfig/config-util.go @@ -17,6 +17,7 @@ limitations under the License. package kubeconfig import ( + "context" "encoding/json" "fmt" @@ -46,8 +47,8 @@ func merge(existing, new *clientcmdapi.Config) error { existing.Contexts[k] = v } - if !IsInitialConfigSet(existing) { - saveInitialContextName(existing) + if !IsHostingClusterContextPreferenceSet(existing) { + SetHostingClusterContextPreference(existing) } // set the current context to the nex context @@ -83,9 +84,9 @@ func DeleteContext(config *clientcmdapi.Config, cpName string) error { return nil } -func SwitchToInitialContext(config *clientcmdapi.Config, removeExtension bool) error { - if !IsInitialConfigSet(config) { - return nil +func SwitchToHostingClusterContext(config *clientcmdapi.Config, removeExtension bool) error { + if !IsHostingClusterContextPreferenceSet(config) { + return fmt.Errorf("hosting cluster preference context not set") } // found that the only way to unmarshal the runtime.Object into a ConfigMap // was to use the unMarshallCM() function based on json marshal/unmarshal @@ -96,7 +97,7 @@ func SwitchToInitialContext(config *clientcmdapi.Config, removeExtension bool) e contextData, ok := cm.Data[InitialContextName] if !ok { - return fmt.Errorf("initial context data not set") + return fmt.Errorf("hosting cluster preference context data not set") } config.CurrentContext = contextData @@ -107,7 +108,7 @@ func SwitchToInitialContext(config *clientcmdapi.Config, removeExtension bool) e return nil } -func saveInitialContextName(config *clientcmdapi.Config) { +func SetHostingClusterContextPreference(config *clientcmdapi.Config) { runtimeObjects := make(map[string]runtime.Object) runtimeObjects[ConfigExtensionName] = &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ @@ -123,7 +124,7 @@ func saveInitialContextName(config *clientcmdapi.Config) { } } -func IsInitialConfigSet(config *clientcmdapi.Config) bool { +func IsHostingClusterContextPreferenceSet(config *clientcmdapi.Config) bool { if config.Preferences.Extensions != nil { _, ok := config.Preferences.Extensions[ConfigExtensionName] if ok { @@ -133,6 +134,15 @@ func IsInitialConfigSet(config *clientcmdapi.Config) bool { return false } +func SaveHostingClusterContextPreference(ctx context.Context) error { + kconfig, err := LoadKubeconfig(ctx) + if err != nil { + return fmt.Errorf("setHostingClusterContextPreference: error loading kubeconfig %s", err) + } + SetHostingClusterContextPreference(kconfig) + return WriteKubeconfig(ctx, kconfig) +} + func unMarshallCM(obj runtime.Object) (*corev1.ConfigMap, error) { jsonData, err := json.Marshal(obj) if err != nil { diff --git a/pkg/kubeconfig/kubeconfig.go b/pkg/kubeconfig/kubeconfig.go index 18d89c6..ce29fc7 100644 --- a/pkg/kubeconfig/kubeconfig.go +++ b/pkg/kubeconfig/kubeconfig.go @@ -82,18 +82,18 @@ func loadControlPlaneKubeconfig(ctx context.Context, client kubernetes.Clientset namespace := util.GenerateNamespaceFromControlPlaneName(name) var ks *v1.Secret + var errGet error err := wait.PollUntilContextTimeout(ctx, 1*time.Second, 5*time.Minute, false, func(ctx context.Context) (bool, error) { - var err error - ks, err = client.CoreV1().Secrets(namespace).Get(ctx, + ks, errGet = client.CoreV1().Secrets(namespace).Get(ctx, util.GetKubeconfSecretNameByControlPlaneType(controlPlaneType), metav1.GetOptions{}) - if err != nil { + if errGet != nil { return false, nil } return true, nil }) if err != nil { - return nil, fmt.Errorf("error waiting for control plane kubeconfig secret: %s", err) + return nil, fmt.Errorf("error waiting for control plane kubeconfig secret: %s, %s", err, errGet) } key := util.GetKubeconfSecretKeyNameByControlPlaneType(controlPlaneType) diff --git a/pkg/util/ocp.go b/pkg/util/ocp.go index 3a16080..3332e13 100644 --- a/pkg/util/ocp.go +++ b/pkg/util/ocp.go @@ -1,37 +1,9 @@ package util import ( - "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" ) -const ( - kubeflexServiceAccount = "system:serviceaccount:kubeflex-system:kubeflex-controller-manager" -) - func IsOpenShift(clientset kubernetes.Clientset) bool { - return CheckResourceExists(clientset, "project.openshift.io", "v1", "Project") -} - -func CheckResourceExists(clientset kubernetes.Clientset, group, version, kind string) bool { - discoveryClient := clientset.Discovery() - - exists, _ := checkResourceExists(discoveryClient, group, version, kind) - - return exists -} - -func checkResourceExists(discoveryClient discovery.DiscoveryInterface, group, version, kind string) (bool, error) { - resourceList, err := discoveryClient.ServerResourcesForGroupVersion(group + "/" + version) - if err != nil { - return false, err - } - - for _, resource := range resourceList.APIResources { - if resource.Kind == kind { - return true, nil - } - } - - return false, nil + return CheckResourceExists(clientset, "project.openshift.io", "v1", "projects") } diff --git a/pkg/util/unstructured.go b/pkg/util/unstructured.go index 81d5d2d..9e424d8 100644 --- a/pkg/util/unstructured.go +++ b/pkg/util/unstructured.go @@ -25,6 +25,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" "k8s.io/client-go/restmapper" ) @@ -124,3 +125,26 @@ func IsClusterScoped(gvk schema.GroupVersionKind, apiResourceLists []*metav1.API } return false, fmt.Errorf("resource %s in group %s with version %s was not found", gvk.Kind, gvk.Group, gvk.Version) } + +func CheckResourceExists(clientset kubernetes.Clientset, group, version, name string) bool { + discoveryClient := clientset.Discovery() + + exists, _ := findResource(discoveryClient, group, version, name) + + return exists +} + +func findResource(discoveryClient discovery.DiscoveryInterface, group, version, name string) (bool, error) { + resourceList, err := discoveryClient.ServerResourcesForGroupVersion(group + "/" + version) + if err != nil { + return false, err + } + + for _, resource := range resourceList.APIResources { + if resource.Name == name { + return true, nil + } + } + + return false, nil +}