Selaa lähdekoodia

show addons attached to app template (#4104)

ianedwards 2 vuotta sitten
vanhempi
sitoutus
0edf70a667

+ 24 - 0
api/server/handlers/porter_app/get_app_template.go

@@ -1,6 +1,7 @@
 package porter_app
 
 import (
+	"context"
 	"encoding/base64"
 	"net/http"
 
@@ -40,6 +41,8 @@ type GetAppTemplateResponse struct {
 	TemplateB64AppProto string `json:"template_b64_app_proto"`
 	// AppEnv is the base set of environment variables that will be used in subsequent preview deploys
 	AppEnv environment_groups.EnvironmentGroup `json:"app_env"`
+	// Addons is a list of encoded addons that will be used alongside the app template
+	Addons []Base64AddonWithEnvVars `json:"addons"`
 }
 
 // ServeHTTP creates or updates an app template for a given porter app
@@ -123,10 +126,31 @@ func (c *GetAppTemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 		SecretVariables: ccpResp.Msg.AppEnv.Secret,
 	}
 
+	addons := encodedAddonsWithEnvFromProto(ctx, ccpResp.Msg.AddonTemplates)
+
 	res := &GetAppTemplateResponse{
 		TemplateB64AppProto: encoded,
 		AppEnv:              appEnv,
+		Addons:              addons,
 	}
 
 	c.WriteResult(w, r, res)
 }
+
+func encodedAddonsWithEnvFromProto(ctx context.Context, addons []*porterv1.AddonWithEnvVars) []Base64AddonWithEnvVars {
+	var res []Base64AddonWithEnvVars
+
+	for _, addon := range addons {
+		by, _ := helpers.MarshalContractObject(ctx, addon.Addon)
+
+		encoded := base64.StdEncoding.EncodeToString(by)
+
+		res = append(res, Base64AddonWithEnvVars{
+			Base64Addon: encoded,
+			Variables:   addon.EnvVars.Normal,
+			Secrets:     addon.EnvVars.Secret,
+		})
+	}
+
+	return res
+}

+ 44 - 0
dashboard/src/lib/addons/index.ts

@@ -63,3 +63,47 @@ export function clientAddonToProto(addon: ClientAddon): Addon {
 
   return proto;
 }
+
+export function clientAddonFromProto(args: {
+  addon: Addon;
+  variables: Record<string, string>;
+  secrets: Record<string, string>;
+}): ClientAddon {
+  const addon = args.addon;
+  const variables = args.variables;
+  const secrets = args.secrets;
+
+  if (!addon.config.case) {
+    throw new Error("Addon type is unspecified");
+  }
+
+  const config = match(addon.config)
+    .with({ case: "postgres" }, (data) => ({
+      type: "postgres" as const,
+      cpuCores: {
+        readOnly: false,
+        value: data.value.cpuCores,
+      },
+      ramMegabytes: {
+        readOnly: false,
+        value: data.value.ramMegabytes,
+      },
+      storageGigabytes: {
+        readOnly: false,
+        value: data.value.storageGigabytes,
+      },
+      username: variables.POSTGRESQL_USERNAME,
+      password: secrets.POSTGRESQL_PASSWORD,
+    }))
+    .exhaustive();
+
+  const clientAddon = clientAddonValidator.parse({
+    name: { readOnly: false, value: addon.name },
+    envGroups: addon.envGroups.map((envGroup) => ({
+      value: envGroup.name,
+    })),
+    config,
+  });
+
+  return clientAddon;
+}

+ 26 - 4
dashboard/src/main/home/cluster-dashboard/preview-environments/v2/setup-app/PreviewAppDataContainer.tsx

@@ -20,7 +20,11 @@ import TabSelector from "components/TabSelector";
 import { useLatestRevision } from "main/home/app-dashboard/app-view/LatestRevisionContext";
 import Environment from "main/home/app-dashboard/app-view/tabs/Environment";
 import GithubActionModal from "main/home/app-dashboard/new-app-flow/GithubActionModal";
-import { clientAddonToProto, clientAddonValidator } from "lib/addons";
+import {
+  clientAddonFromProto,
+  clientAddonToProto,
+  clientAddonValidator,
+} from "lib/addons";
 import { useAppWithPreviewOverrides } from "lib/hooks/useAppWithPreviewOverrides";
 import {
   basePorterAppFormValidator,
@@ -115,11 +119,27 @@ export const PreviewAppDataContainer: React.FC<Props> = ({
   const withPreviewOverrides = useAppWithPreviewOverrides({
     latestApp: latestProto,
     detectedServices: servicesFromYaml,
-    existingTemplate: existingTemplate?.template,
-    templateEnv: existingTemplate?.env,
+    existingTemplate: existingTemplate?.template_b64_app_proto,
+    templateEnv: existingTemplate?.app_env,
     appEnv,
   });
 
+  const existingAddonsWithEnv = useMemo(() => {
+    if (!existingTemplate) {
+      return [];
+    }
+
+    const existingAddons = existingTemplate.addons.map((addon) =>
+      clientAddonFromProto({
+        addon: addon.addon,
+        variables: addon.variables,
+        secrets: addon.secrets,
+      })
+    );
+
+    return existingAddons;
+  }, [existingTemplate?.addons]);
+
   const porterAppFormMethods = useForm<AppTemplateFormData>({
     reValidateMode: "onSubmit",
     resolver: zodResolver(appTemplateClientValidator),
@@ -131,6 +151,7 @@ export const PreviewAppDataContainer: React.FC<Props> = ({
         envGroupNames: [],
         predeploy: [],
       },
+      addons: [],
     },
   });
 
@@ -287,8 +308,9 @@ export const PreviewAppDataContainer: React.FC<Props> = ({
         envGroupNames: [],
         predeploy: [],
       },
+      addons: existingAddonsWithEnv,
     });
-  }, [withPreviewOverrides, latestSource]);
+  }, [withPreviewOverrides, latestSource, existingAddonsWithEnv]);
 
   if (latestSource.type !== "github") {
     return <Redirect to={`/apps/${porterApp.name}`} />;

+ 4 - 21
dashboard/src/main/home/cluster-dashboard/preview-environments/v2/setup-app/SetupApp.tsx

@@ -1,10 +1,8 @@
 import React, { useContext, useMemo } from "react";
-import { PorterApp } from "@porter-dev/api-contracts";
 import { useQuery } from "@tanstack/react-query";
 import { withRouter, type RouteComponentProps } from "react-router";
 import styled from "styled-components";
 import { match } from "ts-pattern";
-import { z } from "zod";
 
 import Loading from "components/Loading";
 import Back from "components/porter/Back";
@@ -16,6 +14,7 @@ import api from "shared/api";
 import { Context } from "shared/Context";
 import pull_request from "assets/pull_request_icon.svg";
 
+import { existingTemplateWithEnvValidator } from "../types";
 import { PreviewAppDataContainer } from "./PreviewAppDataContainer";
 
 type Props = RouteComponentProps;
@@ -57,27 +56,11 @@ const SetupApp: React.FC<Props> = ({ location }) => {
           }
         );
 
