From 4c6414f4a388637822f1b5dfd8402e8820b63fe4 Mon Sep 17 00:00:00 2001 From: sh0rez Date: Thu, 25 Jul 2019 13:16:09 +0200 Subject: [PATCH] feat(kubernetes): apply note --- go.mod | 1 + go.sum | 2 ++ pkg/provider/kubernetes/kubectl.go | 50 ++++++++++++++++++++++-------- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index a3a81b171..e5e71dd8f 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.12 require ( github.com/Masterminds/semver v1.4.2 github.com/alecthomas/chroma v0.6.6 + github.com/fatih/color v1.7.0 github.com/google/go-jsonnet v0.13.0 github.com/mitchellh/mapstructure v1.1.2 github.com/spf13/cobra v0.0.5 diff --git a/go.sum b/go.sum index b4403dddd..2f0967e9a 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg= github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -90,6 +91,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= diff --git a/pkg/provider/kubernetes/kubectl.go b/pkg/provider/kubernetes/kubectl.go index 3ee92f229..2e06b4533 100644 --- a/pkg/provider/kubernetes/kubectl.go +++ b/pkg/provider/kubernetes/kubectl.go @@ -1,20 +1,28 @@ package kubernetes import ( + "bufio" "bytes" "encoding/json" + "errors" "fmt" "os" "os/exec" "github.com/Masterminds/semver" + "github.com/fatih/color" "github.com/stretchr/objx" funk "github.com/thoas/go-funk" ) +var ( + alert = color.New(color.FgRed, color.Bold).SprintFunc() +) + // Kubectl uses the `kubectl` command to operate on a Kubernetes cluster type Kubectl struct { - context string + context objx.Map + cluster objx.Map APIServer string } @@ -23,7 +31,7 @@ func (k Kubectl) Version() (client, server semver.Version, err error) { zero := *semver.MustParse("0.0.0") cmd := exec.Command("kubectl", "version", "-o", "json", - "--context", k.context, + "--context", k.context.Get("name").MustStr(), ) var buf bytes.Buffer cmd.Stdout = &buf @@ -37,7 +45,7 @@ func (k Kubectl) Version() (client, server semver.Version, err error) { } // setupContext uses `kubectl config view` to obtain the KUBECONFIG and extracts the correct context from it -func (k Kubectl) setupContext() error { +func (k *Kubectl) setupContext() error { cmd := exec.Command("kubectl", "config", "view", "-o", "json") cfgJSON := bytes.Buffer{} cmd.Stdout = &cfgJSON @@ -50,7 +58,7 @@ func (k Kubectl) setupContext() error { } var err error - k.context, err = contextFromKubeconfig(cfg, k.APIServer) + k.cluster, k.context, err = contextFromKubeconfig(cfg, k.APIServer) if err != nil { return err } @@ -58,28 +66,28 @@ func (k Kubectl) setupContext() error { } // contextFromKubeconfig searches a kubeconfig for a context of a cluster that matches the apiServer -func contextFromKubeconfig(kubeconfig map[string]interface{}, apiServer string) (string, error) { +func contextFromKubeconfig(kubeconfig map[string]interface{}, apiServer string) (cluster, context objx.Map, err error) { cfg := objx.New(kubeconfig) // find the correct cluster - cluster := objx.New(funk.Find(cfg.Get("clusters").MustMSISlice(), func(x map[string]interface{}) bool { + cluster = objx.New(funk.Find(cfg.Get("clusters").MustMSISlice(), func(x map[string]interface{}) bool { host := objx.New(x).Get("cluster.server").MustStr() return host == apiServer })) if !(len(cluster) > 0) { // empty map means no result - return "", fmt.Errorf("no cluster that matches the apiServer `%s` was found. Please check your $KUBECONFIG", apiServer) + return nil, nil, fmt.Errorf("no cluster that matches the apiServer `%s` was found. Please check your $KUBECONFIG", apiServer) } // find a context that uses the cluster - context := objx.New(funk.Find(cfg.Get("contexts").MustMSISlice(), func(x map[string]interface{}) bool { + context = objx.New(funk.Find(cfg.Get("contexts").MustMSISlice(), func(x map[string]interface{}) bool { c := objx.New(x) return c.Get("context.cluster").MustStr() == cluster.Get("name").MustStr() })) if !(len(context) > 0) { - return "", fmt.Errorf("no context that matches the cluster `%s` was found. Please check your $KUBECONFIG", cluster.Get("name").MustStr()) + return nil, nil, fmt.Errorf("no context that matches the cluster `%s` was found. Please check your $KUBECONFIG", cluster.Get("name").MustStr()) } - return context.Get("name").MustStr(), nil + return cluster, context, nil } // Get retrieves an Kubernetes object from the API @@ -90,7 +98,7 @@ func (k Kubectl) Get(namespace, kind, name string) (map[string]interface{}, erro argv := []string{"get", "-o", "json", "-n", namespace, - "--context", k.context, + "--context", k.context.Get("name").MustStr(), kind, name, } cmd := exec.Command("kubectl", argv...) @@ -112,8 +120,24 @@ func (k Kubectl) Apply(yaml string) error { if err := k.setupContext(); err != nil { return err } + + reader := bufio.NewReader(os.Stdin) + fmt.Printf(`!!! Applying to cluster '%s' at '%s' using context '%s'. +!!! Please type 'yes' to perform: `, + alert(k.cluster.Get("name").MustStr()), + alert(k.cluster.Get("cluster.server").MustStr()), + alert(k.context.Get("name").MustStr()), + ) + approve, err := reader.ReadString('\n') + if err != nil { + return err + } + if approve != "yes\n" { + return errors.New("aborted by user") + } + argv := []string{"apply", - "--context", k.context, + "--context", k.context.Get("name").MustStr(), "-f", "-", } cmd := exec.Command("kubectl", argv...) @@ -149,7 +173,7 @@ func (k Kubectl) Diff(yaml string) (string, error) { } argv := []string{"diff", - "--context", k.context, + "--context", k.context.Get("name").MustStr(), "-f", "-", } cmd := exec.Command("kubectl", argv...)