Browse Source

improve handling of missing porter.yaml in preview envs (#3843)

ianedwards 2 years ago
parent
commit
ef8dcef32a

+ 63 - 17
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)

+ 25 - 12
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 == "" {

+ 48 - 36
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;
 }