Procházet zdrojové kódy

PorterYaml SubDirectory (#3010)

* PorterYaml SubDirectory

* DB additions

* Final UI changes

---------

Co-authored-by: Feroze Mohideen <feroze@porter.run>
sdess09 před 3 roky
rodič
revize
f90c43a620

+ 9 - 1
api/server/handlers/stacks/create_porter_app.go

@@ -99,6 +99,13 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 		releaseDependencies = helmRelease.Chart.Metadata.Dependencies
 	}
 
+	if request.Builder == "" {
+		// attempt to get builder from db
+		app, err := c.Repo().PorterApp().ReadPorterAppByName(cluster.ID, stackName)
+		if err == nil {
+			request.Builder = app.Builder
+		}
+	}
 	injectLauncher := strings.Contains(request.Builder, "heroku") ||
 		strings.Contains(request.Builder, "paketo")
 
@@ -185,7 +192,7 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 			return
 		} else if existing.Name != "" {
 			c.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(
-				fmt.Errorf("porter app with name %s already exists in this environment", existing.Name), http.StatusForbidden))
+				fmt.Errorf("app with name %s already exists in your project", existing.Name), http.StatusForbidden))
 			return
 		}
 
@@ -203,6 +210,7 @@ func (c *CreatePorterAppHandler) ServeHTTP(w http.ResponseWriter, r *http.Reques
 			Dockerfile:     request.Dockerfile,
 			ImageRepoURI:   request.ImageRepoURI,
 			PullRequestURL: request.PullRequestURL,
+			PorterYamlPath: request.PorterYamlPath,
 		}
 
 		// create the db entry

+ 11 - 9
api/server/handlers/stacks/create_secret_and_open_pr.go

@@ -83,15 +83,17 @@ func (c *OpenStackPRHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	var pr *github.PullRequest
 	if request.OpenPr {
 		pr, err = actions.OpenGithubPR(&actions.GithubPROpts{
-			Client:        client,
-			GitRepoOwner:  request.GithubRepoOwner,
-			GitRepoName:   request.GithubRepoName,
-			StackName:     stackName,
-			ProjectID:     project.ID,
-			ClusterID:     cluster.ID,
-			ServerURL:     c.Config().ServerConf.ServerURL,
-			DefaultBranch: request.Branch,
-			SecretName:    secretName,
+			Client:         client,
+			GitRepoOwner:   request.GithubRepoOwner,
+			GitRepoName:    request.GithubRepoName,
+			StackName:      stackName,
+			ProjectID:      project.ID,
+			ClusterID:      cluster.ID,
+			ServerURL:      c.Config().ServerConf.ServerURL,
+			DefaultBranch:  request.Branch,
+			SecretName:     secretName,
+			PorterYamlPath: request.PorterYamlPath,
+			Body:           "Hello 👋 from Porter! Please merge this PR to finish setting up your application.",
 		})
 	}
 

+ 13 - 21
api/server/handlers/stacks/parse.go

@@ -123,36 +123,28 @@ func buildStackValues(parsed *PorterStackYAML, imageInfo types.ImageInfo, existi
 			}
 		}
 
