Skip to content

Commit

Permalink
[POR-1721] Add status footers to porter yaml v2 (#3584)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feroze Mohideen authored Sep 19, 2023
1 parent f9eea61 commit e34ed7d
Show file tree
Hide file tree
Showing 11 changed files with 816 additions and 86 deletions.
112 changes: 112 additions & 0 deletions api/server/handlers/porter_app/pod_status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package porter_app

import (
"net/http"

"connectrpc.com/connect"
porterv1 "github.com/porter-dev/api-contracts/generated/go/porter/v1"
"github.com/porter-dev/porter/api/server/authz"
"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
"github.com/porter-dev/porter/api/server/shared/apierrors"
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/telemetry"
v1 "k8s.io/api/core/v1"
)

// PodStatusHandler is the handler for GET /apps/pods
type PodStatusHandler struct {
handlers.PorterHandlerReadWriter
authz.KubernetesAgentGetter
}

// NewPodStatusHandler returns a new PodStatusHandler
func NewPodStatusHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *PodStatusHandler {
return &PodStatusHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
}
}

// PodStatusRequest is the expected format for a request body on GET /apps/pods
type PodStatusRequest struct {
DeploymentTargetID string `schema:"deployment_target_id"`
Selectors string `schema:"selectors"`
}

func (c *PodStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-pod-status")
defer span.End()

request := &PodStatusRequest{}
if ok := c.DecodeAndValidate(w, r, request); !ok {
err := telemetry.Error(ctx, span, nil, "invalid request")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

cluster, _ := r.Context().Value(types.ClusterScope).(*models.Cluster)
project, _ := r.Context().Value(types.ProjectScope).(*models.Project)

telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "selectors", Value: request.Selectors})

if request.DeploymentTargetID == "" {
err := telemetry.Error(ctx, span, nil, "must provide deployment target id")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetID})

deploymentTargetDetailsReq := connect.NewRequest(&porterv1.DeploymentTargetDetailsRequest{
ProjectId: int64(project.ID),
DeploymentTargetId: request.DeploymentTargetID,
})

deploymentTargetDetailsResp, err := c.Config().ClusterControlPlaneClient.DeploymentTargetDetails(ctx, deploymentTargetDetailsReq)
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting deployment target details from cluster control plane client")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

if deploymentTargetDetailsResp == nil || deploymentTargetDetailsResp.Msg == nil {
err := telemetry.Error(ctx, span, err, "deployment target details resp is nil")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

if deploymentTargetDetailsResp.Msg.ClusterId != int64(cluster.ID) {
err := telemetry.Error(ctx, span, err, "deployment target details resp cluster id does not match cluster id")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

namespace := deploymentTargetDetailsResp.Msg.Namespace
telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "namespace", Value: namespace})

