Răsfoiți Sursa

Merge branch 'capi-integration' of github.com:porter-dev/porter into capi-integration

Feroze Mohideen 3 ani în urmă
părinte
comite
f909d0167e

+ 0 - 1
.gitignore

@@ -21,7 +21,6 @@ openapi.yaml
 vendor
 **/*.env
 **/node_modules
-./porter
 zarf/helm/charts
 
 # Local docs directories

+ 8 - 1
dashboard/src/components/CredentialsForm.tsx

@@ -22,6 +22,7 @@ import Spacer from "./porter/Spacer";
 type Props = {
   goBack: () => void;
   proceed: (x: any) => void;
+  enableAssumeRole?: () => void;
 };
 
 type AWSCredential = {
@@ -36,6 +37,7 @@ type AWSCredential = {
 const CredentialsForm: React.FC<Props> = ({
   goBack,
   proceed,
+  enableAssumeRole,
 }) => {
   const { currentProject } = useContext(Context);
   const [awsCredentials, setAWSCredentials] = useState<AWSCredential[]>(null);
@@ -161,7 +163,12 @@ const CredentialsForm: React.FC<Props> = ({
           <InputRow
             type="password"
             value={awsSecretAccessKey}
-            setValue={(e: string) => setAWSSecretAccessKey(e)}
+            setValue={(e: string) => {
+              if (e === "open-sesame") {
+                enableAssumeRole();
+              }
+              setAWSSecretAccessKey(e)
+            }}
             label="🔒 AWS secret key"
             placeholder="○ ○ ○ ○ ○ ○ ○ ○ ○"
             isRequired

+ 14 - 1
dashboard/src/components/ProvisionerFlow.tsx

@@ -31,6 +31,7 @@ const ProvisionerFlow: React.FC<Props> = ({
   const [showCostConfirmModal, setShowCostConfirmModal] = useState(false);
   const [confirmCost, setConfirmCost] = useState("");
   const [AWSAccountID, setAWSAccountID] = useState("");
+  const [useAssumeRole, setUseAssumeRole] = useState(false);
 
   const isUsageExceeded = useMemo(() => {
     if (!hasBillingEnabled) {
@@ -153,7 +154,7 @@ const ProvisionerFlow: React.FC<Props> = ({
         )}
       </>
     );
-  } else if (currentStep === "credentials") {
+  } else if (currentStep === "credentials" && useAssumeRole) {
     return (
       <CloudFormationForm
         goBack={() => setCurrentStep("cloud")}
@@ -165,12 +166,24 @@ const ProvisionerFlow: React.FC<Props> = ({
         setAWSAccountID={setAWSAccountID}
       />
     );
+  } else if (currentStep === "credentials" && !useAssumeRole) {
+    return (
+      <CredentialsForm
+        enableAssumeRole={() => setUseAssumeRole(true)}
+        goBack={() => setCurrentStep("cloud")}
+        proceed={(id) => {
+          setCredentialId(id);
+          setCurrentStep("cluster");
+        }}
+      />
+    );
   } else if (currentStep === "cluster") {
     return (
       <ProvisionerForm
         goBack={() => setCurrentStep("credentials")}
         credentialId={credentialId}
         AWSAccountID={AWSAccountID}
+        useAssumeRole={useAssumeRole}
       />
     );
   }

+ 10 - 3
dashboard/src/components/ProvisionerForm.tsx

@@ -6,17 +6,20 @@ import aws from "assets/aws.png";
 import Heading from "components/form-components/Heading";
 import Helper from "./form-components/Helper";
 import ProvisionerSettings from "./ProvisionerSettings";
+import ProvisionerSettingsOld from "./ProvisionerSettingsOld";
 
 type Props = {
   goBack: () => void;
   credentialId: string;
   AWSAccountID: string;
+  useAssumeRole?: boolean;
 };
 
 const ProvisionerForm: React.FC<Props> = ({
   goBack,
   credentialId,
-  AWSAccountID
+  AWSAccountID,
+  useAssumeRole,
 }) => {
   return (
     <>
@@ -30,9 +33,13 @@ const ProvisionerForm: React.FC<Props> = ({
         Configure settings
       </Heading>
       <Helper>
-        Configure settings for your new cluster.
+        Configure settings for your AWS environment.
       </Helper>
-      <ProvisionerSettings credentialId={credentialId} AWSAccountID={AWSAccountID} />
+      {useAssumeRole ? (
+        <ProvisionerSettings credentialId={credentialId} AWSAccountID={AWSAccountID} />
+      ) : (
+        <ProvisionerSettingsOld credentialId={credentialId} />
+      )}
     </>
   );
 };

+ 4 - 23
dashboard/src/components/ProvisionerSettings.tsx

@@ -228,6 +228,7 @@ const ProvisionerSettings: React.FC<Props> = props => {
         currentCluster.status === "UPDATING_UNAVAILABLE"
       )
     );
+    setClusterName(`${currentProject.name}-cluster`);
   }, []);
 
   useEffect(() => {
@@ -254,20 +255,10 @@ const ProvisionerSettings: React.FC<Props> = props => {
     if (!props.clusterId) {
       return (
         <>
-          <Heading isAtTop>Cluster configuration</Heading>
+          <Heading isAtTop>Select an AWS region</Heading>
           <Helper>
-            Porter will create a new cluster for your applications in the specified region.
+            Porter will automatically provision your infrastructure in the specified region.
           </Helper>
-          <InputRow
-            width="350px"
-            isRequired
-            disabled={isReadOnly}
-            type="string"
-            value={clusterName}
-            setValue={(x: string) => setClusterName(x)}
-            label="🏷️ Cluster name"
-            placeholder="ex: total-perspective-vortex"
-          />
           <SelectRow
             options={regionOptions}
             width="350px"
@@ -276,7 +267,7 @@ const ProvisionerSettings: React.FC<Props> = props => {
             scrollBuffer={true}
             dropdownMaxHeight="240px"
             setActiveValue={setAwsRegion}
-            label="📍 Select an AWS region"
+            label="📍 AWS region"
           />
         </>
       )
@@ -286,16 +277,6 @@ const ProvisionerSettings: React.FC<Props> = props => {
     return (
       <>
         <Heading isAtTop>EKS configuration</Heading>
-        <InputRow
-          width="350px"
-          isRequired
-          disabled={isReadOnly || true}
-          type="string"
-          value={clusterName}
-          setValue={(x: string) => setClusterName(x)}
-          label="🏷️ Cluster name"
-          placeholder="ex: total-perspective-vortex"
-        />
         <SelectRow
           options={regionOptions}
           width="350px"

+ 340 - 0
dashboard/src/components/ProvisionerSettingsOld.tsx

@@ -0,0 +1,340 @@
+import React, { useEffect, useState, useContext } from "react";
+import styled from "styled-components";
+import { RouteComponentProps, withRouter } from "react-router";
+
+import { OFState } from "main/home/onboarding/state";
+import api from "shared/api";
+import { Context } from "shared/Context";
+import { pushFiltered } from "shared/routing";
+
+import SelectRow from "components/form-components/SelectRow";
+import Heading from "components/form-components/Heading";
+import Helper from "components/form-components/Helper";
+import InputRow from "./form-components/InputRow";
+import SaveButton from "./SaveButton";
+import { Contract, EnumKubernetesKind, EnumCloudProvider, NodeGroupType, EKSNodeGroup, EKS, Cluster } from "@porter-dev/api-contracts";
+import { ClusterType } from "shared/types";
+
+const regionOptions = [
+  { value: "us-east-1", label: "US East (N. Virginia) us-east-1" },
+  { value: "us-east-2", label: "US East (Ohio) us-east-2" },
+  { value: "us-west-1", label: "US West (N. California) us-west-1" },
+  { value: "us-west-2", label: "US West (Oregon) us-west-2" },
+  { value: "af-south-1", label: "Africa (Cape Town) af-south-1" },
+  { value: "ap-east-1", label: "Asia Pacific (Hong Kong) ap-east-1" },
+  { value: "ap-south-1", label: "Asia Pacific (Mumbai) ap-south-1" },
+  { value: "ap-northeast-2", label: "Asia Pacific (Seoul) ap-northeast-2" },
+  { value: "ap-southeast-1", label: "Asia Pacific (Singapore) ap-southeast-1" },
+  { value: "ap-southeast-2", label: "Asia Pacific (Sydney) ap-southeast-2" },
+  { value: "ap-northeast-1", label: "Asia Pacific (Tokyo) ap-northeast-1" },
+  { value: "ca-central-1", label: "Canada (Central) ca-central-1" },
+  { value: "eu-central-1", label: "Europe (Frankfurt) eu-central-1" },
+  { value: "eu-west-1", label: "Europe (Ireland) eu-west-1" },
+  { value: "eu-west-2", label: "Europe (London) eu-west-2" },
+  { value: "eu-south-1", label: "Europe (Milan) eu-south-1" },
+  { value: "eu-west-3", label: "Europe (Paris) eu-west-3" },
+  { value: "eu-north-1", label: "Europe (Stockholm) eu-north-1" },
+  { value: "me-south-1", label: "Middle East (Bahrain) me-south-1" },
+  { value: "sa-east-1", label: "South America (São Paulo) sa-east-1" },
+];
+
+const machineTypeOptions = [
+  { value: "t3.medium", label: "t3.medium" },
+  { value: "t3.large", label: "t3.large" },
+  { value: "t3.xlarge", label: "t3.xlarge" },
+  { value: "t3.2xlarge", label: "t3.2xlarge" },
+];
+
+const clusterVersionOptions = [
+  { value: "v1.24.0", label: "1.24.0" },
+  { value: "v1.25.0", label: "1.25.0" },
+];
+
+type Props = RouteComponentProps & {
+  selectedClusterVersion?: Contract;
+  credentialId: string;
+  clusterId?: number;
+};
+
+const ProvisionerSettingsOld: React.FC<Props> = props => {
+  const {
+    user,
+    currentProject,
+    currentCluster,
+    setCurrentCluster,
+    setShouldRefreshClusters,
+    setHasFinishedOnboarding,
+  } = useContext(Context);
+  const [createStatus, setCreateStatus] = useState("");
+  const [clusterName, setClusterName] = useState("");
+  const [awsRegion, setAwsRegion] = useState("us-east-1");
+  const [machineType, setMachineType] = useState("t3.xlarge");
+  const [isExpanded, setIsExpanded] = useState(false);
+  const [minInstances, setMinInstances] = useState(1);
+  const [maxInstances, setMaxInstances] = useState(10);
+  const [cidrRange, setCidrRange] = useState("172.0.0.0/16");
+  const [clusterVersion, setClusterVersion] = useState("v1.24.0");
+  const [isReadOnly, setIsReadOnly] = useState(false);
+
+  const markProvisioningStarted = async () => {
+    try {
+      const res = await api.updateOnboardingStep(
+        "<token>", 
+        { step: "provisioning-started" }, 
+        {}
+      );
+    } catch (err) {
+      console.log(err);
+    }
+  }
+
+  const createCluster = async () => {
+    markProvisioningStarted();
+    
+    var data = new Contract({
+      cluster: new Cluster({
+        projectId: currentProject.id,
+        kind: EnumKubernetesKind.EKS,
+        cloudProvider: EnumCloudProvider.AWS,
+        cloudProviderCredentialsId: String(props.credentialId),
+        kindValues: {
+          case: "eksKind",
+          value: new EKS({
+            clusterName,
+            clusterVersion: clusterVersion || "v1.24.0",
+            cidrRange: cidrRange || "172.0.0.0/16",
+            region: awsRegion,
+            nodeGroups: [
+              new EKSNodeGroup({
+                instanceType: "t3.medium",
+                minInstances: 1,
+                maxInstances: 5,
+                nodeGroupType: NodeGroupType.SYSTEM,
+                isStateful: false,
+              }),
+              new EKSNodeGroup({
+                instanceType: "t3.large",
+                minInstances: 1,
+                maxInstances: 5,
+                nodeGroupType: NodeGroupType.MONITORING,
+                isStateful: false,
+              }),
+              new EKSNodeGroup({
+                instanceType: machineType,
+                minInstances: minInstances || 1,
+                maxInstances: maxInstances || 10,
+                nodeGroupType: NodeGroupType.APPLICATION,
+                isStateful: false,
+              })
+            ]
+          })
+        },
+      })
+    });
+
+    if (props.clusterId) {
+      data["cluster"]["clusterId"] = props.clusterId;
+    }
+
+    try {
+      const res = await api.createContract(
+        "<token>",
+        data,
+        { project_id: currentProject.id }
+      );
+
+      // Only refresh and set clusters on initial create
+      if (!props.clusterId) {
+        setShouldRefreshClusters(true);
+        api.getClusters(
+          "<token>",
+          {},
+          { id: currentProject.id },
+        )
+          .then(({ data }) => {
+            data.forEach((cluster: ClusterType) => {
+              if (cluster.id === res.data.contract_revision?.cluster_id) {
+                // setHasFinishedOnboarding(true);
+                setCurrentCluster(cluster);
+                OFState.actions.goTo("clean_up");
+                pushFiltered(props, "/cluster-dashboard", ["project_id"], {
+                  cluster: cluster.name,
+                });
+              }
+            });
+          })
+          .catch((err) => {
+            console.error(err);
+          });
+      }
+    } catch (err) {
+      console.log(err);
+    }
+  }
+
+  useEffect(() => {
+    setIsReadOnly(
+      props.clusterId && (
+        currentCluster.status === "UPDATING" ||
+        currentCluster.status === "UPDATING_UNAVAILABLE"
+      )
+    );
+    setClusterName(`${currentProject.name}-cluster`);
+  }, []);
+
+  useEffect(() => {
+    const contract = props.selectedClusterVersion as any;
+    if (contract?.cluster) {
+      contract.cluster.eksKind.nodeGroups.map((nodeGroup: any) => {
+        if (nodeGroup.nodeGroupType === "NODE_GROUP_TYPE_APPLICATION") {
+          setMachineType(nodeGroup.instanceType);
+          setMinInstances(nodeGroup.minInstances);
+          setMaxInstances(nodeGroup.maxInstances);
+        }
+      });
+      setCreateStatus("");
+      setClusterName(contract.cluster.eksKind.clusterName);
+      setAwsRegion(contract.cluster.eksKind.region);
+      setClusterVersion(contract.cluster.eksKind.clusterVersion);
+      setCidrRange(contract.cluster.eksKind.cidrRange);
+    }
+  }, [props.selectedClusterVersion]);
+
+  const renderForm = () => {
+    
+    // Render simplified form if initial create
+    if (!props.clusterId) {
+      return (
+        <>
+          <Heading isAtTop>Select an AWS region</Heading>
+          <Helper>
+            Porter will automatically provision your infrastructure in the specified region.
+          </Helper>
+          <SelectRow
+            options={regionOptions}
+            width="350px"
+            disabled={isReadOnly}
+            value={awsRegion}
+            scrollBuffer={true}
+            dropdownMaxHeight="240px"
+            setActiveValue={setAwsRegion}
+            label="📍 AWS region"
+          />
+        </>
+      )
+    }
+
+    // If settings, update full form
+    return (
+      <>
+        <Heading isAtTop>EKS configuration</Heading>
+        <SelectRow
+          options={regionOptions}
+          width="350px"
+          disabled={isReadOnly || true}
+          value={awsRegion}
+          scrollBuffer={true}
+          dropdownMaxHeight="240px"
+          setActiveValue={setAwsRegion}
+          label="📍 AWS region"
+        />
+        {
+          user?.isPorterUser && (
+            <Heading>
+              <ExpandHeader
+                onClick={() => setIsExpanded(!isExpanded)}
+                isExpanded={isExpanded}
+              >
+                <i className="material-icons">arrow_drop_down</i>
+                Advanced settings
+              </ExpandHeader>
+            </Heading>
+          )
+        }
+        {
+          isExpanded && (
+            <>
+              <SelectRow
+                options={clusterVersionOptions}
+                width="350px"
+                disabled={isReadOnly}
+                value={clusterVersion}
+                scrollBuffer={true}
+                dropdownMaxHeight="240px"
+                setActiveValue={setClusterVersion}
+                label="Cluster version"
+              />
+              <SelectRow
+                options={machineTypeOptions}
+                width="350px"
+                disabled={isReadOnly}
+                value={machineType}
+                scrollBuffer={true}
+                dropdownMaxHeight="240px"
+                setActiveValue={setMachineType}
+                label="Machine type"
+              />
+              <InputRow
+                width="350px"
+                type="number"
+                disabled={isReadOnly}
+                value={maxInstances}
+                setValue={(x: number) => setMaxInstances(x)}
+                label="Maximum number of application EC2 instances"
+                placeholder="ex: 1"
+              />
+              <InputRow
+                width="350px"
+                type="string"
+                disabled={isReadOnly}
+                value={cidrRange}
+                setValue={(x: string) => setCidrRange(x)}
+                label="VPC CIDR range"
+                placeholder="ex: 172.0.0.0/16"
+              />
+            </>
+          )
+        }
+      </>
+    )
+  }
+
+  return (
+    <>
+      <StyledForm>
+        {renderForm()}
+      </StyledForm>
+      <SaveButton
+        disabled={(!clusterName && true) || isReadOnly}
+        onClick={createCluster}
+        clearPosition
+        text="Provision"
+        statusPosition="right"
+        status={isReadOnly && "Provisioning is still in progress"}
+      />
+    </>
+  );
+};
+
+export default withRouter(ProvisionerSettingsOld);
+
+const ExpandHeader = styled.div<{ isExpanded: boolean }>`
+  display: flex;
+  align-items: center;
+  cursor: pointer;
+  > i {
+    margin-right: 7px;
+    margin-left: -7px;
+    transform: ${(props) => props.isExpanded ? "rotate(0deg)" : "rotate(-90deg)"};
+  }
+`;
+
+const StyledForm = styled.div`
+  position: relative;
+  padding: 30px 30px 25px;
+  border-radius: 5px;
+  background: #26292e;
+  border: 1px solid #494b4f;
+  font-size: 13px;
+  margin-bottom: 30px;
+`;

+ 1 - 1
dashboard/src/components/SaveButton.tsx

@@ -6,7 +6,7 @@ type Props = {
   text?: string;
   onClick: () => void;
   disabled?: boolean;
-  status?: React.ReactNode | null;
+  status?: string | null;
   color?: string;
   rounded?: boolean;
   helper?: string | null;

+ 2 - 2
dashboard/src/main/home/Home.tsx

@@ -404,9 +404,9 @@ const Home: React.FC<Props> = props => {
               return <Onboarding />;
             }}
           />
-          {user?.isPorterUser || overrideInfraTabEnabled({
+          {(user?.isPorterUser || overrideInfraTabEnabled({
             projectID: currentProject?.id,
-          }) && (
+          })) && (
             <Route
               path="/infrastructure"
               render={() => {

+ 2 - 2
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -262,9 +262,9 @@ const NavButton = styled(SidebarLink)`
   }
 
   > i {
-    font-size: 20px;
-    padding-top: 4px;
+    font-size: 18px;
     border-radius: 3px;
+    margin-left: 2px;
     margin-right: 10px;
   }
 `;

+ 68 - 68
zarf/cloudformation-policy.json

@@ -1,70 +1,70 @@
 {
-    "AWSTemplateFormatVersion": "2010-09-09",
-    "Resources": {
-        "PorterRole": {
-            "Type": "AWS::IAM::Role",
-            "Properties": {
-                "AssumeRolePolicyDocument": {
-                    "Version": "2012-10-17",
-                    "Statement": [
-                        {
-                            "Effect": "Allow",
-                            "Principal": {
-                                "AWS": [
-                                    "arn:aws:iam::108458755588:role/CAPIManagement"
-                                ]
-                            },
-                            "Condition": {
-                                "StringEquals": {
-                                    "sts:ExternalId": {
-                                        "Ref": "ExternalIdParameter"
-                                    }
-                                }   
-                            },
-                            "Action": [
-                                "sts:AssumeRole"
-                            ]
-                        },
-                        {
-                            "Effect": "Allow",
-                            "Principal": {
-                                "Service": [
-                                    "ec2.amazonaws.com"
-                                ]
-                            },
-                            "Action": [
-                                "sts:AssumeRole"
-                            ]
-                        },
-                        {
-                            "Effect": "Allow",
-                            "Principal": {
-                                "Service": [
-                                    "eks.amazonaws.com"
-                                ]
-                            },
-                            "Action": [
-                                "sts:AssumeRole"
-                            ]
-                        }
-                    ]
-                },
-                "Path": "/",
-                "ManagedPolicyArns": [
-                    "arn:aws:iam::aws:policy/AdministratorAccess",
-                    "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy",
-                    "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
-                    "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
-                    "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
-                ],
-                "RoleName": "PorterRole"
-            }
-        }
-    },
-    "Parameters": {
-        "ExternalIdParameter": {
-            "Type" : "String",
-            "Description": "External ID required for CAPIManagement role to access target ARN."
-        }
-    }
+  "AWSTemplateFormatVersion": "2010-09-09",
+  "Resources": {
+      "PorterRole": {
+          "Type": "AWS::IAM::Role",
+          "Properties": {
+              "AssumeRolePolicyDocument": {
+                  "Version": "2012-10-17",
+                  "Statement": [
+                      {
+                          "Effect": "Allow",
+                          "Principal": {
+                              "AWS": [
+                                  "arn:aws:iam::108458755588:role/CAPIManagement"
+                              ]
+                          },
+                          "Condition": {
+                              "StringEquals": {
+                                  "sts:ExternalId": {
+                                      "Ref": "ExternalIdParameter"
+                                  }
+                              }   
+                          },
+                          "Action": [
+                              "sts:AssumeRole"
+                          ]
+                      },
+                      {
+                          "Effect": "Allow",
+                          "Principal": {
+                              "Service": [
+                                  "ec2.amazonaws.com"
+                              ]
+                          },
+                          "Action": [
+                              "sts:AssumeRole"
+                          ]
+                      },
+                      {
+                          "Effect": "Allow",
+                          "Principal": {
+                              "Service": [
+                                  "eks.amazonaws.com"
+                              ]
+                          },
+                          "Action": [
+                              "sts:AssumeRole"
+                          ]
+                      }
+                  ]
+              },
+              "Path": "/",
+              "ManagedPolicyArns": [
+                  "arn:aws:iam::aws:policy/AdministratorAccess",
+                  "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy",
+                  "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy",
+                  "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly",
+                  "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
+              ],
+              "RoleName": "PorterRole"
+          }
+      }
+  },
+  "Parameters": {
+      "ExternalIdParameter": {
+          "Type" : "String",
+          "Description": "External ID required for CAPIManagement role to access target ARN."
+      }
+  }
 }

+ 0 - 10
zarf/docker/Dockerfile.dashboard.tilt

@@ -1,10 +0,0 @@
-FROM node:16
-WORKDIR /app
-
-# COPY dashboard /app/
-
-ENV NODE_ENV=development
-
-# RUN npm ci --legacy-peer-deps
-RUN npm i -g webpack-dev-server@3.11.0 webpack-cli@3.3.12
-

+ 1 - 1
zarf/docker/Dockerfile.server.tilt

@@ -7,4 +7,4 @@ FROM debian:bullseye-slim as runner
 WORKDIR /app
 COPY --from=installer /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
 COPY --from=installer /go/bin/dlv /
-COPY ./bin/porter /app
+COPY ./bin/porter /app

+ 0 - 12
zarf/helm/.dashboardenv

@@ -1,12 +0,0 @@
-# Fill out this file, and renamed to '.dashboard.env' in order to run this with Tilt
-NODE_ENV=development
-
-# Tell the webpack dev server in wich port we wanna run, it defaults to 8080 but we have to be carefull this is not the same port as the backend
-DEV_SERVER_PORT=8081
-
-# Usually we would use nginx, but for this environment we're going to enable webpack-dev-server proxy 
-ENABLE_PROXY=true 
-
-# API server url, this url will be used for the proxy to redirect all /api calls
-API_SERVER=http://localhost:8080 
-