|
|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useEffect, useState, useContext, useMemo } from "react";
|
|
|
+import React, { useState, useContext } from "react";
|
|
|
import styled from "styled-components";
|
|
|
import { v4 as uuidv4 } from 'uuid';
|
|
|
|
|
|
@@ -9,16 +9,13 @@ import { Context } from "shared/Context";
|
|
|
|
|
|
import Text from "./porter/Text";
|
|
|
import Spacer from "./porter/Spacer";
|
|
|
-import InputRow from "./form-components/InputRow";
|
|
|
-import SaveButton from "./SaveButton";
|
|
|
-import Fieldset from "./porter/Fieldset";
|
|
|
import Input from "./porter/Input";
|
|
|
import Button from "./porter/Button";
|
|
|
-import DocsHelper from "./DocsHelper";
|
|
|
import Error from "./porter/Error";
|
|
|
import Step from "./porter/Step";
|
|
|
import Link from "./porter/Link";
|
|
|
import Container from "./porter/Container";
|
|
|
+import VerticalSteps from "./porter/VerticalSteps";
|
|
|
|
|
|
type Props = {
|
|
|
goBack: () => void;
|
|
|
@@ -36,6 +33,9 @@ const CloudFormationForm: React.FC<Props> = ({
|
|
|
const [roleStatus, setRoleStatus] = useState("");
|
|
|
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined);
|
|
|
const [AWSAccountID, setAWSAccountID] = useState("");
|
|
|
+ const [AWSAccountIDInputError, setAWSAccountIDInputError] = useState<string | undefined>(undefined);
|
|
|
+ const [currentStep, setCurrentStep] = useState<number>(0);
|
|
|
+
|
|
|
const { currentProject } = useContext(Context);
|
|
|
const markStepStarted = async (
|
|
|
{
|
|
|
@@ -43,21 +43,57 @@ const CloudFormationForm: React.FC<Props> = ({
|
|
|
account_id = "",
|
|
|
cloudformation_url = "",
|
|
|
error_message = "",
|
|
|
+ login_url = "",
|
|
|
}:
|
|
|
{
|
|
|
step: string;
|
|
|
account_id?: string
|
|
|
cloudformation_url?: string
|
|
|
error_message?: string
|
|
|
+ login_url?: string
|
|
|
}
|
|
|
) => {
|
|
|
try {
|
|
|
- await api.updateOnboardingStep("<token>", { step, account_id, cloudformation_url, error_message }, {});
|
|
|
+ await api.updateOnboardingStep("<token>", { step, account_id, cloudformation_url, error_message, login_url }, {});
|
|
|
} catch (err) {
|
|
|
// console.log(err);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ const getAccountIdInputError = (accountId: string) => {
|
|
|
+ const regex = /^\d{12}$/;
|
|
|
+ if (accountId === "") {
|
|
|
+ return undefined;
|
|
|
+ } else if (!regex.test(accountId)) {
|
|
|
+ return 'A valid AWS Account ID must be a 12-digit number.';
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleAWSAccountIDChange = (accountId: string) => {
|
|
|
+ setAWSAccountID(accountId);
|
|
|
+ if (accountId === "open-sesame") {
|
|
|
+ switchToCredentialFlow();
|
|
|
+ }
|
|
|
+ // handle case where user resets the input to empty
|
|
|
+ if (accountId.trim().length === 0) {
|
|
|
+ setCurrentStep(0);
|
|
|
+ setAWSAccountIDInputError(undefined);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const accountIdInputError = getAccountIdInputError(accountId);
|
|
|
+ if (accountIdInputError == null) {
|
|
|
+ setCurrentStep(1);
|
|
|
+ if (!hasSentAWSNotif) {
|
|
|
+ setHasSentAWSNotif(true);
|
|
|
+ markStepStarted({ step: "aws-account-id-complete", account_id: accountId });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ setCurrentStep(0);
|
|
|
+ }
|
|
|
+ setAWSAccountIDInputError(accountIdInputError);
|
|
|
+ };
|
|
|
+
|
|
|
const getExternalId = () => {
|
|
|
let externalId = localStorage.getItem(AWSAccountID)
|
|
|
if (!externalId) {
|
|
|
@@ -105,130 +141,166 @@ const CloudFormationForm: React.FC<Props> = ({
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const directToCloudFormation = () => {
|
|
|
+ const directToAWSLoginAndProceedStep = () => {
|
|
|
+ const login_url = `https://${AWSAccountID}.signin.aws.amazon.com/console`;
|
|
|
+ markStepStarted({ step: "aws-login-redirect-success", account_id: AWSAccountID, login_url })
|
|
|
+ setCurrentStep(2);
|
|
|
+ window.open(login_url, "_blank")
|
|
|
+ }
|
|
|
+
|
|
|
+ const directToCloudFormationAndProceedStep = () => {
|
|
|
let externalId = getExternalId();
|
|
|
let trustArn = process.env.TRUST_ARN ? process.env.TRUST_ARN : "arn:aws:iam::108458755588:role/CAPIManagement";
|
|
|
const cloudformation_url = `https://console.aws.amazon.com/cloudformation/home?#/stacks/create/review?templateURL=https://porter-role.s3.us-east-2.amazonaws.com/cloudformation-policy.json&stackName=PorterRole¶m_ExternalIdParameter=${externalId}¶m_TrustArnParameter=${trustArn}`
|
|
|
markStepStarted({ step: "aws-cloudformation-redirect-success", account_id: AWSAccountID, cloudformation_url })
|
|
|
+ setCurrentStep(3);
|
|
|
window.open(cloudformation_url, "_blank")
|
|
|
}
|
|
|
|
|
|
const renderContent = () => {
|
|
|
return (
|
|
|
<>
|
|
|
+ <Text>Grant Porter permissions to create infrastructure in your AWS account by following 4 simple steps.</Text>
|
|
|
<Spacer y={1} />
|
|
|
- <Fieldset>
|
|
|
- <Text size={16}>
|
|
|
- Log in to AWS and "Create stack"
|
|
|
- </Text>
|
|
|
- <Spacer height="15px" />
|
|
|
- <Text color="helper">
|
|
|
- Provide your AWS account ID to log in and grant Porter access to AWS by clicking 'Grant permissions' below. You will need to select "Create stack" after being redirected to the AWS console.
|
|
|
- </Text>
|
|
|
- <Spacer y={1} />
|
|
|
- <Input
|
|
|
- label={
|
|
|
- <Flex>
|
|
|
- 👤 AWS account ID
|
|
|
- <i
|
|
|
- className="material-icons"
|
|
|
- onClick={() => {
|
|
|
- window.open("https://console.aws.amazon.com/billing/home?region=us-east-1#/account", "_blank")
|
|
|
- }}
|
|
|
- >
|
|
|
- help_outline
|
|
|
- </i>
|
|
|
- </Flex>
|
|
|
- }
|
|
|
- value={AWSAccountID}
|
|
|
- setValue={(e) => {
|
|
|
- if (e === "open-sesame") {
|
|
|
- switchToCredentialFlow();
|
|
|
- }
|
|
|
- if (e.trim().length === 12 && !hasSentAWSNotif) {
|
|
|
- setHasSentAWSNotif(true);
|
|
|
- markStepStarted({ step: "aws-account-id-complete", account_id: e.trim() });
|
|
|
- }
|
|
|
- setGrantPermissionsError("");
|
|
|
- setAWSAccountID(e.trim());
|
|
|
- }}
|
|
|
- placeholder="ex: 915037676314"
|
|
|
- />
|
|
|
- <Spacer y={1} />
|
|
|
- <Button
|
|
|
- onClick={() => {
|
|
|
- if (AWSAccountID.length === 12 && !isNaN(Number(AWSAccountID))) {
|
|
|
- directToCloudFormation();
|
|
|
- } else {
|
|
|
- setGrantPermissionsError("Invalid AWS account ID");
|
|
|
- }
|
|
|
- }}
|
|
|
- status={
|
|
|
- grantPermissionsError && (
|
|
|
- <Error message={grantPermissionsError} />
|
|
|
- )
|
|
|
- }
|
|
|
- color="#1E2631"
|
|
|
- withBorder
|
|
|
- >
|
|
|
- <ButtonImg src={aws} /> Grant permissions
|
|
|
- </Button>
|
|
|
- <Spacer y={1} />
|
|
|
- <Text color="helper">
|
|
|
- Make sure that the stack status has changed from "CREATE_IN_PROGRESS" to "CREATE_COMPLETE" before clicking Continue below.
|
|
|
- </Text>
|
|
|
- </Fieldset>
|
|
|
- <Spacer y={1} />
|
|
|
- <Button
|
|
|
- onClick={() => {
|
|
|
- checkIfRoleExists()
|
|
|
- }}
|
|
|
- status={
|
|
|
- errorMessage ? (
|
|
|
- <Error
|
|
|
- message={errorMessage}
|
|
|
- ctaText="Troubleshooting steps"
|
|
|
- errorModalContents={
|
|
|
+ <VerticalSteps
|
|
|
+ currentStep={currentStep}
|
|
|
+ steps={
|
|
|
+ [
|
|
|
+ <>
|
|
|
+ <Text size={16}>1. Provide your AWS Account ID.</Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <Input
|
|
|
+ label={
|
|
|
+ <Flex>
|
|
|
+ 👤 AWS account ID
|
|
|
+ <i
|
|
|
+ className="material-icons"
|
|
|
+ onClick={() => {
|
|
|
+ window.open("https://console.aws.amazon.com/billing/home?region=us-east-1#/account", "_blank")
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ help_outline
|
|
|
+ </i>
|
|
|
+ </Flex>
|
|
|
+ }
|
|
|
+ value={AWSAccountID}
|
|
|
+ setValue={handleAWSAccountIDChange}
|
|
|
+ placeholder="ex: 915037676314"
|
|
|
+ error={AWSAccountIDInputError}
|
|
|
+ />
|
|
|
+ </>,
|
|
|
+ <>
|
|
|
+ <Text size={16}>2. Log in to your AWS Account.</Text>
|
|
|
+ <Spacer y={0.25} />
|
|
|
+ <Text color="helper">Return to Porter after successful log-in.</Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <AWSButtonContainer>
|
|
|
+ <ButtonImg src={aws} />
|
|
|
+ <Button
|
|
|
+ width={"170px"}
|
|
|
+ onClick={directToAWSLoginAndProceedStep}
|
|
|
+ color="#1E2631"
|
|
|
+ withBorder
|
|
|
+ >
|
|
|
+ Log in
|
|
|
+ </Button>
|
|
|
+ </AWSButtonContainer>
|
|
|
+ {/* escape hatch for dev use only */}
|
|
|
+ {process.env.TRUST_ARN != null && process.env.TRUST_ARN !== "arn:aws:iam::108458755588:role/CAPIManagement" &&
|
|
|
<>
|
|
|
- <Text size={16}>Granting Porter access to AWS</Text>
|
|
|
- <Spacer y={1} />
|
|
|
- <Text color="helper">
|
|
|
- Porter needs access to your AWS account in order to create infrastructure. You can grant Porter access to AWS by following these steps:
|
|
|
- </Text>
|
|
|
- <Spacer y={1} />
|
|
|
- <Step number={1}>
|
|
|
- <Link to="https://aws.amazon.com/resources/create-account/" target="_blank">
|
|
|
- Create an AWS account
|
|
|
- </Link>
|
|
|
- <Spacer inline width="5px" />
|
|
|
- if you don't already have one.
|
|
|
- </Step>
|
|
|
- <Spacer y={1} />
|
|
|
- <Step number={2}>
|
|
|
- Once you are logged in to your AWS account,
|
|
|
- <Spacer inline width="5px" />
|
|
|
- <Link to="https://console.aws.amazon.com/billing/home?region=us-east-1#/account" target="_blank">
|
|
|
- copy your account ID
|
|
|
- </Link>.
|
|
|
- </Step>
|
|
|
- <Spacer y={1} />
|
|
|
- <Step number={3}>Fill in your account ID on Porter and select "Grant permissions".</Step>
|
|
|
- <Spacer y={1} />
|
|
|
- <Step number={4}>After being redirected to AWS, select "Create stack" on the AWS console.</Step>
|
|
|
- <Spacer y={1} />
|
|
|
- <Step number={5}>Wait until the stack status has changed from "CREATE_IN_PROGRESS" to "CREATE_COMPLETE".</Step>
|
|
|
- <Spacer y={1} />
|
|
|
- <Step number={6}>Return to Porter and select "Continue".</Step>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <Link onClick={() => setCurrentStep(4)} hasunderline>Skip this step</Link>
|
|
|
</>
|
|
|
}
|
|
|
- />
|
|
|
- ) : (
|
|
|
- roleStatus
|
|
|
- )
|
|
|
+ </>,
|
|
|
+ <>
|
|
|
+ <Text size={16}>3. Create an AWS Cloudformation Stack.</Text>
|
|
|
+ <Spacer y={0.25} />
|
|
|
+ <Text color="helper">This grants Porter permissions to create infrastructure.</Text>
|
|
|
+ <Spacer y={0.25} />
|
|
|
+ <Text color="helper">
|
|
|
+ Return to Porter once the stack status has changed from "CREATE_IN_PROGRESS" to "CREATE_COMPLETE".
|
|
|
+ </Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <AWSButtonContainer>
|
|
|
+ <ButtonImg src={aws} />
|
|
|
+ <Button
|
|
|
+ width={"170px"}
|
|
|
+ onClick={() => {
|
|
|
+ if (AWSAccountID.length === 12 && !isNaN(Number(AWSAccountID))) {
|
|
|
+ directToCloudFormationAndProceedStep();
|
|
|
+ } else {
|
|
|
+ setGrantPermissionsError("Invalid AWS account ID");
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ status={
|
|
|
+ grantPermissionsError && (
|
|
|
+ <Error message={grantPermissionsError} />
|
|
|
+ )
|
|
|
+ }
|
|
|
+ color="#1E2631"
|
|
|
+ withBorder
|
|
|
+ >
|
|
|
+ Grant permissions
|
|
|
+ </Button>
|
|
|
+ </AWSButtonContainer>
|
|
|
+ </>,
|
|
|
+ <>
|
|
|
+ <Text size={16}>4. Continue to the provision step.</Text>
|
|
|
+ <Spacer y={0.5} />
|
|
|
+ <Button
|
|
|
+ width={"200px"}
|
|
|
+ onClick={checkIfRoleExists}
|
|
|
+ status={
|
|
|
+ errorMessage ? (
|
|
|
+ <Error
|
|
|
+ message={errorMessage}
|
|
|
+ ctaText="Troubleshooting steps"
|
|
|
+ errorModalContents={
|
|
|
+ <>
|
|
|
+ <Text size={16}>Granting Porter access to AWS</Text>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Text color="helper">
|
|
|
+ Porter needs access to your AWS account in order to create infrastructure. You can grant Porter access to AWS by following these steps:
|
|
|
+ </Text>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Step number={1}>
|
|
|
+ <Link to="https://aws.amazon.com/resources/create-account/" target="_blank">
|
|
|
+ Create an AWS account
|
|
|
+ </Link>
|
|
|
+ <Spacer inline width="5px" />
|
|
|
+ if you don't already have one.
|
|
|
+ </Step>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Step number={2}>
|
|
|
+ Once you are logged in to your AWS account,
|
|
|
+ <Spacer inline width="5px" />
|
|
|
+ <Link to="https://console.aws.amazon.com/billing/home?region=us-east-1#/account" target="_blank">
|
|
|
+ copy your account ID
|
|
|
+ </Link>.
|
|
|
+ </Step>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Step number={3}>Fill in your account ID on Porter and select "Grant permissions".</Step>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Step number={4}>After being redirected to AWS, select "Create stack" on the AWS console.</Step>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Step number={5}>Wait until the stack status has changed from "CREATE_IN_PROGRESS" to "CREATE_COMPLETE".</Step>
|
|
|
+ <Spacer y={1} />
|
|
|
+ <Step number={6}>Return to Porter and select "Continue".</Step>
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ roleStatus
|
|
|
+ )
|
|
|
+ }
|
|
|
+ >
|
|
|
+ Continue
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ ].filter(step => step != null)
|
|
|
}
|
|
|
- >
|
|
|
- Continue
|
|
|
- </Button>
|
|
|
+ />
|
|
|
</>
|
|
|
);
|
|
|
}
|
|
|
@@ -247,9 +319,6 @@ const CloudFormationForm: React.FC<Props> = ({
|
|
|
</Text>
|
|
|
</Container>
|
|
|
<Spacer y={1} />
|
|
|
- <Text color="helper">
|
|
|
- Grant Porter permissions to create infrastructure in your AWS account.
|
|
|
- </Text>
|
|
|
{renderContent()}
|
|
|
</>
|
|
|
);
|
|
|
@@ -302,4 +371,9 @@ const BackButton = styled.div`
|
|
|
margin-right: 6px;
|
|
|
margin-left: -2px;
|
|
|
}
|
|
|
-`;
|
|
|
+`;
|
|
|
+
|
|
|
+const AWSButtonContainer = styled.div`
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ `;
|