From ef8dcef32ae9baf68dc10b234d849f75ce36682d Mon Sep 17 00:00:00 2001 From: ianedwards Date: Thu, 19 Oct 2023 16:49:56 -0400 Subject: [PATCH] improve handling of missing porter.yaml in preview envs (#3843) --- .../update_app_environment_group.go | 80 ++++++++++++++---- cli/cmd/v2/apply.go | 37 +++++--- dashboard/src/lib/porter-apps/index.ts | 84 +++++++++++-------- 3 files changed, 136 insertions(+), 65 deletions(-) diff --git a/api/server/handlers/porter_app/update_app_environment_group.go b/api/server/handlers/porter_app/update_app_environment_group.go index f0879981f7..178650bf1d 100644 --- a/api/server/handlers/porter_app/update_app_environment_group.go +++ b/api/server/handlers/porter_app/update_app_environment_group.go @@ -69,7 +69,8 @@ type UpdateAppEnvironmentRequest struct { // UpdateAppEnvironmentResponse represents the fields on the response object from the /apps/{porter_app_name}/environment-group endpoint type UpdateAppEnvironmentResponse struct { - EnvGroups []environment_groups.EnvironmentGroup `json:"env_groups"` + Base64AppProto string `json:"b64_app_proto"` + EnvGroups []environment_groups.EnvironmentGroup `json:"env_groups"` } // ServeHTTP updates or creates the environment group for an app @@ -114,24 +115,35 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R } telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "deployment-target-id", Value: request.DeploymentTargetID}) + appProto := &porterv1.PorterApp{} + if request.Base64AppProto == "" { - err := telemetry.Error(ctx, span, nil, "b64 yaml is empty") - c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) - return - } + if appName == "" { + err := telemetry.Error(ctx, span, nil, "app name is empty and no base64 proto provided") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) + return + } - decoded, err := base64.StdEncoding.DecodeString(request.Base64AppProto) - if err != nil { - err := telemetry.Error(ctx, span, err, "error decoding base yaml") - c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) - return + appProto.Name = appName + } else { + decoded, err := base64.StdEncoding.DecodeString(request.Base64AppProto) + if err != nil { + err := telemetry.Error(ctx, span, err, "error decoding base yaml") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) + return + } + + err = helpers.UnmarshalContractObject(decoded, appProto) + if err != nil { + err := telemetry.Error(ctx, span, err, "error unmarshalling app proto") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) + return + } } - appProto := &porterv1.PorterApp{} - err = helpers.UnmarshalContractObject(decoded, appProto) - if err != nil { - err := telemetry.Error(ctx, span, err, "error unmarshalling app proto") - c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) + if appProto.Name == "" { + err := telemetry.Error(ctx, span, nil, "app proto name is empty") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest)) return } @@ -252,8 +264,25 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R Version: latestEnvironmentGroup.Version, }) + var protoEnvGroups []*porterv1.EnvGroup + for _, envGroup := range latestEnvGroups { + protoEnvGroups = append(protoEnvGroups, &porterv1.EnvGroup{ + Name: envGroup.Name, + Version: int64(envGroup.Version), + }) + } + appProto.EnvGroups = protoEnvGroups + + encodedApp, err := encodeAppProto(ctx, appProto) + if err != nil { + err := telemetry.Error(ctx, span, err, "error encoding app proto") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) + return + } + res := &UpdateAppEnvironmentResponse{ - EnvGroups: latestEnvGroups, + EnvGroups: latestEnvGroups, + Base64AppProto: encodedApp, } c.WriteResult(w, r, res) @@ -363,8 +392,25 @@ func (c *UpdateAppEnvironmentHandler) ServeHTTP(w http.ResponseWriter, r *http.R Version: version, }) + var protoEnvGroups []*porterv1.EnvGroup + for _, envGroup := range latestEnvGroups { + protoEnvGroups = append(protoEnvGroups, &porterv1.EnvGroup{ + Name: envGroup.Name, + Version: int64(envGroup.Version), + }) + } + appProto.EnvGroups = protoEnvGroups + + encodedApp, err := encodeAppProto(ctx, appProto) + if err != nil { + err := telemetry.Error(ctx, span, err, "error encoding app proto") + c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError)) + return + } + res := &UpdateAppEnvironmentResponse{ - EnvGroups: latestEnvGroups, + EnvGroups: latestEnvGroups, + Base64AppProto: encodedApp, } c.WriteResult(w, r, res) diff --git a/cli/cmd/v2/apply.go b/cli/cmd/v2/apply.go index f081f14965..70c63c9771 100644 --- a/cli/cmd/v2/apply.go +++ b/cli/cmd/v2/apply.go @@ -76,7 +76,7 @@ func Apply(ctx context.Context, inp ApplyInput) error { } // overrides incorporated into the app contract baed on the deployment target - var b64AppOverrides string + var overrides *porter_app.EncodedAppWithEnv appName := inp.AppName if porterYamlExists { @@ -98,6 +98,8 @@ func Apply(ctx context.Context, inp ApplyInput) error { } b64AppProto = parseResp.B64AppProto + overrides = parseResp.PreviewApp + // override app name if provided appName, err = appNameFromB64AppProto(parseResp.B64AppProto) if err != nil { @@ -128,21 +130,32 @@ func Apply(ctx context.Context, inp ApplyInput) error { return fmt.Errorf("error updating app env group in proto: %w", err) } - if inp.PreviewApply && parseResp.PreviewApp != nil { - b64AppOverrides = parseResp.PreviewApp.B64AppProto + color.New(color.FgGreen).Printf("Successfully parsed Porter YAML: applying app \"%s\"\n", appName) // nolint:errcheck,gosec + } - envGroupResp, err := client.CreateOrUpdateAppEnvironment(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetID, parseResp.PreviewApp.EnvVariables, parseResp.PreviewApp.EnvSecrets, parseResp.PreviewApp.B64AppProto) - if err != nil { - return fmt.Errorf("error calling create or update app environment group endpoint: %w", err) - } + // b64AppOverrides is the base64-encoded app proto with preview environment specific overrides and env groups + var b64AppOverrides string - b64AppOverrides, err = updateEnvGroupsInProto(ctx, b64AppOverrides, envGroupResp.EnvGroups) - if err != nil { - return fmt.Errorf("error updating app env group in proto: %w", err) - } + if inp.PreviewApply { + var previewEnvVariables map[string]string + var previewEnvSecrets map[string]string + + if overrides != nil { + b64AppOverrides = overrides.B64AppProto + previewEnvVariables = overrides.EnvVariables + previewEnvSecrets = overrides.EnvSecrets } - color.New(color.FgGreen).Printf("Successfully parsed Porter YAML: applying app \"%s\"\n", appName) // nolint:errcheck,gosec + envGroupResp, err := client.CreateOrUpdateAppEnvironment(ctx, cliConf.Project, cliConf.Cluster, appName, deploymentTargetID, previewEnvVariables, previewEnvSecrets, b64AppOverrides) + if err != nil { + return fmt.Errorf("error calling create or update app environment group endpoint: %w", err) + } + b64AppOverrides = envGroupResp.Base64AppProto + + b64AppOverrides, err = updateEnvGroupsInProto(ctx, b64AppOverrides, envGroupResp.EnvGroups) + if err != nil { + return fmt.Errorf("error updating app env group in proto: %w", err) + } } if appName == "" { diff --git a/dashboard/src/lib/porter-apps/index.ts b/dashboard/src/lib/porter-apps/index.ts index 40a079a0f3..07e44f41f4 100644 --- a/dashboard/src/lib/porter-apps/index.ts +++ b/dashboard/src/lib/porter-apps/index.ts @@ -485,14 +485,10 @@ export function applyPreviewOverrides({ overrides, }: { app: ClientPorterApp; - overrides: DetectedServices["previews"]; + overrides?: DetectedServices["previews"]; }): ClientPorterApp { - if (!overrides) { - return app; - } - const services = app.services.map((svc) => { - const override = overrides.services.find( + const override = overrides?.services.find( (s) => s.name.value === svc.name.value ); if (override) { @@ -502,24 +498,41 @@ export function applyPreviewOverrides({ }); if (ds.config.type == "web") { - ds.config.domains = []; + return { + ...ds, + config: { + ...ds.config, + domains: [], + }, + }; } return ds; } if (svc.config.type == "web") { - svc.config.domains = []; + return { + ...svc, + config: { + ...svc.config, + domains: [], + }, + }; } + return svc; }); - const additionalServices = overrides.services - .filter((s) => !app.services.find((svc) => svc.name.value === s.name.value)) - .map((svc) => deserializeService({ service: serializeService(svc) })); + const additionalServices = + overrides?.services + .filter( + (s) => !app.services.find((svc) => svc.name.value === s.name.value) + ) + .map((svc) => deserializeService({ service: serializeService(svc) })) ?? + []; app.services = [...services, ...additionalServices]; if (app.predeploy) { - const predeployOverride = overrides.predeploy; + const predeployOverride = overrides?.predeploy; if (predeployOverride) { app.predeploy = [ deserializeService({ @@ -530,33 +543,32 @@ export function applyPreviewOverrides({ } } - const envOverrides = overrides.variables; - if (envOverrides) { - const env = app.env.map((e) => { - const override = envOverrides[e.key]; - if (override) { - return { - ...e, - locked: true, - value: override, - }; - } - - return e; - }); + const envOverrides = overrides?.variables; - const additionalEnv = Object.entries(envOverrides) - .filter(([key]) => !app.env.find((e) => e.key === key)) - .map(([key, value]) => ({ - key, - value, - hidden: false, + const env = app.env.map((e) => { + const override = envOverrides?.[e.key]; + if (override) { + return { + ...e, locked: true, - deleted: false, - })); + value: override, + }; + } - app.env = [...env, ...additionalEnv]; - } + return e; + }); + + const additionalEnv = Object.entries(envOverrides ?? {}) + .filter(([key]) => !app.env.find((e) => e.key === key)) + .map(([key, value]) => ({ + key, + value, + hidden: false, + locked: true, + deleted: false, + })); + + app.env = [...env, ...additionalEnv]; return app; }