add_application.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. nameValidator := make(map[string]bool)
  68. for _, res := range appResources {
  69. if _, ok := nameValidator[res.Name]; ok {
  70. p.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("duplicate app resource name: %s", res.Name),
  71. http.StatusBadRequest))
  72. return
  73. }
  74. nameValidator[res.Name] = true
  75. }
  76. envGroups, err := stacks.CloneEnvGroups(latestRevision.EnvGroups)
  77. if err != nil {
  78. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  79. return
  80. }
  81. newRevision := &models.StackRevision{
  82. StackID: stack.ID,
  83. RevisionNumber: latestRevision.RevisionNumber + 1,
  84. Status: string(types.StackRevisionStatusDeploying),
  85. SourceConfigs: newSourceConfigs,
  86. Resources: appResources,
  87. EnvGroups: envGroups,
  88. }
  89. revision, err := p.Repo().Stack().AppendNewRevision(newRevision)
  90. if err != nil {
  91. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  92. return
  93. }
  94. // re-read the stack to get the most upto date information
  95. stack, err = p.Repo().Stack().ReadStackByID(proj.ID, stack.ID)
  96. if err != nil {
  97. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  98. return
  99. }
  100. registries, err := p.Repo().Registry().ListRegistriesByProjectID(cluster.ProjectID)
  101. if err != nil {
  102. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  103. return
  104. }
  105. helmAgent, err := p.GetHelmAgent(r, cluster, "")
  106. if err != nil {
  107. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  108. return
  109. }
  110. helmReleaseMap := make(map[string]*helmrelease.Release)
  111. deployErrs := make([]string, 0)
  112. for _, appResource := range newResources {
  113. rel, err := applyAppResource(&applyAppResourceOpts{
  114. config: p.Config(),
  115. projectID: proj.ID,
  116. namespace: namespace,
  117. cluster: cluster,
  118. registries: registries,
  119. helmAgent: helmAgent,
  120. request: req,
  121. stackName: stack.Name,
  122. stackRevision: stack.Revisions[0].RevisionNumber,
  123. })
  124. if err != nil {
  125. deployErrs = append(deployErrs, err.Error())
  126. } else {
  127. helmReleaseMap[fmt.Sprintf("%s/%s", namespace, appResource.Name)] = rel
  128. }
  129. }
  130. // update stack revision status
  131. if len(deployErrs) > 0 {
  132. revision.Status = string(types.StackRevisionStatusFailed)
  133. revision.Reason = "DeployError"
  134. revision.Message = strings.Join(deployErrs, " , ")
  135. } else {
  136. revision.Status = string(types.StackRevisionStatusDeployed)
  137. }
  138. revision, err = p.Repo().Stack().UpdateStackRevision(revision)
  139. if err != nil {
  140. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  141. return
  142. }
  143. saveErrs := make([]string, 0)
  144. for _, resource := range revision.Resources {
  145. if rel, exists := helmReleaseMap[fmt.Sprintf("%s/%s", namespace, resource.Name)]; exists {
  146. _, err = release.CreateAppReleaseFromHelmRelease(p.Config(), proj.ID, cluster.ID, resource.ID, rel)
  147. if err != nil {
  148. saveErrs = append(saveErrs, fmt.Sprintf("the resource %s/%s could not be saved right now", namespace, resource.Name))
  149. }
  150. }
  151. }
  152. if len(saveErrs) > 0 {
  153. revision.Reason = "SaveError"
  154. revision.Message = strings.Join(saveErrs, " , ")
  155. _, err = p.Repo().Stack().UpdateStackRevision(revision)
  156. if err != nil {
  157. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  158. return
  159. }
  160. } else {
  161. revision.Reason = "AddAppSuccess"
  162. revision.Message = "New application " + req.Name + " added successfully."
  163. _, err = p.Repo().Stack().UpdateStackRevision(revision)
  164. if err != nil {
  165. p.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  166. return
  167. }
  168. }
  169. }