Skip to content

Commit

Permalink
Allow porter operators to skip preflight checks (#4386)
Browse files Browse the repository at this point in the history
  • Loading branch information
Feroze Mohideen authored Mar 6, 2024
1 parent a3d3ca2 commit 70e61c9
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 83 deletions.
154 changes: 79 additions & 75 deletions dashboard/src/lib/hooks/useCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ export const useClusterState = ({
type TUseUpdateCluster = {
updateCluster: (
clientContract: ClientClusterContract,
baseContract: Contract
baseContract: Contract,
skipPreflightChecks?: boolean
) => Promise<UpdateClusterResponse>;
isHandlingPreflightChecks: boolean;
isCreatingContract: boolean;
Expand All @@ -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");
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type ClusterFormContextType = {
showFailedPreflightChecksModal: boolean;
updateClusterButtonProps: UpdateClusterButtonProps;
setCurrentContract: (contract: Contract) => void;
submitSkippingPreflightChecks: () => Promise<void>;
};

const ClusterFormContext = createContext<ClusterFormContextType | null>(null);
Expand Down Expand Up @@ -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({
Expand Down Expand Up @@ -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={{
Expand All @@ -204,6 +229,7 @@ const ClusterFormContextProvider: React.FC<ClusterFormContextProviderProps> = ({
updateClusterButtonProps,
isAdvancedSettingsEnabled,
isMultiClusterEnabled,
submitSkippingPreflightChecks,
}}
>
<Wrapper ref={scrollToTopRef}>
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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 = {
Expand Down Expand Up @@ -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>
Expand All @@ -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>
);
Expand Down

0 comments on commit 70e61c9

Please sign in to comment.