Feroze Mohideen 2 лет назад
Родитель
Сommit
61e0b62a53

+ 3 - 0
api/server/handlers/datastore/update.go

@@ -187,6 +187,9 @@ func (h *UpdateDatastoreHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 				MasterUserPasswordLiteral: pointer.String(datastoreValues.Config.MasterUserPassword),
 			},
 		}
+	case "NEON":
+		datastoreProto.Kind = porterv1.EnumDatastoreKind_ENUM_DATASTORE_KIND_NEON
+		datastoreProto.KindValues = &porterv1.ManagedDatastore_NeonKind{}
 	default:
 		err = telemetry.Error(ctx, span, nil, "invalid datastore type")
 		h.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusBadRequest))

+ 1 - 0
api/server/handlers/oauth_callback/neon.go

@@ -93,6 +93,7 @@ func (p *OAuthCallbackNeonHandler) ServeHTTP(w http.ResponseWriter, r *http.Requ
 
 	oauthInt := integrations.NeonIntegration{
 		SharedOAuthModel: integrations.SharedOAuthModel{
+			ClientID:     []byte(p.Config().NeonConf.ClientID),
 			AccessToken:  []byte(token.AccessToken),
 			RefreshToken: []byte(token.RefreshToken),
 			Expiry:       token.Expiry,

+ 20 - 0
dashboard/src/assets/neon.svg

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="256px" height="256px" viewBox="0 0 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
+    <title>Neon</title>
+    <defs>
+        <linearGradient x1="100%" y1="100%" x2="12.0694444%" y2="0%" id="linearGradient-1">
+            <stop stop-color="#62F755" offset="0%"></stop>
+            <stop stop-color="#8FF986" stop-opacity="0" offset="100%"></stop>
+        </linearGradient>
+        <linearGradient x1="100%" y1="100%" x2="40.6027778%" y2="76.8972222%" id="linearGradient-2">
+            <stop stop-color="#000000" stop-opacity="0.9" offset="0%"></stop>
+            <stop stop-color="#1A1A1A" stop-opacity="0" offset="100%"></stop>
+        </linearGradient>
+    </defs>
+    <g>
+        <path d="M0,44.1386667 C0,19.7615542 19.7615542,0 44.1386667,0 L211.861333,0 C236.238446,0 256,19.7615542 256,44.1386667 L256,186.787556 C256,212.003556 224.085333,222.947556 208.611556,203.043556 L160.220444,140.792889 L160.220444,216.277333 C160.220444,238.215556 142.436001,256 120.497778,256 L44.1386667,256 C19.7615542,256 0,236.238446 0,211.861333 L0,44.1386667 Z M44.1386667,35.3137778 C39.2604444,35.3137778 35.3137778,39.2604444 35.3137778,44.1315556 L35.3137778,211.861333 C35.3137778,216.739556 39.2604444,220.693333 44.1315556,220.693333 L121.820444,220.693333 C124.259556,220.693333 124.906667,218.716444 124.906667,216.277333 L124.906667,115.057778 C124.906667,89.8346667 156.821333,78.8906667 172.302222,98.8017778 L220.693333,161.045333 L220.693333,44.1386667 C220.693333,39.2604444 221.148444,35.3137778 216.277333,35.3137778 L44.1386667,35.3137778 Z" fill="#00E0D9"></path>
+        <path d="M0,44.1386667 C0,19.7615542 19.7615542,0 44.1386667,0 L211.861333,0 C236.238446,0 256,19.7615542 256,44.1386667 L256,186.787556 C256,212.003556 224.085333,222.947556 208.611556,203.043556 L160.220444,140.792889 L160.220444,216.277333 C160.220444,238.215556 142.436001,256 120.497778,256 L44.1386667,256 C19.7615542,256 0,236.238446 0,211.861333 L0,44.1386667 Z M44.1386667,35.3137778 C39.2604444,35.3137778 35.3137778,39.2604444 35.3137778,44.1315556 L35.3137778,211.861333 C35.3137778,216.739556 39.2604444,220.693333 44.1315556,220.693333 L121.820444,220.693333 C124.259556,220.693333 124.906667,218.716444 124.906667,216.277333 L124.906667,115.057778 C124.906667,89.8346667 156.821333,78.8906667 172.302222,98.8017778 L220.693333,161.045333 L220.693333,44.1386667 C220.693333,39.2604444 221.148444,35.3137778 216.277333,35.3137778 L44.1386667,35.3137778 Z" fill="url(#linearGradient-1)"></path>
+        <path d="M0,44.1386667 C0,19.7615542 19.7615542,0 44.1386667,0 L211.861333,0 C236.238446,0 256,19.7615542 256,44.1386667 L256,186.787556 C256,212.003556 224.085333,222.947556 208.611556,203.043556 L160.220444,140.792889 L160.220444,216.277333 C160.220444,238.215556 142.436001,256 120.497778,256 L44.1386667,256 C19.7615542,256 0,236.238446 0,211.861333 L0,44.1386667 Z M44.1386667,35.3137778 C39.2604444,35.3137778 35.3137778,39.2604444 35.3137778,44.1315556 L35.3137778,211.861333 C35.3137778,216.739556 39.2604444,220.693333 44.1315556,220.693333 L121.820444,220.693333 C124.259556,220.693333 124.906667,218.716444 124.906667,216.277333 L124.906667,115.057778 C124.906667,89.8346667 156.821333,78.8906667 172.302222,98.8017778 L220.693333,161.045333 L220.693333,44.1386667 C220.693333,39.2604444 221.148444,35.3137778 216.277333,35.3137778 L44.1386667,35.3137778 Z" fill-opacity="0.4" fill="url(#linearGradient-2)"></path>
+        <path d="M211.861333,0 C236.238446,0 256,19.7615542 256,44.1386667 L256,186.787556 C256,212.003556 224.085333,222.947556 208.611556,203.043556 L160.220444,140.792889 L160.220444,216.277333 C160.220444,238.215556 142.436001,256 120.497778,256 C121.667088,256 122.788506,255.535493 123.615333,254.708666 C124.44216,253.881839 124.906667,252.760421 124.906667,251.591111 L124.906667,115.057778 C124.906667,89.8346667 156.821333,78.8906667 172.302222,98.8017778 L220.693333,161.045333 L220.693333,8.82488889 C220.693333,3.95377778 216.739556,0 211.861333,0 Z" fill="#63F655"></path>
+    </g>
+</svg>

+ 10 - 1
dashboard/src/lib/databases/types.ts

@@ -36,6 +36,7 @@ const datastoreTypeValidator = z.enum([
   "ELASTICACHE",
   "MANAGED_REDIS",
   "MANAGED_POSTGRES",
+  "NEON",
 ]);
 const datastoreEngineValidator = z.enum([
   "UNKNOWN",
@@ -109,6 +110,10 @@ export const DATASTORE_TYPE_MANAGED_REDIS: DatastoreType = {
   name: "MANAGED_REDIS" as const,
   displayName: "Managed Redis",
 };
+export const DATASTORE_TYPE_NEON: DatastoreType = {
+  name: "NEON" as const,
+  displayName: "Neon",
+};
 
 export type DatastoreState = {
   state: z.infer<typeof datastoreValidator>["status"];
@@ -170,7 +175,6 @@ export type DatastoreTemplate = {
   disabled: boolean;
   instanceTiers: ResourceOption[];
   supportedEngineVersions: EngineVersion[];
-  formTitle: string;
   creationStateProgression: DatastoreState[];
   deletionStateProgression: DatastoreState[];
 };
@@ -312,6 +316,10 @@ const managedRedisConfigValidator = z.object({
     .default(1),
 });
 
+const neonValidator = z.object({
+  type: z.literal("neon"),
+});
+
 export const dbFormValidator = z.object({
   name: z
     .string()
@@ -332,6 +340,7 @@ export const dbFormValidator = z.object({
     elasticacheRedisConfigValidator,
     managedRedisConfigValidator,
     managedPostgresConfigValidator,
+    neonValidator,
   ]),
   clusterId: z.number(),
 });

+ 14 - 1
dashboard/src/lib/hooks/useDatastore.ts

@@ -21,7 +21,7 @@ type DatastoreHook = {
 };
 type CreateDatastoreInput = {
   name: string;
-  type: "RDS" | "ELASTICACHE" | "MANAGED-POSTGRES" | "MANAGED-REDIS";
+  type: "RDS" | "ELASTICACHE" | "MANAGED-POSTGRES" | "MANAGED-REDIS" | "NEON";
   engine: "POSTGRES" | "AURORA-POSTGRES" | "REDIS";
   values: object;
 };
@@ -134,6 +134,19 @@ const clientDbToCreateInput = (values: DbFormData): CreateDatastoreInput => {
         };
       }
     )
+    .with(
+      { config: { type: "neon" } },
+      (values): CreateDatastoreInput => ({
+        name: values.name,
+        values: {
+          config: {
+            name: values.name,
+          },
+        },
+        type: "NEON",
+        engine: "POSTGRES",
+      })
+    )
     .exhaustive();
 };
 

+ 1 - 2
dashboard/src/main/home/database-dashboard/DatabaseHeader.tsx

@@ -11,7 +11,6 @@ import Text from "components/porter/Text";
 import { readableDate } from "shared/string_utils";
 
 import { useDatastoreContext } from "./DatabaseContextProvider";
-import { getDatastoreIcon } from "./icons";
 import EngineTag from "./tags/EngineTag";
 
 const DatabaseHeader: React.FC = () => {
@@ -22,7 +21,7 @@ const DatabaseHeader: React.FC = () => {
       <Container row style={{ width: "100%" }}>
         <Container row spaced style={{ width: "100%" }}>
           <Container row>
-            <Icon src={getDatastoreIcon(datastore.type)} height={"25px"} />
+            <Icon src={datastore.template.icon} height={"25px"} />
             <Spacer inline x={1} />
             <Text size={21}>{datastore.name}</Text>
             <Spacer inline x={1} />

+ 1 - 0
dashboard/src/main/home/database-dashboard/DatastoreFormContextProvider.tsx

@@ -96,6 +96,7 @@ const DatastoreFormContextProvider: React.FC<
       await createDatastore(data);
       history.push(`/datastores/${data.name}`);
     } catch (err) {
+      console.error(err);
       const errorMessage = getErrorMessageFromNetworkCall(
         err,
         "Datastore creation"

+ 40 - 5
dashboard/src/main/home/database-dashboard/constants.ts

@@ -13,6 +13,7 @@ import {
   DATASTORE_TYPE_ELASTICACHE,
   DATASTORE_TYPE_MANAGED_POSTGRES,
   DATASTORE_TYPE_MANAGED_REDIS,
+  DATASTORE_TYPE_NEON,
   DATASTORE_TYPE_RDS,
   type DatastoreEngine,
   type DatastoreTemplate,
@@ -21,6 +22,7 @@ import {
 import awsRDS from "assets/amazon-rds.png";
 import awsElastiCache from "assets/aws-elasticache.png";
 import infra from "assets/cluster.svg";
+import neon from "assets/neon.svg";
 import postgresql from "assets/postgresql.svg";
 import redis from "assets/redis.svg";
 
@@ -103,7 +105,6 @@ export const DATASTORE_TEMPLATE_AWS_RDS: DatastoreTemplate = Object.freeze({
       storageGigabytes: 2048,
     },
   ],
-  formTitle: "Create an RDS PostgreSQL instance",
   creationStateProgression: [
     DATASTORE_STATE_CREATING,
     DATASTORE_STATE_CONFIGURING_LOG_EXPORTS,
@@ -145,7 +146,6 @@ export const DATASTORE_TEMPLATE_AWS_AURORA: DatastoreTemplate = Object.freeze({
       storageGigabytes: 256,
     },
   ],
-  formTitle: "Create an Aurora PostgreSQL instance",
   creationStateProgression: [
     DATASTORE_STATE_CREATING,
     DATASTORE_STATE_AVAILABLE,
@@ -205,7 +205,6 @@ export const DATASTORE_TEMPLATE_AWS_ELASTICACHE: DatastoreTemplate =
         storageGigabytes: 0,
       },
     ],
-    formTitle: "Create an ElastiCache Redis instance",
     creationStateProgression: [
       DATASTORE_STATE_CREATING,
       DATASTORE_STATE_MODIFYING,
@@ -246,7 +245,6 @@ export const DATASTORE_TEMPLATE_MANAGED_REDIS: DatastoreTemplate =
         storageGigabytes: 2,
       },
     ],
-    formTitle: "Create an ElastiCache Memcached instance",
     creationStateProgression: [
       DATASTORE_STATE_CREATING,
       DATASTORE_STATE_AVAILABLE,
@@ -284,7 +282,6 @@ export const DATASTORE_TEMPLATE_MANAGED_POSTGRES: DatastoreTemplate =
         storageGigabytes: 2,
       },
     ],
-    formTitle: "Create a managed PostgreSQL instance",
     creationStateProgression: [
       DATASTORE_STATE_CREATING,
       DATASTORE_STATE_AVAILABLE,
@@ -296,10 +293,48 @@ export const DATASTORE_TEMPLATE_MANAGED_POSTGRES: DatastoreTemplate =
     ],
   });
 
+export const DATASTORE_TEMPLATE_NEON: DatastoreTemplate = Object.freeze({
+  name: "Neon",
+  displayName: "Neon",
+  highLevelType: DATASTORE_ENGINE_POSTGRES,
+  type: DATASTORE_TYPE_NEON,
+  engine: DATASTORE_ENGINE_POSTGRES,
+  supportedEngineVersions: [],
+  icon: neon as string,
+  description: "A postgresql instance hosted by Neon.",
+  disabled: true,
+  instanceTiers: [
+    {
+      tier: "db.t4g.micro" as const,
+      label: "Micro",
+      cpuCores: 1,
+      ramGigabytes: 1,
+      storageGigabytes: 1,
+    },
+    {
+      tier: "db.t4g.small" as const,
+      label: "Small",
+      cpuCores: 2,
+      ramGigabytes: 2,
+      storageGigabytes: 2,
+    },
+  ],
+  creationStateProgression: [
+    DATASTORE_STATE_CREATING,
+    DATASTORE_STATE_AVAILABLE,
+  ],
+  deletionStateProgression: [
+    DATASTORE_STATE_AWAITING_DELETION,
+    DATASTORE_STATE_DELETING_RECORD,
+    DATASTORE_STATE_DELETED,
+  ],
+});
+
 export const SUPPORTED_DATASTORE_TEMPLATES: DatastoreTemplate[] = [
   DATASTORE_TEMPLATE_AWS_RDS,
   DATASTORE_TEMPLATE_AWS_AURORA,
   DATASTORE_TEMPLATE_AWS_ELASTICACHE,
   DATASTORE_TEMPLATE_MANAGED_POSTGRES,
   DATASTORE_TEMPLATE_MANAGED_REDIS,
+  DATASTORE_TEMPLATE_NEON,
 ];

+ 18 - 0
dashboard/src/main/home/database-dashboard/forms/DatastoreForm.tsx

@@ -34,6 +34,7 @@ import {
   DATASTORE_TEMPLATE_AWS_RDS,
   DATASTORE_TEMPLATE_MANAGED_POSTGRES,
   DATASTORE_TEMPLATE_MANAGED_REDIS,
+  DATASTORE_TEMPLATE_NEON,
   SUPPORTED_DATASTORE_TEMPLATES,
 } from "../constants";
 import { useDatastoreFormContext } from "../DatastoreFormContextProvider";
@@ -104,6 +105,7 @@ const DatastoreForm: React.FC = () => {
             DATASTORE_TEMPLATE_AWS_RDS,
             DATASTORE_TEMPLATE_AWS_AURORA,
             DATASTORE_TEMPLATE_AWS_ELASTICACHE,
+            DATASTORE_TEMPLATE_NEON,
           ]
         : [
             DATASTORE_TEMPLATE_MANAGED_POSTGRES,
@@ -286,6 +288,14 @@ const DatastoreForm: React.FC = () => {
                             setValue("config.masterUsername", "postgres");
                             setValue("config.engineVersion", "15.4");
                           }
+                        )
+                        .with(
+                          {
+                            name: DATASTORE_TEMPLATE_NEON.name,
+                          },
+                          () => {
+                            setValue("config.type", "neon");
+                          }
                         );
                       setValue("config.instanceClass", "unspecified");
                       setValue("config.masterUserPassword", uuidv4());
@@ -370,6 +380,14 @@ const DatastoreForm: React.FC = () => {
                             username: "",
                             database_name: "",
                           }
+                        : template === DATASTORE_TEMPLATE_NEON
+                        ? {
+                            host: "(determined after creation)",
+                            port: 5432,
+                            password: "(determined after creation)",
+                            username: "(determined after creation)",
+                            database_name: "(determined after creation)",
+                          }
                         : {
                             host: "(determined after creation)",
                             port: 5432,

+ 52 - 43
dashboard/src/shared/api.tsx

@@ -386,8 +386,9 @@ const getFeedEvents = baseApi<
   }
 >("GET", (pathParams) => {
   const { project_id, cluster_id, stack_name, page } = pathParams;
-  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${page || 1
-    }`;
+  return `/api/projects/${project_id}/clusters/${cluster_id}/applications/${stack_name}/events?page=${
+    page || 1
+  }`;
 });
 
 const createEnvironment = baseApi<
@@ -875,9 +876,11 @@ const detectBuildpack = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/buildpack/detect`;
 });
 
 const detectGitlabBuildpack = baseApi<
@@ -908,9 +911,11 @@ const getBranchContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/contents`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/contents`;
 });
 
 const getProcfileContents = baseApi<
@@ -926,9 +931,11 @@ const getProcfileContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/procfile`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/procfile`;
 });
 
 const getPorterYamlContents = baseApi<
