Parcourir la source

Style hotfix (#3184)

* Provisioner Settitngs / Style Updates

* Test

* Test

* Test

* Fixed Provisioner Settings

* Tested Changes

* Tested Changes

* updating crash

* Tested Changes

* Tested Changes

* Tested Changes

* Tested Changes

* Tested Changes

---------

Co-authored-by: Stefan McShane <stefanmcshane@users.noreply.github.com>
sdess09 il y a 2 ans
Parent
commit
e8e8a455df

+ 7 - 7
dashboard/package-lock.json

@@ -12,7 +12,7 @@
         "@loadable/component": "^5.15.2",
         "@material-ui/core": "^4.11.3",
         "@material-ui/lab": "^4.0.0-alpha.61",
-        "@porter-dev/api-contracts": "^0.0.66",
+        "@porter-dev/api-contracts": "^0.0.68",
         "@sentry/react": "^6.13.2",
         "@sentry/tracing": "^6.13.2",
         "@tanstack/react-query": "^4.13.0",
@@ -2434,9 +2434,9 @@
       }
     },
     "node_modules/@porter-dev/api-contracts": {
-      "version": "0.0.66",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.66.tgz",
-      "integrity": "sha512-RkmHrTWt1Ta3BDpI6Ne6HSlxa8FLBayLwxm2vCfJZrYI2v0BvGoSy6kf7SCBVhMx+GbluWTlMgdAaQ6ymYo34w==",
+      "version": "0.0.68",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.68.tgz",
+      "integrity": "sha512-T4lsYOn8ympv5OrY6CkXDR3W9/J4UaOTrDjdnaVkMmQK8P0V71CvTRP6S66BCeYqxV7yDQ+3IMRTeDv5Ov/OtQ==",
       "dependencies": {
         "@bufbuild/protobuf": "^1.1.0"
       }
@@ -16671,9 +16671,9 @@
       "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A=="
     },
     "@porter-dev/api-contracts": {
-      "version": "0.0.66",
-      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.66.tgz",
-      "integrity": "sha512-RkmHrTWt1Ta3BDpI6Ne6HSlxa8FLBayLwxm2vCfJZrYI2v0BvGoSy6kf7SCBVhMx+GbluWTlMgdAaQ6ymYo34w==",
+      "version": "0.0.68",
+      "resolved": "https://registry.npmjs.org/@porter-dev/api-contracts/-/api-contracts-0.0.68.tgz",
+      "integrity": "sha512-T4lsYOn8ympv5OrY6CkXDR3W9/J4UaOTrDjdnaVkMmQK8P0V71CvTRP6S66BCeYqxV7yDQ+3IMRTeDv5Ov/OtQ==",
       "requires": {
         "@bufbuild/protobuf": "^1.1.0"
       }

+ 2 - 2
dashboard/package.json

@@ -7,7 +7,7 @@
     "@loadable/component": "^5.15.2",
     "@material-ui/core": "^4.11.3",
     "@material-ui/lab": "^4.0.0-alpha.61",
-    "@porter-dev/api-contracts": "^0.0.66",
+    "@porter-dev/api-contracts": "^0.0.68",
     "@sentry/react": "^6.13.2",
     "@sentry/tracing": "^6.13.2",
     "@tanstack/react-query": "^4.13.0",
@@ -135,4 +135,4 @@
     "webpack-cli": "^3.3.12",
     "webpack-dev-server": "^3.11.0"
   }
-}
+}

+ 368 - 35
dashboard/src/components/ProvisionerSettings.tsx

@@ -6,6 +6,7 @@ import { OFState } from "main/home/onboarding/state";
 import api from "shared/api";
 import { Context } from "shared/Context";
 import { pushFiltered } from "shared/routing";
+import info from "assets/info-outlined.svg";
 
 import SelectRow from "components/form-components/SelectRow";
 import Heading from "components/form-components/Heading";
@@ -29,7 +30,14 @@ import Spacer from "./porter/Spacer";
 import Step from "./porter/Step";
 import Link from "./porter/Link";
 import Text from "./porter/Text";
