github.com/grafana/tanka@v0.26.1-0.20240506093700-c22cfc35c21a/pkg/kubernetes/client/get.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "strings" 9 10 "github.com/Masterminds/semver" 11 12 "github.com/grafana/tanka/pkg/kubernetes/manifest" 13 ) 14 15 // Get retrieves a single Kubernetes object from the cluster 16 func (k Kubectl) Get(namespace, kind, name string) (manifest.Manifest, error) { 17 return k.get(namespace, kind, []string{name}, getOpts{}) 18 } 19 20 // GetByLabels retrieves all objects matched by the given labels from the cluster. 21 // Set namespace to empty string for --all-namespaces 22 func (k Kubectl) GetByLabels(namespace, kind string, labels map[string]string) (manifest.List, error) { 23 lArgs := make([]string, 0, len(labels)) 24 for k, v := range labels { 25 lArgs = append(lArgs, fmt.Sprintf("-l=%s=%s", k, v)) 26 } 27 // Needed to properly filter for resources that should be pruned 28 if k.info.ClientVersion.GreaterThan(semver.MustParse("1.21.0")) { 29 lArgs = append(lArgs, "--show-managed-fields") 30 } 31 var opts getOpts 32 if namespace == "" { 33 opts.allNamespaces = true 34 } 35 list, err := k.get(namespace, kind, lArgs, opts) 36 if err != nil { 37 return nil, err 38 } 39 40 return unwrapList(list) 41 } 42 43 // GetByState returns the full object, including runtime fields for each 44 // resource in the state 45 func (k Kubectl) GetByState(data manifest.List, opts GetByStateOpts) (manifest.List, error) { 46 list, err := k.get("", "", []string{"-f", "-"}, getOpts{ 47 ignoreNotFound: opts.IgnoreNotFound, 48 stdin: data.String(), 49 }) 50 if err != nil { 51 return nil, err 52 } 53 54 return unwrapList(list) 55 } 56 57 type getOpts struct { 58 allNamespaces bool 59 ignoreNotFound bool 60 stdin string 61 } 62 63 func (k Kubectl) get(namespace, kind string, selector []string, opts getOpts) (manifest.Manifest, error) { 64 // build cli flags and args 65 argv := []string{ 66 "-o", "json", 67 } 68 if opts.ignoreNotFound { 69 argv = append(argv, "--ignore-not-found") 70 } 71 72 if opts.allNamespaces { 73 argv = append(argv, "--all-namespaces") 74 } else if namespace != "" { 75 argv = append(argv, "-n", namespace) 76 } 77 78 if kind != "" { 79 argv = append(argv, kind) 80 } 81 82 argv = append(argv, selector...) 83 84 // setup command environment 85 cmd := k.ctl("get", argv...) 86 var sout, serr bytes.Buffer 87 cmd.Stdout = &sout 88 cmd.Stderr = &serr 89 if opts.stdin != "" { 90 cmd.Stdin = strings.NewReader(opts.stdin) 91 } 92 93 // run command 94 if err := cmd.Run(); err != nil { 95 return nil, parseGetErr(err, serr.String()) 96 } 97 98 // return error if nothing was returned 99 // because parsing empty output as json would cause errors 100 if sout.Len() == 0 { 101 return nil, ErrorNothingReturned{} 102 } 103 104 // parse result 105 var m manifest.Manifest 106 if err := json.Unmarshal(sout.Bytes(), &m); err != nil { 107 return nil, err 108 } 109 110 return m, nil 111 } 112 113 func parseGetErr(err error, stderr string) error { 114 if strings.HasPrefix(stderr, "Error from server (NotFound)") { 115 return ErrorNotFound{stderr} 116 } 117 if strings.HasPrefix(stderr, "error: the server doesn't have a resource type") { 118 return ErrorUnknownResource{stderr} 119 } 120 121 return errors.New(strings.TrimPrefix(fmt.Sprintf("%s\n%s", stderr, err), "\n")) 122 } 123 124 func unwrapList(list manifest.Manifest) (manifest.List, error) { 125 if list.Kind() != "List" { 126 return nil, fmt.Errorf("expected kind `List` but got `%s` instead", list.Kind()) 127 } 128 129 items := list["items"].([]interface{}) 130 ms := make(manifest.List, 0, len(items)) 131 for _, i := range items { 132 ms = append(ms, manifest.Manifest(i.(map[string]interface{}))) 133 } 134 135 return ms, nil 136 }