@@ -944,9 +951,11 @@ const getPorterYamlContents = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/porteryaml`;
 });
 
 const parsePorterYaml = baseApi<
@@ -1006,30 +1015,32 @@ const getBranchHead = baseApi<
     branch: string;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.project_id}/gitrepos/${pathParams.git_repo_id
-    }/repos/${pathParams.kind}/${pathParams.owner}/${pathParams.name
-    }/${encodeURIComponent(pathParams.branch)}/head`;
+  return `/api/projects/${pathParams.project_id}/gitrepos/${
+    pathParams.git_repo_id
+  }/repos/${pathParams.kind}/${pathParams.owner}/${
+    pathParams.name
+  }/${encodeURIComponent(pathParams.branch)}/head`;
 });
 
 const createApp = baseApi<
   | {
-    name: string;
-    deployment_target_id: string;
-    type: "github";
-    git_repo_id: number;
-    git_branch: string;
-    git_repo_name: string;
-    porter_yaml_path: string;
-  }
+      name: string;
+      deployment_target_id: string;
+      type: "github";
+      git_repo_id: number;
+      git_branch: string;
+      git_repo_name: string;
+      porter_yaml_path: string;
+    }
   | {
-    name: string;
-    deployment_target_id: string;
-    type: "docker-registry";
-    image: {
-      repository: string;
-      tag: string;
-    };
-  },
+      name: string;
+      deployment_target_id: string;
+      type: "docker-registry";
+      image: {
+        repository: string;
+        tag: string;
+      };
+    },
   {
     project_id: number;
     cluster_id: number;
@@ -2298,9 +2309,11 @@ const getEnvGroup = baseApi<
     version?: number;
   }
 >("GET", (pathParams) => {
-  return `/api/projects/${pathParams.id}/clusters/${pathParams.cluster_id
-    }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${pathParams.version ? "&version=" + pathParams.version : ""
-    }`;
+  return `/api/projects/${pathParams.id}/clusters/${
+    pathParams.cluster_id
+  }/namespaces/${pathParams.namespace}/envgroup?name=${pathParams.name}${
+    pathParams.version ? "&version=" + pathParams.version : ""
+  }`;
 });
 
 const getConfigMap = baseApi<
