|
|
@@ -1,13 +1,11 @@
|
|
|
package porter_app
|
|
|
|
|
|
import (
|
|
|
- "context"
|
|
|
"net/http"
|
|
|
|
|
|
"connectrpc.com/connect"
|
|
|
"github.com/google/uuid"
|
|
|
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
|
|
|
- "github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
|
|
|
"github.com/porter-dev/porter/api/server/authz"
|
|
|
"github.com/porter-dev/porter/api/server/handlers"
|
|
|
"github.com/porter-dev/porter/api/server/shared"
|
|
|
@@ -101,46 +99,15 @@ func (c *RollbackAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
|
|
|
return
|
|
|
}
|
|
|
|
|
|
- var targetProto *porterv1.PorterApp
|
|
|
- var targetRevisionNumber int
|
|
|
-
|
|
|
- if request.AppRevisionID != "" {
|
|
|
- targetProto, targetRevisionNumber, err = revisionByID(ctx, revisionByIDInput{
|
|
|
- projectID: int64(project.ID),
|
|
|
- appRevisionID: request.AppRevisionID,
|
|
|
- ccpClient: c.Config().ClusterControlPlaneClient,
|
|
|
- })
|
|
|
- if err != nil {
|
|
|
- err = telemetry.Error(ctx, span, err, "error getting revision proto")
|
|
|
- c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if targetProto == nil {
|
|
|
- targetProto, targetRevisionNumber, err = lastDeployedRevision(ctx, lastDeployedRevisionInput{
|
|
|
- appName: appName,
|
|
|
- projectID: int64(project.ID),
|
|
|
- deploymentTargetID: deploymentTargetID.String(),
|
|
|
- ccpClient: c.Config().ClusterControlPlaneClient,
|
|
|
- })
|
|
|
- if err != nil {
|
|
|
- err = telemetry.Error(ctx, span, err, "error getting last deployed proto")
|
|
|
- c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- applyReq := connect.NewRequest(&porterv1.ApplyPorterAppRequest{
|
|
|
- ProjectId: int64(project.ID),
|
|
|
- DeploymentTargetId: deploymentTargetID.String(),
|
|
|
- App: targetProto,
|
|
|
- PorterAppRevisionId: "",
|
|
|
- ForceBuild: false,
|
|
|
+ rollbackReq := connect.NewRequest(&porterv1.RollbackRevisionRequest{
|
|
|
+ ProjectId: int64(project.ID),
|
|
|
+ AppId: int64(app.ID),
|
|
|
+ DeploymentTargetId: deploymentTargetID.String(),
|
|
|
+ AppRevisionId: request.AppRevisionID,
|
|
|
})
|
|
|
- ccpResp, err := c.Config().ClusterControlPlaneClient.ApplyPorterApp(ctx, applyReq)
|
|
|
+ ccpResp, err := c.Config().ClusterControlPlaneClient.RollbackRevision(ctx, rollbackReq)
|
|
|
if err != nil {
|
|
|
- err := telemetry.Error(ctx, span, err, "error calling ccp apply porter app")
|
|
|
+ err := telemetry.Error(ctx, span, err, "error calling ccp rollback porter app")
|
|
|
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
return
|
|
|
}
|
|
|
@@ -155,143 +122,13 @@ func (c *RollbackAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
|
|
|
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
- if ccpResp.Msg.PorterAppRevisionId == "" {
|
|
|
- err := telemetry.Error(ctx, span, err, "ccp resp app revision id is nil")
|
|
|
- c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "resp-app-revision-id", Value: ccpResp.Msg.PorterAppRevisionId})
|
|
|
-
|
|
|
- if ccpResp.Msg.CliAction == porterv1.EnumCLIAction_ENUM_CLI_ACTION_UNSPECIFIED {
|
|
|
- err := telemetry.Error(ctx, span, err, "ccp resp cli action is nil")
|
|
|
- c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
- return
|
|
|
- }
|
|
|
- if ccpResp.Msg.CliAction != porterv1.EnumCLIAction_ENUM_CLI_ACTION_NONE {
|
|
|
- err := telemetry.Error(ctx, span, err, "ccp resp cli action is not none")
|
|
|
+ if ccpResp.Msg.TargetRevisionNumber == 0 {
|
|
|
+ err := telemetry.Error(ctx, span, err, "ccp resp target revision number is 0")
|
|
|
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
|
|
|
return
|
|
|
}
|
|
|
|
|
|
c.WriteResult(w, r, &RollbackAppRevisionResponse{
|
|
|
- TargetRevisionNumber: targetRevisionNumber,
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-type revisionByIDInput struct {
|
|
|
- projectID int64
|
|
|
- appRevisionID string
|
|
|
- ccpClient porterv1connect.ClusterControlPlaneServiceClient
|
|
|
-}
|
|
|
-
|
|
|
-func revisionByID(ctx context.Context, inp revisionByIDInput) (*porterv1.PorterApp, int, error) {
|
|
|
- ctx, span := telemetry.NewSpan(ctx, "get-revision-proto")
|
|
|
- defer span.End()
|
|
|
-
|
|
|
- var proto *porterv1.PorterApp
|
|
|
- var revisionNumber int
|
|
|
-
|
|
|
- if inp.projectID == 0 {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "project id is empty")
|
|
|
- }
|
|
|
- if inp.appRevisionID == "" {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "app revision id is empty")
|
|
|
- }
|
|
|
- if inp.ccpClient == nil {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "cluster control plane client is nil")
|
|
|
- }
|
|
|
-
|
|
|
- getRevisionReq := connect.NewRequest(&porterv1.GetAppRevisionRequest{
|
|
|
- ProjectId: inp.projectID,
|
|
|
- AppRevisionId: inp.appRevisionID,
|
|
|
- })
|
|
|
- ccpResp, err := inp.ccpClient.GetAppRevision(ctx, getRevisionReq)
|
|
|
- if err != nil {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, err, "error getting app revision")
|
|
|
- }
|
|
|
-
|
|
|
- if ccpResp == nil || ccpResp.Msg == nil {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "get app revision response is nil")
|
|
|
- }
|
|
|
-
|
|
|
- proto = ccpResp.Msg.AppRevision.App
|
|
|
- revisionNumber = int(ccpResp.Msg.AppRevision.RevisionNumber)
|
|
|
- if proto == nil || revisionNumber == 0 {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "app revision proto is nil")
|
|
|
- }
|
|
|
-
|
|
|
- return proto, revisionNumber, nil
|
|
|
-}
|
|
|
-
|
|
|
-type lastDeployedRevisionInput struct {
|
|
|
- appName string
|
|
|
- projectID int64
|
|
|
- deploymentTargetID string
|
|
|
- ccpClient porterv1connect.ClusterControlPlaneServiceClient
|
|
|
-}
|
|
|
-
|
|
|
-func lastDeployedRevision(ctx context.Context, inp lastDeployedRevisionInput) (*porterv1.PorterApp, int, error) {
|
|
|
- ctx, span := telemetry.NewSpan(ctx, "rollback-to-last-deployed-revision")
|
|
|
- defer span.End()
|
|
|
-
|
|
|
- var proto *porterv1.PorterApp
|
|
|
- var revisionNumber int
|
|
|
-
|
|
|
- if inp.appName == "" {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "app name is empty")
|
|
|
- }
|
|
|
- if inp.projectID == 0 {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "project id is empty")
|
|
|
- }
|
|
|
- if inp.deploymentTargetID == "" {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "deployment target id is empty")
|
|
|
- }
|
|
|
- if inp.ccpClient == nil {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "cluster control plane client is nil")
|
|
|
- }
|
|
|
-
|
|
|
- listAppRevisionsReq := connect.NewRequest(&porterv1.LatestAppRevisionsRequest{
|
|
|
- ProjectId: inp.projectID,
|
|
|
- DeploymentTargetId: inp.deploymentTargetID,
|
|
|
+ TargetRevisionNumber: int(ccpResp.Msg.TargetRevisionNumber),
|
|
|
})
|
|
|
-
|
|
|
- latestAppRevisionsResp, err := inp.ccpClient.LatestAppRevisions(ctx, listAppRevisionsReq)
|
|
|
- if err != nil {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, err, "error getting latest app revisions")
|
|
|
- }
|
|
|
-
|
|
|
- if latestAppRevisionsResp == nil || latestAppRevisionsResp.Msg == nil {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "latest app revisions response is nil")
|
|
|
- }
|
|
|
-
|
|
|
- revisions := latestAppRevisionsResp.Msg.AppRevisions
|
|
|
- if len(revisions) == 0 {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "no revisions found for app")
|
|
|
- }
|
|
|
-
|
|
|
- // a failed revision is added to the head of the list of revisions if it is the most recent revision
|
|
|
- // if the most recent revision is successful, then the failed revision will be ignored in the loop below
|
|
|
- // if the most recent revision is successful (revision number != 0), then skip it and start looking for the previous successful revision
|
|
|
- skip := 0
|
|
|
- if revisions[0].RevisionNumber != 0 {
|
|
|
- skip = 1
|
|
|
- }
|
|
|
- if len(revisions) <= skip {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "no previous successful revisions found for app")
|
|
|
- }
|
|
|
-
|
|
|
- for _, rev := range revisions[skip:] {
|
|
|
- if rev.RevisionNumber != 0 {
|
|
|
- proto = rev.App
|
|
|
- revisionNumber = int(rev.RevisionNumber)
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- if proto == nil || revisionNumber == 0 {
|
|
|
- return proto, revisionNumber, telemetry.Error(ctx, span, nil, "no previous successful revisions found for app")
|
|
|
- }
|
|
|
-
|
|
|
- return proto, revisionNumber, nil
|
|
|
}
|