parser.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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. th "github.com/porter-dev/porter/internal/templater/helm"
  14. )
  15. // TODO -- handle all continue statements, errors should at least be logged if not
  16. // thrown
  17. type ClientConfigDefault struct {
  18. DynamicClient dynamic.Interface
  19. DynamicObject *td.Object
  20. HelmAgent *helm.Agent
  21. HelmRelease *release.Release
  22. HelmChart *chart.Chart
  23. // for installing a new chart
  24. HelmChartPath string
  25. }
  26. func FormYAMLFromBytes(def *ClientConfigDefault, bytes []byte) (*models.FormYAML, error) {
  27. form, err := unqueriedFormYAMLFromBytes(bytes)
  28. if err != nil {
  29. return nil, err
  30. }
  31. lookup := formToLookupTable(def, form)
  32. // merge data from lookup
  33. data := make(map[string]interface{})
  34. for _, lookupVal := range lookup {
  35. queryRes, err := lookupVal.TemplateReader.Read()
  36. if err != nil {
  37. continue
  38. }
  39. for queryResKey, queryResVal := range queryRes {
  40. data[queryResKey] = queryResVal
  41. }
  42. }
  43. for i, tab := range form.Tabs {
  44. for j, section := range tab.Sections {
  45. for k, content := range section.Contents {
  46. key := fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k)
  47. if val, ok := data[key]; ok {
  48. content.Value = val
  49. }
  50. }
  51. }
  52. }
  53. return form, nil
  54. }
  55. // unqueriedFormYAMLFromBytes returns a FormYAML without values queries populated
  56. func unqueriedFormYAMLFromBytes(bytes []byte) (*models.FormYAML, error) {
  57. // parse bytes into object
  58. form := &models.FormYAML{}
  59. err := yaml.Unmarshal(bytes, form)
  60. if err != nil {
  61. return nil, err
  62. }
  63. // populate all context fields, with default set to helm/values with no config
  64. parent := &models.FormContext{
  65. Type: "helm/values",
  66. }
  67. for _, tab := range form.Tabs {
  68. if tab.Context == nil {
  69. tab.Context = parent
  70. }
  71. for _, section := range tab.Sections {
  72. if section.Context == nil {
  73. section.Context = tab.Context
  74. }
  75. for _, content := range section.Contents {
  76. if content.Context == nil {
  77. content.Context = section.Context
  78. }
  79. }
  80. }
  81. }
  82. return form, nil
  83. }
  84. type ContextConfig struct {
  85. FromType string // "live" or "declared"
  86. Capabilities []string // "read", "write"
  87. TemplateReader templater.TemplateReader
  88. TemplateWriter templater.TemplateWriter
  89. }
  90. // create map[*FormContext]*ContextConfig
  91. // assumes all contexts populated
  92. func formToLookupTable(def *ClientConfigDefault, form *models.FormYAML) map[*models.FormContext]*ContextConfig {
  93. lookup := make(map[*models.FormContext]*ContextConfig)
  94. for i, tab := range form.Tabs {
  95. for j, section := range tab.Sections {
  96. for k, content := range section.Contents {
  97. if content.Context == nil {
  98. continue
  99. }
  100. if _, ok := lookup[content.Context]; !ok {
  101. lookup[content.Context] = formContextToContextConfig(def, content.Context)
  102. }
  103. // TODO -- case on whether value is proper query string, if not resolve it to a
  104. // proper query string
  105. query, err := utils.NewQuery(
  106. fmt.Sprintf("tabs[%d].sections[%d].contents[%d]", i, j, k),
  107. fmt.Sprintf("%v", content.Value),
  108. )
  109. if err != nil {
  110. continue
  111. }
  112. lookup[content.Context].TemplateReader.RegisterQuery(query)
  113. }
  114. }
  115. }
  116. return lookup
  117. }
  118. // TODO -- this needs to be able to construct new context configs based on
  119. // configuration for each context, but right now just uses the default config
  120. // for everything
  121. func formContextToContextConfig(def *ClientConfigDefault, context *models.FormContext) *ContextConfig {
  122. res := &ContextConfig{}
  123. switch context.Type {
  124. case "helm/values":
  125. res.FromType = "declared"
  126. res.Capabilities = []string{"read", "write"}
  127. res.TemplateReader = &th.ValuesTemplateReader{
  128. Release: def.HelmRelease,
  129. Chart: def.HelmChart,
  130. }
  131. relName := ""
  132. if def.HelmRelease != nil {
  133. relName = def.HelmRelease.Name
  134. }
  135. res.TemplateWriter = &th.ValuesTemplateWriter{
  136. Agent: def.HelmAgent,
  137. ChartPath: def.HelmChartPath,
  138. ReleaseName: relName,
  139. }
  140. case "helm/manifests":
  141. res.FromType = "live"
  142. res.Capabilities = []string{"read"}
  143. res.TemplateReader = &th.ManifestsTemplateReader{
  144. Release: def.HelmRelease,
  145. }
  146. case "cluster":
  147. res.FromType = "live"
  148. res.Capabilities = []string{"read"}
  149. res.TemplateReader = td.NewDynamicTemplateReader(def.DynamicClient, def.DynamicObject)
  150. default:
  151. return nil
  152. }
  153. return res
  154. }