-
+import Select from "./porter/Select";
+import Input from "./porter/Input";
+import Checkbox from "./porter/Checkbox";
+import { Certificate } from "crypto";
+import Tooltip from "./porter/Tooltip";
+import Icon from "./porter/Icon";
+import { set } from "traverse";
+import { load } from "js-yaml";
 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" },
@@ -86,6 +94,14 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   const [clusterName, setClusterName] = useState("");
   const [awsRegion, setAwsRegion] = useState("us-east-1");
   const [machineType, setMachineType] = useState("t3.xlarge");
+  const [loadBalancerType, setLoadBalancerType] = useState(false);
+  const [wildCardDomain, setWildCardDomain] = useState("")
+  const [IPAllowList, setIPAllowList] = useState<string>("")
+  //const [accessS3Logs, setAccessS3Logs] = useState<boolean>(false)
+  const [wafV2Enabled, setWaf2Enabled] = useState<boolean>(false)
+  const [awsTags, setAwsTags] = useState<string>("")
+  const [wafV2ARN, setwafV2ARN] = useState("")
+  const [certificateARN, seCertificateARN] = useState("")
   const [isExpanded, setIsExpanded] = useState(false);
   const [minInstances, setMinInstances] = useState(1);
   const [maxInstances, setMaxInstances] = useState(10);
@@ -96,7 +112,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
   const [isReadOnly, setIsReadOnly] = useState(false);
   const [errorMessage, setErrorMessage] = useState<string>(undefined);
   const [isClicked, setIsClicked] = useState(false);
-
+  const [inputError, setInputError] = useState<boolean>(false);
   const markStepStarted = async (step: string) => {
     try {
       await api.updateOnboardingStep("<token>", { step }, {});
@@ -123,18 +139,132 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     }
     return undefined;
   };
+  const validateInput = (wildCardDomainer) => {
+    if (!wildCardDomainer) {
+      return "Required for ALB Load Balancer"
+    }
+    if (wildCardDomainer?.charAt(0) == "*") {
+      return "Wildcard domain cannot start with *"
+    }
+    return false;
+
+  };
+  function validateIPInput(IPAllowList) {
+    // This regular expression checks for an IP address with a subnet mask.
+    const regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2])$/;
+    if (!IPAllowList) {
+      return false
+    }
+    // Split the input string by comma and remove any empty elements
+    const ipAddresses = IPAllowList.split(",").filter(Boolean);
+    // Validate each IP address
+    for (let ip of ipAddresses) {
+      if (!regex.test(ip.trim())) {
+        // If any IP is invalid, return true (error)
+        return true;
+      }
+    }
+    // If all IPs are valid, return false (no error)
+    return false;
+  }
+  function validateTags(awsTags) {
+    // Regular expression t o check for a key-value pair format "key=value"
+    const regex = /^[a-zA-Z0-9]+=[a-zA-Z0-9]+$/;
+    // Split the input string by comma and remove any empty elements
+    const tags = awsTags.split(",").filter(Boolean);
+    // Validate each tag
+    for (let tag of tags) {
+      if (!regex.test(tag.trim())) {
+        // If any tag is invalid, return true (error)
+        return true;
+      }
+    }
+    // If all tags are valid, return false (no error)
+    return false;
+  }
+  function validateAllInputs() {
+
+    if (validateInput(wildCardDomain) != false) {
+      setInputError(true);
+      return true;
+    }
+    if (validateTags(awsTags)) {
+      setInputError(true);
+      return true;
+    }
+    if (validateIPInput(IPAllowList)) {
+      setInputError(true);
+      return true;
+    }
+
+  }
   const isDisabled = () => {
     return (
       !user.email.endsWith("porter.run") &&
       ((!clusterName && true) ||
         (isReadOnly && props.provisionerError === "") ||
         props.provisionerError === "" ||
-        currentCluster?.status === "UPDATING")
+        currentCluster?.status === "UPDATING" ||
+        isClicked || validateAllInputs())
     );
   };
