Quellcode durchsuchen

add guidance on creating custom domains (#3898)

Feroze Mohideen vor 2 Jahren
Ursprung
Commit
6567084625

+ 57 - 23
dashboard/src/lib/hooks/useClusterResourceLimits.ts

@@ -5,16 +5,20 @@ import { useQuery } from "@tanstack/react-query";
 import { z } from "zod";
 import api from "shared/api";
 
-const clusterDataValidator = z.object({
+const DEFAULT_INSTANCE_CLASS = "t3";
+const DEFAULT_INSTANCE_SIZE = "medium";
+
+const clusterNodesValidator = z.object({
     labels: z.object({
         "beta.kubernetes.io/instance-type": z.string().nullish(),
         "porter.run/workload-kind": z.string().nullish(),
     }).optional(),
 }).transform((data) => {
     const defaultResources = {
-        maxCPU: AWS_INSTANCE_LIMITS["t3"]["medium"]["vCPU"],
-        maxRAM: AWS_INSTANCE_LIMITS["t3"]["medium"]["RAM"],
-        instanceType: "t3.medium",
+        maxCPU: AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE]["vCPU"],
+        maxRAM: AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE]["RAM"],
+        instanceClass: DEFAULT_INSTANCE_CLASS,
+        instanceSize: DEFAULT_INSTANCE_SIZE,
     };
     if (!data.labels) {
         return defaultResources;
@@ -34,7 +38,8 @@ const clusterDataValidator = z.object({
         return {
             maxCPU: vCPU,
             maxRAM: RAM,
-            instanceType: instanceType,
+            instanceClass,
+            instanceSize,
         };
     }
     return defaultResources;
@@ -55,33 +60,35 @@ export const useClusterResourceLimits = (
     defaultCPU: number,
     defaultRAM: number,
     clusterContainsGPUNodes: boolean,
+    clusterIngressIp: string,
 } => {
     const SMALL_INSTANCE_UPPER_BOUND = 0.75;
     const LARGE_INSTANCE_UPPER_BOUND = 0.9;
     const DEFAULT_MULTIPLIER = 0.125;
-    const [clusterContainsGPUNodes, setGpuNodes] = useState(false);
+    const [clusterContainsGPUNodes, setClusterContainsGPUNodes] = useState(false);
     const [maxCPU, setMaxCPU] = useState(
-        AWS_INSTANCE_LIMITS["t3"]["medium"]["vCPU"] * SMALL_INSTANCE_UPPER_BOUND
-    ); //default is set to a t3 medium
+        AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE]["vCPU"] * SMALL_INSTANCE_UPPER_BOUND
+    );
     const [maxRAM, setMaxRAM] = useState(
         // round to nearest 100
         Math.round(
-            convert(AWS_INSTANCE_LIMITS["t3"]["medium"]["RAM"], "GiB").to("MB") *
+            convert(AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE]["RAM"], "GiB").to("MB") *
             SMALL_INSTANCE_UPPER_BOUND / 100
         ) * 100
-    ); //default is set to a t3 medium
+    );
     const [defaultCPU, setDefaultCPU] = useState(
-        AWS_INSTANCE_LIMITS["t3"]["medium"]["vCPU"] * DEFAULT_MULTIPLIER
-    ); //default is set to a t3 medium
+        AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE]["vCPU"] * DEFAULT_MULTIPLIER
+    );
     const [defaultRAM, setDefaultRAM] = useState(
         // round to nearest 100
         Math.round(
-            convert(AWS_INSTANCE_LIMITS["t3"]["medium"]["RAM"], "GiB").to("MB") *
+            convert(AWS_INSTANCE_LIMITS[DEFAULT_INSTANCE_CLASS][DEFAULT_INSTANCE_SIZE]["RAM"], "GiB").to("MB") *
             DEFAULT_MULTIPLIER / 100
         ) * 100
-    ); //default is set to a t3 medium
+    );
+    const [clusterIngressIp, setClusterIngressIp] = useState<string>("");
 
