Przeglądaj źródła

refactor application launch to read from chart-repo-application and support jobs, workers, and web services. also closes #247

sunguroku 5 lat temu
rodzic
commit
f4f970a5ff

+ 34 - 28
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -519,34 +519,41 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
           revision: currentChart.version,
         }
       )
-      .then((res) => this.setState({ components: res.data.Objects }))
-      .catch(console.log);
-
-    api
-      .getIngress(
-        "<token>",
-        {
-          cluster_id: currentCluster.id,
-        },
-        {
-          id: currentProject.id,
-          name: `${this.props.currentChart.name}-docker`,
-          namespace: `${this.props.currentChart.namespace}`,
-        }
-      )
-      .then((res) => {
-        if (res.data?.spec?.rules && res.data?.spec?.rules[0]?.host) {
-          this.setState({ url: `https://${res.data?.spec?.rules[0]?.host}` });
-          return;
+      .then((res) => this.setState({ components: res.data.Objects }, () => {
+        let ingressName = null;
+        for (var i = 0; i < this.state.components.length; i++) {
+          if (this.state.components[i].Kind === "Ingress") {
+            ingressName = this.state.components[i].Name;
+          }
         }
 
-        if (res.data?.status?.loadBalancer?.ingress) {
-          this.setState({
-            url: `http://${res.data?.status?.loadBalancer?.ingress[0]?.hostname}`,
-          });
-          return;
-        }
-      })
+      api
+        .getIngress(
+          "<token>",
+          {
+            cluster_id: currentCluster.id,
+          },
+          {
+            id: currentProject.id,
+            name: ingressName,
+            namespace: `${this.props.currentChart.namespace}`,
+          }
+        )
+        .then((res) => {
+          if (res.data?.spec?.rules && res.data?.spec?.rules[0]?.host) {
+            this.setState({ url: `https://${res.data?.spec?.rules[0]?.host}` });
+            return;
+          }
+  
+          if (res.data?.status?.loadBalancer?.ingress) {
+            this.setState({
+              url: `http://${res.data?.status?.loadBalancer?.ingress[0]?.hostname}`,
+            });
+            return;
+          }
+        })
+        .catch(console.log);
+      }))
       .catch(console.log);
 
     this.updateTabs();
@@ -632,7 +639,6 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     let { currentChart, setCurrentChart } = this.props;
     let chart = currentChart;
     let status = this.getChartStatus(chart.info.status);
-
     return (
       <>
         <CloseOverlay onClick={() => setCurrentChart(null)} />
@@ -651,7 +657,7 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
                 <IconWrapper>{this.renderIcon()}</IconWrapper>
                 {chart.name}
               </Title>
-              {this.renderUrl()}
+              {chart.chart.metadata.name != "worker" && chart.chart.metadata.name != "job" && this.renderUrl()}
               <InfoWrapper>
                 <StatusIndicator
                   controllers={this.state.controllers}

+ 79 - 25
dashboard/src/main/home/launch/Launch.tsx

@@ -22,7 +22,8 @@ type PropsType = {};
 type StateType = {
   currentTemplate: PorterTemplate | null;
   currentTab: string;
-  porterTemplates: PorterTemplate[];
+  addonTemplates: PorterTemplate[];
+  applicationTemplates: PorterTemplate[];
   loading: boolean;
   error: boolean;
 };
@@ -31,24 +32,35 @@ export default class Templates extends Component<PropsType, StateType> {
   state = {
     currentTemplate: null as PorterTemplate | null,
     currentTab: "docker",
-    porterTemplates: [] as PorterTemplate[],
+    addonTemplates: [] as PorterTemplate[],
+    applicationTemplates: [] as PorterTemplate[],
     loading: true,
     error: false,
   };
 
   componentDidMount() {
     api
-      .getTemplates("<token>", {}, {})
+      .getAddonTemplates("<token>", {}, {})
       .then((res) => {
-        this.setState({ porterTemplates: res.data, error: false }, () => {
-          this.state.porterTemplates.sort((a, b) => (a.name > b.name ? 1 : -1));
-          this.state.porterTemplates.sort((a, b) =>
-            a.name === "docker" ? -1 : b.name === "docker" ? 1 : 0
-          );
-          // TODO: properly find "docker" template instead of relying on first entry
+        this.setState({ addonTemplates: res.data, error: false }, () => {
+          this.state.addonTemplates.sort((a, b) => (a.name > b.name ? 1 : -1));
+          this.setState({
+            loading: false,
+          });
+        });
+      })
+      .catch(() => this.setState({ loading: false, error: true }));
+
+    api
+      .getApplicationTemplates("<token>", { 
+        repo_url: process.env.APPLICATION_CHART_REPO_URL 
+      }, {})
+      .then((res) => {
+        console.log(res.data)
+        this.setState({ applicationTemplates: res.data, error: false }, () => {    
+          this.state.applicationTemplates.sort((a, b) => (a.version > b.version ? 1 : -1));      
           this.setState({
             loading: false,
-            currentTemplate: this.state.porterTemplates[0],
           });
         });
       })
@@ -67,8 +79,50 @@ export default class Templates extends Component<PropsType, StateType> {
     );
   };
 
-  renderTemplateList = () => {
-    let { loading, error, porterTemplates } = this.state;
+  renderApplicationList = () => {
+    let { loading, error, applicationTemplates } = this.state;
+
+    if (loading) {
+      return (
+        <LoadingWrapper>
+          <Loading />
+        </LoadingWrapper>
+      );
+    } else if (error) {
+      return (
+        <Placeholder>
+          <i className="material-icons">error</i> Error retrieving templates.
+        </Placeholder>
+      );
+    } else if (applicationTemplates.length === 0) {
+      return (
+        <Placeholder>
+          <i className="material-icons">category</i> No templates found.
+        </Placeholder>
+      );
+    }
+
+    return this.state.applicationTemplates
+      .map((template: PorterTemplate, i: number) => {
+        let { name, icon, description } = template;
+        if (hardcodedNames[name]) {
+          name = hardcodedNames[name];
+        }
+        return (
+          <TemplateBlock
+            key={i}
+            onClick={() => this.setState({ currentTemplate: template })}
+          >
+            {this.renderIcon(icon)}
+            <TemplateTitle>{name}</TemplateTitle>
+            <TemplateDescription>{description}</TemplateDescription>
+          </TemplateBlock>
+        );
+      });
+  };
+
+  renderAddonList = () => {
+    let { loading, error, addonTemplates } = this.state;
 
     if (loading) {
       return (
@@ -82,7 +136,7 @@ export default class Templates extends Component<PropsType, StateType> {
           <i className="material-icons">error</i> Error retrieving templates.
         </Placeholder>
       );
-    } else if (porterTemplates.length === 0) {
+    } else if (addonTemplates.length === 0) {
       return (
         <Placeholder>
           <i className="material-icons">category</i> No templates found.
@@ -90,8 +144,7 @@ export default class Templates extends Component<PropsType, StateType> {
       );
     }
 
-    return this.state.porterTemplates
-      .filter((t) => t.name.toLowerCase() !== "docker")
+    return this.state.addonTemplates
       .map((template: PorterTemplate, i: number) => {
         let { name, icon, description } = template;
         if (hardcodedNames[name]) {
@@ -110,7 +163,7 @@ export default class Templates extends Component<PropsType, StateType> {
       });
   };
 
-  renderDefaultTemplate = () => {
+  renderApplicationTemplates = () => {
     if (!this.context.currentCluster) {
       return (
         <>
@@ -133,21 +186,23 @@ export default class Templates extends Component<PropsType, StateType> {
     if (this.state.currentTemplate) {
       return (
         <ExpandedTemplate
-          currentTemplate={this.state.porterTemplates[0]}
+          currentTab={this.state.currentTab}
+          currentTemplate={this.state.currentTemplate}
           setCurrentTemplate={(currentTemplate: PorterTemplate) =>
             this.setState({ currentTemplate })
           }
-          skipDescription={true}
+          skipDescription={false}
         />
       );
     }
-    return null;
+    return <TemplateList>{this.renderApplicationList()}</TemplateList>;
   };
 
-  renderCommunityTemplates = () => {
+  renderAddonTemplates = () => {
     if (this.state.currentTemplate) {
       return (
         <ExpandedTemplate
+          currentTab={this.state.currentTab}
           currentTemplate={this.state.currentTemplate}
           setCurrentTemplate={(currentTemplate: PorterTemplate) =>
             this.setState({ currentTemplate })
@@ -155,7 +210,7 @@ export default class Templates extends Component<PropsType, StateType> {
         />
       );
     }
-    return <TemplateList>{this.renderTemplateList()}</TemplateList>;
+    return <TemplateList>{this.renderAddonList()}</TemplateList>;
   };
 
   render() {
@@ -176,14 +231,13 @@ export default class Templates extends Component<PropsType, StateType> {
           setCurrentTab={(value: string) =>
             this.setState({
               currentTab: value,
-              currentTemplate:
-                value === "docker" ? this.state.porterTemplates[0] : null,
+              currentTemplate: null,
             })
           }
         />
         {this.state.currentTab === "docker"
-          ? this.renderDefaultTemplate()
-          : this.renderCommunityTemplates()}
+          ? this.renderApplicationTemplates()
+          : this.renderAddonTemplates()}
       </TemplatesWrapper>
     );
   }

+ 6 - 1
dashboard/src/main/home/launch/expanded-template/ExpandedTemplate.tsx

@@ -10,6 +10,7 @@ import Loading from "components/Loading";
 
 type PropsType = {
   currentTemplate: PorterTemplate;
+  currentTab: string;
   setCurrentTemplate: (x: PorterTemplate) => void;
   skipDescription?: boolean;
 };
@@ -41,10 +42,12 @@ export default class ExpandedTemplate extends Component<PropsType, StateType> {
 
   fetchTemplateInfo = () => {
     this.setState({ loading: true });
+    let params = this.props.currentTab == "docker" ? { repo_url: process.env.APPLICATION_CHART_REPO_URL} : {}
+    
     api
       .getTemplateInfo(
         "<token>",
-        {},
+        params,
         {
           name: this.props.currentTemplate.name.toLowerCase().trim(),
           version: "latest",
@@ -82,6 +85,7 @@ export default class ExpandedTemplate extends Component<PropsType, StateType> {
     if (this.props.skipDescription || this.state.showLaunchTemplate) {
       return (
         <LaunchTemplate
+          currentTab={this.props.currentTab}
           currentTemplate={this.props.currentTemplate}
           hideLaunch={() => this.setState({ showLaunchTemplate: false })}
           hideBackButton={this.props.skipDescription}
@@ -94,6 +98,7 @@ export default class ExpandedTemplate extends Component<PropsType, StateType> {
     return (
       <FadeWrapper>
         <TemplateInfo
+          currentTab={this.props.currentTab}
           currentTemplate={this.props.currentTemplate}
           setCurrentTemplate={this.props.setCurrentTemplate}
           launchTemplate={() => this.setState({ showLaunchTemplate: true })}

+ 15 - 13
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -26,6 +26,7 @@ import { isAlphanumeric } from "shared/common";
 
 type PropsType = RouteComponentProps & {
   currentTemplate: any;
+  currentTab: string;
   hideLaunch: () => void;
   values: any;
   form: any;
@@ -234,6 +235,7 @@ class LaunchTemplate extends Component<PropsType, StateType> {
           cluster_id: currentCluster.id,
           name: this.props.currentTemplate.name.toLowerCase().trim(),
           version: "latest",
+          repo_url: process.env.APPLICATION_CHART_REPO_URL,
         }
       )
       .then((_) => {
@@ -342,7 +344,7 @@ class LaunchTemplate extends Component<PropsType, StateType> {
       <ValuesWrapper
         formTabs={this.props.form?.tabs}
         onSubmit={
-          this.props.currentTemplate.name === "docker"
+          this.props.currentTab === "docker"
             ? this.onSubmit
             : this.onSubmitAddon
         }
@@ -631,17 +633,15 @@ class LaunchTemplate extends Component<PropsType, StateType> {
 
     return (
       <StyledLaunchTemplate>
-        {name !== "docker" && (
-          <HeaderSection>
-            <i className="material-icons" onClick={this.props.hideLaunch}>
-              keyboard_backspace
-            </i>
-            {icon
-              ? this.renderIcon(icon)
-              : this.renderIcon(currentTemplate.icon)}
-            <Title>{name}</Title>
-          </HeaderSection>
-        )}
+        <HeaderSection>
+          <i className="material-icons" onClick={this.props.hideLaunch}>
+            keyboard_backspace
+          </i>
+          {icon
+            ? this.renderIcon(icon)
+            : this.renderIcon(currentTemplate.icon)}
+          <Title>{name}</Title>
+        </HeaderSection>
         <DarkMatter antiHeight="-13px" />
         <Heading isAtTop={true}>Name</Heading>
         <Subtitle>
@@ -822,6 +822,7 @@ const Title = styled.div`
 const HeaderSection = styled.div`
   display: flex;
   align-items: center;
+  margin-bottom: 30px;
 
   > i {
     cursor: pointer;
@@ -918,7 +919,8 @@ const NamespaceLabel = styled.div`
 
 const Icon = styled.img`
   width: 21px;
-  margin-right: 10px;
+  margin-right: 6px;
+  margin-left: 10px;
 `;
 
 const Polymer = styled.div`

+ 2 - 1
dashboard/src/main/home/launch/expanded-template/TemplateInfo.tsx

@@ -12,6 +12,7 @@ import hardcodedNames from "../hardcodedNameDict";
 
 type PropsType = {
   currentTemplate: any;
+  currentTab: string;
   setCurrentTemplate: (x: PorterTemplate) => void;
   launchTemplate: () => void;
   markdown: string | null;
@@ -73,7 +74,7 @@ export default class TemplateInfo extends Component<PropsType, StateType> {
           </Banner>
         </>
       );
-    } else if (this.props.currentTemplate.name.toLowerCase() === "docker") {
+    } else if (this.props.currentTab == 'docker') {
       return (
         <>
           <Br />

+ 3 - 0
dashboard/src/main/home/launch/hardcodedNameDict.tsx

@@ -7,6 +7,9 @@ const hardcodedNames: { [key: string]: string } = {
   postgresql: "PostgreSQL",
   redis: "Redis",
   ubuntu: "Ubuntu",
+  web: "Web Service",
+  worker: "Worker",
+  job: "Job"
 };
 
 export default hardcodedNames;

+ 20 - 5
dashboard/src/shared/api.tsx

@@ -178,9 +178,14 @@ const deployTemplate = baseApi<
     cluster_id: number;
     name: string;
     version: string;
+    repo_url?: string;
   }
 >("POST", pathParams => {
-  let { cluster_id, id, name, version } = pathParams;
+  let { cluster_id, id, name, version, repo_url } = pathParams;
+
+  if (repo_url) {
+    return `/api/projects/${id}/deploy/${name}/${version}?cluster_id=${cluster_id}&repo_url=${repo_url}`;
+  }
   return `/api/projects/${id}/deploy/${name}/${version}?cluster_id=${cluster_id}`;
 });
 
@@ -476,14 +481,23 @@ const getRevisions = baseApi<
   return `/api/projects/${pathParams.id}/releases/${pathParams.name}/history`;
 });
 
-const getTemplateInfo = baseApi<{}, { name: string; version: string }>(
+const getTemplateInfo = baseApi<{
+  repo_url?: string
+}, { name: string; version: string }>(
   "GET",
   pathParams => {
     return `/api/templates/${pathParams.name}/${pathParams.version}`;
   }
 );
 
-const getTemplates = baseApi("GET", "/api/templates");
+const getAddonTemplates = baseApi("GET", "/api/templates");
+
+const getApplicationTemplates = baseApi<
+  {
+    repo_url?: string
+  },
+  {}
+>("GET", "/api/templates");
 
 const getUser = baseApi<{}, { id: number }>("GET", pathParams => {
   return `/api/users/${pathParams.id}`;
@@ -557,7 +571,7 @@ const uninstallTemplate = baseApi<
   }
 >("POST", pathParams => {
   let { id, name, cluster_id, storage, namespace } = pathParams;
-  return `/api/projects/${id}/deploy/${name}?cluster_id=${cluster_id}&namespace=${namespace}&storage=${storage}`;
+  return `/api/projects/${id}/delete/${name}?cluster_id=${cluster_id}&namespace=${namespace}&storage=${storage}`;
 });
 
 const updateUser = baseApi<
@@ -637,7 +651,8 @@ export default {
   getRepos,
   getRevisions,
   getTemplateInfo,
-  getTemplates,
+  getAddonTemplates,
+  getApplicationTemplates,
   getUser,
   linkGithubProject,
   logInUser,

+ 1 - 0
go.mod

@@ -36,6 +36,7 @@ require (
 	github.com/google/go-github/v33 v33.0.0
 	github.com/google/go-querystring v1.0.0 // indirect
 	github.com/googleapis/gnostic v0.2.2 // indirect
+	github.com/gorilla/schema v1.2.0
 	github.com/gorilla/securecookie v1.1.1
 	github.com/gorilla/sessions v1.2.1
 	github.com/gorilla/websocket v1.4.2

+ 3 - 0
go.sum

@@ -513,6 +513,8 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
 github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
+github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
@@ -1498,6 +1500,7 @@ k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
 k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
 k8s.io/helm v2.16.12+incompatible h1:K2zhF8+B85Ya1n7n3eH34xwwp5qNUM42TBFENDZJT7w=
 k8s.io/helm v2.16.12+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
+k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao=
 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=

+ 1 - 1
internal/forms/chart.go

@@ -4,7 +4,7 @@ import "net/url"
 
 // ChartForm is the base type for CRUD operations on charts
 type ChartForm struct {
-	RepoURL string
+	RepoURL string 
 	Name    string `json:"name"`
 	Version string `json:"version"`
 }

+ 16 - 2
server/api/template_handler.go

@@ -17,8 +17,22 @@ import (
 // HandleListTemplates retrieves a list of Porter templates
 // TODO: test and reduce fragility (handle untar/parse error for individual charts)
 // TODO: separate markdown retrieval into its own query if necessary
-func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
-	repoIndex, err := loader.LoadRepoIndexPublic(app.ServerConf.DefaultHelmRepoURL)
+func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {		
+	
+	vals, err := url.ParseQuery(r.URL.RawQuery)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)
+		return
+	}
+
+	repoURL := app.ServerConf.DefaultHelmRepoURL
+
+	if inputRepoURL, ok := vals["repo_url"]; ok && len(inputRepoURL) == 1 {
+		repoURL = inputRepoURL[0]
+	}
+
+	repoIndex, err := loader.LoadRepoIndexPublic(repoURL)
 
 	if err != nil {
 		app.handleErrorFormDecoding(err, ErrReleaseDecode, w)

+ 1 - 1
server/router/router.go

@@ -1023,7 +1023,7 @@ func New(a *api.App) *chi.Mux {
 		// /api/projects/{project_id}/deploy routes
 		r.Method(
 			"POST",
-			"/projects/{project_id}/deploy/{name}",
+			"/projects/{project_id}/delete/{name}",
 			auth.DoesUserHaveProjectAccess(
 				auth.DoesUserHaveClusterAccess(
 					requestlog.NewHandler(a.HandleUninstallTemplate, l),