Explorar el Código

move common methods/types to cluster folder (#4255)

Feroze Mohideen hace 2 años
padre
commit
c6d67e695e

+ 33 - 0
dashboard/src/lib/clusters/constants.ts

@@ -0,0 +1,33 @@
+import aws from "assets/aws.png";
+import azure from "assets/azure.png";
+import infra from "assets/cluster.svg";
+import gcp from "assets/gcp.png";
+
+import { type CloudProvider } from "./types";
+
+export const CloudProviderAWS: CloudProvider = {
+  name: "AWS",
+  displayName: "Amazon Web Services",
+  icon: aws,
+};
+export const CloudProviderGCP: CloudProvider = {
+  name: "GCP",
+  displayName: "Google Cloud Platform",
+  icon: gcp,
+};
+export const CloudProviderAzure: CloudProvider = {
+  name: "Azure",
+  displayName: "Microsoft Azure",
+  icon: azure,
+};
+export const CloudProviderLocal: CloudProvider = {
+  name: "Local",
+  displayName: "Local",
+  icon: infra,
+};
+export const SUPPORTED_CLOUD_PROVIDERS = [
+  CloudProviderAWS,
+  CloudProviderGCP,
+  CloudProviderAzure,
+  CloudProviderLocal,
+];

+ 98 - 0
dashboard/src/lib/clusters/types.ts

@@ -0,0 +1,98 @@
+import { z } from "zod";
+
+import { checkGroupValidator } from "main/home/compliance-dashboard/types";
+
+import { CloudProviderAWS } from "./constants";
+
+export type CloudProvider = {
+  name: SerializedCluster["cloud_provider"];
+  displayName: string;
+  icon: string;
+};
+
+export const clusterValidator = z.object({
+  id: z.number(),
+  name: z.string(),
+  vanity_name: z.string(),
+  cloud_provider: z.enum(["AWS", "GCP", "Azure", "Local"]),
+  cloud_provider_credential_identifier: z.string(),
+  status: z.string(),
+  created_at: z.string(),
+  updated_at: z.string(),
+});
+export type SerializedCluster = z.infer<typeof clusterValidator>;
+export type ClientCluster = Omit<SerializedCluster, "cloud_provider"> & {
+  cloud_provider: CloudProvider;
+};
+export const isAWSCluster = (
+  cluster: ClientCluster
+): cluster is ClientCluster => {
+  return cluster.cloud_provider === CloudProviderAWS;
+};
+
+export const contractValidator = z.object({
+  id: z.string(),
+  base64_contract: z.string(),
+  cluster_id: z.number(),
+  project_id: z.number(),
+  condition: z.enum([
+    "",
+    "QUOTA_REQUEST_FAILED",
+    "RETRYING_TOO_LONG",
+    "KUBE_APPLY_FAILED",
+    "FATAL_PROVISIONING_ERROR",
+    "ERROR_READING_MSG",
+    "MSG_CAUSED_PANIC",
+    "SUCCESS",
+    "DELETING",
+    "DELETED",
+    "COMPLIANCE_CHECK_FAILED",
+  ]),
+  condition_metadata: z
+    .discriminatedUnion("code", [
+      z.object({
+        code: z.literal("SUCCESS"),
+        message: z.string().optional(),
+      }),
+      z.object({
+        code: z.literal("COMPLIANCE_CHECK_FAILED"),
+        message: z.string().optional(),
+        metadata: z.object({
+          check_groups: z.array(checkGroupValidator),
+        }),
+      }),
+      // all other codes are just "code" and "message"
+      z.object({
+        code: z.literal("QUOTA_REQUEST_FAILED"),
+        message: z.string().optional(),
+      }),
+      z.object({
+        code: z.literal("RETRYING_TOO_LONG"),
+        message: z.string().optional(),
+      }),
+      z.object({
+        code: z.literal("KUBE_APPLY_FAILED"),
+        message: z.string().optional(),
+      }),
+      z.object({
+        code: z.literal("FATAL_PROVISIONING_ERROR"),
+        message: z.string().optional(),
+      }),
+      z.object({
+        code: z.literal("ERROR_READING_MSG"),
+        message: z.string().optional(),
+      }),
+      z.object({
+        code: z.literal("MSG_CAUSED_PANIC"),
+        message: z.string().optional(),
+      }),
+    ])
+    .nullable()
+    .or(
+      z.object({}).transform(() => ({
+        code: "SUCCESS",
+        message: "",
+      }))
+    ),
+});
+export type APIContract = z.infer<typeof contractValidator>;

+ 173 - 0
dashboard/src/lib/hooks/useCluster.ts

@@ -0,0 +1,173 @@
+import { useContext } from "react";
+import { Contract } from "@porter-dev/api-contracts";
+import { useQuery } from "@tanstack/react-query";
+import { z } from "zod";
+
+import { SUPPORTED_CLOUD_PROVIDERS } from "lib/clusters/constants";
+import {
+  clusterValidator,
+  contractValidator,
+  type APIContract,
+  type ClientCluster,
+} from "lib/clusters/types";
+
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { valueExists } from "shared/util";
+
+type TUseClusterList = {
+  clusters: ClientCluster[];
+  isLoading: boolean;
+};
+export const useClusterList = (): TUseClusterList => {
+  const { currentProject } = useContext(Context);
+
+  const clusterReq = useQuery(
+    ["getClusters", currentProject?.id],
+    async () => {
+      if (!currentProject?.id || currentProject.id === -1) {
+        return;
+      }
+      const res = await api.getClusters(
+        "<token>",
+        {},
+        { id: currentProject.id }
+      );
+      const parsed = await z.array(clusterValidator).parseAsync(res.data);
+      return parsed
+        .map((c) => {
+          const cloudProviderMatch = SUPPORTED_CLOUD_PROVIDERS.find(
+            (s) => s.name === c.cloud_provider
+          );
+          return cloudProviderMatch
+            ? { ...c, cloud_provider: cloudProviderMatch }
+            : null;
+        })
+        .filter(valueExists);
+    },
+    {
+      enabled: !!currentProject && currentProject.id !== -1,
+    }
+  );
+
+  return {
+    clusters: clusterReq.data ?? [],
+    isLoading: clusterReq.isLoading,
+  };
+};
+
+type TUseCluster = {
+  cluster: ClientCluster | undefined;
+  isLoading: boolean;
+  isError: boolean;
+};
+export const useCluster = ({
+  clusterId,
+}: {
+  clusterId: number | undefined;
+}): TUseCluster => {
+  const { currentProject } = useContext(Context);
+
+  const clusterReq = useQuery(
+    ["getCluster", currentProject?.id, clusterId],
+    async () => {
+      if (
+        !currentProject?.id ||
+        currentProject.id === -1 ||
+        !clusterId ||
+        clusterId === -1
+      ) {
+        return;
+      }
+      const res = await api.getCluster(
+        "<token>",
+        {},
+        { project_id: currentProject.id, cluster_id: clusterId }
+      );
+      const parsed = await clusterValidator.parseAsync(res.data);
+      const cloudProviderMatch = SUPPORTED_CLOUD_PROVIDERS.find(
+        (s) => s.name === parsed.cloud_provider
+      );
+      if (!cloudProviderMatch) {
+        return;
+      }
+      return { ...parsed, cloud_provider: cloudProviderMatch };
+    },
+    {
+      enabled:
+        !!currentProject &&
+        currentProject.id !== -1 &&
+        !!clusterId &&
+        clusterId !== -1,
+    }
+  );
+
+  return {
+    cluster: clusterReq.data,
+    isLoading: clusterReq.isLoading,
+    isError: clusterReq.isError,
+  };
+};
+
+export const useLatestClusterContract = ({
+  clusterId,
+}: {
+  clusterId: number | undefined;
+}): {
+  contractDB: APIContract | undefined;
+  contractProto: Contract | undefined;
+  isLoading: boolean;
+  isError: boolean;
+} => {
+  const { currentProject } = useContext(Context);
+
+  const latestClusterContractReq = useQuery(
+    ["getLatestClusterContract", currentProject?.id, clusterId],
+    async () => {
+      if (
+        !currentProject?.id ||
+        currentProject.id === -1 ||
+        !clusterId ||
+        clusterId === -1
+      ) {
+        return;
+      }
+
+      const res = await api.getContracts(
+        "<token>",
+        {},
+        { project_id: currentProject.id }
+      );
+
+      const data = await z.array(contractValidator).parseAsync(res.data);
+      const filtered = data.filter(
+        (contract) => contract.cluster_id === clusterId
+      );
+      if (filtered.length === 0) {
+        return;
+      }
+      const match = filtered[0];
+      return {
+        contractDB: match,
+        contractProto: Contract.fromJsonString(atob(match.base64_contract), {
+          ignoreUnknownFields: true,
+        }),
+      };
+    },
+    {
+      refetchInterval: 3000,
+      enabled:
+        !!currentProject &&
+        currentProject.id !== -1 &&
+        !!clusterId &&
+        clusterId !== -1,
+    }
+  );
+
+  return {
+    contractDB: latestClusterContractReq.data?.contractDB,
+    contractProto: latestClusterContractReq.data?.contractProto,
+    isLoading: latestClusterContractReq.isLoading,
+    isError: latestClusterContractReq.isError,
+  };
+};

+ 0 - 51
dashboard/src/lib/hooks/useClusterList.ts

@@ -1,51 +0,0 @@
-import { useContext } from "react";
-import { useQuery } from "@tanstack/react-query";
-import { z } from "zod";
-
-import api from "shared/api";
-import { Context } from "shared/Context";
-
-export const clusterValidator = z.object({
-  id: z.number(),
-  name: z.string(),
-  vanity_name: z.string(),
-  cloud_provider: z.enum(["AWS", "GCP", "AZURE"]),
-});
-export type Cluster = z.infer<typeof clusterValidator>;
-export const isAWSCluster = (
-  cluster: Cluster
-): cluster is Cluster & { cloud_provider: "AWS" } => {
-  return cluster.cloud_provider === "AWS";
-};
-
-type TUseClusterList = {
-  clusters: Array<z.infer<typeof clusterValidator>>;
-  isLoading: boolean;
-};
-export const useClusterList = (): TUseClusterList => {
-  const { currentProject } = useContext(Context);
-
-  const clusterReq = useQuery(
-    ["getClusters", currentProject?.id],
-    async () => {
-      if (!currentProject?.id || currentProject.id === -1) {
-        return;
-      }
-      const res = await api.getClusters(
-        "<token>",
-        {},
-        { id: currentProject.id }
-      );
-      const parsed = await z.array(clusterValidator).parseAsync(res.data);
-      return parsed;
-    },
-    {
-      enabled: !!currentProject && currentProject.id !== -1,
-    }
-  );
-
-  return {
-    clusters: clusterReq.data ?? [],
-    isLoading: clusterReq.isLoading,
-  };
-};

+ 17 - 39
dashboard/src/main/home/compliance-dashboard/ComplianceContext.tsx

@@ -1,7 +1,6 @@
 import React, {
   createContext,
   useContext,
-  useMemo,
   useState,
   type Dispatch,
   type SetStateAction,
@@ -16,13 +15,14 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
 import { match } from "ts-pattern";
 import { z } from "zod";
 
+import { type APIContract } from "lib/clusters/types";
+import { useLatestClusterContract } from "lib/hooks/useCluster";
+
 import api from "shared/api";
 
 import {
   checkGroupValidator,
-  contractValidator,
   vendorCheckValidator,
-  type APIContract,
   type CheckGroup,
   type VendorCheck,
 } from "./types";
@@ -34,7 +34,7 @@ type ProjectComplianceContextType = {
   clusterId: number;
   checkGroups: CheckGroup[];
   vendorChecks: VendorCheck[];
-  latestContractProto: Contract | null;
+  latestContractProto: Contract | undefined;
   latestContractDB?: APIContract;
   checksLoading: boolean;
   contractLoading: boolean;
@@ -70,23 +70,11 @@ export const ProjectComplianceProvider: React.FC<
   const [updateInProgress, setUpdateInProgress] = useState(false);
   const [profile, setProfile] = useState<ComplianceProfileType>("soc2");
 
-  const { data: baseContract, isLoading: contractLoading } = useQuery(
-    [projectId, clusterId, "getContracts"],
-    async () => {
-      const res = await api.getContracts(
-        "<token>",
-        {},
-        { project_id: projectId }
-      );
-
-      const data = await z.array(contractValidator).parseAsync(res.data);
-
-      return data.filter((contract) => contract.cluster_id === clusterId)[0];
-    },
-    {
-      refetchInterval: 3000,
-    }
-  );
+  const {
+    contractDB: latestContractDB,
+    contractProto: latestContractProto,
+    isLoading: contractLoading,
+  } = useLatestClusterContract({ clusterId });
 
   const {
     data: { checkGroups = [], vendorChecks = [] } = {},
@@ -96,7 +84,7 @@ export const ProjectComplianceProvider: React.FC<
       {
         projectId,
         clusterId,
-        condition: baseContract?.condition ?? "",
+        condition: latestContractDB?.condition ?? "",
         profile,
         name: "getComplianceChecks",
       },
@@ -122,25 +110,15 @@ export const ProjectComplianceProvider: React.FC<
     }
   );
 
-  const latestContract = useMemo(() => {
-    if (!baseContract) {
-      return null;
-    }
-
-    return Contract.fromJsonString(atob(baseContract.base64_contract), {
-      ignoreUnknownFields: true,
-    });
-  }, [baseContract?.base64_contract]);
-
   const updateContractWithProfile = async (): Promise<void> => {
     try {
       setUpdateInProgress(true);
 
-      if (!latestContract?.cluster) {
+      if (!latestContractProto?.cluster) {
         return;
       }
 
-      const updatedKindValues = match(latestContract.cluster.kindValues)
+      const updatedKindValues = match(latestContractProto.cluster.kindValues)
         .with({ case: "eksKind" }, ({ value }) => ({
           case: "eksKind" as const,
           value: new EKS({
@@ -160,15 +138,15 @@ export const ProjectComplianceProvider: React.FC<
         .otherwise((kind) => kind);
 
       const complianceProfiles = new ComplianceProfile({
-        ...latestContract.complianceProfiles,
+        ...latestContractProto.complianceProfiles,
         ...(profile === "soc2" && { soc2: true }),
         ...(profile === "hipaa" && { hipaa: true }),
       });
 
       const updatedContract = new Contract({
-        ...latestContract,
+        ...latestContractProto,
         cluster: {
-          ...latestContract.cluster,
+          ...latestContractProto.cluster,
           kindValues: updatedKindValues,
           isSoc2Compliant: true,
         },
@@ -196,8 +174,8 @@ export const ProjectComplianceProvider: React.FC<
         clusterId,
         vendorChecks,
         checkGroups,
-        latestContractProto: latestContract,
-        latestContractDB: baseContract,
+        latestContractProto,
+        latestContractDB,
         checksLoading,
         contractLoading,
         updateInProgress,

+ 0 - 67
dashboard/src/main/home/compliance-dashboard/types.ts

@@ -15,70 +15,3 @@ export const vendorCheckValidator = z.object({
   vendor_check_id: z.string(),
 });
 export type VendorCheck = z.infer<typeof vendorCheckValidator>;
-
-export const contractValidator = z.object({
-  id: z.string(),
-  base64_contract: z.string(),
-  cluster_id: z.number(),
-  project_id: z.number(),
-  condition: z.enum([
-    "",
-    "QUOTA_REQUEST_FAILED",
-    "RETRYING_TOO_LONG",
-    "KUBE_APPLY_FAILED",
-    "FATAL_PROVISIONING_ERROR",
-    "ERROR_READING_MSG",
-    "MSG_CAUSED_PANIC",
-    "SUCCESS",
-    "DELETING",
-    "DELETED",
-    "COMPLIANCE_CHECK_FAILED",
-  ]),
-  condition_metadata: z
-    .discriminatedUnion("code", [
-      z.object({
-        code: z.literal("SUCCESS"),
-        message: z.string().optional(),
-      }),
-      z.object({
-        code: z.literal("COMPLIANCE_CHECK_FAILED"),
-        message: z.string().optional(),
-        metadata: z.object({
-          check_groups: z.array(checkGroupValidator),
-        }),
-      }),
-      // all other codes are just "code" and "message"
-      z.object({
-        code: z.literal("QUOTA_REQUEST_FAILED"),
-        message: z.string().optional(),
-      }),
-      z.object({
-        code: z.literal("RETRYING_TOO_LONG"),
-        message: z.string().optional(),
-      }),
-      z.object({
-        code: z.literal("KUBE_APPLY_FAILED"),
-        message: z.string().optional(),
-      }),
-      z.object({
-        code: z.literal("FATAL_PROVISIONING_ERROR"),
-        message: z.string().optional(),
-      }),
-      z.object({
-        code: z.literal("ERROR_READING_MSG"),
-        message: z.string().optional(),
-      }),
-      z.object({
-        code: z.literal("MSG_CAUSED_PANIC"),
-        message: z.string().optional(),
-      }),
-    ])
-    .nullable()
-    .or(
-      z.object({}).transform(() => ({
-        code: "SUCCESS",
-        message: "",
-      }))
-    ),
-});
-export type APIContract = z.infer<typeof contractValidator>;

+ 2 - 1
dashboard/src/main/home/database-dashboard/DatabaseDashboard.tsx

@@ -19,8 +19,9 @@ import StatusDot from "components/porter/StatusDot";
 import Text from "components/porter/Text";
 import Toggle from "components/porter/Toggle";
 import DashboardHeader from "main/home/cluster-dashboard/DashboardHeader";
+import { isAWSCluster } from "lib/clusters/types";
 import { type ClientDatastore } from "lib/databases/types";
-import { isAWSCluster, useClusterList } from "lib/hooks/useClusterList";
+import { useClusterList } from "lib/hooks/useCluster";
 import { useDatastoreList } from "lib/hooks/useDatabaseList";
 
 import { Context } from "shared/Context";

+ 2 - 1
dashboard/src/main/home/database-dashboard/forms/DatabaseForm.tsx

@@ -12,8 +12,9 @@ import Selector from "components/porter/Selector";
 import Spacer from "components/porter/Spacer";
 import Text from "components/porter/Text";
 import VerticalSteps from "components/porter/VerticalSteps";
+import { isAWSCluster } from "lib/clusters/types";
 import { type DbFormData } from "lib/databases/types";
-import { isAWSCluster, useClusterList } from "lib/hooks/useClusterList";
+import { useClusterList } from "lib/hooks/useCluster";
 import { useDatastoreList } from "lib/hooks/useDatabaseList";
 import { useDatastoreMethods } from "lib/hooks/useDatabaseMethods";
 import { useIntercom } from "lib/hooks/useIntercom";