rollback_revision.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package porter_app
  2. import (
  3. "net/http"
  4. "connectrpc.com/connect"
  5. porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
  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/server/shared/requestutils"
  12. "github.com/porter-dev/porter/api/types"
  13. "github.com/porter-dev/porter/internal/models"
  14. "github.com/porter-dev/porter/internal/telemetry"
  15. )
  16. // RollbackAppRevisionHandler rolls back an app revision to the last deployed revision
  17. type RollbackAppRevisionHandler struct {
  18. handlers.PorterHandlerReadWriter
  19. authz.KubernetesAgentGetter
  20. }
  21. // NewRollbackAppRevisionHandler returns a new RollbackAppRevisionHandler
  22. func NewRollbackAppRevisionHandler(
  23. config *config.Config,
  24. decoderValidator shared.RequestDecoderValidator,
  25. writer shared.ResultWriter,
  26. ) *RollbackAppRevisionHandler {
  27. return &RollbackAppRevisionHandler{
  28. PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
  29. KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
  30. }
  31. }
  32. // RollbackAppRevisionRequest is the request body for the /apps/{porter_app_name}/rollback endpoint
  33. type RollbackAppRevisionRequest struct {
  34. DeploymentTargetID string `json:"deployment_target_id"`
  35. DeploymentTargetName string `json:"deployment_target_name"`
  36. AppRevisionID string `json:"app_revision_id"`
  37. }
  38. // RollbackAppRevisionResponse is the response body for the /apps/{porter_app_name}/rollback endpoint
  39. type RollbackAppRevisionResponse struct {
  40. TargetRevisionNumber int `json:"target_revision_number"`
  41. }
  42. // ServeHTTP handles the request and rolls back the app revision
  43. func (c *RollbackAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  44. ctx, span := telemetry.NewSpan(r.Context(), "serve-rollback-app-revision")
  45. defer span.End()
  46. project, _ := ctx.Value(types.ProjectScope).(*models.Project)
  47. cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)
  48. if !project.GetFeatureFlag(models.ValidateApplyV2, c.Config().LaunchDarklyClient) {
  49. err := telemetry.Error(ctx, span, nil, "project does not have validate apply v2 enabled")
  50. c.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
  51. return
  52. }
  53. request := &RollbackAppRevisionRequest{}
  54. if ok := c.DecodeAndValidate(w, r, request); !ok {
  55. err := telemetry.Error(ctx, span, nil, "error decoding request")
  56. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  57. return
  58. }
  59. appName, reqErr := requestutils.GetURLParamString(r, types.URLParamPorterAppName)
  60. if reqErr != nil {
  61. err := telemetry.Error(ctx, span, nil, "error parsing porter app name")
  62. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  63. return
  64. }
  65. telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "application-name", Value: appName})
  66. app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, appName)
  67. if err != nil {
  68. err = telemetry.Error(ctx, span, err, "error reading porter app by name")
  69. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  70. return
  71. }
  72. if app.ID == 0 {
  73. err = telemetry.Error(ctx, span, nil, "app with name does not exist in project")
  74. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
  75. return
  76. }
  77. deploymentTargetName := request.DeploymentTargetName
  78. if request.DeploymentTargetName == "" && request.DeploymentTargetID == "" {
  79. defaultDeploymentTarget, err := defaultDeploymentTarget(ctx, defaultDeploymentTargetInput{
  80. ProjectID: project.ID,
  81. ClusterID: cluster.ID,
  82. ClusterControlPlaneClient: c.Config().ClusterControlPlaneClient,
  83. })
  84. if err != nil {
  85. err := telemetry.Error(ctx, span, err, "error getting default deployment target")
  86. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  87. return
  88. }
  89. deploymentTargetName = defaultDeploymentTarget.Name
  90. }
  91. telemetry.WithAttributes(span,
  92. telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetID},
  93. telemetry.AttributeKV{Key: "deployment-target-name", Value: request.DeploymentTargetName},
  94. )
  95. rollbackReq := connect.NewRequest(&porterv1.RollbackRevisionRequest{
  96. ProjectId: int64(project.ID),
  97. AppId: int64(app.ID),
  98. DeploymentTargetIdentifier: &porterv1.DeploymentTargetIdentifier{
  99. Id: request.DeploymentTargetID,
  100. Name: deploymentTargetName,
  101. },
  102. AppRevisionId: request.AppRevisionID,
  103. AppName: appName,
  104. })
  105. ccpResp, err := c.Config().ClusterControlPlaneClient.RollbackRevision(ctx, rollbackReq)
  106. if err != nil {
  107. err := telemetry.Error(ctx, span, err, "error calling ccp rollback porter app")
  108. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  109. return
  110. }
  111. if ccpResp == nil {
  112. err := telemetry.Error(ctx, span, err, "ccp resp is nil")
  113. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  114. return
  115. }
  116. if ccpResp.Msg == nil {
  117. err := telemetry.Error(ctx, span, err, "ccp resp msg is nil")
  118. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  119. return
  120. }
  121. if ccpResp.Msg.TargetRevisionNumber == 0 {
  122. err := telemetry.Error(ctx, span, err, "ccp resp target revision number is 0")
  123. c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
  124. return
  125. }
  126. c.WriteResult(w, r, &RollbackAppRevisionResponse{
  127. TargetRevisionNumber: int(ccpResp.Msg.TargetRevisionNumber),
  128. })
  129. }