Parcourir la source

fixing some bugs (#2990)

Feroze Mohideen il y a 3 ans
Parent
commit
5cbb28c807

+ 31 - 39
api/server/handlers/stacks/create_porter_app.go

@@ -14,6 +14,7 @@ import (
 	"github.com/porter-dev/porter/api/types"
 	"github.com/porter-dev/porter/internal/helm"
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/stefanmcshane/helm/pkg/chart"
 )
 
 type CreatePorterAppHandler struct {
@@ -68,7 +69,7 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 	porterYamlBase64 := request.PorterYAMLBase64
 	porterYaml, err := base64.StdEncoding.DecodeString(porterYamlBase64)
 	if err != nil {
-		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error decoding porter yaml: %w", err)))
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error decoding porter.yaml: %w", err)))
 		return
 	}
 	imageInfo := request.ImageInfo
@@ -78,26 +79,36 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
-	if shouldCreate {
-		chart, values, err := parse(
-			porterYaml,
-			imageInfo,
-			c.Config(),
-			cluster.ProjectID,
-			nil,
-			nil,
-			SubdomainCreateOpts{
-				k8sAgent:       k8sAgent,
-				dnsRepo:        c.Repo().DNSRecord(),
-				powerDnsClient: c.Config().PowerDNSClient,
-				appRootDomain:  c.Config().ServerConf.AppRootDomain,
-				stackName:      stackName,
-			})
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error parsing porter yaml into chart and values: %w", err)))
-			return
-		}
+	var releaseValues map[string]interface{}
+	var releaseDependencies []*chart.Dependency
+	if shouldCreate || request.OverrideRelease {
+		releaseValues = nil
+		releaseDependencies = nil
+	} else {
+		releaseValues = helmRelease.Config
+		releaseDependencies = helmRelease.Chart.Metadata.Dependencies
+	}
 
+	chart, values, err := parse(
+		porterYaml,
+		imageInfo,
+		c.Config(),
+		cluster.ProjectID,
+		releaseValues,
+		releaseDependencies,
+		SubdomainCreateOpts{
+			k8sAgent:       k8sAgent,
+			dnsRepo:        c.Repo().DNSRecord(),
+			powerDnsClient: c.Config().PowerDNSClient,
+			appRootDomain:  c.Config().ServerConf.AppRootDomain,
+			stackName:      stackName,
+		})
+	if err != nil {
+		c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error parsing porter yaml into chart and values: %w", err)))
+		return
+	}
+
+	if shouldCreate {
 		// create the namespace if it does not exist already
 		_, err = k8sAgent.CreateNamespace(namespace, nil)
 		if err != nil {
@@ -163,25 +174,6 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 
 		c.WriteResult(w, r, porterApp.ToPorterAppType())
 	} else {
-		chart, values, err := parse(
-			porterYaml,
-			imageInfo,
-			c.Config(),
-			cluster.ProjectID,
-			helmRelease.Config,
-			helmRelease.Chart.Metadata.Dependencies,
-			SubdomainCreateOpts{
-				k8sAgent:       k8sAgent,
-				dnsRepo:        c.Repo().DNSRecord(),
-				powerDnsClient: c.Config().PowerDNSClient,
-				appRootDomain:  c.Config().ServerConf.AppRootDomain,
-				stackName:      stackName,
-			})
-		if err != nil {
-			c.HandleAPIError(w, r, apierrors.NewErrInternal(fmt.Errorf("error parsing porter yaml into chart and values: %w", err)))
-			return
-		}
-
 		conf := &helm.InstallChartConfig{
 			Chart:      chart,
 			Name:       stackName,

+ 11 - 4
api/server/handlers/stacks/parse.go

@@ -81,15 +81,17 @@ func parse(
 func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo, existingValues map[string]interface{}, opts SubdomainCreateOpts) (map[string]interface{}, error) {
 	values := make(map[string]interface{})
 
+	if parsed.Apps == nil {
+		if existingValues == nil {
+			return nil, fmt.Errorf("porter.yaml must contain at least one app, or release must exist and have values")
+		}
+	}
+
 	for name, app := range parsed.Apps {
 		appType := getType(name, app)
 		defaultValues := getDefaultValues(app, parsed.Env, appType)
 		convertedConfig := convertMap(app.Config).(map[string]interface{})
 		helm_values := utils.DeepCoalesceValues(defaultValues, convertedConfig)
-		err := createSubdomainIfRequired(helm_values, opts) // modifies helm_values to add subdomains if necessary
-		if err != nil {
-			return nil, err
-		}
 
 		// required to identify the chart type because of https://github.com/helm/helm/issues/9214
 		helmName := getHelmName(name, appType)
@@ -100,6 +102,11 @@ func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo, existi
 			}
 		}
 
+		err := createSubdomainIfRequired(helm_values, opts) // modifies helm_values to add subdomains if necessary
+		if err != nil {
+			return nil, err
+		}
+
 		values[helmName] = helm_values
 	}
 

+ 2 - 1
api/types/porter_app.go

@@ -35,8 +35,9 @@ type CreatePorterAppRequest struct {
 	Dockerfile       string    `json:"dockerfile"`
 	ImageRepoURI     string    `json:"image_repo_uri"`
 	PullRequestURL   string    `json:"pull_request_url"`
-	PorterYAMLBase64 string    `json:"porter_yaml" form:"required"`
+	PorterYAMLBase64 string    `json:"porter_yaml"`
 	ImageInfo        ImageInfo `json:"image_info" form:"omitempty"`
+	OverrideRelease  bool      `json:"override_release"`
 }
 
 type UpdatePorterAppRequest struct {

+ 12 - 3
cli/cmd/apply.go

@@ -107,9 +107,18 @@ func init() {
 }
 
 func apply(_ *types.GetAuthenticatedUserResponse, client *api.Client, _ []string) error {
-	fileBytes, err := ioutil.ReadFile(porterYAML)
-	if err != nil {
-		return fmt.Errorf("error reading porter.yaml: %w", err)
+	var fileBytes []byte
+	var err error
+	if porterYAML == "" {
+		stackName := os.Getenv("PORTER_STACK_NAME")
+		if stackName == "" {
+			return fmt.Errorf("a valid porter.yaml file must be specified. Run porter apply --help for more information")
+		}
+	} else {
+		fileBytes, err = ioutil.ReadFile(porterYAML)
+		if err != nil {
+			return fmt.Errorf("error reading porter.yaml: %w", err)
+		}
 	}
 
 	var previewVersion struct {

+ 18 - 8
cli/cmd/stack/apply.go

@@ -49,6 +49,7 @@ func CreateV1BuildResources(client *api.Client, raw []byte, stackName string, pr
 			}
 		}
 	} else {
+		color.New(color.FgYellow).Printf("No build values specified in porter.yaml, attempting to load stack build settings instead \n")
 		bi, pi, err = createV1BuildResourcesFromDB(client, stackConf)
 		if err != nil {
 			return nil, err
@@ -61,20 +62,23 @@ func CreateV1BuildResources(client *api.Client, raw []byte, stackName string, pr
 }
 
 func createStackConf(client *api.Client, raw []byte, stackName string, projectID uint, clusterID uint) (*StackConf, error) {
-	parsed := &PorterStackYAML{}
-
-	err := yaml.Unmarshal(raw, parsed)
-	if err != nil {
-		errMsg := composePreviewMessage("error parsing porter.yaml", Error)
-		return nil, fmt.Errorf("%s: %w", errMsg, err)
+	var parsed *PorterStackYAML
+	if raw == nil {
+		parsed = createDefaultPorterYaml()
+	} else {
+		parsed = &PorterStackYAML{}
+		err := yaml.Unmarshal(raw, parsed)
+		if err != nil {
+			errMsg := composePreviewMessage("error parsing porter.yaml", Error)
+			return nil, fmt.Errorf("%s: %w", errMsg, err)
+		}
 	}
 
-	err = config.ValidateCLIEnvironment()
+	err := config.ValidateCLIEnvironment()
 	if err != nil {
 		errMsg := composePreviewMessage("porter CLI is not configured correctly", Error)
 		return nil, fmt.Errorf("%s: %w", errMsg, err)
 	}
-
 	return &StackConf{
 		apiClient: client,
 		rawBytes:  raw,
@@ -176,3 +180,9 @@ func convertToBuild(porterApp *types.PorterApp) Build {
 		Image:      image,
 	}
 }
+
+func createDefaultPorterYaml() *PorterStackYAML {
+	return &PorterStackYAML{
+		Apps: nil,
+	}
+}

+ 1 - 0
cli/cmd/stack/hooks.go

@@ -86,6 +86,7 @@ func (t *DeployStackHook) applyStack(client *api.Client, shouldCreate bool, driv
 			ProjectID:        t.ProjectID,
 			PorterYAMLBase64: base64.StdEncoding.EncodeToString(t.PorterYAML),
 			ImageInfo:        imageInfo,
+			OverrideRelease:  false, // deploying from the cli will never delete release resources, only append or override
 		},
 	)
 	if err != nil {

+ 1 - 0
dashboard/src/main/home/app-dashboard/expanded-app/ExpandedApp.tsx

@@ -235,6 +235,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
           {
             porter_yaml: base64Encoded,
             ...options,
+            override_release: true,
           },
           {
             cluster_id: currentCluster.id,

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

@@ -105,7 +105,7 @@ const JobTabs: React.FC<Props> = ({
           } else if (value === 'resources') {
             setHeight(244);
           } else if (value === 'advanced') {
-            setHeight(118.5);
+            setHeight(118);
           }
           setCurrentTab(value);
         }}

+ 8 - 10
dashboard/src/main/home/app-dashboard/new-app-flow/ServiceContainer.tsx

@@ -99,9 +99,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
         </ServiceTitle>
         {service.canDelete && (
           <ActionButton
-            onClick={(e) => {
-              deleteService();
-            }}
+            onClick={deleteService}
           >
             <span className="material-icons">delete</span>
           </ActionButton>
@@ -110,7 +108,7 @@ const ServiceContainer: React.FC<ServiceProps> = ({
       <AnimateHeight
         height={showExpanded ? height : 0}
       >
-        <StyledSourceBox 
+        <StyledSourceBox
           showExpanded={showExpanded}
           chart={chart}
           hasFooter={getHasBuiltImage()}
@@ -124,11 +122,11 @@ const ServiceContainer: React.FC<ServiceProps> = ({
         // Check if has built image
         getHasBuiltImage()
       ) && (
-        <StatusFooter
-          chart={chart}
-          service={service}
-        />
-      )}
+          <StatusFooter
+            chart={chart}
+            service={service}
+          />
+        )}
       <Spacer y={0.5} />
     </>
   );
@@ -141,7 +139,7 @@ const ServiceTitle = styled.div`
   align-items: center;
 `;
 
-const StyledSourceBox = styled.div<{ 
+const StyledSourceBox = styled.div<{
   showExpanded: boolean,
   chart: any,
   hasFooter?: boolean,

+ 25 - 26
dashboard/src/main/home/app-dashboard/new-app-flow/WebTabs.tsx

@@ -6,7 +6,6 @@ import TabSelector from "components/TabSelector";
 import Checkbox from "components/porter/Checkbox";
 import { WebService } from "./serviceTypes";
 import { Height } from "react-animate-height";
-import Tooltip from "components/porter/Tooltip";
 
 interface Props {
   service: WebService
@@ -62,8 +61,8 @@ const WebTabs: React.FC<Props> = ({
       <>
         <Spacer y={1} />
         <Input
-          label="CPUs"
-          placeholder="ex: 0.5"
+          label="CPUs (Millicores)"
+          placeholder="ex: 500"
           value={service.cpu.value}
           disabled={service.cpu.readOnly}
           width="300px"
@@ -85,16 +84,16 @@ const WebTabs: React.FC<Props> = ({
           label="Replicas"
           placeholder="ex: 1"
           value={service.replicas.value}
-          disabled={service.replicas.readOnly || service.autoscalingOn.value}
+          disabled={service.replicas.readOnly || service.autoscaling.enabled.value}
           width="300px"
           setValue={(e) => { editService({ ...service, replicas: { readOnly: false, value: e } }) }}
           disabledTooltip={service.replicas.readOnly ? "You may only edit this field in your porter.yaml." : "Disable autoscaling to specify replicas."}
         />
         <Spacer y={1} />
         <Checkbox
-          checked={service.autoscalingOn.value}
-          toggleChecked={() => { editService({ ...service, autoscalingOn: { readOnly: false, value: !service.autoscalingOn.value } }) }}
-          disabled={service.autoscalingOn.readOnly}
+          checked={service.autoscaling.enabled.value}
+          toggleChecked={() => { editService({ ...service, autoscaling: { ...service.autoscaling, enabled: { readOnly: false, value: !service.autoscaling.enabled.value } } }) }}
+          disabled={service.autoscaling.enabled.readOnly}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         >
           <Text color="helper">Enable autoscaling (overrides replicas)</Text>
@@ -103,41 +102,41 @@ const WebTabs: React.FC<Props> = ({
         <Input
           label="Min replicas"
           placeholder="ex: 1"
-          value={service.minReplicas.value}
-          disabled={service.minReplicas.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.minReplicas.value}
+          disabled={service.autoscaling.minReplicas.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, minReplicas: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.minReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify min replicas."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, minReplicas: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.minReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify min replicas."}
         />
         <Spacer y={1} />
         <Input
           label="Max replicas"
           placeholder="ex: 10"
-          value={service.maxReplicas.value}
-          disabled={service.maxReplicas.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.maxReplicas.value}
+          disabled={service.autoscaling.maxReplicas.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, maxReplicas: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.maxReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify max replicas."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, maxReplicas: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.maxReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify max replicas."}
         />
         <Spacer y={1} />
         <Input
           label="Target CPU utilization (%)"
           placeholder="ex: 50"
-          value={service.targetCPUUtilizationPercentage.value}
-          disabled={service.targetCPUUtilizationPercentage.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.targetCPUUtilizationPercentage.value}
+          disabled={service.autoscaling.targetCPUUtilizationPercentage.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, targetCPUUtilizationPercentage: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.targetCPUUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target CPU utilization."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, targetCPUUtilizationPercentage: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.targetCPUUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target CPU utilization."}
         />
         <Spacer y={1} />
         <Input
           label="Target RAM utilization (%)"
           placeholder="ex: 50"
-          value={service.targetRAMUtilizationPercentage.value}
-          disabled={service.targetRAMUtilizationPercentage.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.targetMemoryUtilizationPercentage.value}
+          disabled={service.autoscaling.targetMemoryUtilizationPercentage.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, targetRAMUtilizationPercentage: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.targetRAMUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target RAM utilization."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, targetMemoryUtilizationPercentage: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.targetMemoryUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target RAM utilization."}
         />
       </>
     )
@@ -171,11 +170,11 @@ const WebTabs: React.FC<Props> = ({
         currentTab={currentTab}
         setCurrentTab={(value: string) => {
           if (value === 'main') {
-            setHeight(300);
+            setHeight(287);
           } else if (value === 'resources') {
-            setHeight(713.5);
+            setHeight(713);
           } else if (value === 'advanced') {
-            setHeight(159);
+            setHeight(158);
           }
           setCurrentTab(value);
         }}

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

@@ -42,8 +42,8 @@ const WorkerTabs: React.FC<Props> = ({
       <>
         <Spacer y={1} />
         <Input
-          label="CPUs"
-          placeholder="ex: 0.5"
+          label="CPUs (Millicores)"
+          placeholder="ex: 500"
           value={service.cpu.value}
           disabled={service.cpu.readOnly}
           width="300px"
@@ -65,16 +65,16 @@ const WorkerTabs: React.FC<Props> = ({
           label="Replicas"
           placeholder="ex: 1"
           value={service.replicas.value}
-          disabled={service.replicas.readOnly || service.autoscalingOn.value}
+          disabled={service.replicas.readOnly || service.autoscaling.enabled.value}
           width="300px"
           setValue={(e) => { editService({ ...service, replicas: { readOnly: false, value: e } }) }}
           disabledTooltip={service.replicas.readOnly ? "You may only edit this field in your porter.yaml." : "Disable autoscaling to specify replicas."}
         />
         <Spacer y={1} />
         <Checkbox
-          checked={service.autoscalingOn.value}
-          toggleChecked={() => { editService({ ...service, autoscalingOn: { readOnly: false, value: !service.autoscalingOn.value } }) }}
-          disabled={service.autoscalingOn.readOnly}
+          checked={service.autoscaling.enabled.value}
+          toggleChecked={() => { editService({ ...service, autoscaling: { ...service.autoscaling, enabled: { readOnly: false, value: !service.autoscaling.enabled.value } } }) }}
+          disabled={service.autoscaling.enabled.readOnly}
           disabledTooltip={"You may only edit this field in your porter.yaml."}
         >
           <Text color="helper">Enable autoscaling (overrides replicas)</Text>
@@ -83,41 +83,41 @@ const WorkerTabs: React.FC<Props> = ({
         <Input
           label="Min replicas"
           placeholder="ex: 1"
-          value={service.minReplicas.value}
-          disabled={service.minReplicas.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.minReplicas.value}
+          disabled={service.autoscaling.minReplicas.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, minReplicas: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.minReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify min replicas."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, minReplicas: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.minReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify min replicas."}
         />
         <Spacer y={1} />
         <Input
           label="Max replicas"
           placeholder="ex: 10"
-          value={service.maxReplicas.value}
-          disabled={service.maxReplicas.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.maxReplicas.value}
+          disabled={service.autoscaling.maxReplicas.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, maxReplicas: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.maxReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify max replicas."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, maxReplicas: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.maxReplicas.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify max replicas."}
         />
         <Spacer y={1} />
         <Input
           label="Target CPU utilization (%)"
           placeholder="ex: 50"
-          value={service.targetCPUUtilizationPercentage.value}
-          disabled={service.targetCPUUtilizationPercentage.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.targetCPUUtilizationPercentage.value}
+          disabled={service.autoscaling.targetCPUUtilizationPercentage.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, targetCPUUtilizationPercentage: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.targetCPUUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target CPU utilization."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, targetCPUUtilizationPercentage: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.targetCPUUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target CPU utilization."}
         />
         <Spacer y={1} />
         <Input
           label="Target RAM utilization (%)"
           placeholder="ex: 50"
-          value={service.targetRAMUtilizationPercentage.value}
-          disabled={service.targetRAMUtilizationPercentage.readOnly || !service.autoscalingOn.value}
+          value={service.autoscaling.targetMemoryUtilizationPercentage.value}
+          disabled={service.autoscaling.targetMemoryUtilizationPercentage.readOnly || !service.autoscaling.enabled.value}
           width="300px"
-          setValue={(e) => { editService({ ...service, targetRAMUtilizationPercentage: { readOnly: false, value: e } }) }}
-          disabledTooltip={service.targetRAMUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target RAM utilization."}
+          setValue={(e) => { editService({ ...service, autoscaling: { ...service.autoscaling, targetMemoryUtilizationPercentage: { readOnly: false, value: e } } }) }}
+          disabledTooltip={service.autoscaling.targetMemoryUtilizationPercentage.readOnly ? "You may only edit this field in your porter.yaml." : "Enable autoscaling to specify target RAM utilization."}
         />
       </>
     )
@@ -133,9 +133,9 @@ const WorkerTabs: React.FC<Props> = ({
         currentTab={currentTab}
         setCurrentTab={(value: string) => {
           if (value === 'main') {
-            setHeight(159);
+            setHeight(158);
           } else if (value === 'resources') {
-            setHeight(713.5);
+            setHeight(713);
           }
           setCurrentTab(value);
         }}

+ 71 - 79
dashboard/src/main/home/app-dashboard/new-app-flow/serviceTypes.ts

@@ -42,11 +42,7 @@ type SharedServiceParams = {
 export type WorkerService = SharedServiceParams & {
     type: 'worker';
     replicas: ServiceString;
-    autoscalingOn: ServiceBoolean;
-    minReplicas: ServiceString;
-    maxReplicas: ServiceString;
-    targetCPUUtilizationPercentage: ServiceString;
-    targetRAMUtilizationPercentage: ServiceString;
+    autoscaling: Autoscaling;
 }
 const WorkerService = {
     default: (name: string, porterJson?: PorterJson): WorkerService => ({
@@ -56,23 +52,16 @@ const WorkerService = {
         startCommand: ServiceField.string('', porterJson?.apps?.[name]?.run),
         type: 'worker',
         replicas: ServiceField.string('1', porterJson?.apps?.[name]?.config?.replicaCount),
-        autoscalingOn: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
-        minReplicas: ServiceField.string('1', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
-        maxReplicas: ServiceField.string('10', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
-        targetCPUUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
-        targetRAMUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+        autoscaling: {
+            enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
+            minReplicas: ServiceField.string('1', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
+            maxReplicas: ServiceField.string('10', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
+            targetCPUUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
+            targetMemoryUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+        },
         canDelete: porterJson?.apps?.[name] == null,
     }),
     serialize: (service: WorkerService) => {
-        const autoscaling = service.autoscalingOn.value ? {
-            autoscaling: {
-                enabled: true,
-                minReplicas: service.minReplicas.value,
-                maxReplicas: service.maxReplicas.value,
-                targetCPUUtilizationPercentage: service.targetCPUUtilizationPercentage.value,
-                targetMemoryUtilizationPercentage: service.targetRAMUtilizationPercentage.value,
-            }
-        } : {};
         return {
             replicaCount: service.replicas.value,
             container: {
@@ -84,7 +73,13 @@ const WorkerService = {
                     memory: service.ram.value + 'Mi',
                 }
             },
-            ...autoscaling,
+            autoscaling: {
+                enabled: service.autoscaling.enabled.value,
+                minReplicas: service.autoscaling.minReplicas.value,
+                maxReplicas: service.autoscaling.maxReplicas.value,
+                targetCPUUtilizationPercentage: service.autoscaling.targetCPUUtilizationPercentage.value,
+                targetMemoryUtilizationPercentage: service.autoscaling.targetMemoryUtilizationPercentage.value,
+            },
         }
     },
     deserialize: (name: string, values: any, porterJson?: PorterJson): WorkerService => {
@@ -95,11 +90,13 @@ const WorkerService = {
             startCommand: ServiceField.string(values.container?.command ?? '', porterJson?.apps?.[name]?.run),
             type: 'worker',
             replicas: ServiceField.string(values.replicaCount ?? '', porterJson?.apps?.[name]?.config?.replicaCount),
-            autoscalingOn: ServiceField.boolean(values.autoscaling?.enabled ?? false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
-            minReplicas: ServiceField.string(values.autoscaling?.minReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
-            maxReplicas: ServiceField.string(values.autoscaling?.maxReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
-            targetCPUUtilizationPercentage: ServiceField.string(values.autoscaling?.targetCPUUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
-            targetRAMUtilizationPercentage: ServiceField.string(values.autoscaling?.targetMemoryUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+            autoscaling: {
+                enabled: ServiceField.boolean(values.autoscaling?.enabled ?? false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
+                minReplicas: ServiceField.string(values.autoscaling?.minReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
+                maxReplicas: ServiceField.string(values.autoscaling?.maxReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
+                targetCPUUtilizationPercentage: ServiceField.string(values.autoscaling?.targetCPUUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
+                targetMemoryUtilizationPercentage: ServiceField.string(values.autoscaling?.targetMemoryUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+            },
             canDelete: porterJson?.apps?.[name] == null,
         }
     }
@@ -110,6 +107,7 @@ export type WebService = SharedServiceParams & Omit<WorkerService, 'type'> & {
     port: ServiceString;
     generateUrlForExternalTraffic: ServiceBoolean;
     customDomain: ServiceString;
+    ingress: Ingress;
 }
 const WebService = {
     default: (name: string, porterJson?: PorterJson): WebService => ({
@@ -119,53 +117,24 @@ const WebService = {
         startCommand: ServiceField.string('', porterJson?.apps?.[name]?.run),
         type: 'web',
         replicas: ServiceField.string('1', porterJson?.apps?.[name]?.config?.replicaCount),
-        autoscalingOn: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
-        minReplicas: ServiceField.string('1', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
-        maxReplicas: ServiceField.string('10', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
-        targetCPUUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
-        targetRAMUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+        autoscaling: {
+            enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
+            minReplicas: ServiceField.string('1', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
+            maxReplicas: ServiceField.string('10', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
+            targetCPUUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
+            targetMemoryUtilizationPercentage: ServiceField.string('50', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+        },
+        ingress: {
+            enabled: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.ingress?.enabled),
+            hosts: ServiceField.string('', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
+            porterHosts: ServiceField.string('', porterJson?.apps?.[name]?.config?.ingress?.porter_hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.porter_hosts[0] : undefined),
+        },
         port: ServiceField.string('80', porterJson?.apps?.[name]?.config?.container?.port),
         generateUrlForExternalTraffic: ServiceField.boolean(false, porterJson?.apps?.[name]?.config?.ingress?.enabled),
         customDomain: ServiceField.string('', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
         canDelete: porterJson?.apps?.[name] == null,
     }),
     serialize: (service: WebService) => {
-        const getIngress = (service: WebService): Ingress => {
-            if (!service.generateUrlForExternalTraffic.value) {
-                return {
-                    ingress: {
-                        enabled: false,
-                        hosts: [],
-                        custom_domain: false,
-                        porter_hosts: [],
-                    }
-                }
-            }
-            const ingress: Ingress = {
-                ingress: {
-                    enabled: true,
-                    hosts: [],
-                    custom_domain: false,
-                    porter_hosts: [],
-                }
-            };
-            if (service.customDomain.value) {
-                ingress.ingress.hosts.push(service.customDomain.value);
-                ingress.ingress.custom_domain = true;
-            }
-            return ingress;
-        }
-
-        const autoscaling = service.autoscalingOn.value ? {
-            autoscaling: {
-                enabled: true,
-                minReplicas: service.minReplicas.value,
-                maxReplicas: service.maxReplicas.value,
-                targetCPUUtilizationPercentage: service.targetCPUUtilizationPercentage.value,
-                targetMemoryUtilizationPercentage: service.targetRAMUtilizationPercentage.value,
-            }
-        } : {};
-
         return {
             replicaCount: service.replicas.value,
             resources: {
@@ -181,8 +150,19 @@ const WebService = {
             service: {
                 port: service.port.value,
             },
-            ...autoscaling,
-            ...getIngress(service),
+            autoscaling: {
+                enabled: service.autoscaling.enabled.value,
+                minReplicas: service.autoscaling.minReplicas.value,
+                maxReplicas: service.autoscaling.maxReplicas.value,
+                targetCPUUtilizationPercentage: service.autoscaling.targetCPUUtilizationPercentage.value,
+                targetMemoryUtilizationPercentage: service.autoscaling.targetMemoryUtilizationPercentage.value,
+            },
+            ingress: {
+                enabled: service.ingress.enabled.value,
+                hosts: service.ingress.hosts.value ? [service.ingress.hosts.value] : [],
+                custom_domain: service.ingress.hosts.value ? true : false,
+                porter_hosts: service.ingress.porterHosts.value ? [service.ingress.porterHosts.value] : [],
+            }
         }
     },
     deserialize: (name: string, values: any, porterJson?: PorterJson): WebService => {
@@ -193,11 +173,18 @@ const WebService = {
             startCommand: ServiceField.string(values.container?.command ?? '', porterJson?.apps?.[name]?.run),
             type: 'web',
             replicas: ServiceField.string(values.replicaCount ?? '', porterJson?.apps?.[name]?.config?.replicaCount),
-            autoscalingOn: ServiceField.boolean(values.autoscaling?.enabled ?? false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
-            minReplicas: ServiceField.string(values.autoscaling?.minReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
-            maxReplicas: ServiceField.string(values.autoscaling?.maxReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
-            targetCPUUtilizationPercentage: ServiceField.string(values.autoscaling?.targetCPUUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
-            targetRAMUtilizationPercentage: ServiceField.string(values.autoscaling?.targetMemoryUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+            autoscaling: {
+                enabled: ServiceField.boolean(values.autoscaling?.enabled ?? false, porterJson?.apps?.[name]?.config?.autoscaling?.enabled),
+                minReplicas: ServiceField.string(values.autoscaling?.minReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.minReplicas),
+                maxReplicas: ServiceField.string(values.autoscaling?.maxReplicas ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.maxReplicas),
+                targetCPUUtilizationPercentage: ServiceField.string(values.autoscaling?.targetCPUUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetCPUUtilizationPercentage),
+                targetMemoryUtilizationPercentage: ServiceField.string(values.autoscaling?.targetMemoryUtilizationPercentage ?? '', porterJson?.apps?.[name]?.config?.autoscaling?.targetMemoryUtilizationPercentage),
+            },
+            ingress: {
+                enabled: ServiceField.boolean(values.ingress?.enabled ?? false, porterJson?.apps?.[name]?.config?.ingress?.enabled),
+                hosts: ServiceField.string(values.ingress?.hosts?.length ? values.ingress.hosts[0] : '', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
+                porterHosts: ServiceField.string(values.ingress?.porter_hosts?.length ? values.ingress.porter_hosts[0] : '', porterJson?.apps?.[name]?.config?.ingress?.porter_hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.porter_hosts[0] : undefined),
+            },
             port: ServiceField.string(values.container?.port ?? '', porterJson?.apps?.[name]?.config?.container?.port),
             generateUrlForExternalTraffic: ServiceField.boolean(values.ingress?.enabled ?? false, porterJson?.apps?.[name]?.config?.ingress?.enabled),
             customDomain: ServiceField.string(values.ingress?.hosts?.length ? values.ingress.hosts[0] : '', porterJson?.apps?.[name]?.config?.ingress?.hosts?.length ? porterJson?.apps?.[name]?.config?.ingress?.hosts[0] : undefined),
@@ -381,10 +368,15 @@ export const Service = {
 }
 
 type Ingress = {
-    ingress: {
-        enabled: boolean;
-        hosts: string[];
-        custom_domain: boolean;
-        porter_hosts: string[];
-    }
+    enabled: ServiceBoolean;
+    hosts: ServiceString;
+    porterHosts: ServiceString;
+}
+
+type Autoscaling = {
+    enabled: ServiceBoolean,
+    minReplicas: ServiceString,
+    maxReplicas: ServiceString,
+    targetCPUUtilizationPercentage: ServiceString,
+    targetMemoryUtilizationPercentage: ServiceString,
 }

+ 1 - 0
dashboard/src/shared/types.tsx

@@ -657,4 +657,5 @@ export interface PorterAppOptions {
     repository: string;
     tag: string;
   };
+  override_release?: boolean;
 }