github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/kubernetes/client/kubectl.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "os" 8 "regexp" 9 10 "github.com/pkg/errors" 11 12 "github.com/grafana/tanka/pkg/kubernetes/manifest" 13 ) 14 15 // Kubectl uses the `kubectl` command to operate on a Kubernetes cluster 16 type Kubectl struct { 17 info Info 18 } 19 20 // New returns a instance of Kubectl with a correct context already discovered. 21 func New(endpoint string) (*Kubectl, error) { 22 k := Kubectl{} 23 24 // discover context 25 var err error 26 k.info.Kubeconfig, err = findContextFromEndpoint(endpoint) 27 if err != nil { 28 return nil, errors.Wrap(err, "finding usable context") 29 } 30 31 // query versions (requires context) 32 k.info.ClientVersion, k.info.ServerVersion, err = k.version() 33 if err != nil { 34 return nil, errors.Wrap(err, "obtaining versions") 35 } 36 37 return &k, nil 38 } 39 40 func NewFromNames(names []string) (*Kubectl, error) { 41 k := Kubectl{} 42 43 var err error 44 k.info.Kubeconfig, err = findContextFromNames(names) 45 if err != nil { 46 return nil, errors.Wrap(err, "finding usable context") 47 } 48 49 // query versions (requires context) 50 k.info.ClientVersion, k.info.ServerVersion, err = k.version() 51 if err != nil { 52 return nil, errors.Wrap(err, "obtaining versions") 53 } 54 55 return &k, nil 56 } 57 58 // Info returns known informational data about the client and its environment 59 func (k Kubectl) Info() Info { 60 return k.info 61 } 62 63 // Close runs final cleanup: 64 func (k Kubectl) Close() error { 65 return nil 66 } 67 68 // Namespaces of the cluster 69 func (k Kubectl) Namespaces() (map[string]bool, error) { 70 cmd := k.ctl("get", "namespaces", "-o", "json") 71 72 var sout bytes.Buffer 73 var serr bytes.Buffer 74 cmd.Stdout = &sout 75 cmd.Stderr = &serr 76 77 err := cmd.Run() 78 if err != nil { 79 return nil, errors.Wrap(err, serr.String()) 80 } 81 82 var list manifest.Manifest 83 if err := json.Unmarshal(sout.Bytes(), &list); err != nil { 84 return nil, err 85 } 86 87 items, err := list.Items() 88 if err != nil { 89 return nil, err 90 } 91 92 namespaces := make(map[string]bool) 93 for _, m := range items { 94 namespaces[m.Metadata().Name()] = true 95 } 96 return namespaces, nil 97 } 98 99 type ErrNamespaceNotFound struct { 100 Namespace string 101 } 102 103 func (e ErrNamespaceNotFound) Error() string { 104 return fmt.Sprintf("Namespace not found: %s", e.Namespace) 105 } 106 107 // Namespace finds a single namespace in the cluster 108 func (k Kubectl) Namespace(namespace string) (manifest.Manifest, error) { 109 cmd := k.ctl("get", "namespaces", namespace, "-o", "json", "--ignore-not-found") 110 111 var sout bytes.Buffer 112 cmd.Stdout = &sout 113 cmd.Stderr = os.Stderr 114 115 err := cmd.Run() 116 if err != nil { 117 return nil, err 118 } 119 if len(sout.Bytes()) == 0 { 120 return nil, ErrNamespaceNotFound{ 121 Namespace: namespace, 122 } 123 } 124 var ns manifest.Manifest 125 if err := json.Unmarshal(sout.Bytes(), &ns); err != nil { 126 return nil, err 127 } 128 return ns, nil 129 } 130 131 // FilterWriter is an io.Writer that discards every message that matches at 132 // least one of the regular expressions. 133 type FilterWriter struct { 134 buf string 135 filters []*regexp.Regexp 136 } 137 138 func (r *FilterWriter) Write(p []byte) (n int, err error) { 139 for _, re := range r.filters { 140 if re.Match(p) { 141 // silently discard 142 return len(p), nil 143 } 144 } 145 r.buf += string(p) 146 return os.Stderr.Write(p) 147 }