update_deployment_status_by_cluster.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package environment
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  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/shared"
  9. "github.com/porter-dev/porter/api/server/shared/apierrors"
  10. "github.com/porter-dev/porter/api/server/shared/config"
  11. "github.com/porter-dev/porter/api/types"
  12. "github.com/porter-dev/porter/internal/models"
  13. "gorm.io/gorm"
  14. )
  15. type UpdateDeploymentStatusByClusterHandler struct {
  16. handlers.PorterHandlerReadWriter
  17. authz.KubernetesAgentGetter
  18. }
  19. func NewUpdateDeploymentStatusByClusterHandler(
  20. config *config.Config,
  21. decoderValidator shared.RequestDecoderValidator,
  22. writer shared.ResultWriter,
  23. ) *UpdateDeploymentStatusByClusterHandler {
  24. return &UpdateDeploymentStatusByClusterHandler{
  25. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  26. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  27. }
  28. }
  29. func (c *UpdateDeploymentStatusByClusterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  30. project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
  31. cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
  32. request := &types.UpdateDeploymentStatusByClusterRequest{}
  33. if ok := c.DecodeAndValidate(w, r, request); !ok {
  34. return
  35. }
  36. if request.Namespace == "" && request.PRNumber == 0 {
  37. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  38. fmt.Errorf("either namespace or pr_number must be present in request body"), http.StatusBadRequest,
  39. ))
  40. return
  41. }
  42. var err error
  43. // read the environment to get the environment id
  44. env, err := c.Repo().Environment().ReadEnvironmentByOwnerRepoName(
  45. project.ID, cluster.ID, request.RepoOwner, request.RepoName,
  46. )
  47. if err != nil {
  48. if errors.Is(err, gorm.ErrRecordNotFound) {
  49. c.HandleAPIError(w, r, apierrors.NewErrNotFound(errEnvironmentNotFound))
  50. return
  51. }
  52. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  53. return
  54. }
  55. depl, err := c.Repo().Environment().ReadDeploymentForBranch(
  56. env.ID, request.RepoOwner, request.RepoName, request.PRBranchFrom,
  57. )
  58. if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
  59. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  60. return
  61. }
  62. if depl == nil {
  63. if request.PRNumber != 0 {
  64. depl, err = c.Repo().Environment().ReadDeploymentByGitDetails(
  65. env.ID, request.RepoOwner, request.RepoName, request.PRNumber,
  66. )
  67. if err != nil {
  68. if errors.Is(err, gorm.ErrRecordNotFound) {
  69. c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
  70. return
  71. }
  72. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  73. return
  74. }
  75. } else if request.Namespace != "" {
  76. depl, err = c.Repo().Environment().ReadDeployment(env.ID, request.Namespace)
  77. if err != nil {
  78. if errors.Is(err, gorm.ErrRecordNotFound) {
  79. c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
  80. return
  81. }
  82. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  83. return
  84. }
  85. }
  86. }
  87. if depl == nil {
  88. c.HandleAPIError(w, r, apierrors.NewErrNotFound(errDeploymentNotFound))
  89. return
  90. }
  91. client, err := getGithubClientFromEnvironment(c.Config(), env)
  92. if err != nil {
  93. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  94. fmt.Errorf("unable to get github client: %w", err), http.StatusConflict,
  95. ))
  96. return
  97. }
  98. if !depl.IsBranchDeploy() {
  99. // add a check for the PR to be open before creating a comment
  100. prClosed, err := isGithubPRClosed(client, depl.RepoOwner, depl.RepoName, int(depl.PullRequestID))
  101. if err != nil {
  102. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
  103. fmt.Errorf("error fetching details of github PR for deployment ID: %d. Error: %w",
  104. depl.ID, err), http.StatusConflict,
  105. ))
  106. return
  107. }
  108. if prClosed {
  109. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(fmt.Errorf("Github PR has been closed"),
  110. http.StatusConflict))
  111. return
  112. }
  113. }
  114. if depl.Status == types.DeploymentStatusInactive && request.Status != string(types.DeploymentStatusCreating) {
  115. // a deployment from "inactive" state can only transition to "creating"
  116. c.WriteResult(w, r, depl.ToDeploymentType())
  117. return
  118. }
  119. depl.Status = types.DeploymentStatus(request.Status)
  120. // create the deployment
  121. depl, err = c.Repo().Environment().UpdateDeployment(depl)
  122. if err != nil {
  123. c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
  124. return
  125. }
  126. c.WriteResult(w, r, depl.ToDeploymentType())
  127. }