-        const data = await z
-          .object({
-            template_b64_app_proto: z.string(),
-            app_env: z.object({
-              variables: z.record(z.string()).default({}),
-              secret_variables: z.record(z.string()).default({}),
-            }),
-          })
-          .parseAsync(res.data);
-
-        const template = PorterApp.fromJsonString(
-          atob(data.template_b64_app_proto),
-          {
-            ignoreUnknownFields: true,
-          }
+        const template = await existingTemplateWithEnvValidator.parseAsync(
+          res.data
         );
 
-        return {
-          template,
-          env: data.app_env,
-        };
+        return template;
       } catch (err) {
         return null;
       }

+ 36 - 8
dashboard/src/main/home/cluster-dashboard/preview-environments/v2/types.ts

@@ -1,9 +1,37 @@
-import { type PorterApp } from "@porter-dev/api-contracts";
+import { PorterApp } from "@porter-dev/api-contracts";
+import { Addon } from "@porter-dev/api-contracts/src/porter/v1/addons_pb";
+import { z } from "zod";
 
-export type ExistingTemplateWithEnv = {
-  template: PorterApp;
-  env: {
-    variables: Record<string, string>;
-    secret_variables: Record<string, string>;
-  };
-};
+export const existingTemplateWithEnvValidator = z.object({
+  template_b64_app_proto: z.string().transform((a) =>
+    PorterApp.fromJsonString(atob(a), {
+      ignoreUnknownFields: true,
+    })
+  ),
+  app_env: z.object({
+    variables: z.record(z.string()).default({}),
+    secret_variables: z.record(z.string()).default({}),
+  }),
+  addons: z
+    .array(
+      z.object({
+        base64_addon: z.string(),
+        variables: z.record(z.string()).default({}),
+        secrets: z.record(z.string()).default({}),
+      })
+    )
+    .transform((addons) =>
+      addons.map((a) => {
+        return {
+          ...a,
+          addon: Addon.fromJsonString(atob(a.base64_addon), {
+            ignoreUnknownFields: true,
+          }),
+        };
+      })
+    ),
+});
+
+export type ExistingTemplateWithEnv = z.infer<
+  typeof existingTemplateWithEnvValidator
+>;

+ 1 - 1
go.mod

@@ -83,7 +83,7 @@ require (
 	github.com/matryer/is v1.4.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.2.73
+	github.com/porter-dev/api-contracts v0.2.76
 	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

+ 2 - 2
go.sum

@@ -1520,8 +1520,8 @@ 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.2.73 h1:hsFcJSf0HLxS7VgV36qn5X3tYPzWG48mCvHwuOlU2eE=
-github.com/porter-dev/api-contracts v0.2.73/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
+github.com/porter-dev/api-contracts v0.2.76 h1:hQrtYKG7Z0JQlCXpmmXH+pNI2CcyXfdDpiH4VKSChw0=
+github.com/porter-dev/api-contracts v0.2.76/go.mod h1:fX6JmP5QuzxDLvqP3evFOTXjI4dHxsG0+VKNTjImZU8=
 github.com/porter-dev/switchboard v0.0.3 h1:dBuYkiVLa5Ce7059d6qTe9a1C2XEORFEanhbtV92R+M=
 github.com/porter-dev/switchboard v0.0.3/go.mod h1:xSPzqSFMQ6OSbp42fhCi4AbGbQbsm6nRvOkrblFeXU4=
 github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=