فهرست منبع

support percent completion indication for cloud credential update (#4380)

Feroze Mohideen 2 سال پیش
والد
کامیت
37889b57f0

+ 1 - 0
api/server/handlers/project_integration/create_aws.go

@@ -139,6 +139,7 @@ func (p *CreateAWSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 				return
 			}
 			res.CloudProviderCredentialIdentifier = credResp.Msg.CredentialsIdentifier
+			res.PercentCompleted = credResp.Msg.PercentCompleted
 		} else {
 			credReq := porterv1.CreateAssumeRoleChainRequest{ //nolint:staticcheck // being deprecated by the above UpdateCloudProviderCredentials
 				ProjectId:       int64(project.ID),

+ 2 - 1
api/types/project_integration.go

@@ -96,7 +96,8 @@ type CreateAWSRequest struct {
 
 type CreateAWSResponse struct {
 	*AWSIntegration
-	CloudProviderCredentialIdentifier string `json:"cloud_provider_credentials_id"`
+	CloudProviderCredentialIdentifier string  `json:"cloud_provider_credentials_id"`
+	PercentCompleted                  float32 `json:"percent_completed"`
 }
 
 type OverwriteAWSRequest struct {

+ 9 - 3
dashboard/src/lib/hooks/useCloudProvider.ts

@@ -11,8 +11,8 @@ export const isAWSArnAccessible = async ({
   targetArn: string;
   externalId: string;
   projectId: number;
-}): Promise<boolean> => {
-  await api.createAWSIntegration(
+}): Promise<number> => {
+  const res = await api.createAWSIntegration(
     "<token>",
     {
       aws_target_arn: targetArn,
@@ -20,7 +20,13 @@ export const isAWSArnAccessible = async ({
     },
     { id: projectId }
   );
-  return true;
+  const parsed = await z
+    .object({
+      percent_completed: z.number(),
+    })
+    .parseAsync(res.data);
+
+  return parsed.percent_completed;
 };
 
 export const connectToAzureAccount = async ({

+ 63 - 43
dashboard/src/main/home/infrastructure-dashboard/forms/aws/GrantAWSPermissions.tsx

@@ -13,6 +13,7 @@ import Image from "components/porter/Image";
 import Input from "components/porter/Input";
 import Link from "components/porter/Link";
 import Spacer from "components/porter/Spacer";
+import StatusBar from "components/porter/StatusBar";
 import Text from "components/porter/Text";
 import VerticalSteps from "components/porter/VerticalSteps";
 import { type ButtonStatus } from "main/home/app-dashboard/app-view/AppDataContainer";
@@ -22,7 +23,6 @@ import { useClusterAnalytics } from "lib/hooks/useClusterAnalytics";
 import { useIntercom } from "lib/hooks/useIntercom";
 
 import GrantAWSPermissionsHelpModal from "../../modals/help/permissions/GrantAWSPermissionsHelpModal";
-import { CheckItem } from "../../modals/PreflightChecksModal";
 
 type Props = {
   goBack: () => void;
@@ -44,7 +44,8 @@ const GrantAWSPermissions: React.FC<Props> = ({
   const [showNeedHelpModal, setShowNeedHelpModal] = useState(false);
   const [accountIdContinueButtonStatus, setAccountIdContinueButtonStatus] =
     useState<ButtonStatus>("");
-  const [isAccountAccessible, setIsAccountAccessible] = useState(false);
+  const [permissionsGrantCompletionPercentage, setAWSPermissionsProgress] =
+    useState(0);
   const { reportToAnalytics } = useClusterAnalytics();
   const { showIntercomWithMessage } = useIntercom();
 
@@ -71,53 +72,74 @@ const GrantAWSPermissions: React.FC<Props> = ({
     return externalId;
   }, [AWSAccountID, awsAccountIdInputError]);
 
+  const awsPermissionsLoadingMessage = useMemo(() => {
+    if (permissionsGrantCompletionPercentage === 0) {
+      return "Setting up access roles and policies...";
+    }
+    if (permissionsGrantCompletionPercentage < 50) {
+      return "Creating cluster management roles...";
+    }
+    if (permissionsGrantCompletionPercentage < 100) {
+      return "Creating cluster management policies...";
+    }
+    return "";
+  }, [permissionsGrantCompletionPercentage]);
+
   const data = useQuery(
     [
       "cloudFormationStackCreated",
       AWSAccountID,
       projectId,
-      isAccountAccessible,
+      permissionsGrantCompletionPercentage,
       externalId,
     ],
     async () => {
       try {
-        await isAWSArnAccessible({
+        const res = await isAWSArnAccessible({
           targetArn: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
           externalId,
           projectId,
         });
-        return true;
+        return res;
       } catch (err) {
-        return false;
+        return 0;
       }
     },
     {
-      enabled: currentStep === 3 && !isAccountAccessible, // no need to check if it's already accessible
+      enabled:
+        currentStep === 3 && permissionsGrantCompletionPercentage !== 100, // no need to check if it's already accessible
       refetchInterval: 5000,
       refetchIntervalInBackground: true,
     }
   );
   useEffect(() => {
     if (data.isSuccess) {
-      setIsAccountAccessible(data.data);
+      setAWSPermissionsProgress(data.data);
     }
   }, [data]);
 
   const handleAWSAccountIDChange = (accountId: string): void => {
     setAWSAccountID(accountId);
-    setIsAccountAccessible(false); // any time they change the account ID, we need to re-check if it's accessible
+    setAWSPermissionsProgress(0); // any time they change the account ID, we need to re-check if it's accessible
   };
 
   const checkIfAlreadyAccessible = async (): Promise<void> => {
     setAccountIdContinueButtonStatus("loading");
     try {
-      await isAWSArnAccessible({
+      const awsIntegrationPercentCompleted = await isAWSArnAccessible({
         targetArn: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
         externalId,
         projectId,
       });
-      setCurrentStep(3);
-      setIsAccountAccessible(true);
+      if (awsIntegrationPercentCompleted > 0) {
+        // this indicates the permission check is already in place; no need to re-create cloudformation stack
+        setCurrentStep(3);
+        setAWSPermissionsProgress(awsIntegrationPercentCompleted);
+        setAccountIdContinueButtonStatus("");
+      } else {
+        setCurrentStep(2);
+        setAccountIdContinueButtonStatus("");
+      }
     } catch (err) {
       let shouldProceed = true;
       if (axios.isAxiosError(err)) {
@@ -266,6 +288,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
                   setCurrentStep(0);
                   setAccountIdContinueButtonStatus("");
                   setAWSAccountID("");
+                  setAWSPermissionsProgress(0);
                 }}
                 color="#222222"
               >
@@ -308,7 +331,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
                 onClick={directToCloudFormation}
                 color="linear-gradient(180deg, #26292e, #24272c)"
                 withBorder
-                disabled={isAccountAccessible}
+                disabled={permissionsGrantCompletionPercentage === 100}
                 disabledTooltipMessage={
                   "Porter can already access your account!"
                 }
@@ -338,36 +361,33 @@ const GrantAWSPermissions: React.FC<Props> = ({
           </>,
           <>
             <Text size={16}>Check permissions</Text>
-            <Spacer y={0.5} />
-            <Text color="helper">
-              Checking if Porter can access AWS account with ID {AWSAccountID}
-              . This can take up to 10 minutes.
-              <Spacer inline width="10px" />
-              <Link
-                hasunderline
-                onClick={() => {
-                  setShowNeedHelpModal(true);
-                }}
-              >
-                Need help?
-              </Link>
-            </Text>
             <Spacer y={1} />
-            {isAccountAccessible ? (
-              <CheckItem
-                preflightCheck={{
-                  title: "AWS account is accessible by Porter!",
-                  status: "success",
-                }}
-              />
-            ) : (
-              <CheckItem
-                preflightCheck={{
-                  title: "Checking if AWS account is accessible by Porter",
-                  status: "pending",
-                }}
-              />
-            )}
+            <StatusBar
+              icon={CloudProviderAWS.icon}
+              title={"AWS permissions setup"}
+              titleDescriptor={awsPermissionsLoadingMessage}
+              subtitle={
+                permissionsGrantCompletionPercentage === 100
+                  ? "Porter can access your account! You may now continue."
+                  : "Porter is creating roles and policies to access your account. This can take up to 15 minutes. Please stay on this page."
+              }
+              percentCompleted={Math.max(
+                permissionsGrantCompletionPercentage,
+                5
+              )}
+            />
+            <Spacer y={0.5} />
+            <Link
+              hasunderline
+              onClick={() => {
+                showIntercomWithMessage({
+                  message: "I need help with AWS permissions setup.",
+                  delaySeconds: 0,
+                });
+              }}
+            >
+              Need help?
+            </Link>
             <Spacer y={1} />
             <Container row>
               <Button
@@ -381,7 +401,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
               <Spacer inline x={0.5} />
               <Button
                 onClick={handleGrantPermissionsComplete}
-                disabled={!isAccountAccessible}
+                disabled={permissionsGrantCompletionPercentage !== 100}
               >
                 Continue
               </Button>

+ 5 - 3
dashboard/src/main/home/infrastructure-dashboard/forms/azure/CreateAKSClusterForm.tsx

@@ -27,9 +27,11 @@ const CreateAKSClusterForm: React.FC<Props> = ({
   const { reportToAnalytics } = useClusterAnalytics();
 
   useEffect(() => {
-    const projectNameLimit = 31 - "-cluster-".length - 6; // 6 characters for the random suffix
-    const truncatedProjectName = projectName.substring(0, projectNameLimit);
-    const clusterName = `${truncatedProjectName}-cluster-${Math.random()
+    const truncatedProjectName = projectName
+      .substring(0, 24)
+      .replace(/-+$/, "");
+
+    const clusterName = `${truncatedProjectName}-${Math.random()
       .toString(36)
       .substring(2, 8)}`;
 

+ 1 - 1
go.mod

@@ -83,7 +83,7 @@ require (
 	github.com/matryer/is v1.4.0
 	github.com/nats-io/nats.go v1.24.0
 	github.com/open-policy-agent/opa v0.44.0
-	github.com/porter-dev/api-contracts v0.2.113
+	github.com/porter-dev/api-contracts v0.2.114
 	github.com/riandyrn/otelchi v0.5.1
 	github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
 	github.com/stefanmcshane/helm v0.0.0-20221213002717-88a4a2c6e77d

+ 2 - 0
go.sum

@@ -1525,6 +1525,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
 github.com/porter-dev/api-contracts v0.2.113 h1:sv1huO9MpykJaWhV2D5zTD2LouMbRSBV5ATt/5Ukrbo=
 github.com/porter-dev/api-contracts v0.2.113/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.114 h1:qfEq70BJ8xXTkiZU7ygzOSGnMCqJHOa5Lbkfu4OzQBI=
+github.com/porter-dev/api-contracts v0.2.114/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=