add_application.go 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package stack
  2. import (
  3. "fmt"
  4. "net/http"
  5. "strings"
  6. "github.com/porter-dev/porter/api/server/authz"
  7. "github.com/porter-dev/porter/api/server/handlers"
  8. "github.com/porter-dev/porter/api/server/handlers/release"
  9. "github.com/porter-dev/porter/api/server/shared"
  10. "github.com/porter-dev/porter/api/server/shared/apierrors"
  11. "github.com/porter-dev/porter/api/server/shared/config"
  12. "github.com/porter-dev/porter/api/types"
  13. "github.com/porter-dev/porter/internal/models"
  14. "github.com/porter-dev/porter/internal/stacks"
  15. helmrelease "helm.sh/helm/v3/pkg/release"
  16. )
  17. type StackAddApplicationHandler struct {
  18. handlers.PorterHandlerReadWriter
  19. authz.KubernetesAgentGetter
  20. }
  21. func NewStackAddApplicationHandler(
  22. config *config.Config,
  23. reader shared.RequestDecoderValidator,
  24. writer shared.ResultWriter,
  25. ) *StackAddApplicationHandler {
  26. return &StackAddApplicationHandler{
  27. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, reader, writer),
  28. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  29. }
  30. }
  31. func (p *StackAddApplicationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  32. proj, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  33. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  34. namespace, _ := r.Context().Value(types.NamespaceScope).(string)
  35. stack, _ := r.Context().Value(types.StackScope).(*models.Stack)
  36. req := &types.CreateStackAppResourceRequest{}
  37. if ok := p.DecodeAndValidate(w, r, req); !ok {
  38. return
  39. }
  40. if len(stack.Revisions) == 0 {
  41. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  42. fmt.Errorf("no stack revisions exist"), http.StatusBadRequest,
  43. ))
  44. return
  45. }
  46. latestRevision, err := p.Repo().Stack().ReadStackRevisionByNumber(stack.ID, stack.Revisions[0].RevisionNumber)
  47. if err != nil {
  48. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  49. return
  50. }
  51. newSourceConfigs, err := stacks.CloneSourceConfigs(latestRevision.SourceConfigs)
  52. if err != nil {
  53. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  54. return
  55. }
  56. appResources, err := stacks.CloneAppResources(latestRevision.Resources, latestRevision.SourceConfigs, newSourceConfigs)
  57. if err != nil {
  58. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  59. return
  60. }
  61. newResources, err := getResourceModels([]*types.CreateStackAppResourceRequest{req}, newSourceConfigs, p.Config().ServerConf.DefaultApplicationHelmRepoURL)
  62. if err != nil {
  63. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  64. return
  65. }
  66. appResources = append(appResources, newResources...)
  67. envGroups, err := stacks.CloneEnvGroups(latestRevision.EnvGroups)
  68. if err != nil {
  69. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  70. return
  71. }
  72. newRevision := &models.StackRevision{
  73. StackID: stack.ID,
  74. RevisionNumber: latestRevision.RevisionNumber + 1,
  75. Status: string(types.StackRevisionStatusDeploying),
  76. SourceConfigs: newSourceConfigs,
  77. Resources: appResources,
  78. EnvGroups: envGroups,
  79. }
  80. revision, err := p.Repo().Stack().AppendNewRevision(newRevision)
  81. if err != nil {
  82. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  83. return
  84. }
  85. registries, err := p.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  86. if err != nil {
  87. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  88. return
  89. }
  90. helmAgent, err := p.GetHelmAgent(r, cluster, "")
  91. if err != nil {
  92. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  93. return
  94. }
  95. helmReleaseMap := make(map[string]*helmrelease.Release)
  96. deployErrs := make([]string, 0)
  97. for _, appResource := range newResources {
  98. rel, err := applyAppResource(&applyAppResourceOpts{
  99. config: p.Config(),
  100. projectID: proj.ID,
  101. namespace: namespace,
  102. cluster: cluster,
  103. registries: registries,
  104. helmAgent: helmAgent,
  105. request: req,
  106. })
  107. if err != nil {
  108. deployErrs = append(deployErrs, err.Error())
  109. } else {
  110. helmReleaseMap[fmt.Sprintf("%s/%s", namespace, appResource.Name)] = rel
  111. }
  112. }
  113. // update stack revision status
  114. if len(deployErrs) > 0 {
  115. revision.Status = string(types.StackRevisionStatusFailed)
  116. revision.Reason = "DeployError"
  117. revision.Message = strings.Join(deployErrs, " , ")
  118. } else {
  119. revision.Status = string(types.StackRevisionStatusDeployed)
  120. }
  121. revision, err = p.Repo().Stack().UpdateStackRevision(revision)
  122. if err != nil {
  123. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  124. return
  125. }
  126. saveErrs := make([]string, 0)
  127. for _, resource := range revision.Resources {
  128. if rel, exists := helmReleaseMap[fmt.Sprintf("%s/%s", namespace, resource.Name)]; exists {
  129. _, err = release.CreateAppReleaseFromHelmRelease(p.Config(), proj.ID, cluster.ID, resource.ID, rel)
  130. if err != nil {
  131. saveErrs = append(saveErrs, fmt.Sprintf("the resource %s/%s could not be saved right now", namespace, resource.Name))
  132. }
  133. }
  134. }
  135. if len(saveErrs) > 0 {
  136. revision.Reason = "SaveError"
  137. revision.Message = strings.Join(saveErrs, " , ")
  138. _, err = p.Repo().Stack().UpdateStackRevision(revision)
  139. if err != nil {
  140. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  141. return
  142. }
  143. } else {
  144. revision.Reason = "AddAppSuccess"
  145. revision.Message = "New application " + req.Name + " added successfully."
  146. _, err = p.Repo().Stack().UpdateStackRevision(revision)
  147. if err != nil {
  148. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  149. return
  150. }
  151. }
  152. }