parser.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. // unqueriedFormYAMLFromBytes returns a FormYAML without values queries populated
  81. func unqueriedFormYAMLFromBytes(bytes []byte) (*types.FormYAML, error) {
  82. // parse bytes into object
  83. form := &types.FormYAML{}
  84. err := yaml.Unmarshal(bytes, form)
  85. if err != nil {
  86. return nil, err
  87. }
  88. // populate all context fields, with default set to helm/values with no config
  89. parent := &types.FormContext{
  90. Type: "helm/values",
  91. }
  92. for _, tab := range form.Tabs {
  93. if tab.Context == nil {
  94. tab.Context = parent
  95. }
  96. for _, section := range tab.Sections {
  97. if section.Context == nil {
  98. section.Context = tab.Context
  99. }
  100. for _, content := range section.Contents {
  101. if content.Context == nil {
  102. content.Context = section.Context
  103. }
  104. }
  105. }
  106. }
  107. return form, nil
  108. }
  109. // create map[*FormContext]*ContextConfig
  110. // assumes all contexts populated
  111. func formToLookupTable(def *ClientConfigDefault, form *types.FormYAML, stateType string) map[*types.FormContext]*ContextConfig {
  112. lookup := make(map[*types.FormContext]*ContextConfig)
  113. for i, tab := range form.Tabs {
  114. for j, section := range tab.Sections {
  115. for k, content := range section.Contents {
  116. if content.Context == nil {
  117. continue
  118. }
  119. if _, ok := lookup[content.Context]; !ok {
  120. lookup[content.Context] = formContextToContextConfig(def, content.Context, stateType)
  121. }
  122. if lookup[content.Context] == nil {
  123. continue
  124. }
  125. if content.Value != nil && fmt.Sprintf("%v", content.Value) != "" {
  126. // TODO -- case on whether value is proper query string, if not resolve it to a
  127. // proper query string
  128. query, err := utils.NewQuery(
  129. fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
  130. fmt.Sprintf("%v", content.Value),
  131. )
  132. if err != nil {
  133. continue
  134. }
  135. if stateType == "" || stateType == lookup[content.Context].FromType {
  136. lookup[content.Context].TemplateReader.RegisterQuery(query)
  137. }
  138. } else if content.Variable != "" {
  139. // if variable field set without value field set, make variable field into jsonpath
  140. // query
  141. query, err := utils.NewQuery(
  142. fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
  143. fmt.Sprintf(".%v", content.Variable),
  144. )
  145. if err != nil {
  146. continue
  147. }
  148. if stateType == "" || stateType == lookup[content.Context].FromType {
  149. lookup[content.Context].TemplateReader.RegisterQuery(query)
  150. }
  151. }
  152. }
  153. }
  154. }
  155. return lookup
  156. }
  157. // TODO -- this needs to be able to construct new context configs based on
  158. // configuration for each context, but right now just uses the default config
  159. // for everything
  160. func formContextToContextConfig(def *ClientConfigDefault, context *types.FormContext, stateType string) *ContextConfig {
  161. res := &ContextConfig{}
  162. if context.Type == "helm/values" && (stateType == "" || stateType == "declared") {
  163. res.FromType = "declared"
  164. res.Capabilities = []string{"read", "write"}
  165. res.TemplateReader = &tv.TemplateReader{
  166. Release: def.HelmRelease,
  167. Chart: def.HelmChart,
  168. }
  169. relName := ""
  170. if def.HelmRelease != nil {
  171. relName = def.HelmRelease.Name
  172. }
  173. res.TemplateWriter = &tv.TemplateWriter{
  174. Agent: def.HelmAgent,
  175. Chart: def.HelmChart,
  176. ReleaseName: relName,
  177. }
  178. } else if context.Type == "helm/manifests" && (stateType == "" || stateType == "live") {
  179. res.FromType = "live"
  180. res.Capabilities = []string{"read"}
  181. res.TemplateReader = &tm.TemplateReader{
  182. Release: def.HelmRelease,
  183. }
  184. } else if context.Type == "cluster" && (stateType == "" || stateType == "live") {
  185. res.FromType = "live"
  186. res.Capabilities = []string{"read"}
  187. // identify object based on passed config
  188. obj := &td.Object{
  189. Group: context.Config["group"],
  190. Version: context.Config["version"],
  191. Resource: context.Config["resource"],
  192. Namespace: context.Config["namespace"],
  193. Name: context.Config["name"],
  194. }
  195. res.TemplateReader = td.NewDynamicTemplateReader(def.DynamicClient, obj)
  196. } else {
  197. return nil
  198. }
  199. return res
  200. }