Skip to content

Commit 3954cb3

Browse files
author
Feroze Mohideen
authored
check app revision status in update flow (#4063)
1 parent 73535ff commit 3954cb3

File tree

7 files changed

+185
-15
lines changed

7 files changed

+185
-15
lines changed

api/client/porter_app.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,26 @@ func (c *Client) GetRevision(
479479
return resp, err
480480
}
481481

482+
// GetRevisionStatus returns the status of an app revision
483+
func (c *Client) GetRevisionStatus(
484+
ctx context.Context,
485+
projectID uint, clusterID uint,
486+
appName string, appRevisionId string,
487+
) (*porter_app.GetAppRevisionStatusResponse, error) {
488+
resp := &porter_app.GetAppRevisionStatusResponse{}
489+
490+
err := c.getRequest(
491+
fmt.Sprintf(
492+
"/projects/%d/clusters/%d/apps/%s/revisions/%s/status",
493+
projectID, clusterID, appName, appRevisionId,
494+
),
495+
nil,
496+
resp,
497+
)
498+
499+
return resp, err
500+
}
501+
482502
// UpdateRevisionStatus updates the status of an app revision
483503
func (c *Client) UpdateRevisionStatus(
484504
ctx context.Context,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package porter_app
2+
3+
import (
4+
"net/http"
5+
6+
"connectrpc.com/connect"
7+
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
8+
"github.com/porter-dev/porter/api/server/authz"
9+
"github.com/porter-dev/porter/api/server/handlers"
10+
"github.com/porter-dev/porter/api/server/shared"
11+
"github.com/porter-dev/porter/api/server/shared/apierrors"
12+
"github.com/porter-dev/porter/api/server/shared/config"
13+
"github.com/porter-dev/porter/api/server/shared/requestutils"
14+
"github.com/porter-dev/porter/api/types"
15+
"github.com/porter-dev/porter/internal/models"
16+
"github.com/porter-dev/porter/internal/porter_app"
17+
"github.com/porter-dev/porter/internal/telemetry"
18+
)
19+
20+
// GetAppRevisionStatusHandler handles requests to the /apps/{porter_app_name}/revisions/{app_revision_id}/status endpoint
21+
type GetAppRevisionStatusHandler struct {
22+
handlers.PorterHandlerReadWriter
23+
authz.KubernetesAgentGetter
24+
}
25+
26+
// NewGetAppRevisionStatusHandler returns a new GetAppRevisionStatusHandler
27+
func NewGetAppRevisionStatusHandler(
28+
config *config.Config,
29+
decoderValidator shared.RequestDecoderValidator,
30+
writer shared.ResultWriter,
31+
) *GetAppRevisionHandler {
32+
return &GetAppRevisionHandler{
33+
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
34+
KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
35+
}
36+
}
37+
38+
// GetAppRevisionStatusResponse represents the response from the /apps/{porter_app_name}/revisions/{app_revision_id}/status endpoint
39+
type GetAppRevisionStatusResponse struct {
40+
AppRevisionStatus porter_app.RevisionStatus `json:"app_revision_status"`
41+
}
42+
43+
// GetAppRevisionStatusHandler returns the status of an app revision
44+
func (c *GetAppRevisionStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
45+
ctx, span := telemetry.NewSpan(r.Context(), "serve-get-app-revision")
46+
defer span.End()
47+
48+
project, _ := r.Context().Value(types.ProjectScope).(*models.Project)
49+
50+
appRevisionID, reqErr := requestutils.GetURLParamString(r, types.URLParamAppRevisionID)
51+
if reqErr != nil {
52+
err := telemetry.Error(ctx, span, nil, "error parsing app revision id")
53+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
54+
return
55+
}
56+
57+
getRevisionStatusReq := connect.NewRequest(&porterv1.AppRevisionStatusRequest{
58+
ProjectId: int64(project.ID),
59+
AppRevisionId: appRevisionID,
60+
})
61+
ccpResp, err := c.Config().ClusterControlPlaneClient.AppRevisionStatus(ctx, getRevisionStatusReq)
62+
if err != nil {
63+
err = telemetry.Error(ctx, span, err, "error getting app revision status")
64+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
65+
return
66+
}
67+
68+
if ccpResp == nil || ccpResp.Msg == nil {
69+
err = telemetry.Error(ctx, span, nil, "get app revision response is nil")
70+
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
71+
return
72+
}
73+
74+
revisionStatus := porter_app.RevisionStatus{
75+
PredeployStarted: ccpResp.Msg.PredeployStarted,
76+
PredeploySuccessful: ccpResp.Msg.PredeploySuccessful,
77+
PredeployFailed: ccpResp.Msg.PredeployFailed,
78+
InstallStarted: ccpResp.Msg.InstallStarted,
79+
InstallSuccessful: ccpResp.Msg.InstallSuccessful,
80+
InstallFailed: ccpResp.Msg.InstallFailed,
81+
DeploymentStarted: ccpResp.Msg.DeploymentStarted,
82+
DeploymentSuccessful: ccpResp.Msg.DeploymentSuccessful,
83+
DeploymentFailed: ccpResp.Msg.DeploymentFailed,
84+
IsInTerminalStatus: ccpResp.Msg.IsInTerminalStatus,
85+
}
86+
87+
res := &GetAppRevisionStatusResponse{
88+
AppRevisionStatus: revisionStatus,
89+
}
90+
91+
c.WriteResult(w, r, res)
92+
}

api/server/router/porter_app.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,35 @@ func getPorterAppRoutes(
12121212
Router: r,
12131213
})
12141214

1215+
// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id}/status -> porter_app.NewGetAppRevisionStatusHandler
1216+
getAppRevisionStatusEndpoint := factory.NewAPIEndpoint(
1217+
&types.APIRequestMetadata{
1218+
Verb: types.APIVerbGet,
1219+
Method: types.HTTPVerbGet,
1220+
Path: &types.Path{
1221+
Parent: basePath,
1222+
RelativePath: fmt.Sprintf("/apps/{%s}/revisions/{%s}/status", types.URLParamPorterAppName, types.URLParamAppRevisionID),
1223+
},
1224+
Scopes: []types.PermissionScope{
1225+
types.UserScope,
1226+
types.ProjectScope,
1227+
types.ClusterScope,
1228+
},
1229+
},
1230+
)
1231+
1232+
getAppRevisionStatusHandler := porter_app.NewGetAppRevisionStatusHandler(
1233+
config,
1234+
factory.GetDecoderValidator(),
1235+
factory.GetResultWriter(),
1236+
)
1237+
1238+
routes = append(routes, &router.Route{
1239+
Endpoint: getAppRevisionStatusEndpoint,
1240+
Handler: getAppRevisionStatusHandler,
1241+
Router: r,
1242+
})
1243+
12151244
// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id} -> porter_app.NewUpdateAppRevisionStatusHandler
12161245
updateAppRevisionStatusEndpoint := factory.NewAPIEndpoint(
12171246
&types.APIRequestMetadata{

cli/cmd/v2/update.go

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -202,31 +202,34 @@ func Update(ctx context.Context, inp UpdateInput) error {
202202

203203
now := time.Now().UTC()
204204

205-
var status models.AppRevisionStatus
205+
var status *porter_app.GetAppRevisionStatusResponse
206206

207207
for {
208208
if time.Since(now) > checkDeployTimeout {
209209
return errors.New("timed out waiting for app to deploy")
210210
}
211211

212-
revision, err := client.GetRevision(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
212+
status, err := client.GetRevisionStatus(ctx, cliConf.Project, cliConf.Cluster, appName, updateResp.AppRevisionId)
213213
if err != nil {
214214
return fmt.Errorf("error getting app revision status: %w", err)
215215
}
216-
status = revision.AppRevision.Status
217-
218-
if status == models.AppRevisionStatus_PredeployFailed ||
219-
status == models.AppRevisionStatus_InstallFailed ||
220-
status == models.AppRevisionStatus_InstallSuccessful ||
221-
status == models.AppRevisionStatus_DeploymentSuccessful ||
222-
status == models.AppRevisionStatus_DeploymentProgressing ||
223-
status == models.AppRevisionStatus_DeploymentFailed {
224-
break
216+
217+
if status == nil {
218+
return errors.New("unable to determine status of app revision")
225219
}
226-
if status == models.AppRevisionStatus_AwaitingPredeploy {
220+
221+
if status.AppRevisionStatus.PredeployStarted {
227222
color.New(color.FgGreen).Printf("Waiting for predeploy to complete...\n") // nolint:errcheck,gosec
228223
}
229224

225+
if status.AppRevisionStatus.InstallStarted {
226+
color.New(color.FgGreen).Printf("Waiting for deploy to complete...\n") // nolint:errcheck,gosec
227+
}
228+
229+
if status.AppRevisionStatus.IsInTerminalStatus {
230+
break
231+
}
232+
230233
time.Sleep(checkDeployFrequency)
231234
}
232235

@@ -239,10 +242,10 @@ func Update(ctx context.Context, inp UpdateInput) error {
239242
CommitSHA: commitSHA,
240243
})
241244

242-
if status == models.AppRevisionStatus_InstallFailed {
245+
if status.AppRevisionStatus.InstallFailed {
243246
return errors.New("app failed to deploy")
244247
}
245-
if status == models.AppRevisionStatus_PredeployFailed {
248+
if status.AppRevisionStatus.PredeployFailed {
246249
return errors.New("predeploy failed for new revision")
247250
}
248251

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ require (
8383
github.com/matryer/is v1.4.0
8484
github.com/nats-io/nats.go v1.24.0
8585
github.com/open-policy-agent/opa v0.44.0
86-
github.com/porter-dev/api-contracts v0.2.59
86+
github.com/porter-dev/api-contracts v0.2.64
8787
github.com/riandyrn/otelchi v0.5.1
8888
github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
8989
github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
15221522
github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
15231523
github.com/porter-dev/api-contracts v0.2.59 h1:EV2xr9a5FpPHnTsz77W3dV/qWC8MnE0kOD+tBZuwhvE=
15241524
github.com/porter-dev/api-contracts v0.2.59/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
1525+
github.com/porter-dev/api-contracts v0.2.64 h1:qLRomQRoOWqSMo8lx/rY+jACiIeCAExog04KfgPlXxY=
1526+
github.com/porter-dev/api-contracts v0.2.64/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
15251527
github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
15261528
github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
15271529
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=

internal/porter_app/revisions.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,30 @@ type Revision struct {
4141
AppInstanceID uuid.UUID `json:"app_instance_id"`
4242
}
4343

44+
// RevisionStatus describes the status of a revision
45+
type RevisionStatus struct {
46+
// PredeployStarted is true if the predeploy process has started
47+
PredeployStarted bool `json:"predeploy_started"`
48+
// PredeploySuccessful is true if the predeploy process has completed successfully
49+
PredeploySuccessful bool `json:"predeploy_successful"`
50+
// PredeployFailed is true if the predeploy process has failed
51+
PredeployFailed bool `json:"predeploy_failed"`
52+
// InstallStarted is true if the install process has started
53+
InstallStarted bool `json:"install_started"`
54+
// InstallSuccessful is true if the install process has completed successfully
55+
InstallSuccessful bool `json:"install_successful"`
56+
// InstallFailed is true if the install process has failed
57+
InstallFailed bool `json:"install_failed"`
58+
// DeploymentStarted is true if the deployment process has started
59+
DeploymentStarted bool `json:"deployment_started"`
60+
// DeploymentSuccessful is true if the deployment process has completed successfully
61+
DeploymentSuccessful bool `json:"deployment_successful"`
62+
// DeploymentFailed is true if the deployment process has failed
63+
DeploymentFailed bool `json:"deployment_failed"`
64+
// IsInTerminalStatus is true if the revision is in a terminal status
65+
IsInTerminalStatus bool `json:"is_in_terminal_status"`
66+
}
67+
4468
// DeploymentTarget is a simplified version of the deployment target struct
4569
type DeploymentTarget struct {
4670
ID string `json:"id"`

0 commit comments

Comments
 (0)