parse.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. package stacks
  2. import (
  3. "fmt"
  4. "github.com/porter-dev/porter/api/server/shared/config"
  5. "github.com/porter-dev/porter/api/types"
  6. "github.com/porter-dev/porter/internal/helm/loader"
  7. "github.com/porter-dev/porter/internal/templater/utils"
  8. "github.com/stefanmcshane/helm/pkg/chart"
  9. "gopkg.in/yaml.v2"
  10. )
  11. type PorterStackYAML struct {
  12. Version *string `yaml:"version"`
  13. Build *Build `yaml:"build"`
  14. Env map[string]string `yaml:"env"`
  15. Apps map[string]*App `yaml:"apps"`
  16. Release *string `yaml:"release"`
  17. }
  18. type Build struct {
  19. Context *string `yaml:"context" validate:"dir"`
  20. Method *string `yaml:"method" validate:"required,oneof=pack docker registry"`
  21. Builder *string `yaml:"builder" validate:"required_if=Method pack"`
  22. Buildpacks []*string `yaml:"buildpacks"`
  23. Dockerfile *string `yaml:"dockerfile" validate:"required_if=Method docker"`
  24. Image *string `yaml:"image" validate:"required_if=Method registry"`
  25. }
  26. type App struct {
  27. Run *string `yaml:"run" validate:"required"`
  28. Config map[string]interface{} `yaml:"config"`
  29. Type *string `yaml:"type" validate:"required, oneof=web worker job"`
  30. }
  31. func parse(porterYaml []byte, imageInfo *types.ImageInfo, config *config.Config, projectID uint) (*chart.Chart, map[string]interface{}, error) {
  32. parsed := &PorterStackYAML{}
  33. err := yaml.Unmarshal(porterYaml, parsed)
  34. if err != nil {
  35. return nil, nil, fmt.Errorf("%s: %w", "error parsing porter.yaml", err)
  36. }
  37. values, err := buildStackValues(parsed, imageInfo)
  38. if err != nil {
  39. return nil, nil, fmt.Errorf("%s: %w", "error building values from porter.yaml", err)
  40. }
  41. convertedValues := convertMap(values)
  42. chart, err := buildStackChart(parsed, config, projectID)
  43. if err != nil {
  44. return nil, nil, fmt.Errorf("%s: %w", "error building chart from porter.yaml", err)
  45. }
  46. return chart, convertedValues.(map[string]interface{}), nil
  47. }
  48. func buildStackValues(parsed *PorterStackYAML, imageInfo *types.ImageInfo) (map[string]interface{}, error) {
  49. values := make(map[string]interface{})
  50. for name, app := range parsed.Apps {
  51. defaultValues := getDefaultValues(app, parsed.Env, imageInfo)
  52. helm_values := utils.CoalesceValues(defaultValues, app.Config)
  53. values[name] = helm_values
  54. }
  55. return values, nil
  56. }
  57. func getDefaultValues(app *App, env map[string]string, imageInfo *types.ImageInfo) map[string]interface{} {
  58. var defaultValues map[string]interface{}
  59. if *app.Type == "web" {
  60. defaultValues = map[string]interface{}{
  61. "ingress": map[string]interface{}{
  62. "enabled": false,
  63. },
  64. "container": map[string]interface{}{
  65. "command": *app.Run,
  66. "env": map[string]interface{}{
  67. "normal": CopyEnv(env),
  68. },
  69. },
  70. }
  71. } else {
  72. defaultValues = map[string]interface{}{
  73. "container": map[string]interface{}{
  74. "command": *app.Run,
  75. "env": map[string]interface{}{
  76. "normal": CopyEnv(env),
  77. },
  78. },
  79. }
  80. }
  81. if imageInfo != nil {
  82. defaultValues["image"] = map[string]interface{}{
  83. "repository": imageInfo.Repository,
  84. "tag": imageInfo.Tag,
  85. }
  86. }
  87. return defaultValues
  88. }
  89. func buildStackChart(parsed *PorterStackYAML, config *config.Config, projectID uint) (*chart.Chart, error) {
  90. deps := make([]*chart.Dependency, 0)
  91. for alias, app := range parsed.Apps {
  92. selectedRepo := "https://charts.getporter.dev"
  93. selectedVersion, err := getLatestTemplateVersion(*app.Type, config, projectID)
  94. if err != nil {
  95. return nil, err
  96. }
  97. deps = append(deps, &chart.Dependency{
  98. Name: *app.Type,
  99. Alias: alias,
  100. Version: selectedVersion,
  101. Repository: selectedRepo,
  102. })
  103. }
  104. chart, err := createChartFromDependencies(deps)
  105. if err != nil {
  106. return nil, err
  107. }
  108. return chart, nil
  109. }
  110. func createChartFromDependencies(deps []*chart.Dependency) (*chart.Chart, error) {
  111. metadata := &chart.Metadata{
  112. Name: "umbrella",
  113. Description: "Web application that is exposed to external traffic.",
  114. Version: "0.96.0",
  115. APIVersion: "v2",
  116. Home: "https://getporter.dev/",
  117. Icon: "https://user-images.githubusercontent.com/65516095/111255214-07d3da80-85ed-11eb-99e2-fddcbdb99bdb.png",
  118. Keywords: []string{
  119. "porter",
  120. "application",
  121. "service",
  122. "umbrella",
  123. },
  124. Type: "application",
  125. Dependencies: deps,
  126. }
  127. // create a new chart object with the metadata
  128. c := &chart.Chart{
  129. Metadata: metadata,
  130. }
  131. return c, nil
  132. }
  133. func getLatestTemplateVersion(templateName string, config *config.Config, projectID uint) (string, error) {
  134. repoIndex, err := loader.LoadRepoIndexPublic(config.ServerConf.DefaultApplicationHelmRepoURL)
  135. if err != nil {
  136. return "", fmt.Errorf("%s: %w", "unable to load porter chart repo", err)
  137. }
  138. templates := loader.RepoIndexToPorterChartList(repoIndex, config.ServerConf.DefaultApplicationHelmRepoURL)
  139. if err != nil {
  140. return "", fmt.Errorf("%s: %w", "unable to load porter chart list", err)
  141. }
  142. var version string
  143. // find the matching template name
  144. for _, template := range templates {
  145. if templateName == template.Name {
  146. version = template.Versions[0]
  147. break
  148. }
  149. }
  150. if version == "" {
  151. return "", fmt.Errorf("matching template version not found")
  152. }
  153. return version, nil
  154. }
  155. func convertMap(m interface{}) interface{} {
  156. switch m := m.(type) {
  157. case map[string]interface{}:
  158. for k, v := range m {
  159. m[k] = convertMap(v)
  160. }
  161. case map[interface{}]interface{}:
  162. result := map[string]interface{}{}
  163. for k, v := range m {
  164. result[k.(string)] = convertMap(v)
  165. }
  166. return result
  167. case []interface{}:
  168. for i, v := range m {
  169. m[i] = convertMap(v)
  170. }
  171. }
  172. return m
  173. }
  174. func CopyEnv(env map[string]string) map[string]string {
  175. envCopy := make(map[string]string)
  176. if env == nil {
  177. return envCopy
  178. }
  179. for k, v := range env {
  180. if k == "" || v == "" {
  181. continue
  182. }
  183. envCopy[k] = v
  184. }
  185. return envCopy
  186. }