Skip to content

Commit

Permalink
feat(kubectl): add cache to kubectl
Browse files Browse the repository at this point in the history
  • Loading branch information
EladLeev authored and JanDeDobbeleer committed Jul 27, 2024
1 parent 7a64782 commit 576ec37
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 0 deletions.
54 changes: 54 additions & 0 deletions src/segments/kubectl.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package segments

import (
"encoding/json"
"fmt"
"path/filepath"

"github.com/jandedobbeleer/oh-my-posh/src/properties"
Expand All @@ -13,12 +15,15 @@ import (
const (
ParseKubeConfig properties.Property = "parse_kubeconfig"
ContextAliases properties.Property = "context_aliases"
kubectlCacheKey = "kubectl"
)

type Kubectl struct {
props properties.Properties
env runtime.Environment

dirty bool

Context string

KubeContext
Expand Down Expand Up @@ -47,11 +52,50 @@ func (k *Kubectl) Init(props properties.Properties, env runtime.Environment) {
k.env = env
}

func (k *Kubectl) setCacheValue(timeout int) {
if !k.dirty {
return
}

cachedData, _ := json.Marshal(k)
k.env.Cache().Set(kubectlCacheKey, string(cachedData), timeout)
}

func (k *Kubectl) restoreCacheValue() error {
if val, found := k.env.Cache().Get(kubectlCacheKey); found {
err := json.Unmarshal([]byte(val), k)
if err != nil {
k.env.Error(err)
return err
}

return nil
}

return fmt.Errorf("no data in cache")
}

func (k *Kubectl) Enabled() bool {
cacheTimeout := k.props.GetInt(properties.CacheTimeout, 1)

if cacheTimeout > 0 {
if err := k.restoreCacheValue(); err == nil {
return true
}
}

defer func() {
if cacheTimeout > 0 {
k.setCacheValue(cacheTimeout)
}
}()

parseKubeConfig := k.props.GetBool(ParseKubeConfig, false)

if parseKubeConfig {
return k.doParseKubeConfig()
}

return k.doCallKubectl()
}

Expand All @@ -62,8 +106,10 @@ func (k *Kubectl) doParseKubeConfig() bool {
if len(kubeconfigs) == 0 {
kubeconfigs = []string{filepath.Join(k.env.Home(), ".kube/config")}
}

contexts := make(map[string]*KubeContext)
k.Context = ""

for _, kubeconfig := range kubeconfigs {
if len(kubeconfig) == 0 {
continue
Expand Down Expand Up @@ -97,6 +143,7 @@ func (k *Kubectl) doParseKubeConfig() bool {
}

k.SetContextAlias()
k.dirty = true

return true
}
Expand All @@ -114,12 +161,14 @@ func (k *Kubectl) doCallKubectl() bool {
if !k.env.HasCommand(cmd) {
return false
}

result, err := k.env.RunCommand(cmd, "config", "view", "--output", "yaml", "--minify")
displayError := k.props.GetBool(properties.DisplayError, false)
if err != nil && displayError {
k.setError("KUBECTL ERR")
return true
}

if err != nil {
return false
}
Expand All @@ -129,18 +178,23 @@ func (k *Kubectl) doCallKubectl() bool {
if err != nil {
return false
}

k.Context = config.CurrentContext
k.SetContextAlias()
k.dirty = true

if len(config.Contexts) > 0 {
k.KubeContext = *config.Contexts[0].Context
}

return true
}

func (k *Kubectl) setError(message string) {
if len(k.Context) == 0 {
k.Context = message
}

k.Namespace = message
k.User = message
k.Cluster = message
Expand Down
7 changes: 7 additions & 0 deletions src/segments/kubectl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,28 @@ func TestKubectlSegment(t *testing.T) {
for _, tc := range cases {
env := new(mock.Environment)
env.On("HasCommand", "kubectl").Return(tc.KubectlExists)

var kubeconfig string
content, err := os.ReadFile("../test/kubectl.yml")
if err == nil {
kubeconfig = fmt.Sprintf(string(content), tc.Cluster, tc.UserName, tc.Namespace, tc.Context)
}

var kubectlErr error
if tc.KubectlErr {
kubectlErr = &runtime.CommandError{
Err: "oops",
ExitCode: 1,
}
}

env.On("RunCommand", "kubectl", []string{"config", "view", "--output", "yaml", "--minify"}).Return(kubeconfig, kubectlErr)
env.On("Getenv", "KUBECONFIG").Return(tc.Kubeconfig)

for path, content := range tc.Files {
env.On("FileContent", path).Return(content)
}

env.On("Home").Return("testhome")

k := &Kubectl{
Expand All @@ -159,8 +164,10 @@ func TestKubectlSegment(t *testing.T) {
properties.DisplayError: tc.DisplayError,
ParseKubeConfig: tc.ParseKubeConfig,
ContextAliases: tc.ContextAliases,
properties.CacheTimeout: 0,
},
}

assert.Equal(t, tc.ExpectedEnabled, k.Enabled(), tc.Case)
if tc.ExpectedEnabled {
assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, k), tc.Case)
Expand Down
5 changes: 5 additions & 0 deletions website/docs/segments/cli/kubectl.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ sidebar_label: Kubectl

Display the currently active Kubernetes context name and namespace name.

:::caution
The Kubernetes context is cached for 1 minute by default. To avoid caching, set `cache_timeout` to 0.
:::

## Sample Configuration

import Config from "@site/src/components/Config.js";
Expand Down Expand Up @@ -35,6 +39,7 @@ import Config from "@site/src/components/Config.js";
| `display_error` | `boolean` | `false` | show the error context when failing to retrieve the kubectl information |
| `parse_kubeconfig` | `boolean` | `false` | parse kubeconfig files instead of calling out to kubectl to improve performance |
| `context_aliases` | `object` | | custom context namespace |
| `cache_timeout` | `int` | `1` | in minutes - how long is the context cached |

## Template ([info][templates])

Expand Down

0 comments on commit 576ec37

Please sign in to comment.