ianedwards 2 лет назад
Родитель
Сommit
c8690d53fd

+ 16 - 4
api/server/handlers/porter_app/validate.go

@@ -38,10 +38,11 @@ func NewValidatePorterAppHandler(
 
 // Deletions are the names of services and env variables to delete
 type Deletions struct {
-	ServiceNames     []string `json:"service_names"`
-	Predeploy        []string `json:"predeploy"`
-	EnvVariableNames []string `json:"env_variable_names"`
-	EnvGroupNames    []string `json:"env_group_names"`
+	ServiceNames        []string            `json:"service_names"`
+	Predeploy           []string            `json:"predeploy"`
+	EnvVariableNames    []string            `json:"env_variable_names"`
+	EnvGroupNames       []string            `json:"env_group_names"`
+	DomainNameDeletions map[string][]string `json:"domain_name_deletions"`
 }
 
 // ValidatePorterAppRequest is the request object for the /apps/validate endpoint
@@ -143,6 +144,16 @@ func (c *ValidatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 		telemetry.WithAttributes(span, telemetry.AttributeKV{Key: "validated-with-overrides", Value: true})
 	}
 
+	var ServiceDomainsDeletions map[string]*porterv1.DomainNameList
+	if request.Deletions.DomainNameDeletions != nil {
+		ServiceDomainsDeletions = make(map[string]*porterv1.DomainNameList)
+		for k, v := range request.Deletions.DomainNameDeletions {
+			ServiceDomainsDeletions[k] = &porterv1.DomainNameList{
+				DomainNames: v,
+			}
+		}
+	}
+
 	validateReq := connect.NewRequest(&porterv1.ValidatePorterAppRequest{
 		ProjectId:          int64(project.ID),
 		DeploymentTargetId: request.DeploymentTargetId,
@@ -154,6 +165,7 @@ func (c *ValidatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 			PredeployNames:   request.Deletions.Predeploy,
 			EnvVariableNames: request.Deletions.EnvVariableNames,
 			EnvGroupNames:    request.Deletions.EnvGroupNames,
+			ServiceDomains:   ServiceDomainsDeletions,
 		},
 	})
 	ccpResp, err := c.Config().ClusterControlPlaneClient.ValidatePorterApp(ctx, validateReq)

+ 11 - 0
dashboard/src/lib/hooks/useAppValidation.ts

@@ -107,6 +107,16 @@ export const useAppValidation = ({
         })
         .exhaustive();
 
+      const domainDeletions = data.app.services.reduce(
+        (acc: Record<string, string[]>, svc) => {
+          if (svc.domainDeletions.length) {
+            acc[svc.name.value] = svc.domainDeletions.map((d) => d.name);
+          }
+          return acc;
+        },
+        {}
+      );
+
       const res = await api.validatePorterApp(
         "<token>",
         {
@@ -122,6 +132,7 @@ export const useAppValidation = ({
             predeploy: data.deletions.predeploy.map((s) => s.name),
             env_group_names: data.deletions.envGroupNames.map((eg) => eg.name),
             env_variable_names: [],
+            domain_name_deletions: domainDeletions,
           },
         },
         {

+ 8 - 8
dashboard/src/lib/porter-apps/index.ts

@@ -363,15 +363,15 @@ export function clientAppFromProto({
   const predeployOverrides = serializeService(overrides.predeploy);
   const predeploy = proto.predeploy
     ? [
-      deserializeService({
-        service: serializedServiceFromProto({
-          name: "pre-deploy",
-          service: proto.predeploy,
-          isPredeploy: true,
+        deserializeService({
+          service: serializedServiceFromProto({
+            name: "pre-deploy",
+            service: proto.predeploy,
+            isPredeploy: true,
+          }),
+          override: predeployOverrides,
         }),
-        override: predeployOverrides,
-      }),
-    ]
+      ]
     : undefined;
 
   return {

+ 52 - 16
dashboard/src/lib/porter-apps/services.ts

@@ -53,12 +53,18 @@ export const serviceValidator = z.object({
       allowConcurrent: serviceBooleanValidator.optional(),
       cron: serviceStringValidator,
       suspendCron: serviceBooleanValidator.optional(),
-      timeoutSeconds: serviceNumberValidator
+      timeoutSeconds: serviceNumberValidator,
     }),
     z.object({
       type: z.literal("predeploy"),
     }),
   ]),
+  domainDeletions: z
+    .object({
+      name: z.string(),
+    })
+    .array()
+    .default([]),
 });
 
 export type ClientService = z.infer<typeof serviceValidator>;
@@ -273,6 +279,7 @@ export function deserializeService({
       service.ramMegabytes,
       override?.ramMegabytes
     ),
+    domainDeletions: [],
   };
 
   return match(service.config)
@@ -280,6 +287,13 @@ export function deserializeService({
       const overrideWebConfig =
         override?.config.type == "web" ? override.config : undefined;
 
+      const uniqueDomains = Array.from(
+        new Set([
+          ...config.domains.map((domain) => domain.name),
+          ...(overrideWebConfig?.domains ?? []).map((domain) => domain.name),
+        ])
+      ).map((domain) => ({ name: domain }));
+
       return {
         ...baseService,
         config: {
@@ -293,9 +307,7 @@ export function deserializeService({
             override: overrideWebConfig?.healthCheck,
           }),
 
-          domains: Array.from(
-            new Set([...config.domains, ...(overrideWebConfig?.domains ?? [])])
-          ).map((domain) => ({
+          domains: uniqueDomains.map((domain) => ({
             name: ServiceField.string(
               domain.name,
               overrideWebConfig?.domains.find(
@@ -337,15 +349,27 @@ export function deserializeService({
           allowConcurrent:
             typeof config.allowConcurrent === "boolean" ||
             typeof overrideJobConfig?.allowConcurrent === "boolean"
-              ? ServiceField.boolean(config.allowConcurrent, overrideJobConfig?.allowConcurrent)
+              ? ServiceField.boolean(
+                  config.allowConcurrent,
+                  overrideJobConfig?.allowConcurrent
+                )
               : ServiceField.boolean(false, undefined),
           cron: ServiceField.string(config.cron, overrideJobConfig?.cron),
           suspendCron:
             typeof config.suspendCron === "boolean" ||
             typeof overrideJobConfig?.suspendCron === "boolean"
-              ? ServiceField.boolean(config.suspendCron, overrideJobConfig?.suspendCron)
+              ? ServiceField.boolean(
+                  config.suspendCron,
+                  overrideJobConfig?.suspendCron
+                )
               : ServiceField.boolean(false, undefined),
-           timeoutSeconds: config.timeoutSeconds == 0 ? ServiceField.number(3600, overrideJobConfig?.timeoutSeconds) : ServiceField.number(config.timeoutSeconds, overrideJobConfig?.timeoutSeconds),
+          timeoutSeconds:
+            config.timeoutSeconds == 0
+              ? ServiceField.number(3600, overrideJobConfig?.timeoutSeconds)
+              : ServiceField.number(
+                  config.timeoutSeconds,
+                  overrideJobConfig?.timeoutSeconds
+                ),
         },
       };
     })
@@ -410,6 +434,7 @@ export function serviceProto(service: SerializedService): Service {
             value: {
               ...config,
               allowConcurrentOptional: config.allowConcurrent,
+              timeoutSeconds: BigInt(config.timeoutSeconds),
             },
             case: "jobConfig",
           },
@@ -466,14 +491,25 @@ export function serializedServiceFromProto({
         ...value,
       },
     }))
-    .with({ case: "jobConfig" }, ({ value }) => ({
-      ...service,
-      name,
-      config: {
-        type: isPredeploy ? ("predeploy" as const) : ("job" as const),
-        ...value,
-        allowConcurrent: value.allowConcurrentOptional
-      },
-    }))
+    .with({ case: "jobConfig" }, ({ value }) =>
+      isPredeploy
+        ? {
+            ...service,
+            name,
+            config: {
+              type: "predeploy" as const,
+            },
+          }
+        : {
+            ...service,
+            name,
+            config: {
+              type: "job" as const,
+              ...value,
+              allowConcurrent: value.allowConcurrentOptional,
+              timeoutSeconds: Number(value.timeoutSeconds),
+            },
+          }
+    )
     .exhaustive();
 }

+ 9 - 4
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -329,7 +329,11 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
 
         const msg =
           "An error occurred while deploying your application. Please try again.";
-        updateAppStep({ step: "stack-launch-failure", errorMessage: msg, appName: name.value });
+        updateAppStep({
+          step: "stack-launch-failure",
+          errorMessage: msg,
+          appName: name.value,
+        });
         setDeployError(msg);
         return false;
       } finally {
@@ -373,7 +377,7 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
       setStep((prev) => Math.max(prev, 5));
     } else {
       setStep((prev) => Math.min(prev, 2));
-    };
+    }
   }, [services]);
 
   // todo(ianedwards): it's a bit odd that the button error can be set to either a string or JSX,
@@ -606,8 +610,9 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
                             }
                           >
                             {detectedServices.count > 0
-                              ? `Detected ${detectedServices.count} service${detectedServices.count > 1 ? "s" : ""
-                              } from porter.yaml.`
+                              ? `Detected ${detectedServices.count} service${
+                                  detectedServices.count > 1 ? "s" : ""
+                                } from porter.yaml.`
                               : `Could not detect any services from porter.yaml. Make sure it exists in the root of your repo.`}
                           </Text>
                         </AppearingDiv>

+ 14 - 2
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/CustomDomains.tsx

@@ -16,6 +16,17 @@ const CustomDomains: React.FC<Props> = ({ index }) => {
     control,
     name: `app.services.${index}.config.domains`,
   });
+  const { append: appendDomainDeletion } = useFieldArray({
+    control,
+    name: `app.services.${index}.domainDeletions`,
+  });
+
+  const onRemove = (i: number, name: string) => {
+    remove(i);
+    appendDomainDeletion({
+      name,
+    });
+  };
 
   return (
     <CustomDomainsContainer>
@@ -39,8 +50,9 @@ const CustomDomains: React.FC<Props> = ({ index }) => {
                   />
                   <DeleteButton
                     onClick={() => {
-                      //remove customDomain at the index
-                      remove(i);
+                      if (!customDomain.name.readOnly) {
+                        onRemove(i, customDomain.name.value);
+                      }
                     }}
                   >
                     <i className="material-icons">cancel</i>

+ 1 - 0
dashboard/src/shared/api.tsx

@@ -912,6 +912,7 @@ const validatePorterApp = baseApi<
       predeploy: string[];
       env_variable_names: string[];
       env_group_names: string[];
+      domain_name_deletions: Record<string, string[]>;
     };
   },
   {