-    const { data } = useQuery(
+    const getClusterNodes = useQuery(
         ["getClusterNodes", projectId, clusterId],
         async () => {
             if (!projectId || !clusterId || clusterId === -1) {
@@ -97,7 +104,7 @@ export const useClusterResourceLimits = (
                 }
             )
 
-            return await z.array(clusterDataValidator).parseAsync(res.data);
+            return await z.array(clusterNodesValidator).parseAsync(res.data);
         },
         {
             enabled: !!projectId && !!clusterId,
@@ -105,9 +112,9 @@ export const useClusterResourceLimits = (
             retry: false,
         }
     );
-
     useEffect(() => {
-        if (data) {
+        if (getClusterNodes.isSuccess) {
+            const data = getClusterNodes.data;
             // this logic handles CPU and RAM independently - we might want to change this later
             const maxCPU = data.reduce((acc, curr) => {
                 return Math.max(acc, curr.maxCPU);
@@ -131,13 +138,39 @@ export const useClusterResourceLimits = (
             setMaxRAM(newMaxRAM);
             setDefaultCPU(Number((newMaxCPU * DEFAULT_MULTIPLIER).toFixed(2)));
             setDefaultRAM(Number((newMaxRAM * DEFAULT_MULTIPLIER).toFixed(0)));
+            setClusterContainsGPUNodes(data.some(item => item.instanceClass === "g4dn"));
+        }
+    }, [getClusterNodes])
 
-            // Check if any instance type has "gd4n" and update clusterContainsGPUNodes accordingly
-            setGpuNodes(data.some(item =>
-                item.instanceType.includes("g4dn")
-            ));
+    const getCluster = useQuery(
+        ["getCluster", projectId, clusterId],
+        async () => {
+            if (!projectId || !clusterId || clusterId === -1) {
+                return Promise.resolve({ ingress_ip: "" });
+            }
+
+            const res = await api.getCluster(
+                "<token>",
+                {},
+                {
+                    project_id: projectId,
+                    cluster_id: clusterId,
+                }
+            )
+
+            return await z.object({ ingress_ip: z.string() }).parseAsync(res.data);
+        },
+        {
+            enabled: !!projectId && !!clusterId,
+            refetchOnWindowFocus: false,
+            retry: false,
+        }
+    );
+    useEffect(() => {
+        if (getCluster.isSuccess) {
+            setClusterIngressIp(getCluster.data.ingress_ip);
         }
-    }, [data])
+    }, [getCluster])
 
 
     return {
@@ -146,6 +179,7 @@ export const useClusterResourceLimits = (
         defaultCPU,
         defaultRAM,
         clusterContainsGPUNodes,
+        clusterIngressIp,
     }
 }
 

+ 0 - 6
dashboard/src/main/home/app-dashboard/app-view/tabs/Overview.tsx

@@ -59,9 +59,6 @@ const Overview: React.FC<Props> = ({ buttonStatus }) => {
             existingServiceNames={latestProto.predeploy ? ["pre-deploy"] : []}
             isPredeploy
             fieldArrayName={"app.predeploy"}
-            maxCPU={currentClusterResources.maxCPU}
-            maxRAM={currentClusterResources.maxRAM}
-            clusterContainsGPUNodes={currentClusterResources.clusterContainsGPUNodes}
           />
           <Spacer y={0.5} />
         </>
@@ -73,9 +70,6 @@ const Overview: React.FC<Props> = ({ buttonStatus }) => {
         fieldArrayName={"app.services"}
         existingServiceNames={Object.keys(latestProto.services)}
         serviceVersionStatus={serviceVersionStatus}
-        maxCPU={currentClusterResources.maxCPU}
-        maxRAM={currentClusterResources.maxRAM}
-        clusterContainsGPUNodes={currentClusterResources.clusterContainsGPUNodes}
         internalNetworkingDetails={{
           namespace: deploymentTarget.namespace,
           appName: porterApp.name,

+ 0 - 10
dashboard/src/main/home/app-dashboard/create-app/CreateApp.tsx

@@ -659,11 +659,6 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
                     <ServiceList
                       addNewText={"Add a new service"}
                       fieldArrayName={"app.services"}
-                      maxCPU={currentClusterResources.maxCPU}
-                      maxRAM={currentClusterResources.maxRAM}
-                      clusterContainsGPUNodes={
-                        currentClusterResources.clusterContainsGPUNodes
-                      }
                     />
                   </>,
                   <>
@@ -697,11 +692,6 @@ const CreateApp: React.FC<CreateAppProps> = ({ history }) => {
                         })}
                         isPredeploy
                         fieldArrayName={"app.predeploy"}
-                        maxCPU={currentClusterResources.maxCPU}
-                        maxRAM={currentClusterResources.maxRAM}
-                        clusterContainsGPUNodes={
-                          currentClusterResources.clusterContainsGPUNodes
-                        }
                       />
                     </>
                   ),

+ 3 - 0
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceContainer.tsx

@@ -32,6 +32,7 @@ interface ServiceProps {
     namespace: string;
     appName: string;
   };
+  clusterIngressIp: string;
 }
 
 const ServiceContainer: React.FC<ServiceProps> = ({
@@ -44,6 +45,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
   maxRAM,
   clusterContainsGPUNodes,
   internalNetworkingDetails,
+  clusterIngressIp, 
 }) => {
   const [height, setHeight] = useState<Height>(service.expanded ? "auto" : 0);
 
@@ -77,6 +79,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
           maxRAM={maxRAM}
           clusterContainsGPUNodes={clusterContainsGPUNodes}
           internalNetworkingDetails={internalNetworkingDetails}
+          clusterIngressIp={clusterIngressIp}
         />
       ))
       .with({ config: { type: "worker" } }, (svc) => (

+ 5 - 10
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/ServiceList.tsx

@@ -49,9 +49,6 @@ type ServiceListProps = {
   existingServiceNames?: string[];
   fieldArrayName: "app.services" | "app.predeploy";
   serviceVersionStatus?: Record<string, PorterAppVersionStatus[]>;
-  maxCPU: number;
-  maxRAM: number;
-  clusterContainsGPUNodes: boolean;
   internalNetworkingDetails?: {
     namespace: string;
     appName: string;
@@ -61,13 +58,10 @@ type ServiceListProps = {
 const ServiceList: React.FC<ServiceListProps> = ({
   addNewText,
   prePopulateService,
+  fieldArrayName,
   isPredeploy = false,
   existingServiceNames = [],
-  fieldArrayName,
   serviceVersionStatus,
-  maxCPU,
-  maxRAM,
-  clusterContainsGPUNodes,
   internalNetworkingDetails = {
     namespace: "",
     appName: "",
@@ -76,7 +70,7 @@ const ServiceList: React.FC<ServiceListProps> = ({
   // top level app form
   const { control: appControl } = useFormContext<PorterAppFormData>();
 
-  const { currentClusterResources } = useClusterResources();
+  const { currentClusterResources: {maxCPU, maxRAM, clusterContainsGPUNodes, clusterIngressIp, defaultCPU, defaultRAM} } = useClusterResources();
 
   // add service modal form
   const {
@@ -186,8 +180,8 @@ const ServiceList: React.FC<ServiceListProps> = ({
       deserializeService({
         service: defaultSerialized({
           ...data,
-          defaultCPU: currentClusterResources.defaultCPU,
-          defaultRAM: currentClusterResources.defaultRAM,
+          defaultCPU,
+          defaultRAM,
         }),
         expanded: true,
       })
@@ -223,6 +217,7 @@ const ServiceList: React.FC<ServiceListProps> = ({
                 maxRAM={maxRAM}
                 clusterContainsGPUNodes={clusterContainsGPUNodes}
                 internalNetworkingDetails={internalNetworkingDetails}
+                clusterIngressIp={clusterIngressIp}
               />
             ) : null;
           })}

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

@@ -2,15 +2,26 @@ import React from "react";
 import Button from "components/porter/Button";
 import styled from "styled-components";
 import Spacer from "components/porter/Spacer";
+import Text from "components/porter/Text";
 import { useFieldArray, useFormContext } from "react-hook-form";
 import { PorterAppFormData } from "lib/porter-apps";
 import { ControlledInput } from "components/porter/ControlledInput";
+import CopyToClipboard from "components/CopyToClipboard";
+import copy from "assets/copy-left.svg";
 
 interface Props {
   index: number;
+  clusterIngressIp: string;
 }
 
-const CustomDomains: React.FC<Props> = ({ index }) => {
+const isCustomDomain = (domain: string) => {
+  return !domain.includes("onporter.run") && !domain.includes("withporter.run");
+}
+
+const CustomDomains: React.FC<Props> = ({ 
+  index, 
+  clusterIngressIp,
+ }) => {
   const { control, register } = useFormContext<PorterAppFormData>();
   const { remove, append, fields } = useFieldArray({
     control,
@@ -33,8 +44,7 @@ const CustomDomains: React.FC<Props> = ({ index }) => {
       {fields.length !== 0 && (
         <>
           {fields.map((customDomain, i) => {
-            return !customDomain.name.value.includes("onporter.run") &&
-              !customDomain.name.value.includes("withporter.run") ? (
+            return isCustomDomain(customDomain.name.value) && (
               <div key={customDomain.id}>
                 <AnnotationContainer>
                   <ControlledInput
@@ -61,7 +71,7 @@ const CustomDomains: React.FC<Props> = ({ index }) => {
                 </AnnotationContainer>
                 <Spacer y={0.25} />
               </div>
-            ) : null;
+            );
           })}
         </>
       )}
@@ -77,6 +87,24 @@ const CustomDomains: React.FC<Props> = ({ index }) => {
       >
         + Add Custom Domain
       </Button>
+      {clusterIngressIp !== "" && (
+        <>
+          <Spacer y={0.5} />
+          <div style={{width: "550px"}}>
+            <Text color="helper">To configure a custom domain, you must add a CNAME record pointing to the following Ingress IP for your cluster: </Text>
+          </div>
+          <Spacer y={0.5} />
+          <IdContainer>
+            <Code>{clusterIngressIp}</Code>
+            <CopyContainer>
+                <CopyToClipboard text={clusterIngressIp}>
+                    <CopyIcon src={copy} alt="copy" />
+                </CopyToClipboard>
+            </CopyContainer>
+          </IdContainer>
+          <Spacer y={0.5} />
+        </>
+      )}
     </CustomDomainsContainer>
   );
 };
@@ -112,3 +140,36 @@ const DeleteButton = styled.div`
     }
   }
 `;
+
+const Code = styled.span`
+  font-family: monospace;
+`;
+
+const IdContainer = styled.div`
+    background: #26292E;  
+    border-radius: 5px;
+    padding: 10px;
+    display: flex;
+    width: 550px;
+    border-radius: 5px;
+    border: 1px solid ${({ theme }) => theme.border};
+    align-items: center;
+    user-select: text;
+`;
+
+const CopyContainer = styled.div`
+  display: flex;
+  align-items: center;
+  margin-left: auto;
+`;
+
+const CopyIcon = styled.img`
+  cursor: pointer;
+  margin-left: 5px;
+  margin-right: 5px;
+  width: 15px;
+  height: 15px;
+  :hover {
+    opacity: 0.8;
+  }
+`;

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

@@ -23,9 +23,15 @@ type NetworkingProps = {
     namespace: string;
     appName: string;
   };
+  clusterIngressIp: string;
 };
 
-const Networking: React.FC<NetworkingProps> = ({ index, service, internalNetworkingDetails: {namespace, appName} }) => {
+const Networking: React.FC<NetworkingProps> = ({ 
+  index, 
+  service, 
+  internalNetworkingDetails: { namespace, appName }, 
+  clusterIngressIp,
+}) => {
   const { register, control, watch } = useFormContext<PorterAppFormData>();
 
   const privateService = watch(`app.services.${index}.config.private.value`);
@@ -132,7 +138,10 @@ const Networking: React.FC<NetworkingProps> = ({ index, service, internalNetwork
             </a>
           </Text>
           <Spacer y={0.5} />
-          <CustomDomains index={index} />
+          <CustomDomains 
+            index={index} 
+            clusterIngressIp={clusterIngressIp} 
+          />
           <Spacer y={0.5} />
           <Text color="helper">
             Ingress Custom Annotations

+ 11 - 1
dashboard/src/main/home/app-dashboard/validate-apply/services-settings/tabs/WebTabs.tsx

@@ -23,9 +23,18 @@ interface Props {
     namespace: string;
     appName: string;
   };
+  clusterIngressIp: string;
 }
 
-const WebTabs: React.FC<Props> = ({ index, service, maxRAM, maxCPU, clusterContainsGPUNodes, internalNetworkingDetails }) => {
+const WebTabs: React.FC<Props> = ({ 
+  index, 
+  service, 
+  maxRAM, 
+  maxCPU, 
+  clusterContainsGPUNodes, 
+  internalNetworkingDetails, 
+  clusterIngressIp,
+}) => {
   const [currentTab, setCurrentTab] = React.useState<
     "main" | "resources" | "networking" | "advanced"
   >("main");
@@ -49,6 +58,7 @@ const WebTabs: React.FC<Props> = ({ index, service, maxRAM, maxCPU, clusterConta
             index={index}
             service={service}
             internalNetworkingDetails={internalNetworkingDetails}
+            clusterIngressIp={clusterIngressIp}
           />
         ))
         .with("resources", () => (

+ 0 - 10
dashboard/src/main/home/cluster-dashboard/preview-environments/v2/setup-app/AppTemplateForm.tsx

@@ -279,11 +279,6 @@ const AppTemplateForm: React.FC<Props> = ({ existingTemplate }) => {
               <ServiceList
                 addNewText={"Add a new service"}
                 fieldArrayName={"app.services"}
-                maxCPU={currentClusterResources.maxCPU}
-                maxRAM={currentClusterResources.maxRAM}
-                clusterContainsGPUNodes={
-                  currentClusterResources.clusterContainsGPUNodes
-                }
                 internalNetworkingDetails={{
                   namespace: deploymentTarget.namespace,
                   appName: porterApp.name,
@@ -322,11 +317,6 @@ const AppTemplateForm: React.FC<Props> = ({ existingTemplate }) => {
                 }
                 isPredeploy
                 fieldArrayName={"app.predeploy"}
-                maxCPU={currentClusterResources.maxCPU}
-                maxRAM={currentClusterResources.maxRAM}
-                clusterContainsGPUNodes={
-                  currentClusterResources.clusterContainsGPUNodes
-                }
               />
             </>,
             <Button

+ 3 - 1
dashboard/src/shared/ClusterResourcesContext.tsx

@@ -9,6 +9,7 @@ export type ClusterResources = {
   defaultCPU: number;
   defaultRAM: number;
   clusterContainsGPUNodes: boolean;
+  clusterIngressIp: string;
 };
 
 export const ClusterResourcesContext = createContext<{
@@ -28,7 +29,7 @@ export const useClusterResources = () => {
 const ClusterResourcesProvider = ({ children }: { children: JSX.Element }) => {
   const { currentCluster, currentProject } = useContext(Context);
 
-  const { maxCPU, maxRAM, defaultCPU, defaultRAM, clusterContainsGPUNodes } = useClusterResourceLimits({
+  const { maxCPU, maxRAM, defaultCPU, defaultRAM, clusterContainsGPUNodes, clusterIngressIp } = useClusterResourceLimits({
     projectId: currentProject?.id,
     clusterId: currentCluster?.id,
   });
@@ -42,6 +43,7 @@ const ClusterResourcesProvider = ({ children }: { children: JSX.Element }) => {
           defaultCPU,
           defaultRAM,
           clusterContainsGPUNodes,
+          clusterIngressIp,
         },
       }}
     >