Skip to content

Commit

Permalink
move rollback logic to ccp (#3804)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Oct 12, 2023
1 parent 69a3af9 commit 46d811b
Show file tree
Hide file tree
Showing 3 changed files with 13 additions and 176 deletions.
183 changes: 10 additions & 173 deletions api/server/handlers/porter_app/rollback_revision.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ require (
github.com/matryer/is v1.4.0
github.com/nats-io/nats.go v1.24.0
github.com/open-policy-agent/opa v0.44.0
github.com/porter-dev/api-contracts v0.2.15
github.com/porter-dev/api-contracts v0.2.17
github.com/riandyrn/otelchi v0.5.1
github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1516,8 +1516,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
github.com/porter-dev/api-contracts v0.2.15 h1:o8Mk2hx0flRl8TXG+PVhMvphSPk1McfO/Frx82udr0Q=
github.com/porter-dev/api-contracts v0.2.15/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
github.com/porter-dev/api-contracts v0.2.17 h1:VG7iRdl5GZ0DzPFUnyfo8BAkWDTQx3SH2bH2nYj6icA=
github.com/porter-dev/api-contracts v0.2.17/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
Expand Down

0 comments on commit 46d811b

Please sign in to comment.