parser.go 6.1 KB

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