ソースを参照

Allow porter operators to skip preflight checks (#4386)

Feroze Mohideen 2 年 前
コミット
70e61c9d6c

+ 79 - 75
dashboard/src/lib/hooks/useCluster.ts

@@ -337,7 +337,8 @@ export const useClusterState = ({
 type TUseUpdateCluster = {
   updateCluster: (
     clientContract: ClientClusterContract,
-    baseContract: Contract
+    baseContract: Contract,
+    skipPreflightChecks?: boolean
   ) => Promise<UpdateClusterResponse>;
   isHandlingPreflightChecks: boolean;
   isCreatingContract: boolean;
@@ -353,7 +354,8 @@ export const useUpdateCluster = ({
 
   const updateCluster = async (
     clientContract: ClientClusterContract,
-    baseContract: Contract
+    baseContract: Contract,
+    skipPreflightChecks: boolean = false
   ): Promise<UpdateClusterResponse> => {
     if (!projectId) {
       throw new Error("Project ID is missing");
@@ -369,88 +371,90 @@ export const useUpdateCluster = ({
       ),
     });
 
-    setIsHandlingPreflightChecks(true);
-    try {
-      let preflightCheckResp;
-      if (
-        clientContract.cluster.cloudProvider === "AWS" ||
-        clientContract.cluster.cloudProvider === "Azure"
-      ) {
-        preflightCheckResp = await api.cloudContractPreflightCheck(
-          "<token>",
-          newContract,
-          {
-            project_id: projectId,
-          }
-        );
-      } else {
-        preflightCheckResp = await api.legacyPreflightCheck(
-          "<token>",
-          new PreflightCheckRequest({
-            contract: newContract,
-          }),
-          {
-            id: projectId,
-          }
-        );
-      }
+    if (!skipPreflightChecks) {
+      setIsHandlingPreflightChecks(true);
+      try {
+        let preflightCheckResp;
+        if (
+          clientContract.cluster.cloudProvider === "AWS" ||
+          clientContract.cluster.cloudProvider === "Azure"
+        ) {
+          preflightCheckResp = await api.cloudContractPreflightCheck(
+            "<token>",
+            newContract,
+            {
+              project_id: projectId,
+            }
+          );
+        } else {
+          preflightCheckResp = await api.legacyPreflightCheck(
+            "<token>",
+            new PreflightCheckRequest({
+              contract: newContract,
+            }),
+            {
+              id: projectId,
+            }
+          );
+        }
 
-      const parsed = await preflightCheckValidator.parseAsync(
-        preflightCheckResp.data
-      );
+        const parsed = await preflightCheckValidator.parseAsync(
+          preflightCheckResp.data
+        );
 
-      if (parsed.errors.length > 0) {
-        const cloudProviderSpecificChecks = match(
-          clientContract.cluster.cloudProvider
-        )
-          .with("AWS", () => CloudProviderAWS.preflightChecks)
-          .with("GCP", () => CloudProviderGCP.preflightChecks)
-          .with("Azure", () => CloudProviderAzure.preflightChecks)
-          .otherwise(() => []);
-
-        const clientPreflightChecks: ClientPreflightCheck[] = parsed.errors
-          .map((e) => {
-            const preflightCheckMatch = cloudProviderSpecificChecks.find(
-              (cloudProviderCheck) => e.name === cloudProviderCheck.name
-            );
-            if (!preflightCheckMatch) {
+        if (parsed.errors.length > 0) {
+          const cloudProviderSpecificChecks = match(
+            clientContract.cluster.cloudProvider
+          )
+            .with("AWS", () => CloudProviderAWS.preflightChecks)
+            .with("GCP", () => CloudProviderGCP.preflightChecks)
+            .with("Azure", () => CloudProviderAzure.preflightChecks)
+            .otherwise(() => []);
+
+          const clientPreflightChecks: ClientPreflightCheck[] = parsed.errors
+            .map((e) => {
+              const preflightCheckMatch = cloudProviderSpecificChecks.find(
+                (cloudProviderCheck) => e.name === cloudProviderCheck.name
+              );
+              if (!preflightCheckMatch) {
+                return {
+                  title: "Unknown preflight check",
+                  status: "failure" as const,
+                  error: {
+                    detail:
+                      "Your cloud provider returned an unknown error. Please reach out to Porter support.",
+                    metadata: {},
+                  },
+                };
+              }
               return {
-                title: "Unknown preflight check",
+                title: preflightCheckMatch.displayName,
                 status: "failure" as const,
                 error: {
-                  detail:
-                    "Your cloud provider returned an unknown error. Please reach out to Porter support.",
-                  metadata: {},
+                  detail: e.error.message,
+                  metadata: e.error.metadata,
+                  resolution: preflightCheckMatch.resolution,
                 },
               };
-            }
-            return {
-              title: preflightCheckMatch.displayName,
-              status: "failure" as const,
-              error: {
-                detail: e.error.message,
-                metadata: e.error.metadata,
-                resolution: preflightCheckMatch.resolution,
-              },
-            };
-          })
-          .filter(valueExists);
+            })
+            .filter(valueExists);
 
-        return {
-          preflightChecks: clientPreflightChecks,
-        };
+          return {
+            preflightChecks: clientPreflightChecks,
+          };
+        }
+        // otherwise, continue to create the contract
+      } catch (err) {
+        throw new Error(
+          getErrorMessageFromNetworkCall(
+            err,
+            "Cluster preflight checks",
+            preflightCheckErrorReplacements
+          )
+        );
+      } finally {
+        setIsHandlingPreflightChecks(false);
       }
-      // otherwise, continue to create the contract
-    } catch (err) {
-      throw new Error(
-        getErrorMessageFromNetworkCall(
-          err,
-          "Cluster preflight checks",
-          preflightCheckErrorReplacements
-        )
-      );
-    } finally {
-      setIsHandlingPreflightChecks(false);
     }
 
     setIsCreatingContract(true);

+ 28 - 2
dashboard/src/main/home/infrastructure-dashboard/ClusterFormContextProvider.tsx

@@ -33,6 +33,7 @@ type ClusterFormContextType = {
   showFailedPreflightChecksModal: boolean;
   updateClusterButtonProps: UpdateClusterButtonProps;
   setCurrentContract: (contract: Contract) => void;
+  submitSkippingPreflightChecks: () => Promise<void>;
 };
 
 const ClusterFormContext = createContext<ClusterFormContextType | null>(null);
@@ -133,14 +134,21 @@ const ClusterFormContextProvider: React.FC<ClusterFormContextProviderProps> = ({
     errors,
   ]);
 
-  const onSubmit = handleSubmit(async (data) => {
+  const handleClusterUpdate = async (
+    data: ClientClusterContract,
+    skipPreflightChecks?: boolean
+  ): Promise<void> => {
     setUpdateClusterResponse(undefined);
     setUpdateClusterError("");
     if (!currentContract?.cluster || !projectId) {
       return;
     }
     try {
-      const response = await updateCluster(data, currentContract);
+      const response = await updateCluster(
+        data,
+        currentContract,
+        skipPreflightChecks
+      );
       setUpdateClusterResponse(response);
       if (response.preflightChecks) {
         void reportToAnalytics({
@@ -194,8 +202,25 @@ const ClusterFormContextProvider: React.FC<ClusterFormContextProviderProps> = ({
         });
       }
     }
+  };
+
+  const onSubmit = handleSubmit(async (data) => {
+    await handleClusterUpdate(data);
   });
 
+  const submitSkippingPreflightChecks = async (): Promise<void> => {
+    if (clusterForm.formState.isSubmitting) {
+      return;
+    }
+    if (!currentContract?.cluster) {
+      return;
+    }
+    const fullValuesWithDefaults = clusterContractValidator.parse(
+      clusterForm.getValues()
+    );
+    await handleClusterUpdate(fullValuesWithDefaults, true);
+  };
+
   return (
     <ClusterFormContext.Provider
       value={{
@@ -204,6 +229,7 @@ const ClusterFormContextProvider: React.FC<ClusterFormContextProviderProps> = ({
         updateClusterButtonProps,
         isAdvancedSettingsEnabled,
         isMultiClusterEnabled,
+        submitSkippingPreflightChecks,
       }}
     >
       <Wrapper ref={scrollToTopRef}>

+ 28 - 6
dashboard/src/main/home/infrastructure-dashboard/modals/PreflightChecksModal.tsx

@@ -1,8 +1,10 @@
-import React from "react";
+import React, { useContext } from "react";
 import styled from "styled-components";
 import { match } from "ts-pattern";
 
 import Loading from "components/Loading";
+import Button from "components/porter/Button";
+import Container from "components/porter/Container";
 import { Error as ErrorComponent } from "components/porter/Error";
 import Expandable from "components/porter/Expandable";
 import Modal from "components/porter/Modal";
@@ -12,6 +14,9 @@ import StatusDot from "components/porter/StatusDot";
 import Text from "components/porter/Text";
 import { type ClientPreflightCheck } from "lib/clusters/types";
 
+import { Context } from "shared/Context";
+
+import { useClusterFormContext } from "../ClusterFormContextProvider";
 import ResolutionStepsModalContents from "./help/preflight/ResolutionStepsModalContents";
 
 type ItemProps = {
@@ -83,6 +88,9 @@ const PreflightChecksModal: React.FC<Props> = ({
   onClose,
   preflightChecks,
 }) => {
+  const { user } = useContext(Context);
+  const { submitSkippingPreflightChecks } = useClusterFormContext();
+
   return (
     <Modal width="600px" closeModal={onClose}>
       <AppearingDiv>
@@ -104,11 +112,25 @@ const PreflightChecksModal: React.FC<Props> = ({
           ))}
         </div>
         <Spacer y={1} />
-        <ShowIntercomButton
-          message={"I need help resolving cluster preflight checks."}
-        >
-          Talk to support
-        </ShowIntercomButton>
+        <Container row spaced>
+          <ShowIntercomButton
+            message={"I need help resolving cluster preflight checks."}
+          >
+            Talk to support
+          </ShowIntercomButton>
+          {user.email?.endsWith("@porter.run") && (
+            <>
+              <Button
+                onClick={async () => {
+                  await submitSkippingPreflightChecks();
+                }}
+                color="red"
+              >
+                (Porter operators only) Skip preflight checks
+              </Button>
+            </>
+          )}
+        </Container>
       </AppearingDiv>
     </Modal>
   );