-		// prepend launcher if we need to
-		if helm_values["container"] != nil {
-			containerMap := helm_values["container"].(map[string]interface{})
-			if containerMap["command"] != nil {
-				command := containerMap["command"].(string)
-				if injectLauncher && !strings.HasPrefix(command, "launcher") && !strings.HasPrefix(command, "/cnb/lifecycle/launcher") {
-					containerMap["command"] = fmt.Sprintf("/cnb/lifecycle/launcher %s", command)
-				}
-			}
-		}
-
 		values[helmName] = helm_values
 	}
 
 	// add back in the existing services that were not overwritten
 	for k, v := range existingValues {
 		if values[k] == nil {
-			// make sure we prepend launcher to services that aren't specified in porter.yaml as well
-			if existingServiceValues, ok := v.(map[string]interface{}); ok {
-				if existingServiceValues["container"] != nil {
-					containerMap := existingServiceValues["container"].(map[string]interface{})
-					if containerMap["command"] != nil {
-						command := containerMap["command"].(string)
-						if injectLauncher && !strings.HasPrefix(command, "launcher") && !strings.HasPrefix(command, "/cnb/lifecycle/launcher") {
-							containerMap["command"] = fmt.Sprintf("/cnb/lifecycle/launcher %s", command)
-						}
+			values[k] = v
+		}
+	}
+
+	// prepend launcher to all start commands if we need to
+	for _, v := range values {
+		if serviceValues, ok := v.(map[string]interface{}); ok {
+			if serviceValues["container"] != nil {
+				containerMap := serviceValues["container"].(map[string]interface{})
+				if containerMap["command"] != nil {
+					command := containerMap["command"].(string)
+					if injectLauncher && !strings.HasPrefix(command, "launcher") && !strings.HasPrefix(command, "/cnb/lifecycle/launcher") {
+						containerMap["command"] = fmt.Sprintf("/cnb/lifecycle/launcher %s", command)
 					}
 				}
 			}
-			values[k] = v
 		}
 	}
 

+ 5 - 0
api/types/porter_app.go

@@ -20,6 +20,10 @@ type PorterApp struct {
 	Buildpacks     string `json:"build_packs,omitempty"`
 	Dockerfile     string `json:"dockerfile,omitempty"`
 	PullRequestURL string `json:"pull_request_url,omitempty"`
+
+	// Porter YAML
+	PorterYAMLBase64 string `json:"porter_yaml,omitempty"`
+	PorterYamlPath   string `json:"porter_yaml_path,omitempty"`
 }
 
 // swagger:model
@@ -36,6 +40,7 @@ type CreatePorterAppRequest struct {
 	ImageRepoURI     string    `json:"image_repo_uri"`
 	PullRequestURL   string    `json:"pull_request_url"`
 	PorterYAMLBase64 string    `json:"porter_yaml"`
+	PorterYamlPath   string    `json:"porter_yaml_path"`
 	ImageInfo        ImageInfo `json:"image_info" form:"omitempty"`
 	OverrideRelease  bool      `json:"override_release"`
 }

+ 1 - 0
api/types/stack.go

@@ -17,6 +17,7 @@ type CreateSecretAndOpenGHPRRequest struct {
 	GithubRepoName          string `json:"github_repo_name" form:"required"`
 	OpenPr                  bool   `json:"open_pr"`
 	Branch                  string `json:"branch"`
+	PorterYamlPath          string `json:"porter_yaml_path"`
 }
 
 type CreateSecretAndOpenGHPRResponse struct {

+ 11 - 1
dashboard/src/components/SearchBar.tsx

@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
 import Button from "./Button";
 import styled from "styled-components";
 
@@ -16,6 +16,15 @@ const SearchBar: React.FC<Props> = ({
   fullWidth,
 }) => {
   const [searchInput, setSearchInput] = useState("");
+  const inputRef = useRef(null);
+
+  // hack for deferring the focus call to the next tick of the event loop, giving the browser enough time to render the input element before setting focus on it
+  useEffect(() => {
+    setTimeout(() => {
+      inputRef.current.focus();
+    }, 0);
+  }, []);
+
 
   return (
     <SearchRowWrapper fullWidth={fullWidth}>
@@ -33,6 +42,7 @@ const SearchBar: React.FC<Props> = ({
             }
           }}
           placeholder={prompt}
+          ref={inputRef}
         />
       </SearchBarWrapper>
       <ButtonWrapper disabled={disabled}>

+ 10 - 4
dashboard/src/components/repo-selector/ActionConfBranchSelector.tsx

@@ -15,15 +15,20 @@ type Props = {
   branch: string;
   setActionConfig: (x: ActionConfigType) => void;
   setBranch: (x: string) => void;
-
   setDockerfilePath: (x: string) => void;
-
   setFolderPath: (x: string) => void;
   setBuildView?: (x: string) => void;
+  setPorterYamlPath?: (x: string) => void;
 };
 
 const ActionConfEditorStack: React.FC<Props> = (props) => {
-  const { actionConfig, setBranch, setActionConfig, branch } = props;
+  const {
+    actionConfig,
+    setBranch,
+    setActionConfig,
+    branch,
+    setPorterYamlPath,
+  } = props;
 
   if (!actionConfig.git_repo) {
     return (
@@ -57,7 +62,7 @@ const ActionConfEditorStack: React.FC<Props> = (props) => {
         type="text"
         width="100%"
         value={props?.branch}
-        setValue={() => { }}
+        setValue={() => {}}
         placeholder=""
       />
       <BackButton
@@ -68,6 +73,7 @@ const ActionConfEditorStack: React.FC<Props> = (props) => {
           props.setDockerfilePath("");
           props.setActionConfig(actionConfig);
           props.setBuildView("buildpacks");
+          setPorterYamlPath("");
         }}
       >
         <i className="material-icons">keyboard_backspace</i>

+ 4 - 1
dashboard/src/components/repo-selector/ActionConfEditorStack.tsx

@@ -14,6 +14,7 @@ type Props = {
   setDockerfilePath: (x: string) => void;
   setFolderPath: (x: string) => void;
   setBuildView?: (x: string) => void;
+  setPorterYamlPath?: (x: string) => void;
 };
 
 const defaultActionConfig: ActionConfigType = {
@@ -31,6 +32,7 @@ const ActionConfEditorStack: React.FC<Props> = ({
   setFolderPath,
   setDockerfilePath,
   setBuildView,
+  setPorterYamlPath,
 }) => {
   if (!actionConfig.git_repo) {
     return (
@@ -50,7 +52,7 @@ const ActionConfEditorStack: React.FC<Props> = ({
           label="GitHub repository:"
           width="100%"
           value={actionConfig?.git_repo}
-          setValue={() => { }}
+          setValue={() => {}}
           placeholder=""
         />
         <BackButton
@@ -61,6 +63,7 @@ const ActionConfEditorStack: React.FC<Props> = ({
             setFolderPath("");
             setDockerfilePath("");
             setBuildView("buildpacks");
+            setPorterYamlPath("");
           }}
         >
           <i className="material-icons">keyboard_backspace</i>

+ 93 - 7
dashboard/src/components/repo-selector/DetectContentsList.tsx

@@ -13,6 +13,10 @@ import Loading from "../Loading";
 import Spacer from "components/porter/Spacer";
 import AdvancedBuildSettings from "main/home/app-dashboard/new-app-flow/AdvancedBuildSettings";
 import { render } from "react-dom";
+import Modal from "components/porter/Modal";
+import Input from "components/porter/Input";
+import Text from "components/porter/Text";
+import { set } from "lodash";
 
 interface AutoBuildpack {
   name?: string;
@@ -32,9 +36,12 @@ type PropsType = {
   setPorterYaml: (x: any) => void;
   buildView: string;
   setBuildView: (x: string) => void;
+  porterYamlPath: string;
+  setPorterYamlPath: (x: string) => void;
 };
 
 const DetectContentsList: React.FC<PropsType> = (props) => {
+  const [showModal, setShowModal] = useState(false);
   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(false);
   const [contents, setContents] = useState<FileType[]>([]);
@@ -57,15 +64,25 @@ const DetectContentsList: React.FC<PropsType> = (props) => {
     }
   }, []);
 
-  useEffect(() => {
-    const porterYamlItem = contents.find((item: FileType) =>
-      item.path.includes("porter.yaml")
-    );
+  const toggleModal = async () => {
+    if (!showModal) {
+      const porterYamlItem = contents.find((item: FileType) =>
+        item.path.includes(props.porterYamlPath + "porter.yaml")
+      );
+      if (porterYamlItem) {
+        fetchAndSetPorterYaml(props.porterYamlPath + "porter.yaml");
+        props.setPorterYamlPath("porter.yaml");
+        return;
+      }
+    }
+    setShowModal(!showModal);
+  };
 
-    if (porterYamlItem) {
-      fetchAndSetPorterYaml("porter.yaml");
+  useEffect(() => {
+    if (!loading) {
+      toggleModal();
     }
-  }, [contents, fetchAndSetPorterYaml]);
+  }, [loading]);
 
   useEffect(() => {
     updateContents();
@@ -223,8 +240,77 @@ const DetectContentsList: React.FC<PropsType> = (props) => {
       });
     }
   };
+  const updatePorterYamlPath = () => {
+    toggleModal();
+
+    fetchAndSetPorterYaml(props.porterYamlPath);
+  };
+  const ignoreModal = () => {
+    toggleModal();
+
+    props.setPorterYamlPath("");
+  };
+
+  const NoPorterYamlContent = () => (
+    <div>
+      <h3>No porter.yaml detected</h3>
+      <p>
+        We were unable to find{" "}
+        <a
+          href="https://docs.porter.run/"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          porter.yaml
+        </a>{" "}
+        in your root directory. We recommend that you add{" "}
+        <a
+          href="https://docs.porter.run/"
+          target="_blank"
+          rel="noopener noreferrer"
+        >
+          porter.yaml
+        </a>{" "}
+        to your root directory or specify the subdirectory path here.
+      </p>
+    </div>
+  );
   return (
     <>
+      {showModal && (
+        <Modal closeModal={toggleModal}>
+          <NoPorterYamlContent />
+          <Spacer y={0.5} />
+          <Text color="helper">Porter.yaml path:</Text>
+          <Spacer y={0.5} />
+          <Input
+            disabled={false}
+            placeholder="ex: ./subdirectory/porter.yaml"
+            value={props.porterYamlPath}
+            width="100%"
+            setValue={props.setPorterYamlPath}
+          />
+          <Spacer y={1} />
+          <div style={{ display: "flex", justifyContent: "space-between" }}>
+            <Button
+              onClick={ignoreModal}
+              loadingText="Submitting..."
+              color="#ffffff11"
+              status={loading ? "loading" : undefined}
+            >
+              Ignore
+            </Button>
+            <Button
+              onClick={updatePorterYamlPath}
+              loadingText="Submitting..."
+              color="#616fee"
+              status={loading ? "loading" : undefined}
+            >
+              Update Path
+            </Button>
+          </div>
+        </Modal>
+      )}
       {renderContentList() && (
         <>
           <AdvancedBuildSettings

+ 5 - 3
dashboard/src/main/home/app-dashboard/AppDashboard.tsx

@@ -106,9 +106,11 @@ const AppDashboard: React.FC<Props> = ({ }) => {
         })
       );
       apps.forEach((app: any, i: number) => {
-        app["last_deployed"] = readableDate(
-          timeRes[i].data[0]?.info?.last_deployed
-        );
+        if (timeRes?.[i]?.data?.[0]?.info?.last_deployed != null) {
+          app["last_deployed"] = readableDate(
+            timeRes[i].data[0].info.last_deployed
+          );
+        }
       });
       setApps(apps.reverse());
       setIsLoading(false);

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

@@ -136,7 +136,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
         );
       } catch (err) {
         // do nothing, unable to find release chart
-        console.log(err);
+        // console.log(err);
       }
 
       // update apps and release
@@ -146,7 +146,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
         releaseChart: releaseChartData?.data,
       };
       const porterJson = await fetchPorterYamlContent(
-        "porter.yaml",
+        resPorterApp?.data?.porter_yaml_path ?? "porter.yaml",
         newAppData
       );
 
@@ -773,6 +773,7 @@ const ExpandedApp: React.FC<Props> = ({ ...props }) => {
                   pullRequestUrl={appData.app.pull_request_url}
                   stackName={appData.app.name}
                   gitRepoId={appData.app.git_repo_id}
+                  porterYamlPath={appData.app.porter_yaml_path}
                 />
               ) : !hasBuiltImage ? (
                 <Banner

+ 5 - 2
dashboard/src/main/home/app-dashboard/expanded-app/GHABanner.tsx

@@ -18,6 +18,7 @@ type Props = {
   repoName: string;
   stackName: string;
   gitRepoId: number;
+  porterYamlPath?: string;
 };
 
 const GHABanner: React.FC<Props> = ({
@@ -26,6 +27,7 @@ const GHABanner: React.FC<Props> = ({
   repoName,
   stackName,
   gitRepoId,
+  porterYamlPath = "porter.yaml",
 }) => {
   const { currentProject, currentCluster } = useContext(Context);
   const [showGHAModal, setShowGHAModal] = useState(false);
@@ -34,7 +36,7 @@ const GHABanner: React.FC<Props> = ({
       <StyledGHABanner>
         <>
           {pullRequestUrl ? (
-            <Banner 
+            <Banner
               type="warning"
               suffix={
                 <RefreshButton onClick={() => window.location.reload()}>
@@ -57,7 +59,7 @@ const GHABanner: React.FC<Props> = ({
               </Container>
             </Banner>
           ) : (
-            <Banner   
+            <Banner
               type="warning"
               suffix={
                 <RefreshButton onClick={() => window.location.reload()}>
@@ -88,6 +90,7 @@ const GHABanner: React.FC<Props> = ({
           stackName={stackName}
           projectId={currentProject.id}
           clusterId={currentCluster.id}
+          porterYamlPath={porterYamlPath}
         />
       )}
     </>

+ 24 - 0
dashboard/src/main/home/app-dashboard/expanded-app/SharedBuildSettings.tsx

@@ -27,6 +27,8 @@ type Props = {
   setImageUrl: (x: string) => void;
   buildView: string;
   setBuildView: (x: string) => void;
+  porterYamlPath: string;
+  setPorterYamlPath: (x: string) => void;
 };
 
 const SharedBuildSettings: React.FC<Props> = ({
@@ -45,6 +47,8 @@ const SharedBuildSettings: React.FC<Props> = ({
   setImageUrl,
   buildView,
   setBuildView,
+  porterYamlPath,
+  setPorterYamlPath,
 }) => {
   const [isExpanded, setIsExpanded] = useState(false);
 
@@ -67,6 +71,7 @@ const SharedBuildSettings: React.FC<Props> = ({
         setDockerfilePath={setDockerfilePath}
         setFolderPath={setFolderPath}
         setBuildView={setBuildView}
+        setPorterYamlPath={setPorterYamlPath}
       />
       <DarkMatter antiHeight="-4px" />
       <Spacer y={0.3} />
@@ -89,6 +94,7 @@ const SharedBuildSettings: React.FC<Props> = ({
             setDockerfilePath={setDockerfilePath}
             setFolderPath={setFolderPath}
             setBuildView={setBuildView}
+            setPorterYamlPath={setPorterYamlPath}
           />
         </>
       )}
@@ -105,6 +111,22 @@ const SharedBuildSettings: React.FC<Props> = ({
             width="100%"
             setValue={setFolderPath}
           />
+          <Spacer y={1} />
+
+          {porterYamlPath != "porter.yaml" && porterYamlPath && (
+            <>
+              <Text color="helper">Porter.yaml path:</Text>
+              <Spacer y={0.5} />
+              <Input
+                disabled={true}
+                placeholder="ex: ./"
+                value={porterYamlPath}
+                width="100%"
+                setValue={setPorterYamlPath}
+              />
+              <Spacer y={1} />
+            </>
+          )}
           <DetectContentsList
             actionConfig={actionConfig}
             branch={branch}
@@ -118,6 +140,8 @@ const SharedBuildSettings: React.FC<Props> = ({
             setPorterYaml={setPorterYaml}
             buildView={buildView}
             setBuildView={setBuildView}
+            porterYamlPath={porterYamlPath}
+            setPorterYamlPath={setPorterYamlPath}
           />
         </>
       )}

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

@@ -29,6 +29,7 @@ type Props = RouteComponentProps & {
   clusterId?: number;
   deployPorterApp?: () => Promise<boolean>;
   deploymentError?: string;
+  porterYamlPath?: string;
 }
 
 type Choice = "open_pr" | "copy";
@@ -44,6 +45,7 @@ const GithubActionModal: React.FC<Props> = ({
   clusterId,
   deployPorterApp,
   deploymentError,
+  porterYamlPath,
   ...props
 }) => {
   const [choice, setChoice] = React.useState<Choice>("open_pr");
@@ -70,6 +72,7 @@ const GithubActionModal: React.FC<Props> = ({
               github_repo_name: githubRepoName,
               branch,
               open_pr: (choice === "open_pr" || isChecked),
+              porter_yaml_path: porterYamlPath,
             },
             {
               project_id: projectId,
@@ -113,10 +116,10 @@ const GithubActionModal: React.FC<Props> = ({
         }
         isInitiallyExpanded
         spaced
-        copy={getGithubAction(projectId, clusterId, stackName, branch)}
+        copy={getGithubAction(projectId, clusterId, stackName, branch, porterYamlPath)}
         ExpandedSection={
           <YamlEditor
-            value={getGithubAction(projectId, clusterId, stackName, branch)}
+            value={getGithubAction(projectId, clusterId, stackName, branch, porterYamlPath)}
             readOnly={true}
             height="300px"
           />

+ 17 - 30
dashboard/src/main/home/app-dashboard/new-app-flow/NewAppFlow.tsx

@@ -97,6 +97,7 @@ type Provider =
   };
 const NewAppFlow: React.FC<Props> = ({ ...props }) => {
   const [templateName, setTemplateName] = useState("");
+  const [porterYamlPath, setPorterYamlPath] = useState("");
 
   const [imageUrl, setImageUrl] = useState("");
   const [imageTag, setImageTag] = useState("latest");
@@ -331,14 +332,16 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
 
       const yamlString = yaml.dump(finalPorterYaml);
       const base64Encoded = btoa(yamlString);
-      const imageInfo = imageUrl
-        ? {
-          image_info: {
-            repository: imageUrl,
-            tag: imageTag,
-          },
-        }
-        : {};
+      let imageInfo = {
+        repository: "",
+        tag: "",
+      };
+      if (imageUrl && imageTag) {
+        imageInfo = {
+          repository: imageUrl,
+          tag: imageTag,
+        };
+      };
 
       await api.createPorterApp(
         "<token>",
@@ -356,7 +359,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
           image_repo_uri: imageUrl,
           porter_yaml: base64Encoded,
           override_release: true,
-          ...imageInfo,
+          image_info: imageInfo,
+          porter_yaml_path: porterYamlPath,
         },
         {
           cluster_id: currentCluster.id,
@@ -395,27 +399,6 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
       setCurrentStep(Math.max(currentStep, 2));
     }
   }, [imageUrl, buildConfig, dockerfilePath, setCurrentStep, currentStep]);
-  // useEffect(() => {
-  //   const fetchGithubAccounts = async () => {
-  //     try {
-  //       const { data } = await api.getGithubAccounts("<token>", {}, {});
-  //       setAccessData(data);
-  //       if (data) {
-  //         setHasProviders(false);
-  //       }
-  //     } catch (error) {
-  //       setAccessError(true);
-  //     } finally {
-  //       setAccessLoading(false);
-  //     }
-
-  //     setConnectModal(
-  //       !hasClickedDoNotConnect && (!hasProviders || accessError)
-  //     );
-  //   };
-
-  //   fetchGithubAccounts();
-  // }, [hasClickedDoNotConnect, accessData.accounts, accessError]);
 
   return (
     <CenterWrapper>
@@ -483,6 +466,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                 <SourceSelector
                   selectedSourceType={formState.selectedSourceType}
                   setSourceType={(type) => {
+                    setPorterYaml("");
                     setFormState({ ...formState, selectedSourceType: type });
                   }}
                 />
@@ -514,6 +498,8 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
                   setBuildView={setBuildView}
                   setCurrentStep={setCurrentStep}
                   currentStep={currentStep}
+                  porterYamlPath={porterYamlPath}
+                  setPorterYamlPath={setPorterYamlPath}
                 />
               </>,
               <>
@@ -624,6 +610,7 @@ const NewAppFlow: React.FC<Props> = ({ ...props }) => {
           clusterId={currentCluster.id}
           deployPorterApp={deployPorterApp}
           deploymentError={deploymentError}
+          porterYamlPath={porterYamlPath}
         />
       )}
     </CenterWrapper>

+ 11 - 0
dashboard/src/main/home/app-dashboard/new-app-flow/SourceSettings.tsx

@@ -40,6 +40,8 @@ type Props = {
   setBuildView: (x: string) => void;
   setCurrentStep: (x: number) => void;
   currentStep: number;
+  porterYamlPath: string;
+  setPorterYamlPath: (x: string) => void;
 };
 
 const SourceSettings: React.FC<Props> = ({
@@ -63,9 +65,16 @@ const SourceSettings: React.FC<Props> = ({
   setBuildView,
   setCurrentStep,
   currentStep,
+  setPorterYamlPath,
+  porterYamlPath,
   ...props
 }) => {
   const renderDockerSettings = () => {
+    setFolderPath("");
+    setDockerfilePath("");
+    setBuildView("buildpacks");
+    setPorterYamlPath("");
+    setBranch("");
     return (
       <>
         {/* /* <Text size={16}>Registry settings</Text>
@@ -139,6 +148,8 @@ const SourceSettings: React.FC<Props> = ({
               setImageUrl={setImageUrl}
               buildView={buildView}
               setBuildView={setBuildView}
+              porterYamlPath={porterYamlPath}
+              setPorterYamlPath={setPorterYamlPath}
             />
           ) : (
             renderDockerSettings()

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

@@ -13,7 +13,7 @@ export const overrideObjectValues = (obj1: any, obj2: any) => {
   return obj1;
 };
 
-export const getGithubAction = (projectID?: number, clusterId?: number, stackName?: string, branchName?: string) => {
+export const getGithubAction = (projectID: number, clusterId: number, stackName: string, branchName: string, porterYamlPath: string = "porter.yaml") => {
   return `on:
   push:
     branches:
@@ -32,7 +32,7 @@ jobs:
       timeout-minutes: 30
       uses: porter-dev/porter-cli-action@v0.1.0
       with:
-        command: apply -f porter.yaml
+        command: apply -f ${porterYamlPath}
       env:
         PORTER_CLUSTER: ${clusterId}
         PORTER_HOST: https://dashboard.getporter.dev

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

@@ -2496,6 +2496,7 @@ const createSecretAndOpenGitHubPullRequest = baseApi<
     github_repo_name: string;
     open_pr: boolean;
     branch: string;
+    porter_yaml_path: string;
   },
   {
     project_id: number;

+ 7 - 6
dashboard/src/shared/types.tsx

@@ -243,15 +243,15 @@ export interface FormElement {
 export type RepoType = {
   FullName: string;
 } & (
-    | {
+  | {
       Kind: "github";
       GHRepoID: number;
     }
-    | {
+  | {
       Kind: "gitlab";
       GitIntegrationId: number;
     }
-  );
+);
 
 export interface FileType {
   path: string;
@@ -309,15 +309,15 @@ export type ActionConfigType = {
   image_repo_uri: string;
   dockerfile_path?: string;
 } & (
-    | {
+  | {
       kind: "gitlab";
       gitlab_integration_id: number;
     }
-    | {
+  | {
       kind: "github";
       git_repo_id: number;
     }
-  );
+);
 
 export type GithubActionConfigType = ActionConfigType & {
   kind: "github";
@@ -645,6 +645,7 @@ export type BuildConfig = {
 
 export interface PorterAppOptions {
   porter_yaml: string;
+  porter_yaml_path?: string;
   repo_name?: string;
   git_branch?: string;
   git_repo_id?: number;

+ 12 - 6
internal/integrations/ci/actions/stack.go

@@ -18,6 +18,8 @@ type GithubPROpts struct {
 	ServerURL                 string
 	DefaultBranch             string
 	SecretName                string
+	PorterYamlPath            string
+	Body                      string
 }
 
 type GetStackApplyActionYAMLOpts struct {
@@ -26,17 +28,19 @@ type GetStackApplyActionYAMLOpts struct {
 	ProjectID, ClusterID uint
 	DefaultBranch        string
 	SecretName           string
+	PorterYamlPath       string
 }
 
 func OpenGithubPR(opts *GithubPROpts) (*github.PullRequest, error) {
 	var pr *github.PullRequest
 	applyWorkflowYAML, err := getStackApplyActionYAML(&GetStackApplyActionYAMLOpts{
-		ServerURL:     opts.ServerURL,
-		ClusterID:     opts.ClusterID,
-		ProjectID:     opts.ProjectID,
-		StackName:     opts.StackName,
-		DefaultBranch: opts.DefaultBranch,
-		SecretName:    opts.SecretName,
+		ServerURL:      opts.ServerURL,
+		ClusterID:      opts.ClusterID,
+		ProjectID:      opts.ProjectID,
+		StackName:      opts.StackName,
+		DefaultBranch:  opts.DefaultBranch,
+		SecretName:     opts.SecretName,
+		PorterYamlPath: opts.PorterYamlPath,
 	})
 	if err != nil {
 		return pr, err
@@ -75,6 +79,7 @@ func OpenGithubPR(opts *GithubPROpts) (*github.PullRequest, error) {
 			Title: github.String("Enable Porter Application"),
 			Base:  github.String(opts.DefaultBranch),
 			Head:  github.String(prBranchName),
+			Body:  github.String(opts.Body),
 		},
 	)
 	if err != nil {
@@ -95,6 +100,7 @@ func getStackApplyActionYAML(opts *GetStackApplyActionYAMLOpts) ([]byte, error)
 			opts.SecretName,
 			opts.StackName,
 			"v0.1.0",
+			opts.PorterYamlPath,
 			opts.ProjectID,
 			opts.ClusterID,
 		),

+ 8 - 2
internal/integrations/ci/actions/steps.go

@@ -72,14 +72,20 @@ func getCreatePreviewEnvStep(
 }
 
 func getDeployStackStep(
-	serverURL, porterTokenSecretName, stackName, actionVersion string,
+	serverURL, porterTokenSecretName, stackName, actionVersion, porterYamlPath string,
 	projectID, clusterID uint,
 ) GithubActionYAMLStep {
+	var path string
+	if porterYamlPath != "" {
+		path = porterYamlPath
+	} else {
+		path = "porter.yaml"
+	}
 	return GithubActionYAMLStep{
 		Name: "Deploy stack",
 		Uses: fmt.Sprintf("%s@%s", cliActionName, actionVersion),
 		With: map[string]string{
-			"command": "apply -f porter.yaml",
+			"command": fmt.Sprintf("apply -f %s", path),
 		},
 		Env: map[string]string{
 			"PORTER_CLUSTER":    fmt.Sprintf("%d", clusterID),

+ 4 - 0
internal/models/porter_app.go

@@ -27,6 +27,9 @@ type PorterApp struct {
 	Buildpacks     string
 	Dockerfile     string
 	PullRequestURL string
+
+	// Porter YAML
+	PorterYamlPath string
 }
 
 // ToPorterAppType generates an external types.PorterApp to be shared over REST
@@ -45,5 +48,6 @@ func (a *PorterApp) ToPorterAppType() *types.PorterApp {
 		Buildpacks:     a.Buildpacks,
 		Dockerfile:     a.Dockerfile,
 		PullRequestURL: a.PullRequestURL,
+		PorterYamlPath: a.PorterYamlPath,
 	}
 }