@@ -2869,7 +2882,7 @@ const getDatastoreCredential = baseApi<
 const updateDatastore = baseApi<
   {
     name: string;
-    type: "RDS" | "ELASTICACHE" | "MANAGED-POSTGRES" | "MANAGED-REDIS";
+    type: "RDS" | "ELASTICACHE" | "MANAGED-POSTGRES" | "MANAGED-REDIS" | "NEON";
     engine: "POSTGRES" | "AURORA-POSTGRES" | "REDIS";
 
     values: any;
@@ -3582,13 +3595,9 @@ const getReferralDetails = baseApi<
   {
     project_id?: number;
   }
->(
-  "GET",
-  ({ project_id }) =>
-    `/api/projects/${project_id}/referrals/details`
-);
+>("GET", ({ project_id }) => `/api/projects/${project_id}/referrals/details`);
 
-const getGithubStatus = baseApi<{}, {}>("GET", ({ }) => `/api/status/github`);
+const getGithubStatus = baseApi<{}, {}>("GET", ({}) => `/api/status/github`);
 
 const createSecretAndOpenGitHubPullRequest = baseApi<
   {

+ 3 - 0
go.mod

@@ -2,6 +2,9 @@ module github.com/porter-dev/porter
 
 go 1.20
 
+// replace api-contracts with local path
+replace github.com/porter-dev/api-contracts => ../api-contracts
+
 require (
 	cloud.google.com/go v0.110.0 // indirect
 	github.com/AlecAivazis/survey/v2 v2.2.9