agent, err := c.GetAgent(r, cluster, "")
if err != nil {
err = telemetry.Error(ctx, span, err, "unable to get agent")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

pods := []v1.Pod{}

podsList, err := agent.GetPodsByLabel(request.Selectors, namespace)
if err != nil {
err = telemetry.Error(ctx, span, err, "unable to get pods by label")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

pods = append(pods, podsList.Items...)

c.WriteResult(w, r, pods)
}
71 changes: 71 additions & 0 deletions api/server/handlers/porter_app/status.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package porter_app

import (
"net/http"

"github.com/porter-dev/porter/api/server/authz"
"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
"github.com/porter-dev/porter/api/server/shared/apierrors"
"github.com/porter-dev/porter/api/server/shared/config"
"github.com/porter-dev/porter/api/server/shared/requestutils"
"github.com/porter-dev/porter/api/server/shared/websocket"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/telemetry"
)

// AppStatusHandler handles the /apps/{kind}/status endpoint
type AppStatusHandler struct {
handlers.PorterHandlerReadWriter
authz.KubernetesAgentGetter
}

// NewAppStatusHandler returns a new AppStatusHandler
func NewAppStatusHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *AppStatusHandler {
return &AppStatusHandler{
PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
KubernetesAgentGetter: authz.NewOutOfClusterAgentGetter(config),
}
}

// AppStatusRequest represents the accepted fields on a request to the /apps/{kind}/status endpoint
type AppStatusRequest struct {
Selectors string `schema:"selectors"`
}

func (c *AppStatusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-app-logs")
defer span.End()

safeRW := ctx.Value(types.RequestCtxWebsocketKey).(*websocket.WebsocketSafeReadWriter)
request := &AppStatusRequest{}

if ok := c.DecodeAndValidate(w, r, request); !ok {
err := telemetry.Error(ctx, span, nil, "invalid request")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

cluster, _ := ctx.Value(types.ClusterScope).(*models.Cluster)

agent, err := c.GetAgent(r, cluster, "")
if err != nil {
err = telemetry.Error(ctx, span, err, "unable to get agent")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

kind, _ := requestutils.GetURLParamString(r, types.URLParamKind)

err = agent.StreamControllerStatus(kind, request.Selectors, safeRW)

if err != nil {
c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
return
}
}
84 changes: 72 additions & 12 deletions api/server/router/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func getPorterAppRoutes(
factory shared.APIEndpointFactory,
) ([]*router.Route, *types.Path) {
relPath := "/applications"
relPathV2 := "/apps"

newPath := &types.Path{
Parent: basePath,
Expand Down Expand Up @@ -578,7 +579,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/parse",
RelativePath: fmt.Sprintf("%s/parse", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -607,7 +608,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/validate",
RelativePath: fmt.Sprintf("%s/validate", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -636,7 +637,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/create",
RelativePath: fmt.Sprintf("%s/create", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -665,7 +666,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/apply",
RelativePath: fmt.Sprintf("%s/apply", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -723,7 +724,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("/apps/{%s}/latest", types.URLParamPorterAppName),
RelativePath: fmt.Sprintf("%s/{%s}/latest", relPathV2, types.URLParamPorterAppName),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -752,7 +753,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("/apps/{%s}/revisions", types.URLParamPorterAppName),
RelativePath: fmt.Sprintf("%s/{%s}/revisions", relPathV2, types.URLParamPorterAppName),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -781,7 +782,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/revisions",
RelativePath: fmt.Sprintf("%s/revisions", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -810,7 +811,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbPost,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("/apps/{%s}/subdomain", types.URLParamPorterAppName),
RelativePath: fmt.Sprintf("%s/{%s}/subdomain", relPathV2, types.URLParamPorterAppName),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -839,7 +840,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("/apps/{%s}/{%s}/predeploy-status", types.URLParamPorterAppName, types.URLParamAppRevisionID),
RelativePath: fmt.Sprintf("%s/{%s}/{%s}/predeploy-status", relPathV2, types.URLParamPorterAppName, types.URLParamAppRevisionID),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -868,7 +869,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/logs",
RelativePath: fmt.Sprintf("%s/logs", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -897,7 +898,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/logs/loki",
RelativePath: fmt.Sprintf("%s/logs/loki", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand Down Expand Up @@ -927,7 +928,7 @@ func getPorterAppRoutes(
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: "/apps/metrics",
RelativePath: fmt.Sprintf("%s/metrics", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
Expand All @@ -949,6 +950,65 @@ func getPorterAppRoutes(
Router: r,
})

// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/status -> cluster.NewAppStatusHandler
appStatusEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbGet,
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("%s/{%s}/status", relPathV2, types.URLParamKind),
},
Scopes: []types.PermissionScope{
types.UserScope,
types.ProjectScope,
types.ClusterScope,
},
IsWebsocket: true,
},
)

appStatusHandler := porter_app.NewAppStatusHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: appStatusEndpoint,
Handler: appStatusHandler,
Router: r,
})

// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/pods -> cluster.NewPodStatusHandler
appPodStatusEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbGet,
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("%s/pods", relPathV2),
},
Scopes: []types.PermissionScope{
types.UserScope,
types.ProjectScope,
types.ClusterScope,
},
},
)

appPodStatusHandler := porter_app.NewPodStatusHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: appPodStatusEndpoint,
Handler: appPodStatusHandler,
Router: r,
})

// POST /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id} -> porter_app.NewUpdateAppRevisionStatusHandler
updateAppRevisionStatusEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Expand Down
Loading

0 comments on commit e34ed7d

Please sign in to comment.