add_application.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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. // re-read the stack to get the most upto date information
  86. stack, err = p.Repo().Stack().ReadStackByID(proj.ID, stack.ID)
  87. if err != nil {
  88. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  89. return
  90. }
  91. registries, err := p.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  92. if err != nil {
  93. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  94. return
  95. }
  96. helmAgent, err := p.GetHelmAgent(r, cluster, "")
  97. if err != nil {
  98. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  99. return
  100. }
  101. helmReleaseMap := make(map[string]*helmrelease.Release)
  102. deployErrs := make([]string, 0)
  103. for _, appResource := range newResources {
  104. rel, err := applyAppResource(&applyAppResourceOpts{
  105. config: p.Config(),
  106. projectID: proj.ID,
  107. namespace: namespace,
  108. cluster: cluster,
  109. registries: registries,
  110. helmAgent: helmAgent,
  111. request: req,
  112. stack: stack,
  113. })
  114. if err != nil {
  115. deployErrs = append(deployErrs, err.Error())
  116. } else {
  117. helmReleaseMap[fmt.Sprintf("%s/%s", namespace, appResource.Name)] = rel
  118. }
  119. }
  120. // update stack revision status
  121. if len(deployErrs) > 0 {
  122. revision.Status = string(types.StackRevisionStatusFailed)
  123. revision.Reason = "DeployError"
  124. revision.Message = strings.Join(deployErrs, " , ")
  125. } else {
  126. revision.Status = string(types.StackRevisionStatusDeployed)
  127. }
  128. revision, err = p.Repo().Stack().UpdateStackRevision(revision)
  129. if err != nil {
  130. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  131. return
  132. }
  133. saveErrs := make([]string, 0)
  134. for _, resource := range revision.Resources {
  135. if rel, exists := helmReleaseMap[fmt.Sprintf("%s/%s", namespace, resource.Name)]; exists {
  136. _, err = release.CreateAppReleaseFromHelmRelease(p.Config(), proj.ID, cluster.ID, resource.ID, rel)
  137. if err != nil {
  138. saveErrs = append(saveErrs, fmt.Sprintf("the resource %s/%s could not be saved right now", namespace, resource.Name))
  139. }
  140. }
  141. }
  142. if len(saveErrs) > 0 {
  143. revision.Reason = "SaveError"
  144. revision.Message = strings.Join(saveErrs, " , ")
  145. _, err = p.Repo().Stack().UpdateStackRevision(revision)
  146. if err != nil {
  147. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  148. return
  149. }
  150. } else {
  151. revision.Reason = "AddAppSuccess"
  152. revision.Message = "New application " + req.Name + " added successfully."
  153. _, err = p.Repo().Stack().UpdateStackRevision(revision)
  154. if err != nil {
  155. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  156. return
  157. }
  158. }
  159. }