Преглед изворни кода

updating services from dashboard works now

Feroze Mohideen пре 3 година
родитељ
комит
aec21907c7

+ 2 - 1
api/server/handlers/stacks/parse.go

@@ -63,7 +63,8 @@ func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo) (map[s
 	for name, app := range parsed.Apps {
 		appType := getType(name, app)
 		defaultValues := getDefaultValues(app, parsed.Env, appType)
-		helm_values := utils.CoalesceValues(defaultValues, app.Config)
+		convertedConfig := convertMap(app.Config).(map[string]interface{})
+		helm_values := utils.DeepCoalesceValues(defaultValues, convertedConfig)
 		values[name] = helm_values
 	}
 

+ 41 - 13
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -32,6 +32,7 @@ import ConfirmOverlay from "components/porter/ConfirmOverlay";
 import { createFinalPorterYaml } from "../new-app-flow/schema";
 import EnvGroupArray, { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 import { PorterYamlSchema } from "../new-app-flow/schema";
+import Fieldset from "components/porter/Fieldset";
 
 type Props = RouteComponentProps & {};
 
@@ -78,6 +79,9 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     setIsLoading(true);
     const { appName } = props.match.params as any;
     try {
+      if (!currentCluster || !currentProject) {
+        return;
+      }
       const resPorterApp = await api.getPorterApp(
         "<token>",
         {},
@@ -104,17 +108,10 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
         chart: resChartData?.data,
       };
       setAppData(newAppData);
-
-      const helmValues = resChartData?.data?.config;
-      const defaultValues = resChartData?.data?.chart?.values;
-      if ((defaultValues && Object.keys(defaultValues).length > 0) || (helmValues && Object.keys(helmValues).length > 0)) {
-        const svcs = Service.deserialize(helmValues, defaultValues);
-        setServices(svcs);
-        console.log(helmValues);
-      }
-      console.log(newAppData);
+      updateServicesAndEnvVariables(resChartData?.data);
     } catch (err) {
       setError(err);
+      console.log(err);
     } finally {
       setIsLoading(false);
     }
@@ -161,7 +158,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
       ) {
         const finalPorterYaml = createFinalPorterYaml(
           services,
-          [],
+          envVars,
           undefined,
           appData.app.name,
           currentProject.id,
@@ -246,6 +243,19 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     return <Icon src={src} />;
   };
 
+  const updateServicesAndEnvVariables = (currentChart?: ChartType) => {
+    const helmValues = currentChart?.config;
+    const defaultValues = currentChart?.chart?.values;
+    if ((defaultValues && Object.keys(defaultValues).length > 0) || (helmValues && Object.keys(helmValues).length > 0)) {
+      const svcs = Service.deserialize(helmValues, defaultValues);
+      setServices(svcs);
+      if (helmValues && Object.keys(helmValues).length > 0) {
+        const envs = Service.retrieveEnvFromHelmValues(helmValues);
+        setEnvVars(envs)
+      }
+    }
+  }
+
   const updateComponents = async (currentChart: ChartType) => {
     setLoading(true);
     try {
@@ -261,6 +271,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
         }
       );
       setComponents(res.data.Objects);
+      updateServicesAndEnvVariables(currentChart);
       setLoading(false);
     } catch (error) {
       console.log(error);
@@ -297,7 +308,9 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
 
     const updatedChart = res.data;
 
-    setAppData({ chart: updatedChart });
+    if (appData != null && updatedChart != null) {
+      setAppData({ ...appData, chart: updatedChart });
+    }
 
     updateComponents(updatedChart).finally(() => setIsLoadingChartData(false));
   };
@@ -313,6 +326,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
     // setIsPreview(!isCurrent);
     getChartData(chart);
   };
+
   const appUpgradeVersion = useCallback(
     async (version: string, cb: () => void) => {
       // convert current values to yaml
@@ -390,6 +404,17 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
       case "overview":
         return (
           <>
+            {(!isLoading && services.length === 0) && (
+              <>
+                <Fieldset>
+                  <Container row>
+                    <PlaceholderIcon src={notFound} />
+                    <Text color="helper">No services were found.</Text>
+                  </Container>
+                </Fieldset>
+                <Spacer y={0.5} />
+              </>
+            )}
             <Services
               setServices={setServices}
               services={services}
@@ -404,6 +429,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
               ) : undefined}
               loadingText={"Updating..."}
               width={"150px"}
+              disabled={services.length === 0}
             >
               Update app
             </Button>
@@ -447,7 +473,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
             </Text>
             <EnvGroupArray
               values={envVars}
-              setValues={setEnvVars}
+              setValues={(x: any) => setEnvVars(x)}
               fileUpload={true}
             />
             <Spacer y={0.5} />
@@ -486,7 +512,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
           <Link to="/apps">Return to dashboard</Link>
         </Placeholder>
       )}
