2

How can I get the current namespace of an out-of-cluster Go Kubernetes client using the client-go library?

I am using this code example: https://github.com/kubernetes/client-go/blob/master/examples/out-of-cluster-client-configuration/main.go for out-cluster client.

Here is an extract of my KUBECONFIG which might help in clarifying:

- context:
    cluster: kind-kind
    namespace: mynamespace
    user: kind-kind
  name: kind-kind
current-context: kind-kind

I'd like to find an easy way to retrieve mynamespace.

Fabrice Jammes
  • 2,275
  • 1
  • 26
  • 39

2 Answers2

3

There is no such thing as "current namespace".

All namespaces exist at the same time and you can access them all at the same time. The idea of the "current namespace" comes from additional plugins like kubens mostly. The reality is, that it is just a detail to the kube context for the given cluster as explained here where you can for example specify which set of credential/user combination to access a given namespace. As well as a default namespace to execute commands against via kubectl if you don't specify a namespace at all, as you can read in the documentation. Thus the object of type config also does not have the capability to hold any information about that as you can read here

With tools like kubens that allow you to "persist" which namespace you want to execute commands against by default, you get some extra options but they still need to store that state, which they do in state files for each cluster like .kube/kubens/test.

In the code example you posted, you can also see that a namespace is actually specified via the variable namespace, that is set to default here

You can alternatively try to specify the namespace via flag like

func main() {
    var kubeconfig *string
    var namespace *string
    if home := homedir.HomeDir(); home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    } else {
        kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    }
    namespace = flag.String("namespace", "default", "namespace to execute commands against")
    flag.Parse()

    // use the current context in kubeconfig
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err.Error())
    }

    // create the clientset
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }
    for {
        pods, err := clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{})
        if err != nil {
            panic(err.Error())
        }
        fmt.Printf("There are %d pods in the cluster\n", len(pods.Items))

        // Examples for error handling:
        // - Use helper functions like e.g. errors.IsNotFound()
        // - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message
        
        pod := "example-xxxxx"
        _, err = clientset.CoreV1().Pods(*namespace).Get(context.TODO(), pod, metav1.GetOptions{})
        if errors.IsNotFound(err) {
            fmt.Printf("Pod %s in namespace %s not found\n", pod, *namespace)
        } else if statusError, isStatus := err.(*errors.StatusError); isStatus {
            fmt.Printf("Error getting pod %s in namespace %s: %v\n",
                pod, *namespace, statusError.ErrStatus.Message)
        } else if err != nil {
            panic(err.Error())
        } else {
            fmt.Printf("Found pod %s in namespace %s\n", pod, *namespace)
        }

        time.Sleep(10 * time.Second)
    }
}

If you want to replicate a similar behaviour, to what kubens does, you can store the namespace in a file and read it from there, if nothing else is passed as flag, and switch to default if the files doesn't exist, i.e. you never accessed the cluster:

func main() {
    var kubeconfig *string
    var namespace *string
    if home := homedir.HomeDir(); home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    } else {
        kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    }
    namespace = flag.String("namespace", "", "namespace to execute commands against")
    flag.Parse()

    // use the current context in kubeconfig
    config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
    if err != nil {
        panic(err.Error())
    }

    // create the clientset
    clientset, err := kubernetes.NewForConfig(config)
    if err != nil {
        panic(err.Error())
    }

    // write to file if namespace is given as flag
    if *namespace != "" {
        dat := []byte(*namespace)
        err := os.WriteFile(config.ServerName, dat, 0644)
        if err != nil {
            panic(err.Error())
        }
    }
    // read from file if namespace is not given as flag, otherwise use default
    if *namespace == "" {
        _, doesNotExist := os.Stat(config.ServerName)
        if doesNotExist == nil {
            dat, err := os.ReadFile(config.ServerName)
            if err != nil {
                panic(err.Error())
            }
            *namespace = string(dat)
        }
        if doesNotExist != nil {
            *namespace = "default"
        }

    }

Rick Rackow
  • 1,490
  • 8
  • 19
  • Thanks! however, your response does not fully address my question. Allow me to clarify: Is there a direct way to obtain the namespace in my out-cluster client-go program that mimics the approach used by the 'kubectl' command when it fetches the namespace value from the KUBECONFIG file? I'm seeking a simple method to access the namespace stored within the KUBECONFIG for use within my client-go program. Would you please have an idea about it? – Fabrice Jammes Aug 09 '23 at 08:01
  • Added an option there. – Rick Rackow Aug 09 '23 at 08:42
  • In my understanding you are storing the namespace passed in option in a file. What I'd like is being able to parse the namespace from the KUBECONFIG, for the current context. Is there a simple way to do this or shall Ire-implement a parser for the KUBECONFIG? I have added an extract of my KUBECONFIG in the above question in order to clarify. – Fabrice Jammes Aug 09 '23 at 20:45
  • 1
    I can only repeat myself: there is no "current" namespace in the kubeconfig, just a default as you can also read in the documentation --> https://kubernetes.io/docs/reference/config-api/kubeconfig.v1/#Context so while you could parse that it's literal file parsing. The object of type `config` does not hold any information about that at all --> https://pkg.go.dev/k8s.io/client-go/rest@v0.27.4#Config – Rick Rackow Aug 10 '23 at 06:32
0

Thanks to @rick-rackow answer and comment, I was able to improve my understanding of the kubeconfig API, so here is a simple solution which works fine for me:

package main

import (
    "flag"
    "fmt"
    "path/filepath"

    "k8s.io/client-go/tools/clientcmd"
    "k8s.io/client-go/util/homedir"
)

func main() {
    var kubeconfig *string
    if home := homedir.HomeDir(); home != "" {
        kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
    } else {
        kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
    }
    flag.Parse()

    ns := getCurrentNamespace(*kubeconfig)
    fmt.Printf("NAMESPACE: %s", ns)
}

// Get the default namespace specified in the KUBECONFIG file current context
func getCurrentNamespace(kubeconfig string) string {

    config, err := clientcmd.LoadFromFile(kubeconfig)
    if err != nil {
        panic(err.Error())
    }
    ns := config.Contexts[config.CurrentContext].Namespace

    if len(ns) == 0 {
        ns = "default"
    }

    return ns
}
Fabrice Jammes
  • 2,275
  • 1
  • 26
  • 39