Skip to content

Commit dc880cd

Browse files
author
Feroze Mohideen
authored
fail cloud formation flow on specific error (#4356)
1 parent 1300d58 commit dc880cd

File tree

2 files changed

+76
-34
lines changed

2 files changed

+76
-34
lines changed

dashboard/src/lib/hooks/useCloudProvider.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,15 @@ export const isAWSArnAccessible = async ({
1212
externalId: string;
1313
projectId: number;
1414
}): Promise<boolean> => {
15-
try {
16-
await api.createAWSIntegration(
17-
"<token>",
18-
{
19-
aws_target_arn: targetArn,
20-
aws_external_id: externalId,
21-
},
22-
{ id: projectId }
23-
);
24-
return true;
25-
} catch (err) {
26-
return false;
27-
}
15+
await api.createAWSIntegration(
16+
"<token>",
17+
{
18+
aws_target_arn: targetArn,
19+
aws_external_id: externalId,
20+
},
21+
{ id: projectId }
22+
);
23+
return true;
2824
};
2925

3026
export const connectToAzureAccount = async ({

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

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
import React, { useCallback, useEffect, useMemo, useState } from "react";
22
import { useQuery } from "@tanstack/react-query";
3+
import axios from "axios";
34
import styled from "styled-components";
45
import { v4 as uuidv4 } from "uuid";
6+
import { z } from "zod";
57

68
import Back from "components/porter/Back";
79
import Button from "components/porter/Button";
810
import Container from "components/porter/Container";
11+
import { Error as ErrorComponent } from "components/porter/Error";
912
import Image from "components/porter/Image";
1013
import Input from "components/porter/Input";
1114
import Link from "components/porter/Link";
1215
import Spacer from "components/porter/Spacer";
1316
import Text from "components/porter/Text";
1417
import VerticalSteps from "components/porter/VerticalSteps";
18+
import { type ButtonStatus } from "main/home/app-dashboard/app-view/AppDataContainer";
1519
import { CloudProviderAWS } from "lib/clusters/constants";
1620
import { isAWSArnAccessible } from "lib/hooks/useCloudProvider";
1721
import { useClusterAnalytics } from "lib/hooks/useClusterAnalytics";
22+
import { useIntercom } from "lib/hooks/useIntercom";
1823

1924
import GrantAWSPermissionsHelpModal from "../../modals/help/permissions/GrantAWSPermissionsHelpModal";
2025
import { CheckItem } from "../../modals/PreflightChecksModal";
@@ -38,9 +43,11 @@ const GrantAWSPermissions: React.FC<Props> = ({
3843
const [currentStep, setCurrentStep] = useState<number>(0);
3944
const [showNeedHelpModal, setShowNeedHelpModal] = useState(false);
4045
const [accountIdContinueButtonStatus, setAccountIdContinueButtonStatus] =
41-
useState<string>("");
46+
useState<ButtonStatus>("");
4247
const [isAccountAccessible, setIsAccountAccessible] = useState(false);
4348
const { reportToAnalytics } = useClusterAnalytics();
49+
const { showIntercomWithMessage } = useIntercom();
50+
4451
const awsAccountIdInputError = useMemo(() => {
4552
const regex = /^\d{12}$/;
4653
if (AWSAccountID.trim().length === 0) {
@@ -50,6 +57,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
5057
}
5158
return undefined;
5259
}, [AWSAccountID]);
60+
5361
const externalId = useMemo(() => {
5462
if (!AWSAccountID || awsAccountIdInputError) {
5563
return "";
@@ -62,6 +70,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
6270

6371
return externalId;
6472
}, [AWSAccountID, awsAccountIdInputError]);
73+
6574
const data = useQuery(
6675
[
6776
"cloudFormationStackCreated",
@@ -71,11 +80,16 @@ const GrantAWSPermissions: React.FC<Props> = ({
7180
externalId,
7281
],
7382
async () => {
74-
return await isAWSArnAccessible({
75-
targetArn: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
76-
externalId,
77-
projectId,
78-
});
83+
try {
84+
await isAWSArnAccessible({
85+
targetArn: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
86+
externalId,
87+
projectId,
88+
});
89+
return true;
90+
} catch (err) {
91+
return false;
92+
}
7993
},
8094
{
8195
enabled: currentStep === 3 && !isAccountAccessible, // no need to check if it's already accessible
@@ -88,30 +102,58 @@ const GrantAWSPermissions: React.FC<Props> = ({
88102
setIsAccountAccessible(data.data);
89103
}
90104
}, [data]);
105+
91106
const handleAWSAccountIDChange = (accountId: string): void => {
92107
setAWSAccountID(accountId);
93108
setIsAccountAccessible(false); // any time they change the account ID, we need to re-check if it's accessible
94109
};
110+
95111
const checkIfAlreadyAccessible = async (): Promise<void> => {
96112
setAccountIdContinueButtonStatus("loading");
97-
const isAlreadyAccessible = await isAWSArnAccessible({
98-
targetArn: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
99-
externalId,
100-
projectId,
101-
});
102-
if (isAlreadyAccessible) {
113+
try {
114+
await isAWSArnAccessible({
115+
targetArn: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
116+
externalId,
117+
projectId,
118+
});
103119
setCurrentStep(3);
104120
setIsAccountAccessible(true);
105-
} else {
106-
setCurrentStep(2);
121+
} catch (err) {
122+
let shouldProceed = true;
123+
if (axios.isAxiosError(err)) {
124+
const parsed = z
125+
.object({ error: z.string() })
126+
.safeParse(err.response?.data);
127+
if (
128+
parsed.success &&
129+
parsed.data.error.includes(
130+
"user does not have access to all projects"
131+
)
132+
) {
133+
setAccountIdContinueButtonStatus(
134+
<ErrorComponent
135+
message={"Unable to proceed. Please reach out to support."}
136+
/>
137+
);
138+
showIntercomWithMessage({
139+
message: "I need help granting AWS permissions.",
140+
});
141+
shouldProceed = false;
142+
}
143+
}
144+
if (shouldProceed) {
145+
setCurrentStep(2);
146+
setAccountIdContinueButtonStatus("");
147+
}
148+
} finally {
149+
void reportToAnalytics({
150+
projectId,
151+
step: "aws-account-id-complete",
152+
awsAccountId: AWSAccountID,
153+
});
107154
}
108-
void reportToAnalytics({
109-
projectId,
110-
step: "aws-account-id-complete",
111-
awsAccountId: AWSAccountID,
112-
});
113-
setAccountIdContinueButtonStatus("");
114155
};
156+
115157
const directToAWSLogin = (): void => {
116158
const loginUrl = `https://signin.aws.amazon.com/console`;
117159
void reportToAnalytics({
@@ -121,6 +163,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
121163
});
122164
window.open(loginUrl, "_blank");
123165
};
166+
124167
const directToCloudFormation = useCallback(async () => {
125168
const trustArn = process.env.TRUST_ARN
126169
? process.env.TRUST_ARN
@@ -136,6 +179,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
136179
setCurrentStep(3);
137180
window.open(cloudFormationUrl, "_blank");
138181
}, [AWSAccountID, externalId]);
182+
139183
const handleGrantPermissionsComplete = (): void => {
140184
proceed({
141185
cloudProviderCredentialIdentifier: `arn:aws:iam::${AWSAccountID}:role/porter-manager`,
@@ -220,6 +264,8 @@ const GrantAWSPermissions: React.FC<Props> = ({
220264
<Button
221265
onClick={() => {
222266
setCurrentStep(0);
267+
setAccountIdContinueButtonStatus("");
268+
setAWSAccountID("");
223269
}}
224270
color="#222222"
225271
>
@@ -232,7 +278,7 @@ const GrantAWSPermissions: React.FC<Props> = ({
232278
disabled={
233279
awsAccountIdInputError != null ||
234280
AWSAccountID.length === 0 ||
235-
accountIdContinueButtonStatus === "loading"
281+
accountIdContinueButtonStatus !== ""
236282
}
237283
status={accountIdContinueButtonStatus}
238284
loadingText={`Checking if Porter can already access this account`}

0 commit comments

Comments
 (0)