-      {appData && (
+      {appData && appData.app && (
         <StyledExpandedApp>
           <Back to="/apps" />
           <Container row>
@@ -560,6 +586,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                   { label: "Logs", value: "logs" },
                   { label: "Metrics", value: "metrics" },
                   { label: "Overview", value: "overview" },
+                  { label: "Environment variables", value: "environment-variables" },
                   { label: "Build settings", value: "build-settings" },
                   { label: "Settings", value: "settings" },
                 ]
@@ -568,6 +595,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                   { label: "Logs", value: "logs" },
                   { label: "Metrics", value: "metrics" },
                   { label: "Overview", value: "overview" },
+                  { label: "Environment variables", value: "environment-variables" },
                   { label: "Settings", value: "settings" },
                 ]
             }

+ 26 - 4
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -2,6 +2,7 @@ import _ from "lodash";
 import api from "shared/api";
 import { ChartType } from "shared/types";
 import { overrideObjectValues } from "./utils";
+import { KeyValueType } from "main/home/cluster-dashboard/env-groups/EnvGroupArray";
 
 export type Service = WorkerService | WebService | JobService;
 export type ServiceType = 'web' | 'worker' | 'job';
@@ -222,6 +223,7 @@ const SUFFIX_TO_TYPE: Record<string, ServiceType> = {
 }
 
 export const Service = {
+    // populates an empty service
     default: (name: string, type: ServiceType, startCommand: ServiceReadOnlyField) => {
         switch (type) {
             case 'web':
@@ -232,6 +234,8 @@ export const Service = {
                 return JobService.default(name, startCommand);
         }
     },
+
+    // converts a service to a helm values object
     serialize: (service: Service) => {
         switch (service.type) {
             case 'web':
@@ -242,11 +246,9 @@ export const Service = {
                 return JobService.serialize(service);
         }
     },
+
+    // converts a helm values object to a service
     deserialize: (helmValues: any, defaultValues: any): Service[] => {
-        // console.log("helm values")
-        // console.log(helmValues)
-        // console.log("default values")
-        // console.log(defaultValues)
         return Object.keys(defaultValues).map((name: string) => {
             const suffix = name.slice(-4);
             if (suffix in SUFFIX_TO_TYPE) {
@@ -267,9 +269,13 @@ export const Service = {
             }
         }).filter((service: Service | undefined): service is Service => service != null);
     },
+
+    // standard typeguards
     isWeb: (service: Service): service is WebService => service.type === 'web',
     isWorker: (service: Service): service is WorkerService => service.type === 'worker',
     isJob: (service: Service): service is JobService => service.type === 'job',
+
+    // augments ingress of a web service, will be phased out
     handleWebIngress: (service: WebService, stackName: string, projectId?: number, clusterId?: number) => {
         if (projectId == null || clusterId == null) {
             throw new Error('Project ID and Cluster ID must be provided to handle web ingress');
@@ -307,10 +313,26 @@ export const Service = {
 
         return ingress;
     },
+
     // required because of https://github.com/helm/helm/issues/9214
     toHelmName: (service: Service): string => {
         return service.name + TYPE_TO_SUFFIX[service.type]
     },
+
+    retrieveEnvFromHelmValues: (helmValues: any): KeyValueType[] => {
+        const firstService = Object.keys(helmValues)[0];
+        const env = helmValues[firstService]?.container?.env?.normal;
+        if (env == null) {
+            return [];
+        }
+        return Object.keys(env).map((key: string) => ({
+            key,
+            value: env[key],
+            hidden: false,
+            locked: false,
+            deleted: false,
+        }));
+    }
 }
 
 type Ingress = {

+ 1 - 3
dashboard/src/main/home/app-dashboard/new-app-flow/utils.ts

@@ -2,11 +2,9 @@ export const overrideObjectValues = (obj1: any, obj2: any) => {
   // Iterate over the keys in obj2
   for (const key in obj2) {
     // Check if the key exists in obj1 and if its value is an object
-    if (key in obj1 && typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
-      // Recursively call the function to handle the nested object
+    if (key in obj1 && obj1[key] !== null && typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {
       obj1[key] = overrideObjectValues(obj1[key], obj2[key]);
     } else {
-      // Otherwise, just assign the value from obj2 to obj1
       obj1[key] = obj2[key];
     }
   }

+ 29 - 0
internal/templater/utils/values.go

@@ -47,6 +47,35 @@ func CoalesceValues(base, override map[string]interface{}) map[string]interface{
 	return override
 }
 
+func DeepCoalesceValues(base, override map[string]interface{}) map[string]interface{} {
+	if base == nil && override != nil {
+		return override
+	} else if override == nil {
+		return base
+	}
+
+	for key, val := range base {
+		if oVal, ok := override[key]; ok {
+			if oVal == nil {
+				delete(override, key)
+			} else if isYAMLTable(oVal) && isYAMLTable(val) {
+				oMapVal, _ := oVal.(map[string]interface{})
+				bMapVal, _ := val.(map[string]interface{})
+
+				override[key] = mergeMaps(bMapVal, oMapVal)
+			} else if _, ok := val.(map[string]interface{}); ok {
+				override[key] = CoalesceValues(val.(map[string]interface{}), oVal.(map[string]interface{}))
+			} else {
+				override[key] = oVal
+			}
+		} else {
+			override[key] = val
+		}
+	}
+
+	return override
+}
+
 func isYAMLTable(v interface{}) bool {
 	_, ok := v.(map[string]interface{})
 	return ok