+  function convertStringToTags(tagString) {
+    if (typeof tagString !== 'string' || tagString.trim() === '') {
+      return [];
+    }
+
+    // Split the input string by comma, then reduce the resulting array to an object
+    const tags = tagString.split(",").reduce((obj, item) => {
+      // Split each item by "=", 
+      const [key, value] = item.split("=");
+      // Add the key-value pair to the object
+      obj[key] = value;
+      return obj;
+    }, {});
+
+    return tags;
+  }
   const createCluster = async () => {
     setIsClicked(true);
 
+    let loadBalancerObj = new LoadBalancer({});
+    loadBalancerObj.loadBalancerType = LoadBalancerType.ALB;
+    loadBalancerObj.wildcardDomain = wildCardDomain;
+    //loadBalancerObj.enableS3AccessLogs = accessS3Logs;
+    if (loadBalancerType) {
+      loadBalancerObj.loadBalancerType = LoadBalancerType.ALB;
+      loadBalancerObj.wildcardDomain = wildCardDomain;
+
+      if (awsTags) {
+        loadBalancerObj.tags = convertStringToTags(awsTags);
+      }
+      if (IPAllowList) {
+        loadBalancerObj.allowlistIpRanges = IPAllowList
+      }
+      // if (accessS3Logs) {
+      //   loadBalancerObj.enableS3AccessLogs = accessS3Logs;
+      // }
+
+      // if (accessS3Logs) {
+      //   loadBalancerObj.enableS3AccessLogs = accessS3Logs;
+      // }
+      // else {
+      //   loadBalancerObj.enableS3AccessLogs = false;
+      // }
+      if (wafV2Enabled) {
+        loadBalancerObj.enableWafv2 = wafV2Enabled;
+      }
+      else {
+        loadBalancerObj.enableWafv2 = false;
+      }
+      if (wafV2ARN) {
+        loadBalancerObj.wafv2Arn = wafV2ARN;
+      }
+      if (certificateARN) {
+        loadBalancerObj.additionalCertificateArns = certificateARN.split(",");
+      }
+    }
+
     let data = new Contract({
       cluster: new Cluster({
         projectId: currentProject.id,
@@ -148,7 +278,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
             clusterVersion: clusterVersion || "v1.24.0",
             cidrRange: cidrRange || "10.78.0.0/16",
             region: awsRegion,
-            loadBalancer: loadBalancer,
+            loadBalancer: loadBalancerObj,
             nodeGroups: [
               new EKSNodeGroup({
                 instanceType: "t3.medium",
@@ -269,13 +399,15 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
   useEffect(() => {
     const contract = props.selectedClusterVersion as any;
+
     if (contract?.cluster) {
       let eksValues: EKS = contract.cluster?.eksKind as EKS;
+      console.log(eksValues);
       if (eksValues == null) {
         return
       }
       eksValues.nodeGroups.map((nodeGroup: EKSNodeGroup) => {
-        if (nodeGroup.nodeGroupType === NodeGroupType.APPLICATION) {
+        if (nodeGroup.nodeGroupType.toString() === "NODE_GROUP_TYPE_APPLICATION") {
           setMachineType(nodeGroup.instanceType);
           setMinInstances(nodeGroup.minInstances);
           setMaxInstances(nodeGroup.maxInstances);
@@ -286,15 +418,29 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
           setAdditionalNodePolicies(nodeGroup.additionalPolicies);
         }
       });
-
       setCreateStatus("");
       setClusterName(eksValues.clusterName);
       setAwsRegion(eksValues.region);
       setClusterVersion(eksValues.clusterVersion);
       setCidrRange(eksValues.cidrRange);
-      setLoadBalancer(eksValues.loadBalancer)
+      if (eksValues.loadBalancer != null) {
+        setIPAllowList(eksValues.loadBalancer.allowlistIpRanges)
+        setWildCardDomain(eksValues.loadBalancer.wildcardDomain)
+        //setAccessS3Logs(eksValues.loadBalancer.enableS3AccessLogs)
+
+        if (eksValues.loadBalancer.tags) {
+          setAwsTags(Object.entries(eksValues.loadBalancer.tags)
+            .map(([key, value]) => `${key}=${value}`)
+            .join(','));
+        }
+
+        setLoadBalancerType(eksValues.loadBalancer.loadBalancerType?.toString() === "LOAD_BALANCER_TYPE_ALB")
+        setwafV2ARN(eksValues.loadBalancer.wafv2Arn)
+        setWaf2Enabled(eksValues.loadBalancer.enableWafv2)
+      }
     }
-  }, [props.selectedClusterVersion]);
+
+  }, [isExpanded, props.selectedClusterVersion]);
 
   const renderForm = () => {
     // Render simplified form if initial create
@@ -349,27 +495,25 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
 
         {isExpanded && (
           <>
-            {user?.isPorterUser && (<SelectRow
+            {user?.isPorterUser && (<Select
               options={clusterVersionOptions}
               width="350px"
               disabled={isReadOnly}
               value={clusterVersion}
-              scrollBuffer={true}
-              dropdownMaxHeight="240px"
-              setActiveValue={setClusterVersion}
+              setValue={setClusterVersion}
               label="Cluster version"
             />)}
-            <SelectRow
+            <Spacer y={1} />
+            <Select
               options={machineTypeOptions}
               width="350px"
               disabled={isReadOnly}
               value={machineType}
-              scrollBuffer={true}
-              dropdownMaxHeight="240px"
-              setActiveValue={setMachineType}
+              setValue={setMachineType}
               label="Machine type"
             />
-            <InputRow
+            <Spacer y={1} />
+            <Input
               width="350px"
               type="number"
               disabled={isReadOnly}
@@ -377,18 +521,201 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
               setValue={(x: number) => setMaxInstances(x)}
               label="Maximum number of application EC2 instances"
               placeholder="ex: 1"
+
             />
-            <InputRow
+            <Spacer y={1} />
+            <Input
               width="350px"
               type="string"
-              disabled={isReadOnly}
+              disabled={true}
               value={cidrRange}
               setValue={(x: string) => setCidrRange(x)}
               label="VPC CIDR range"
               placeholder="ex: 10.78.0.0/16"
             />
+
+            {!currentProject.simplified_view_enabled &&
+              <>
+                <Spacer y={1} />
+                <Checkbox
+                  checked={loadBalancerType}
+                  disabled={isReadOnly}
+                  toggleChecked={() => {
+                    if (loadBalancerType) {
+                      setWildCardDomain("");
+                      setIPAllowList("");
+                      setwafV2ARN("");
+                      setAwsTags("");
+                      seCertificateARN("");
+                      setWaf2Enabled(false);
+                      //setAccessS3Logs(false);
+                    }
+
+                    setLoadBalancerType(!loadBalancerType)
+                  }}
+                  disabledTooltip={"Wait for provisioning to complete before editing this field."}
+                >
+                  <Text color="helper">Set Load Balancer Type to ALB</Text>
+                </Checkbox>
+                <Spacer y={1} />
+                {loadBalancerType && (<>
+
+                  <FlexCenter>
+                    <Input
+                      width="350px"
+                      disabled={isReadOnly}
+                      value={wildCardDomain}
+                      setValue={(x: string) => setWildCardDomain(x)}
+                      label="Wildcard domain"
+                      placeholder="user-2.porter.run"
+                    />
+                    <Wrapper>
+                      <Tooltip
+                        children={<Icon src={info} />}
+                        content={'The provided domain should have a wildcard subdomain pointed to the LoadBalancer address. Using testing.porter.run will create a certificate for testing.porter.run with a SAN *.testing.porter.run'}
+                        position="right"
+                      />
+                    </Wrapper>
+
+                  </FlexCenter>
+
+                  {validateInput(wildCardDomain) && <ErrorInLine>
+                    <i className="material-icons">error</i>
+                    {validateInput(wildCardDomain)}
+                  </ErrorInLine>}
+                  <Spacer y={1} />
+
+                  <FlexCenter>
+                    <>
+                      <Input
+                        width="350px"
+                        disabled={isReadOnly}
+                        value={IPAllowList}
+                        setValue={(x: string) => setIPAllowList(x)}
+                        label="IP Allow List"
+                        placeholder="160.72.72.58/32,160.72.72.59/32"
+                      />
+                      <Wrapper>
+                        <Tooltip
+                          children={<Icon src={info} />}
+                          content={'Each range should be a CIDR, including netmask such as 10.1.2.3/21. To use multiple values, they should be comma-separated with no spaces'}
+                          position="right"
+                        />
+                      </Wrapper>
+                    </>
+                  </FlexCenter>
+                  {validateIPInput(IPAllowList) && <ErrorInLine>
+                    <i className="material-icons">error</i>
+                    {"Needs to be Comma Separated Valid IP addresses"}
+                  </ErrorInLine>}
+                  <Spacer y={1} />
+
+                  <Input
+                    width="350px"
+                    disabled={isReadOnly}
+                    value={certificateARN}
+                    setValue={(x: string) => seCertificateARN(x)}
+                    label="Certificate ARN"
+                    placeholder="arn:aws:acm:REGION:ACCOUNT_ID:certificate/ACM_ID"
+                  />
+                  <Spacer y={1} />
+
+
+                  <FlexCenter>
+                    <>
+                      <Input
+                        width="350px"
+                        disabled={isReadOnly}
+                        value={awsTags}
+                        setValue={(x: string) => setAwsTags(x)}
+                        label="AWS Tags"
+                        placeholder="costcenter=1,environment=10,project=32"
+                      />
+                      <Wrapper>
+                        <Tooltip
+                          children={<Icon src={info} />}
+                          content={"Each tag should be of the format 'key=value'. To use multiple values, they should be comma-separated with no spaces."}
+                          position="right"
+                        />
+                      </Wrapper>
+                    </>
+                  </FlexCenter>
+                  {validateTags(awsTags) && <ErrorInLine>
+                    <i className="material-icons">error</i>
+                    {"Needs to be Comma Separated Valid Tags"}
+                  </ErrorInLine>}
+
+                  <Spacer y={1} />
+                  {/* <Checkbox
+                    checked={accessS3Logs}
+                    disabled={isReadOnly}
+                    toggleChecked={() => {
+                      {
+                        console.log(!accessS3Logs)
+                      }
+                      setAccessS3Logs(!accessS3Logs)
+                    }}
+                    disabledTooltip={"Wait for provisioning to complete before editing this field."}
+                  >
+                    <Text color="helper">Access Logs to S3</Text>
+                  </Checkbox> */}
+                  <Spacer y={1} />
+                  <Checkbox
+                    checked={wafV2Enabled}
+                    disabled={isReadOnly}
+                    toggleChecked={() => {
+                      if (wafV2Enabled) {
+                        setwafV2ARN("");
+                      }
+                      setWaf2Enabled(!wafV2Enabled);
+                    }}
+                    disabledTooltip={"Wait for provisioning to complete before editing this field."}
+                  >
+                    <Text color="helper">WAFv2 Enabled</Text>
+                  </Checkbox>
+                  {wafV2Enabled && <>
+                    <Spacer y={1} />
+
+
+                    <FlexCenter>
+                      <>
+                        <Input
+                          width="500px"
+                          type="string"
+                          label="WAFv2 ARN"
+                          disabled={isReadOnly}
+                          value={wafV2ARN}
+                          setValue={(x: string) => setwafV2ARN(x)}
+                          placeholder="arn:aws:wafv2:REGION:ACCOUNT_ID:regional/webacl/ACL_NAME/RULE_ID"
+
+                        />
+                        <Wrapper>
+                          <Tooltip
+                            children={<Icon src={info} />}
+                            content={'Only Regional WAFv2 is supported. To find your ARN, navigate to the WAF console, click the Gear icon in the top right, and toggle "ARN" to on'}
+                            position="right"
+                          />
+                        </Wrapper>
+                      </>
+                    </FlexCenter>
+
+                    {(wafV2ARN == undefined || wafV2ARN?.length == 0) &&
+
+                      <ErrorInLine>
+                        <i className="material-icons">error</i>
+                        {"Required if WafV2 is enabled"}
+                      </ErrorInLine>
+
+                    }
+                  </>}
+                  <Spacer y={1} />
+                </>
+                )}
+              </>
+            }
           </>
-        )}
+        )
+        }
       </>
     );
   };
@@ -397,11 +724,7 @@ const ProvisionerSettings: React.FC<Props> = (props) => {
     <>
       <StyledForm>{renderForm()}</StyledForm>
       <Button
-        disabled={
-          isClicked ||
-          isDisabled() ||
-          getStatus() == "Provisioning is still in progress..."
-        }
+        disabled={isDisabled()}
         onClick={createCluster}
         status={getStatus()}
       >
@@ -435,16 +758,26 @@ const StyledForm = styled.div`
   margin-bottom: 30px;
 `;
 
-const ErrorContainer = styled.div`
-  position: relative;
-  margin-top: 20px;
-  padding: 30px 30px 25px;
-  border-radius: 5px;
-  background: #26292e;
-  border: 1px solid #494b4f;
+const FlexCenter = styled.div`
+  display: flex;
+  align-items: center  ;
+  gap: 3px;
+`
+const Wrapper = styled.div`
+  transform: translateY(+13px);
+`;
+
+const ErrorInLine = styled.div`
+  display: flex;
+  align-items: center;
   font-size: 13px;
-  margin-bottom: 30px;
-  color: red;
+  color: #ff3b62;
+  margin-top: 10px;
+
+  > i {
+    font-size: 18px;
+    margin-right: 5px;
+  }
 `;
 
 const AWS_LOGIN_ERROR_MESSAGE =
@@ -733,4 +1066,4 @@ const errorMessageToModal = (errorMessage: string) => {
     default:
       return null;
   }
-};
+};

+ 4 - 2
dashboard/src/main/home/app-dashboard/expanded-app/ChangeLogModal.tsx

@@ -217,11 +217,12 @@ const ChangeLogModal: React.FC<Props> = ({
     <>
       <Modal closeModal={() => setModalVisible(false)} width={"800px"}>
         {revertModal ? <Text size={18}> Revert to version no. {revision} </Text> : <Text size={18}>Changes for version no. {revision}</Text>}
+        <Spacer y={1} />
         {loading ? (
           <Loading /> // <-- Render loading state
         ) : (
           revertModal ? (<>
-            <div style={{ maxHeight: "400px", overflowY: "auto" }}>
+            <div style={{ maxHeight: "400px", overflowY: "auto", borderRadius: "8px" }}>
               <DiffViewer
                 leftTitle={revertModal ? `Current Revision` : `Revision No. ${revision - 1}`}
                 rightTitle={`Revision No. ${revision}`}
@@ -262,7 +263,8 @@ const ChangeLogModal: React.FC<Props> = ({
                 </div>
               )}
 
-              {changesConfig && (<><Spacer y={.3} />
+              {changesConfig && (<>
+                <Spacer y={1} />
                 <div style={{ display: "flex" }}>
 
                   <Checkbox

+ 1 - 1
go.mod

@@ -76,7 +76,7 @@ require (
 	github.com/honeycombio/otel-launcher-go v0.2.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.0.66
+	github.com/porter-dev/api-contracts v0.0.68
 	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

+ 0 - 2
go.sum

@@ -1490,8 +1490,6 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/polyfloyd/go-errorlint v0.0.0-20210722154253-910bb7978349/go.mod h1:wi9BfjxjF/bwiZ701TzmfKu6UKC357IOAtNr0Td0Lvw=
-github.com/porter-dev/api-contracts v0.0.66 h1:sqK6Xu6QtC6cvhXoI1NbsdaruQK23yw07llbn14CWbc=
-github.com/porter-dev/api-contracts v0.0.66/go.mod h1:qr2L58mJLr5DUGV5OPw3REiSrQvJq6TgkKyEWP95dyU=
 github.com/porter-dev/switchboard v0.0.0-20221019155755-67ff2bf04935 h1:hfb3nt3AJXIBbevu6ARTg9SdOkMP6WLbKBiG5hT5rcc=
 github.com/porter-dev/switchboard v0.0.0-20221019155755-67ff2bf04935/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=