|
@@ -1,20 +1,43 @@
|
|
|
|
|
+import { DomainType } from "@porter-dev/api-contracts";
|
|
|
import {
|
|
import {
|
|
|
Addon,
|
|
Addon,
|
|
|
AddonType,
|
|
AddonType,
|
|
|
|
|
+ Datadog,
|
|
|
|
|
+ Metabase,
|
|
|
|
|
+ Mezmo,
|
|
|
|
|
+ Newrelic,
|
|
|
|
|
+ Postgres,
|
|
|
|
|
+ Redis,
|
|
|
|
|
+ Tailscale,
|
|
|
} from "@porter-dev/api-contracts/src/porter/v1/addons_pb";
|
|
} from "@porter-dev/api-contracts/src/porter/v1/addons_pb";
|
|
|
import { match } from "ts-pattern";
|
|
import { match } from "ts-pattern";
|
|
|
import { z } from "zod";
|
|
import { z } from "zod";
|
|
|
|
|
|
|
|
import { serviceStringValidator } from "lib/porter-apps/values";
|
|
import { serviceStringValidator } from "lib/porter-apps/values";
|
|
|
|
|
|
|
|
|
|
+import { datadogConfigValidator } from "./datadog";
|
|
|
|
|
+import { metabaseConfigValidator } from "./metabase";
|
|
|
|
|
+import { mezmoConfigValidator } from "./mezmo";
|
|
|
|
|
+import { newrelicConfigValidator } from "./newrelic";
|
|
|
import { defaultPostgresAddon, postgresConfigValidator } from "./postgres";
|
|
import { defaultPostgresAddon, postgresConfigValidator } from "./postgres";
|
|
|
import { redisConfigValidator } from "./redis";
|
|
import { redisConfigValidator } from "./redis";
|
|
|
|
|
+import { tailscaleConfigValidator } from "./tailscale";
|
|
|
|
|
+import {
|
|
|
|
|
+ ADDON_TEMPLATE_DATADOG,
|
|
|
|
|
+ ADDON_TEMPLATE_METABASE,
|
|
|
|
|
+ ADDON_TEMPLATE_MEZMO,
|
|
|
|
|
+ ADDON_TEMPLATE_NEWRELIC,
|
|
|
|
|
+ ADDON_TEMPLATE_POSTGRES,
|
|
|
|
|
+ ADDON_TEMPLATE_REDIS,
|
|
|
|
|
+ ADDON_TEMPLATE_TAILSCALE,
|
|
|
|
|
+ type AddonTemplate,
|
|
|
|
|
+} from "./template";
|
|
|
|
|
|
|
|
export const clientAddonValidator = z.object({
|
|
export const clientAddonValidator = z.object({
|
|
|
expanded: z.boolean().default(false),
|
|
expanded: z.boolean().default(false),
|
|
|
canDelete: z.boolean().default(true),
|
|
canDelete: z.boolean().default(true),
|
|
|
name: z.object({
|
|
name: z.object({
|
|
|
- readOnly: z.boolean(),
|
|
|
|
|
|
|
+ readOnly: z.boolean().default(false),
|
|
|
value: z
|
|
value: z
|
|
|
.string()
|
|
.string()
|
|
|
.min(1, { message: "Name must be at least 1 character" })
|
|
.min(1, { message: "Name must be at least 1 character" })
|
|
@@ -27,30 +50,106 @@ export const clientAddonValidator = z.object({
|
|
|
config: z.discriminatedUnion("type", [
|
|
config: z.discriminatedUnion("type", [
|
|
|
postgresConfigValidator,
|
|
postgresConfigValidator,
|
|
|
redisConfigValidator,
|
|
redisConfigValidator,
|
|
|
|
|
+ datadogConfigValidator,
|
|
|
|
|
+ mezmoConfigValidator,
|
|
|
|
|
+ metabaseConfigValidator,
|
|
|
|
|
+ newrelicConfigValidator,
|
|
|
|
|
+ tailscaleConfigValidator,
|
|
|
]),
|
|
]),
|
|
|
});
|
|
});
|
|
|
-export type ClientAddon = z.infer<typeof clientAddonValidator>;
|
|
|
|
|
|
|
+export type ClientAddon = z.infer<typeof clientAddonValidator> & {
|
|
|
|
|
+ template: AddonTemplate;
|
|
|
|
|
+};
|
|
|
|
|
+export const legacyAddonValidator = z.object({
|
|
|
|
|
+ name: z.string(),
|
|
|
|
|
+ namespace: z.string(),
|
|
|
|
|
+ info: z.object({
|
|
|
|
|
+ last_deployed: z.string(),
|
|
|
|
|
+ }),
|
|
|
|
|
+ chart: z.object({
|
|
|
|
|
+ metadata: z
|
|
|
|
|
+ .object({
|
|
|
|
|
+ name: z.string().optional(),
|
|
|
|
|
+ icon: z.string().optional(),
|
|
|
|
|
+ })
|
|
|
|
|
+ .optional(),
|
|
|
|
|
+ }),
|
|
|
|
|
+});
|
|
|
|
|
+export type LegacyClientAddon = z.infer<typeof legacyAddonValidator>;
|
|
|
|
|
|
|
|
export function defaultClientAddon(
|
|
export function defaultClientAddon(
|
|
|
type: ClientAddon["config"]["type"]
|
|
type: ClientAddon["config"]["type"]
|
|
|
): ClientAddon {
|
|
): ClientAddon {
|
|
|
return match(type)
|
|
return match(type)
|
|
|
- .with("postgres", () =>
|
|
|
|
|
- clientAddonValidator.parse({
|
|
|
|
|
|
|
+ .returnType<ClientAddon>()
|
|
|
|
|
+ .with("postgres", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
expanded: true,
|
|
expanded: true,
|
|
|
name: { readOnly: false, value: "postgres" },
|
|
name: { readOnly: false, value: "postgres" },
|
|
|
config: defaultPostgresAddon(),
|
|
config: defaultPostgresAddon(),
|
|
|
- })
|
|
|
|
|
- )
|
|
|
|
|
- .with("redis", () =>
|
|
|
|
|
- clientAddonValidator.parse({
|
|
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_POSTGRES,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with("redis", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
expanded: true,
|
|
expanded: true,
|
|
|
name: { readOnly: false, value: "redis" },
|
|
name: { readOnly: false, value: "redis" },
|
|
|
config: redisConfigValidator.parse({
|
|
config: redisConfigValidator.parse({
|
|
|
type: "redis",
|
|
type: "redis",
|
|
|
}),
|
|
}),
|
|
|
- })
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_REDIS,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with("datadog", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
|
|
+ expanded: true,
|
|
|
|
|
+ name: { readOnly: false, value: "datadog" },
|
|
|
|
|
+ config: datadogConfigValidator.parse({
|
|
|
|
|
+ type: "datadog",
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_DATADOG,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with("mezmo", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
|
|
+ expanded: true,
|
|
|
|
|
+ name: { readOnly: false, value: "mezmo" },
|
|
|
|
|
+ config: mezmoConfigValidator.parse({
|
|
|
|
|
+ type: "mezmo",
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_MEZMO,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with("metabase", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
|
|
+ expanded: true,
|
|
|
|
|
+ name: { readOnly: false, value: "metabase" },
|
|
|
|
|
+ config: metabaseConfigValidator.parse({
|
|
|
|
|
+ type: "metabase",
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_METABASE,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with("newrelic", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
|
|
+ expanded: true,
|
|
|
|
|
+ name: { readOnly: false, value: "newrelic" },
|
|
|
|
|
+ config: newrelicConfigValidator.parse({
|
|
|
|
|
+ type: "newrelic",
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_NEWRELIC,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with("tailscale", () => ({
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
|
|
+ expanded: true,
|
|
|
|
|
+ name: { readOnly: false, value: "tailscale" },
|
|
|
|
|
+ config: tailscaleConfigValidator.parse({
|
|
|
|
|
+ type: "tailscale",
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ template: ADDON_TEMPLATE_TAILSCALE,
|
|
|
|
|
+ }))
|
|
|
.exhaustive();
|
|
.exhaustive();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -58,27 +157,98 @@ function addonTypeEnumProto(type: ClientAddon["config"]["type"]): AddonType {
|
|
|
return match(type)
|
|
return match(type)
|
|
|
.with("postgres", () => AddonType.POSTGRES)
|
|
.with("postgres", () => AddonType.POSTGRES)
|
|
|
.with("redis", () => AddonType.REDIS)
|
|
.with("redis", () => AddonType.REDIS)
|
|
|
|
|
+ .with("datadog", () => AddonType.DATADOG)
|
|
|
|
|
+ .with("mezmo", () => AddonType.MEZMO)
|
|
|
|
|
+ .with("metabase", () => AddonType.METABASE)
|
|
|
|
|
+ .with("newrelic", () => AddonType.NEWRELIC)
|
|
|
|
|
+ .with("tailscale", () => AddonType.TAILSCALE)
|
|
|
.exhaustive();
|
|
.exhaustive();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
export function clientAddonToProto(addon: ClientAddon): Addon {
|
|
export function clientAddonToProto(addon: ClientAddon): Addon {
|
|
|
const config = match(addon.config)
|
|
const config = match(addon.config)
|
|
|
|
|
+ .returnType<Addon["config"]>()
|
|
|
.with({ type: "postgres" }, (data) => ({
|
|
.with({ type: "postgres" }, (data) => ({
|
|
|
- value: {
|
|
|
|
|
|
|
+ value: new Postgres({
|
|
|
cpuCores: data.cpuCores.value,
|
|
cpuCores: data.cpuCores.value,
|
|
|
ramMegabytes: data.ramMegabytes.value,
|
|
ramMegabytes: data.ramMegabytes.value,
|
|
|
storageGigabytes: data.storageGigabytes.value,
|
|
storageGigabytes: data.storageGigabytes.value,
|
|
|
- },
|
|
|
|
|
|
|
+ }),
|
|
|
case: "postgres" as const,
|
|
case: "postgres" as const,
|
|
|
}))
|
|
}))
|
|
|
.with({ type: "redis" }, (data) => ({
|
|
.with({ type: "redis" }, (data) => ({
|
|
|
- value: {
|
|
|
|
|
|
|
+ value: new Redis({
|
|
|
cpuCores: data.cpuCores.value,
|
|
cpuCores: data.cpuCores.value,
|
|
|
ramMegabytes: data.ramMegabytes.value,
|
|
ramMegabytes: data.ramMegabytes.value,
|
|
|
storageGigabytes: data.storageGigabytes.value,
|
|
storageGigabytes: data.storageGigabytes.value,
|
|
|
- },
|
|
|
|
|
|
|
+ }),
|
|
|
case: "redis" as const,
|
|
case: "redis" as const,
|
|
|
}))
|
|
}))
|
|
|
|
|
+ .with({ type: "datadog" }, (data) => ({
|
|
|
|
|
+ value: new Datadog({
|
|
|
|
|
+ cpuCores: data.cpuCores,
|
|
|
|
|
+ ramMegabytes: data.ramMegabytes,
|
|
|
|
|
+ site: data.site,
|
|
|
|
|
+ apiKey: data.apiKey,
|
|
|
|
|
+ loggingEnabled: data.loggingEnabled,
|
|
|
|
|
+ apmEnabled: data.apmEnabled,
|
|
|
|
|
+ dogstatsdEnabled: data.dogstatsdEnabled,
|
|
|
|
|
+ }),
|
|
|
|
|
+ case: "datadog" as const,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ type: "mezmo" }, (data) => ({
|
|
|
|
|
+ value: new Mezmo({
|
|
|
|
|
+ ingestionKey: data.ingestionKey,
|
|
|
|
|
+ }),
|
|
|
|
|
+ case: "mezmo" as const,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ type: "metabase" }, (data) => ({
|
|
|
|
|
+ value: new Metabase({
|
|
|
|
|
+ ingressEnabled: data.exposedToExternalTraffic,
|
|
|
|
|
+ domains: [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: data.customDomain,
|
|
|
|
|
+ type: DomainType.UNSPECIFIED,
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: data.porterDomain,
|
|
|
|
|
+ type: DomainType.PORTER,
|
|
|
|
|
+ },
|
|
|
|
|
+ // if not exposed, remove all domains
|
|
|
|
|
+ ].filter((d) => d.name !== "" && data.exposedToExternalTraffic),
|
|
|
|
|
+ datastore: {
|
|
|
|
|
+ host: data.datastore.host,
|
|
|
|
|
+ port: BigInt(data.datastore.port),
|
|
|
|
|
+ databaseName: data.datastore.databaseName,
|
|
|
|
|
+ masterUsername: data.datastore.username,
|
|
|
|
|
+ masterUserPasswordLiteral: data.datastore.password,
|
|
|
|
|
+ },
|
|
|
|
|
+ }),
|
|
|
|
|
+ case: "metabase" as const,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ type: "newrelic" }, (data) => ({
|
|
|
|
|
+ value: new Newrelic({
|
|
|
|
|
+ licenseKey: data.licenseKey,
|
|
|
|
|
+ insightsKey: data.insightsKey,
|
|
|
|
|
+ personalApiKey: data.personalApiKey,
|
|
|
|
|
+ accountId: data.accountId,
|
|
|
|
|
+ loggingEnabled: data.loggingEnabled,
|
|
|
|
|
+ kubeEventsEnabled: data.kubeEventsEnabled,
|
|
|
|
|
+ metricsAdapterEnabled: data.metricsAdapterEnabled,
|
|
|
|
|
+ prometheusEnabled: data.prometheusEnabled,
|
|
|
|
|
+ pixieEnabled: data.pixieEnabled,
|
|
|
|
|
+ }),
|
|
|
|
|
+ case: "newrelic" as const,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ type: "tailscale" }, (data) => ({
|
|
|
|
|
+ value: new Tailscale({
|
|
|
|
|
+ authKey: data.authKey,
|
|
|
|
|
+ subnetRoutes: data.subnetRoutes
|
|
|
|
|
+ .map((r) => r.route)
|
|
|
|
|
+ .filter((r) => r !== ""),
|
|
|
|
|
+ }),
|
|
|
|
|
+ case: "tailscale" as const,
|
|
|
|
|
+ }))
|
|
|
.exhaustive();
|
|
.exhaustive();
|
|
|
|
|
|
|
|
const proto = new Addon({
|
|
const proto = new Addon({
|
|
@@ -107,6 +277,7 @@ export function clientAddonFromProto({
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const config = match(addon.config)
|
|
const config = match(addon.config)
|
|
|
|
|
+ .returnType<ClientAddon["config"]>()
|
|
|
.with({ case: "postgres" }, (data) => ({
|
|
.with({ case: "postgres" }, (data) => ({
|
|
|
type: "postgres" as const,
|
|
type: "postgres" as const,
|
|
|
cpuCores: {
|
|
cpuCores: {
|
|
@@ -140,15 +311,84 @@ export function clientAddonFromProto({
|
|
|
},
|
|
},
|
|
|
password: secrets.REDIS_PASSWORD,
|
|
password: secrets.REDIS_PASSWORD,
|
|
|
}))
|
|
}))
|
|
|
|
|
+ .with({ case: "datadog" }, (data) => ({
|
|
|
|
|
+ type: "datadog" as const,
|
|
|
|
|
+ cpuCores: data.value.cpuCores ?? 0,
|
|
|
|
|
+ ramMegabytes: data.value.ramMegabytes ?? 0,
|
|
|
|
|
+ site: data.value.site ?? "",
|
|
|
|
|
+ apiKey: data.value.apiKey ?? "",
|
|
|
|
|
+ loggingEnabled: data.value.loggingEnabled ?? false,
|
|
|
|
|
+ apmEnabled: data.value.apmEnabled ?? false,
|
|
|
|
|
+ dogstatsdEnabled: data.value.dogstatsdEnabled ?? false,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ case: "mezmo" }, (data) => ({
|
|
|
|
|
+ type: "mezmo" as const,
|
|
|
|
|
+ ingestionKey: data.value.ingestionKey ?? "",
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ case: "metabase" }, (data) => ({
|
|
|
|
|
+ type: "metabase" as const,
|
|
|
|
|
+ exposedToExternalTraffic: data.value.ingressEnabled ?? false,
|
|
|
|
|
+ porterDomain:
|
|
|
|
|
+ data.value.domains.find((domain) => domain.type === DomainType.PORTER)
|
|
|
|
|
+ ?.name ?? "",
|
|
|
|
|
+ customDomain:
|
|
|
|
|
+ data.value.domains.find(
|
|
|
|
|
+ (domain) => domain.type === DomainType.UNSPECIFIED
|
|
|
|
|
+ )?.name ?? "",
|
|
|
|
|
+ datastore: {
|
|
|
|
|
+ host: data.value.datastore?.host ?? "",
|
|
|
|
|
+ port: Number(data.value.datastore?.port) ?? 0,
|
|
|
|
|
+ databaseName: data.value.datastore?.databaseName ?? "",
|
|
|
|
|
+ username: data.value.datastore?.masterUsername ?? "",
|
|
|
|
|
+ password: data.value.datastore?.masterUserPasswordLiteral ?? "",
|
|
|
|
|
+ },
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ case: "newrelic" }, (data) => ({
|
|
|
|
|
+ type: "newrelic" as const,
|
|
|
|
|
+ licenseKey: data.value.licenseKey ?? "",
|
|
|
|
|
+ insightsKey: data.value.insightsKey ?? "",
|
|
|
|
|
+ personalApiKey: data.value.personalApiKey ?? "",
|
|
|
|
|
+ accountId: data.value.accountId ?? "",
|
|
|
|
|
+ loggingEnabled: data.value.loggingEnabled ?? false,
|
|
|
|
|
+ kubeEventsEnabled: data.value.kubeEventsEnabled ?? false,
|
|
|
|
|
+ metricsAdapterEnabled: data.value.metricsAdapterEnabled ?? false,
|
|
|
|
|
+ prometheusEnabled: data.value.prometheusEnabled ?? false,
|
|
|
|
|
+ pixieEnabled: data.value.pixieEnabled ?? false,
|
|
|
|
|
+ }))
|
|
|
|
|
+ .with({ case: "tailscale" }, (data) => ({
|
|
|
|
|
+ type: "tailscale" as const,
|
|
|
|
|
+ authKey: data.value.authKey ?? "",
|
|
|
|
|
+ subnetRoutes: data.value.subnetRoutes.map((r) => ({ route: r })),
|
|
|
|
|
+ }))
|
|
|
.exhaustive();
|
|
.exhaustive();
|
|
|
|
|
|
|
|
- const clientAddon = clientAddonValidator.parse({
|
|
|
|
|
- name: { readOnly: false, value: addon.name },
|
|
|
|
|
- envGroups: addon.envGroups.map((envGroup) => ({
|
|
|
|
|
- value: envGroup.name,
|
|
|
|
|
- })),
|
|
|
|
|
- config,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const template = match(addon.config)
|
|
|
|
|
+ .with({ case: "postgres" }, () => ADDON_TEMPLATE_POSTGRES)
|
|
|
|
|
+ .with({ case: "redis" }, () => ADDON_TEMPLATE_REDIS)
|
|
|
|
|
+ .with({ case: "datadog" }, () => ADDON_TEMPLATE_DATADOG)
|
|
|
|
|
+ .with({ case: "mezmo" }, () => ADDON_TEMPLATE_MEZMO)
|
|
|
|
|
+ .with({ case: "metabase" }, () => ADDON_TEMPLATE_METABASE)
|
|
|
|
|
+ .with({ case: "newrelic" }, () => ADDON_TEMPLATE_NEWRELIC)
|
|
|
|
|
+ .with({ case: "tailscale" }, () => ADDON_TEMPLATE_TAILSCALE)
|
|
|
|
|
+ .exhaustive();
|
|
|
|
|
+
|
|
|
|
|
+ const clientAddon = {
|
|
|
|
|
+ ...clientAddonValidator.parse({
|
|
|
|
|
+ name: { readOnly: false, value: addon.name },
|
|
|
|
|
+ envGroups: addon.envGroups.map((envGroup) => ({
|
|
|
|
|
+ value: envGroup.name,
|
|
|
|
|
+ })),
|
|
|
|
|
+ config,
|
|
|
|
|
+ }),
|
|
|
|
|
+ template,
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
return clientAddon;
|
|
return clientAddon;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+export const tailscaleServiceValidator = z.object({
|
|
|
|
|
+ name: z.string(),
|
|
|
|
|
+ ip: z.string(),
|
|
|
|
|
+ port: z.number(),
|
|
|
|
|
+});
|
|
|
|
|
+export type ClientTailscaleService = z.infer<typeof tailscaleServiceValidator>;
|