test.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package cmd
  2. import (
  3. "context"
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "reflect"
  8. "strings"
  9. "text/tabwriter"
  10. "time"
  11. "k8s.io/apimachinery/pkg/runtime/schema"
  12. "k8s.io/client-go/dynamic"
  13. di "k8s.io/client-go/dynamic/dynamicinformer"
  14. "k8s.io/client-go/tools/cache"
  15. "k8s.io/client-go/tools/clientcmd"
  16. "k8s.io/client-go/util/jsonpath"
  17. "github.com/fatih/color"
  18. "github.com/porter-dev/porter/internal/kubernetes/local"
  19. "github.com/spf13/cobra"
  20. "gopkg.in/yaml.v2"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  23. )
  24. type Object struct {
  25. Group string `yaml:"Group"`
  26. Version string `yaml:"Version"`
  27. Resource string `yaml:"Resource"`
  28. }
  29. type Field struct {
  30. Kind string `yaml:"Kind"`
  31. Query string `yaml:"Query"`
  32. }
  33. type Operation struct {
  34. Kind string `yaml:"Kind"`
  35. Object *Object `yaml:"Object"`
  36. Fields []*Field `yaml:"Fields"`
  37. }
  38. func getOperations() ([]*Operation, error) {
  39. ops := make([]*Operation, 0)
  40. yamlFile, err := ioutil.ReadFile("./cmd/test.yaml")
  41. if err != nil {
  42. return nil, err
  43. }
  44. err = yaml.Unmarshal(yamlFile, &ops)
  45. if err != nil {
  46. return nil, err
  47. }
  48. return ops, nil
  49. }
  50. func initTabWriterArgs(fields []*Field) (string, []interface{}) {
  51. firstArg := ""
  52. nextArgs := make([]interface{}, 0)
  53. for i, field := range fields {
  54. firstArg += "%s"
  55. if len(fields) > i+1 {
  56. firstArg += "\t"
  57. } else {
  58. firstArg += "\n"
  59. }
  60. nextArgs = append(nextArgs, strings.ToUpper(field.Kind))
  61. }
  62. return firstArg, nextArgs
  63. }
  64. func PrintList(op *Operation, items []unstructured.Unstructured) {
  65. w := new(tabwriter.Writer)
  66. w.Init(os.Stdout, 3, 8, 0, '\t', tabwriter.AlignRight)
  67. firstTWArg, twHeaders := initTabWriterArgs(op.Fields)
  68. fmt.Fprintf(w, firstTWArg, twHeaders...)
  69. for _, item := range items {
  70. printRes := make([]interface{}, 0)
  71. for _, field := range op.Fields {
  72. name := item.GetName()
  73. j := jsonpath.New(name)
  74. j.AllowMissingKeys(true)
  75. err := j.Parse(field.Query)
  76. if err != nil {
  77. fmt.Printf("could not parse query for object %s: error=%s", name, err)
  78. continue
  79. }
  80. fullResults, err := j.FindResults(item.Object)
  81. if err != nil {
  82. fmt.Printf("query error for object %s: error=%s", name, err)
  83. continue
  84. }
  85. res := make([]string, 0)
  86. for ix := range fullResults {
  87. for _, result := range fullResults[ix] {
  88. res = append(res, fmt.Sprintf("%v", reflect.ValueOf(result.Interface())))
  89. }
  90. }
  91. switch field.Kind {
  92. case "Title", "Status":
  93. printRes = append(printRes, strings.Join(res, ""))
  94. case "Array":
  95. printRes = append(printRes, strings.Join(res, ","))
  96. }
  97. }
  98. fmt.Fprintf(w, firstTWArg, printRes...)
  99. }
  100. w.Flush()
  101. }
  102. func PrintRead(op *Operation, item *unstructured.Unstructured) {
  103. w := new(tabwriter.Writer)
  104. w.Init(os.Stdout, 3, 8, 0, '\t', tabwriter.AlignRight)
  105. firstTWArg, twHeaders := initTabWriterArgs(op.Fields)
  106. fmt.Fprintf(w, firstTWArg, twHeaders...)
  107. printRes := make([]interface{}, 0)
  108. for _, field := range op.Fields {
  109. name := item.GetName()
  110. j := jsonpath.New(name)
  111. j.AllowMissingKeys(true)
  112. err := j.Parse(field.Query)
  113. if err != nil {
  114. fmt.Printf("could not parse query for object %s: error=%s", name, err)
  115. continue
  116. }
  117. fullResults, err := j.FindResults(item.Object)
  118. if err != nil {
  119. fmt.Printf("query error for object %s: error=%s", name, err)
  120. continue
  121. }
  122. res := make([]string, 0)
  123. for ix := range fullResults {
  124. for _, result := range fullResults[ix] {
  125. res = append(res, fmt.Sprintf("%v", reflect.ValueOf(result.Interface())))
  126. }
  127. }
  128. switch field.Kind {
  129. case "Title", "Status":
  130. printRes = append(printRes, strings.Join(res, ""))
  131. case "Array":
  132. printRes = append(printRes, strings.Join(res, ","))
  133. }
  134. }
  135. fmt.Fprintf(w, firstTWArg, printRes...)
  136. w.Flush()
  137. }
  138. func listOperation(client dynamic.Interface, op *Operation) {
  139. objRes := schema.GroupVersionResource{
  140. Group: op.Object.Group,
  141. Version: op.Object.Version,
  142. Resource: op.Object.Resource,
  143. }
  144. namespace := "default"
  145. fmt.Printf("Listing deployments in namespace %q:\n", namespace)
  146. list, err := client.Resource(objRes).Namespace(namespace).List(context.TODO(), metav1.ListOptions{})
  147. if err != nil {
  148. red := color.New(color.FgRed)
  149. red.Println("Error:", err.Error())
  150. os.Exit(1)
  151. }
  152. PrintList(op, list.Items)
  153. }
  154. var testCmd = &cobra.Command{
  155. Use: "test",
  156. Short: "Testing",
  157. Run: func(cmd *cobra.Command, args []string) {
  158. contextName := "gke_porter-dev-273614_us-central1-f_c-cz2vr"
  159. rawBytes, err := local.GetKubeconfigFromHost("", []string{contextName})
  160. if err != nil {
  161. red := color.New(color.FgRed)
  162. red.Println("Error:", err.Error())
  163. os.Exit(1)
  164. }
  165. conf, err := clientcmd.NewClientConfigFromBytes(rawBytes)
  166. rawConf, err := conf.RawConfig()
  167. conf = clientcmd.NewDefaultClientConfig(rawConf, &clientcmd.ConfigOverrides{
  168. CurrentContext: contextName,
  169. })
  170. restConf, err := conf.ClientConfig()
  171. if err != nil {
  172. red := color.New(color.FgRed)
  173. red.Println("Error:", err.Error())
  174. os.Exit(1)
  175. }
  176. client, err := dynamic.NewForConfig(restConf)
  177. if err != nil {
  178. red := color.New(color.FgRed)
  179. red.Println("Error:", err.Error())
  180. os.Exit(1)
  181. }
  182. ops, err := getOperations()
  183. if err != nil {
  184. red := color.New(color.FgRed)
  185. red.Println("Error:", err.Error())
  186. os.Exit(1)
  187. }
  188. StreamDynamic(client, ops[0])
  189. // listOperation(client, ops[0])
  190. },
  191. }
  192. type Message struct {
  193. EventType string
  194. Object interface{}
  195. Kind string
  196. }
  197. func StreamDynamic(client dynamic.Interface, op *Operation) error {
  198. factory := di.NewDynamicSharedInformerFactory(
  199. client,
  200. 10*time.Second,
  201. )
  202. objRes := schema.GroupVersionResource{
  203. Group: op.Object.Group,
  204. Version: op.Object.Version,
  205. Resource: op.Object.Resource,
  206. }
  207. informer := factory.ForResource(objRes).Informer()
  208. stopper := make(chan struct{})
  209. errorchan := make(chan error)
  210. defer close(errorchan)
  211. defer close(stopper)
  212. informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
  213. UpdateFunc: func(oldObj, newObj interface{}) {
  214. u := newObj.(*unstructured.Unstructured)
  215. PrintRead(op, u)
  216. },
  217. DeleteFunc: func(obj interface{}) {
  218. u := obj.(*unstructured.Unstructured)
  219. PrintRead(op, u)
  220. },
  221. })
  222. // TODO -- websocket
  223. // go func() {
  224. // // listens for websocket closing handshake
  225. // for {
  226. // if _, _, err := conn.ReadMessage(); err != nil {
  227. // defer conn.Close()
  228. // defer close(stopper)
  229. // defer fmt.Println("Successfully closed controller status stream")
  230. // errorchan <- nil
  231. // return
  232. // }
  233. // }
  234. // }()
  235. go informer.Run(stopper)
  236. for {
  237. select {
  238. case err := <-errorchan:
  239. return err
  240. }
  241. }
  242. }
  243. func init() {
  244. rootCmd.AddCommand(testCmd)
  245. }
  246. // replace arrays and scalar values
  247. func CoalesceValues(base, override map[string]interface{}) map[string]interface{} {
  248. for key, val := range base {
  249. if oVal, ok := override[key]; ok {
  250. if oVal == nil {
  251. delete(override, key)
  252. } else if oMapVal, ok := oVal.(map[string]interface{}); ok {
  253. bMapVal, ok := val.(map[string]interface{})
  254. if !ok {
  255. continue
  256. }
  257. override[key] = mergeMaps(bMapVal, oMapVal)
  258. }
  259. } else {
  260. override[key] = val
  261. }
  262. }
  263. return override
  264. }
  265. func isYAMLTable(v interface{}) bool {
  266. _, ok := v.(map[string]interface{})
  267. return ok
  268. }
  269. // mergeMaps merges any number of maps together, with maps later in the slice taking
  270. // precedent
  271. func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {
  272. // merge bottom-up
  273. if len(maps) > 2 {
  274. mLen := len(maps)
  275. newMaps := maps[0 : mLen-2]
  276. // reduce length of maps by 1 and merge again
  277. newMaps = append(newMaps, mergeMaps(maps[mLen-2], maps[mLen-1]))
  278. return mergeMaps(newMaps...)
  279. } else if len(maps) == 2 {
  280. if maps[0] == nil {
  281. return maps[1]
  282. }
  283. if maps[1] == nil {
  284. return maps[0]
  285. }
  286. for key, map0Val := range maps[0] {
  287. if map1Val, ok := maps[1][key]; ok && map1Val == nil {
  288. delete(maps[1], key)
  289. } else if !ok {
  290. maps[1][key] = map0Val
  291. } else if isYAMLTable(map0Val) {
  292. if isYAMLTable(map1Val) {
  293. mergeMaps(map0Val.(map[string]interface{}), map1Val.(map[string]interface{}))
  294. }
  295. }
  296. }
  297. return maps[1]
  298. } else if len(maps) == 1 {
  299. return maps[0]
  300. }
  301. return nil
  302. }