Skip to content

Commit

Permalink
POR-1797 lower latency by moving env off of revision (#3631)
Browse files Browse the repository at this point in the history
  • Loading branch information
ianedwards authored Sep 22, 2023
1 parent 8545694 commit 5824225
Show file tree
Hide file tree
Showing 19 changed files with 396 additions and 176 deletions.
24 changes: 1 addition & 23 deletions api/server/handlers/porter_app/current_app_revision.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,13 +113,6 @@ func (c *LatestAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
return
}

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

currentAppRevisionReq := connect.NewRequest(&porterv1.CurrentAppRevisionRequest{
ProjectId: int64(project.ID),
AppId: int64(porterApps[0].ID),
Expand Down Expand Up @@ -147,23 +140,8 @@ func (c *LatestAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
return
}

revisionWithEnv, err := porter_app.AttachEnvToRevision(ctx, porter_app.AttachEnvToRevisionInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
DeploymentTargetID: request.DeploymentTargetID,
Revision: encodedRevision,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error attaching env to revision")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

response := LatestAppRevisionResponse{
AppRevision: revisionWithEnv,
AppRevision: encodedRevision,
}

c.WriteResult(w, r, response)
Expand Down
17 changes: 17 additions & 0 deletions api/server/handlers/porter_app/get_app_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type GetAppEnvRequest struct {
// GetAppEnvResponse is the response object for the /apps/{porter_app_name}/revisions/{app_revision_id}/env endpoint
type GetAppEnvResponse struct {
EnvGroups []environment_groups.EnvironmentGroup `json:"env_groups"`
AppEnv environment_groups.EnvironmentGroup `json:"app_env"`
}

// ServeHTTP translates the request into a GetAppEnvRequest request, uses the revision proto to query the cluster for the requested env groups, and returns the response
Expand Down Expand Up @@ -138,8 +139,24 @@ func (c *GetAppEnvHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}

revisionWithEnv, err := porter_app.AttachEnvToRevision(ctx, porter_app.AttachEnvToRevisionInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
DeploymentTargetID: revision.DeploymentTargetID,
Revision: revision,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error attaching env to revision")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

res := &GetAppEnvResponse{
EnvGroups: envGroups,
AppEnv: revisionWithEnv.Env,
}

c.WriteResult(w, r, res)
Expand Down
109 changes: 109 additions & 0 deletions api/server/handlers/porter_app/get_app_revision.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
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/server/shared/requestutils"
"github.com/porter-dev/porter/api/types"
"github.com/porter-dev/porter/internal/models"
"github.com/porter-dev/porter/internal/porter_app"
"github.com/porter-dev/porter/internal/telemetry"
)

// GetAppRevisionHandler handles requests to the /apps/{porter_app_name}/revisions/{app_revision_id} endpoint
type GetAppRevisionHandler struct {
handlers.PorterHandlerReadWriter
authz.KubernetesAgentGetter
}

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

// GetAppRevisionResponse represents the response from the /apps/{porter_app_name}/revisions/{app_revision_id} endpoint
type GetAppRevisionResponse struct {
AppRevision porter_app.Revision `json:"app_revision"`
}

// GetAppRevisionHandler returns a single app revision
func (c *GetAppRevisionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-get-app-revision")
defer span.End()

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

appRevisionID, reqErr := requestutils.GetURLParamString(r, types.URLParamAppRevisionID)
if reqErr != nil {
err := telemetry.Error(ctx, span, nil, "error parsing app revision id")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))
return
}

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

getRevisionReq := connect.NewRequest(&porterv1.GetAppRevisionRequest{
ProjectId: int64(project.ID),
AppRevisionId: appRevisionID,
})
ccpResp, err := c.Config().ClusterControlPlaneClient.GetAppRevision(ctx, getRevisionReq)
if err != nil {
err = telemetry.Error(ctx, span, err, "error getting app revision")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

if ccpResp == nil || ccpResp.Msg == nil {
err = telemetry.Error(ctx, span, nil, "get app revision response is nil")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

encodedRevision, err := porter_app.EncodedRevisionFromProto(ctx, ccpResp.Msg.AppRevision)
if err != nil {
err := telemetry.Error(ctx, span, err, "error getting encoded revision from proto")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

revisionWithEnv, err := porter_app.AttachEnvToRevision(ctx, porter_app.AttachEnvToRevisionInput{
ProjectID: project.ID,
ClusterID: int(cluster.ID),
Revision: encodedRevision,
DeploymentTargetID: ccpResp.Msg.AppRevision.DeploymentTargetId,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error attaching env to revision")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

res := &GetAppRevisionResponse{
AppRevision: revisionWithEnv,
}

c.WriteResult(w, r, res)
}
24 changes: 1 addition & 23 deletions api/server/handlers/porter_app/list_app_revisions.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,6 @@ func (c *ListAppRevisionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
appRevisions = []*porterv1.AppRevision{}
}

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

res := &ListAppRevisionsResponse{
AppRevisions: make([]porter_app.Revision, 0),
}
Expand All @@ -137,22 +130,7 @@ func (c *ListAppRevisionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
return
}

revisionWithEnv, err := porter_app.AttachEnvToRevision(ctx, porter_app.AttachEnvToRevisionInput{
Revision: encodedRevision,
ProjectID: project.ID,
ClusterID: int(cluster.ID),
DeploymentTargetID: request.DeploymentTargetID,
K8SAgent: agent,
PorterAppRepository: c.Repo().PorterApp(),
DeploymentTargetRepository: c.Repo().DeploymentTarget(),
})
if err != nil {
err := telemetry.Error(ctx, span, err, "error attaching env to revision")
c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

res.AppRevisions = append(res.AppRevisions, revisionWithEnv)
res.AppRevisions = append(res.AppRevisions, encodedRevision)
}

c.WriteResult(w, r, res)
Expand Down
29 changes: 29 additions & 0 deletions api/server/router/porter_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,35 @@ func getPorterAppRoutes(
Router: r,
})

// GET /api/projects/{project_id}/clusters/{cluster_id}/apps/{porter_app_name}/revisions/{app_revision_id} -> porter_app.NewGetAppRevisionHandler
getAppRevisionEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbGet,
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: fmt.Sprintf("/apps/{%s}/revisions/{%s}", types.URLParamPorterAppName, types.URLParamAppRevisionID),
},
Scopes: []types.PermissionScope{
types.UserScope,
types.ProjectScope,
types.ClusterScope,
},
},
)

getAppRevisionHandler := porter_app.NewGetAppRevisionHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: getAppRevisionEndpoint,
Handler: getAppRevisionHandler,
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
5 changes: 4 additions & 1 deletion dashboard/src/lib/hooks/usePorterYaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ export const usePorterYaml = ({
Boolean(source.git_repo_name) &&
Boolean(source.git_branch),
retry: (_failureCount, error) => {
if (error.response.data?.error?.includes("404")) {
if (
error.response.data?.error?.includes("404") ||
error.response.data?.error?.includes("not found")
) {
setPorterYamlFound(false);
return false;
}
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/lib/porter-apps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export function serviceOverrides({

if (!overrides.predeploy) {
return {
build: validatedBuild,
services,
};
}
Expand Down
7 changes: 0 additions & 7 deletions dashboard/src/lib/revisions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ export const appRevisionValidator = z.object({
id: z.string(),
created_at: z.string(),
updated_at: z.string(),
env: z.object({
name: z.string(),
latest_version: z.number(),
variables: z.record(z.string(), z.string()).optional(),
secret_variables: z.record(z.string(), z.string()).optional(),
created_at: z.string(),
}),
});

export type AppRevision = z.infer<typeof appRevisionValidator>;
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
app: clientAppFromProto({
proto: latestProto,
overrides: servicesFromYaml,
variables: latestRevision.env.variables,
secrets: latestRevision.env.secret_variables,
}),
source: latestSource,
deletions: {
Expand Down Expand Up @@ -250,29 +248,22 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {

// redirect to the default tab after save
history.push(`/apps/${porterApp.name}/${DEFAULT_TAB}`);
} catch (err) { }
} catch (err) {}
});

useEffect(() => {
reset({
app: clientAppFromProto({
proto: latestProto,
overrides: servicesFromYaml,
variables: latestRevision.env.variables,
secrets: latestRevision.env.secret_variables,
}),
source: latestSource,
deletions: {
envGroupNames: [],
serviceNames: [],
},
});
}, [
servicesFromYaml,
currentTab,
latestProto,
latestRevision.revision_number,
]);
}, [servicesFromYaml, latestProto, latestRevision.revision_number]);

return (
<FormProvider {...porterAppFormMethods}>
Expand Down Expand Up @@ -320,11 +311,11 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
{ label: "Environment", value: "environment" },
...(latestProto.build
? [
{
label: "Build Settings",
value: "build-settings",
},
]
{
label: "Build Settings",
value: "build-settings",
},
]
: []),
{ label: "Settings", value: "settings" },
]}
Expand All @@ -343,7 +334,7 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
setRedeployOnSave={setRedeployOnSave}
/>
))
.with("environment", () => <Environment />)
.with("environment", () => <Environment latestSource={latestSource} />)
.with("settings", () => <Settings />)
.with("logs", () => <LogsTab />)
.with("metrics", () => <MetricsTab />)
Expand Down
Loading

0 comments on commit 5824225

Please sign in to comment.