ソースを参照

read helm values back into services

Feroze Mohideen 3 年 前
コミット
3ef2aab5ab

+ 15 - 2
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -24,6 +24,8 @@ import { ChartType, ResourceType } from "shared/types";
 import RevisionSection from "main/home/cluster-dashboard/expanded-chart/RevisionSection";
 import BuildSettingsTabStack from "./BuildSettingsTabStack";
 import Button from "components/porter/Button";
+import Services from "../new-app-flow/Services";
+import { Service } from "../new-app-flow/serviceTypes";
 
 type Props = RouteComponentProps & {};
 
@@ -266,7 +268,18 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
   const renderTabContents = () => {
     switch (tab) {
       case "overview":
-        return <div>TODO: service list</div>;
+        const helmValues = appData?.chart?.config;
+        const defaultValues = appData?.chart?.chart?.values;
+        if ((defaultValues && Object.keys(defaultValues).length > 0) || (helmValues && Object.keys(helmValues).length > 0)) {
+          const svcs = Service.deserialize(helmValues, defaultValues);
+          return <Services
+            setServices={(services: any[]) => {
+            }}
+            services={svcs}
+          />;
+        } else {
+          return <Text>No services found for this application yet.</Text>
+        }
       case "build-settings":
         return (
           <BuildSettingsTabStack appData={appData} setAppData={setAppData} />
@@ -369,7 +382,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
             shouldUpdate={
               appData.chart.latest_version &&
               appData.chart.latest_version !==
-                appData.chart.chart.metadata.version
+              appData.chart.chart.metadata.version
             }
             latestVersion={appData.chart.latest_version}
             upgradeVersion={appUpgradeVersion}

+ 1 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/GithubActionModal.tsx

@@ -74,8 +74,8 @@ const GithubActionModal: React.FC<Props> = ({
           );
           if (res?.data?.url) {
             window.open(res.data.url, "_blank", "noreferrer");
-            props.history.push(`/apps/${stackName}`);
           }
+          props.history.push(`/apps/${stackName}`);
         }
       } catch (error) {
         console.log(error)

+ 12 - 3
dashboard/src/main/home/app-dashboard/new-app-flow/JobTabs.tsx

@@ -5,19 +5,24 @@ import Spacer from "components/porter/Spacer";
 import TabSelector from "components/TabSelector";
 import Checkbox from "components/porter/Checkbox";
 import { JobService } from "./serviceTypes";
+import { Height } from "react-animate-height";
 
 interface Props {
-  service: JobService
-  editService: (service: JobService) => void
+  service: JobService;
+  editService: (service: JobService) => void;
+  setHeight: (height: Height) => void;
 }
 
 const JobTabs: React.FC<Props> = ({
   service,
-  editService
+  editService,
+  setHeight,
 }) => {
   const [currentTab, setCurrentTab] = React.useState<string>('main');
 
   const renderMain = () => {
+    setHeight(244);
+
     return (
       <>
         <Spacer y={1} />
@@ -42,6 +47,8 @@ const JobTabs: React.FC<Props> = ({
   };
 
   const renderResources = () => {
+    setHeight(244);
+
     return (
       <>
         <Spacer y={1} />
@@ -65,6 +72,8 @@ const JobTabs: React.FC<Props> = ({
   };
 
   const renderAdvanced = () => {
+    setHeight(118.5);
+
     return (
       <>
         <Spacer y={1} />

+ 3 - 2
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -308,7 +308,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
           porterJson.apps[service.name].config
         );
       }
-      apps[service.name] = {
+      // required because of https://github.com/helm/helm/issues/9214
+      apps[Service.toHelmName(service)] = {
         type: service.type,
         run: service.startCommand.value,
         config,
@@ -430,7 +431,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                 <Spacer y={0.5} />
 
                 <Services
-                  setServices={(services: any[]) => {
+                  setServices={(services: Service[]) => {
                     setFormState({ ...formState, serviceList: services });
                     if (Validators.serviceList(services)) {
                       setCurrentStep(Math.max(currentStep, 4));

+ 7 - 5
dashboard/src/main/home/app-dashboard/new-app-flow/ServiceContainer.tsx

@@ -1,5 +1,5 @@
 import React from "react"
-import AnimateHeight from "react-animate-height";
+import AnimateHeight, { Height } from "react-animate-height";
 import styled from "styled-components";
 
 import web from "assets/web.png";
@@ -24,15 +24,17 @@ const ServiceContainer: React.FC<ServiceProps> = ({
   editService,
 }) => {
   const [showExpanded, setShowExpanded] = React.useState<boolean>(true)
+  const [height, setHeight] = React.useState<Height>('auto');
 
+  // TODO: calculate heights instead of hardcoding them
   const renderTabs = (service: Service) => {
     switch (service.type) {
       case 'web':
-        return <WebTabs service={service} editService={editService} />
+        return <WebTabs service={service} editService={editService} setHeight={setHeight} />
       case 'worker':
-        return <WorkerTabs service={service} editService={editService} />
+        return <WorkerTabs service={service} editService={editService} setHeight={setHeight} />
       case 'job':
-        return <JobTabs service={service} editService={editService} />
+        return <JobTabs service={service} editService={editService} setHeight={setHeight} />
     }
   }
 
@@ -67,7 +69,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
         </ActionButton>
       </ServiceHeader>
       <AnimateHeight
-        height={showExpanded ? "auto" : 0}
+        height={showExpanded ? height : 0}
       >
         <StyledSourceBox showExpanded={showExpanded}>
           {renderTabs(service)}

+ 1 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/Services.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
 import ServiceContainer from "./ServiceContainer";
 import styled from "styled-components";
 import Spacer from "components/porter/Spacer";

+ 11 - 2
dashboard/src/main/home/app-dashboard/new-app-flow/WebTabs.tsx

@@ -1,23 +1,28 @@
 import Input from "components/porter/Input";
-import React from "react"
+import React, { useEffect } from "react"
 import Text from "components/porter/Text";
 import Spacer from "components/porter/Spacer";
 import TabSelector from "components/TabSelector";
 import Checkbox from "components/porter/Checkbox";
 import { WebService } from "./serviceTypes";
+import { Height } from "react-animate-height";
 
 interface Props {
   service: WebService
   editService: (service: WebService) => void
+  setHeight: (height: Height) => void
 }
 
 const WebTabs: React.FC<Props> = ({
   service,
-  editService
+  editService,
+  setHeight,
 }) => {
   const [currentTab, setCurrentTab] = React.useState<string>('main');
 
   const renderMain = () => {
+    setHeight(300);
+
     return (
       <>
         <Spacer y={1} />
@@ -49,6 +54,8 @@ const WebTabs: React.FC<Props> = ({
   };
 
   const renderResources = () => {
+    setHeight(713.5);
+
     return (
       <>
         <Spacer y={1} />
@@ -119,6 +126,8 @@ const WebTabs: React.FC<Props> = ({
   };
 
   const renderAdvanced = () => {
+    setHeight(159);
+
     return (
       <>
         <Spacer y={1} />

+ 8 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/WorkerTabs.tsx

@@ -5,19 +5,24 @@ import Spacer from "components/porter/Spacer";
 import TabSelector from "components/TabSelector";
 import Checkbox from "components/porter/Checkbox";
 import { WorkerService } from "./serviceTypes";
+import { Height } from "react-animate-height";
 
 interface Props {
   service: WorkerService
   editService: (service: WorkerService) => void
+  setHeight: (height: Height) => void
 }
 
 const WorkerTabs: React.FC<Props> = ({
   service,
-  editService
+  editService,
+  setHeight
 }) => {
   const [currentTab, setCurrentTab] = React.useState<string>('main');
 
   const renderMain = () => {
+    setHeight(159);
+
     return (
       <>
         <Spacer y={1} />
@@ -34,6 +39,8 @@ const WorkerTabs: React.FC<Props> = ({
   };
 
   const renderResources = () => {
+    setHeight(713.5);
+
     return (
       <>
         <Spacer y={1} />

+ 104 - 1
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -1,4 +1,7 @@
+import _ from "lodash";
 import api from "shared/api";
+import { ChartType } from "shared/types";
+import { overrideObjectValues } from "./utils";
 
 export type Service = WorkerService | WebService | JobService;
 export type ServiceType = 'web' | 'worker' | 'job';
@@ -51,6 +54,9 @@ const WorkerService = {
         } : {};
         return {
             replicaCount: service.replicas,
+            container: {
+                command: service.startCommand.value,
+            },
             resources: {
                 requests: {
                     cpu: service.cpu + 'm',
@@ -59,6 +65,24 @@ const WorkerService = {
             },
             ...autoscaling,
         }
+    },
+    deserialize: (name: string, values: any): WorkerService => {
+        return {
+            name,
+            cpu: values.resources?.requests?.cpu?.replace('m', '') ?? '',
+            ram: values.resources?.requests?.memory?.replace('Mi', '') ?? '',
+            startCommand: {
+                readOnly: false,
+                value: values.container?.command ?? '',
+            },
+            type: 'worker',
+            replicas: values.replicaCount ?? '',
+            autoscalingOn: values.autoscaling?.enabled ?? false,
+            minReplicas: values.autoscaling?.minReplicas ?? '',
+            maxReplicas: values.autoscaling?.maxReplicas ?? '',
+            targetCPUUtilizationPercentage: values.autoscaling?.targetCPUUtilizationPercentage ?? '',
+            targetRAMUtilizationPercentage: values.autoscaling?.targetMemoryUtilizationPercentage ?? '',
+        }
     }
 }
 
@@ -104,6 +128,7 @@ const WebService = {
                 }
             },
             container: {
+                command: service.startCommand.value,
                 port: service.port,
             },
             service: {
@@ -111,6 +136,27 @@ const WebService = {
             },
             ...autoscaling,
         }
+    },
+    deserialize: (name: string, values: any): WebService => {
+        return {
+            name,
+            cpu: values.resources?.requests?.cpu?.replace('m', '') ?? '',
+            ram: values.resources?.requests?.memory?.replace('Mi', '') ?? '',
+            startCommand: {
+                readOnly: false,
+                value: values.container?.command ?? ''
+            },
+            type: 'web',
+            replicas: values.replicaCount ?? '',
+            autoscalingOn: values.autoscaling?.enabled ?? false,
+            minReplicas: values.autoscaling?.minReplicas ?? '',
+            maxReplicas: values.autoscaling?.maxReplicas ?? '',
+            targetCPUUtilizationPercentage: values.autoscaling?.targetCPUUtilizationPercentage ?? '',
+            targetRAMUtilizationPercentage: values.autoscaling?.targetMemoryUtilizationPercentage ?? '',
+            port: values.container?.port ?? '',
+            generateUrlForExternalTraffic: values.ingress?.enabled ?? false,
+            customDomain: values.ingress?.hosts?.length ? values.ingress.hosts[0] : '',
+        }
     }
 }
 
@@ -136,6 +182,9 @@ const JobService = {
         } : {};
         return {
             allowConcurrent: service.jobsExecuteConcurrently,
+            container: {
+                command: service.startCommand.value,
+            },
             resources: {
                 requests: {
                     cpu: service.cpu + 'm',
@@ -144,9 +193,34 @@ const JobService = {
             },
             ...schedule,
         }
+    },
+    deserialize: (name: string, values: any): JobService => {
+        return {
+            name,
+            cpu: values.resources?.requests?.cpu?.replace('m', '') ?? '',
+            ram: values.resources?.requests?.memory?.replace('Mi', '') ?? '',
+            startCommand: {
+                readOnly: false,
+                value: values.container?.command ?? ''
+            },
+            type: 'job',
+            jobsExecuteConcurrently: values.allowConcurrent ?? false,
+            cronSchedule: values.schedule?.value ?? '',
+        }
     }
 }
 
+const TYPE_TO_SUFFIX: Record<ServiceType, string> = {
+    'web': '-web',
+    'worker': '-wkr',
+    'job': '-job',
+}
+const SUFFIX_TO_TYPE: Record<string, ServiceType> = {
+    '-web': 'web',
+    '-wkr': 'worker',
+    '-job': 'job',
+}
+
 export const Service = {
     default: (name: string, type: ServiceType, startCommand: ServiceReadOnlyField) => {
         switch (type) {
@@ -168,6 +242,31 @@ export const Service = {
                 return JobService.serialize(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) {
+                const type = SUFFIX_TO_TYPE[suffix];
+                const appName = name.slice(0, -4);
+                const coalescedValues = overrideObjectValues(
+                    defaultValues[name],
+                    helmValues[name] ?? {}
+                );
+                switch (type) {
+                    case 'web':
+                        return WebService.deserialize(appName, coalescedValues);
+                    case 'worker':
+                        return WorkerService.deserialize(appName, coalescedValues);
+                    case 'job':
+                        return JobService.deserialize(appName, coalescedValues);
+                }
+            }
+        }).filter((service: Service | undefined): service is Service => service != null);
+    },
     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',
@@ -207,7 +306,11 @@ 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]
+    },
 }
 
 type Ingress = {

+ 0 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/utils.tsx → dashboard/src/main/home/app-dashboard/new-app-flow/utils.ts