parser.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. package parser
  2. import (
  3. "fmt"
  4. "strings"
  5. "github.com/porter-dev/porter/api/types"
  6. "github.com/porter-dev/porter/internal/helm"
  7. "github.com/porter-dev/porter/internal/templater"
  8. "github.com/porter-dev/porter/internal/templater/utils"
  9. "helm.sh/helm/v3/pkg/chart"
  10. "helm.sh/helm/v3/pkg/release"
  11. "k8s.io/client-go/dynamic"
  12. "sigs.k8s.io/yaml"
  13. td "github.com/porter-dev/porter/internal/templater/dynamic"
  14. tm "github.com/porter-dev/porter/internal/templater/helm/manifests"
  15. tv "github.com/porter-dev/porter/internal/templater/helm/values"
  16. )
  17. // ClientConfigDefault is a set of default clients to be used if a context in
  18. // form.yaml does not declare otherwise.
  19. type ClientConfigDefault struct {
  20. DynamicClient dynamic.Interface
  21. HelmAgent *helm.Agent
  22. HelmRelease *release.Release
  23. HelmChart *chart.Chart
  24. }
  25. // ContextConfig can read/write from a specified context (data source)
  26. type ContextConfig struct {
  27. FromType string // "live" or "declared"
  28. Capabilities []string // "read", "write"
  29. TemplateReader templater.TemplateReader
  30. TemplateWriter templater.TemplateWriter
  31. }
  32. // GetFormFromRelease returns the form by parsing a release's files. Returns nil if
  33. // the form is not found, throws an error if the form was found but there was a parsing
  34. // error.
  35. func GetFormFromRelease(def *ClientConfigDefault, rel *release.Release) (*types.FormYAML, error) {
  36. for _, file := range rel.Chart.Files {
  37. if strings.Contains(file.Name, "form.yaml") {
  38. return FormYAMLFromBytes(def, file.Data, "")
  39. }
  40. }
  41. return nil, nil
  42. }
  43. // FormYAMLFromBytes generates a usable form yaml from raw form config and a
  44. // set of default clients.
  45. //
  46. // stateType refers to the types of state that should be read. The two state types
  47. // are "live" and "declared" -- if stateType is "", this will read both live and
  48. // declared states.
  49. func FormYAMLFromBytes(def *ClientConfigDefault, bytes []byte, stateType string) (*types.FormYAML, error) {
  50. form, err := unqueriedFormYAMLFromBytes(bytes)
  51. if err != nil {
  52. return nil, err
  53. }
  54. lookup := formToLookupTable(def, form, stateType)
  55. // merge data from lookup
  56. data := make(map[string]interface{})
  57. for _, lookupVal := range lookup {
  58. if lookupVal != nil {
  59. queryRes, err := lookupVal.TemplateReader.Read()
  60. if err != nil {
  61. continue
  62. }
  63. for queryResKey, queryResVal := range queryRes {
  64. data[queryResKey] = queryResVal
  65. }
  66. }
  67. }
  68. for i, tab := range form.Tabs {
  69. for j, section := range tab.Sections {
  70. for k, content := range section.Contents {
  71. key := fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k)
  72. if val, ok := data[key]; ok {
  73. content.Value = val
  74. }
  75. }
  76. }
  77. }
  78. return form, nil
  79. }
  80. func FormStreamer(
  81. def *ClientConfigDefault,
  82. bytes []byte,
  83. stateType string,
  84. targetContext *types.FormContext,
  85. on templater.OnDataStream,
  86. stopCh <-chan struct{},
  87. ) error {
  88. form, err := unqueriedFormYAMLFromBytes(bytes)
  89. if err != nil {
  90. return err
  91. }
  92. lookup := formToLookupTable(def, form, stateType)
  93. for lookupContext, lookupVal := range lookup {
  94. if lookupVal != nil && areContextsEqual(targetContext, lookupContext) {
  95. err := lookupVal.TemplateReader.ReadStream(on, stopCh)
  96. if err != nil {
  97. continue
  98. }
  99. }
  100. }
  101. return nil
  102. }
  103. func areContextsEqual(context1, context2 *types.FormContext) bool {
  104. if context1.Type != context2.Type {
  105. return false
  106. }
  107. subset12 := isSubset(context1.Config, context2.Config)
  108. subset21 := isSubset(context2.Config, context1.Config)
  109. return subset12 && subset21
  110. }
  111. func isSubset(map1, map2 map[string]string) bool {
  112. subset12 := true
  113. for key1, val1 := range map1 {
  114. if val2, exists := map2[key1]; !exists || val2 != val1 {
  115. subset12 = false
  116. break
  117. }
  118. }
  119. return subset12
  120. }
  121. // unqueriedFormYAMLFromBytes returns a FormYAML without values queries populated
  122. func unqueriedFormYAMLFromBytes(bytes []byte) (*types.FormYAML, error) {
  123. // parse bytes into object
  124. form := &types.FormYAML{}
  125. err := yaml.Unmarshal(bytes, form)
  126. if err != nil {
  127. return nil, err
  128. }
  129. // populate all context fields, with default set to helm/values with no config
  130. parent := &types.FormContext{
  131. Type: "helm/values",
  132. }
  133. for _, tab := range form.Tabs {
  134. if tab.Context == nil {
  135. tab.Context = parent
  136. }
  137. for _, section := range tab.Sections {
  138. if section.Context == nil {
  139. section.Context = tab.Context
  140. }
  141. for _, content := range section.Contents {
  142. if content.Context == nil {
  143. content.Context = section.Context
  144. }
  145. }
  146. }
  147. }
  148. return form, nil
  149. }
  150. // create map[*FormContext]*ContextConfig
  151. // assumes all contexts populated
  152. func formToLookupTable(def *ClientConfigDefault, form *types.FormYAML, stateType string) map[*types.FormContext]*ContextConfig {
  153. lookup := make(map[*types.FormContext]*ContextConfig)
  154. for i, tab := range form.Tabs {
  155. for j, section := range tab.Sections {
  156. for k, content := range section.Contents {
  157. if content.Context == nil {
  158. continue
  159. }
  160. if _, ok := lookup[content.Context]; !ok {
  161. lookup[content.Context] = formContextToContextConfig(def, content.Context, stateType)
  162. }
  163. if lookup[content.Context] == nil {
  164. continue
  165. }
  166. if content.Value != nil && fmt.Sprintf("%v", content.Value) != "" {
  167. // TODO -- case on whether value is proper query string, if not resolve it to a
  168. // proper query string
  169. query, err := utils.NewQuery(
  170. fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
  171. fmt.Sprintf("%v", content.Value),
  172. )
  173. if err != nil {
  174. continue
  175. }
  176. if stateType == "" || stateType == lookup[content.Context].FromType {
  177. lookup[content.Context].TemplateReader.RegisterQuery(query)
  178. }
  179. } else if content.Variable != "" {
  180. // if variable field set without value field set, make variable field into jsonpath
  181. // query
  182. query, err := utils.NewQuery(
  183. fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
  184. fmt.Sprintf(".%v", content.Variable),
  185. )
  186. if err != nil {
  187. continue
  188. }
  189. if stateType == "" || stateType == lookup[content.Context].FromType {
  190. lookup[content.Context].TemplateReader.RegisterQuery(query)
  191. }
  192. }
  193. }
  194. }
  195. }
  196. return lookup
  197. }
  198. // TODO -- this needs to be able to construct new context configs based on
  199. // configuration for each context, but right now just uses the default config
  200. // for everything
  201. func formContextToContextConfig(def *ClientConfigDefault, context *types.FormContext, stateType string) *ContextConfig {
  202. res := &ContextConfig{}
  203. if context.Type == "helm/values" && (stateType == "" || stateType == "declared") {
  204. res.FromType = "declared"
  205. res.Capabilities = []string{"read", "write"}
  206. res.TemplateReader = &tv.TemplateReader{
  207. Release: def.HelmRelease,
  208. Chart: def.HelmChart,
  209. }
  210. relName := ""
  211. if def.HelmRelease != nil {
  212. relName = def.HelmRelease.Name
  213. }
  214. res.TemplateWriter = &tv.TemplateWriter{
  215. Agent: def.HelmAgent,
  216. Chart: def.HelmChart,
  217. ReleaseName: relName,
  218. }
  219. } else if context.Type == "helm/manifests" && (stateType == "" || stateType == "live") {
  220. res.FromType = "live"
  221. res.Capabilities = []string{"read"}
  222. res.TemplateReader = &tm.TemplateReader{
  223. Release: def.HelmRelease,
  224. }
  225. } else if context.Type == "cluster" && (stateType == "" || stateType == "live") {
  226. res.FromType = "live"
  227. res.Capabilities = []string{"read"}
  228. // identify object based on passed config
  229. obj := &td.Object{
  230. Group: context.Config["group"],
  231. Version: context.Config["version"],
  232. Resource: context.Config["resource"],
  233. Namespace: context.Config["namespace"],
  234. Name: context.Config["name"],
  235. }
  236. res.TemplateReader = td.NewDynamicTemplateReader(def.DynamicClient, obj)
  237. } else {
  238. return nil
  239. }
  240. return res
  241. }