Просмотр исходного кода

/deploy endpoint retrieves default values.yaml

jusrhee 5 лет назад
Родитель
Сommit
d36d2b8ec3

+ 20 - 1
dashboard/src/components/values-form/ValuesForm.tsx

@@ -2,6 +2,8 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 
 import { Section, FormElement } from '../../shared/types';
+import { Context } from '../../shared/Context';
+import api from '../../shared/api';
 
 import SaveButton from '../SaveButton';
 import CheckboxRow from './CheckboxRow';
@@ -55,6 +57,21 @@ export default class ValuesForm extends Component<PropsType, StateType> {
     }
   }
 
+  handleDeploy = () => {
+    console.log(this.state);
+    let { currentProject } = this.context;
+
+    api.deployTemplate('<token>', {}, {
+      id: currentProject.id,
+    }, (err: any, res: any) => {
+      if (err) {
+        // console.log(err)
+      } else {
+        // console.log(res.data)
+      }
+    });
+  }
+
   renderSection = (section: Section) => {
     return section.Contents.map((item: FormElement, i: number) => {
 
@@ -141,7 +158,7 @@ export default class ValuesForm extends Component<PropsType, StateType> {
         </StyledValuesForm>
         <SaveButton
           text='Deploy'
-          onClick={() => console.log(this.state)}
+          onClick={this.handleDeploy}
           status={null}
           makeFlush={true}
         />
@@ -150,6 +167,8 @@ export default class ValuesForm extends Component<PropsType, StateType> {
   }
 }
 
+ValuesForm.contextType = Context;
+
 const DarkMatter = styled.div`
   margin-top: 0px;
 `;

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

@@ -153,6 +153,10 @@ const deleteProject = baseApi<{}, { id: number }>('DELETE', pathParams => {
   return `/api/projects/${pathParams.id}`;
 });
 
+const deployTemplate = baseApi<{}, { id: number }>('POST', pathParams => {
+  return `/api/projects/${pathParams.id}/deploy`;
+});
+
 // Bundle export to allow default api import (api.<method> is more readable)
 export default {
   checkAuth,
@@ -176,5 +180,6 @@ export default {
   getBranchContents,
   getProjects,
   createProject,
-  deleteProject
+  deleteProject,
+  deployTemplate
 }

+ 3 - 0
go.sum

@@ -861,6 +861,7 @@ github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
 github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06 h1:vN4d3jSss3ExzUn2cE0WctxztfOgiKvMKnDrydBsg00=
 github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06/go.mod h1:++9BgZujZd4v0ZTZCb5iPsaomXdZWyxotIAh1IiDm44=
@@ -1010,6 +1011,7 @@ github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96d
 github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
 github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@@ -1826,6 +1828,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

+ 56 - 0
internal/models/templates.go

@@ -0,0 +1,56 @@
+package models
+
+// IndexYAML represents a chart repo's index.yaml
+type IndexYAML struct {
+	APIVersion string                    `yaml:"apiVersion"`
+	Generated  string                    `yaml:"generated"`
+	Entries    map[interface{}]ChartYAML `yaml:"entries"`
+}
+
+// ChartYAML represents the data for chart in index.yaml
+type ChartYAML []struct {
+	APIVersion  string   `yaml:"apiVersion"`
+	AppVersion  string   `yaml:"appVersion"`
+	Created     string   `yaml:"created"`
+	Description string   `yaml:"description"`
+	Digest      string   `yaml:"digest"`
+	Icon        string   `yaml:"icon"`
+	Name        string   `yaml:"name"`
+	Type        string   `yaml:"type"`
+	Urls        []string `yaml:"urls"`
+	Version     string   `yaml:"version"`
+}
+
+// PorterChart represents a bundled Porter template
+type PorterChart struct {
+	Name        string
+	Description string
+	Icon        string
+	Form        FormYAML
+	Markdown    string
+}
+
+// FormYAML represents a chart's values.yaml form abstraction
+type FormYAML struct {
+	Name        string   `yaml:"name"`
+	Icon        string   `yaml:"icon"`
+	Description string   `yaml:"description"`
+	Tags        []string `yaml:"tags"`
+	Tabs        []struct {
+		Name     string `yaml:"name"`
+		Label    string `yaml:"label"`
+		Sections []struct {
+			Name     string `yaml:"name"`
+			ShowIf   string `yaml:"show_if"`
+			Contents []struct {
+				Type     string `yaml:"type"`
+				Label    string `yaml:"label"`
+				Name     string `yaml:"name,omitempty"`
+				Variable string `yaml:"variable,omitempty"`
+				Settings struct {
+					Default interface{}
+				} `yaml:"settings,omitempty"`
+			} `yaml:"contents"`
+		} `yaml:"sections"`
+	} `yaml:"tabs"`
+}

+ 130 - 0
server/api/deploy_handler.go

@@ -0,0 +1,130 @@
+package api
+
+import (
+	"archive/tar"
+	"bytes"
+	"compress/gzip"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
+	"github.com/porter-dev/porter/internal/models"
+	"gopkg.in/yaml.v2"
+)
+
+// HandleDeployTemplate triggers a chart deployment from a template
+func (app *App) HandleDeployTemplate(w http.ResponseWriter, r *http.Request) {
+	tgt := "hello-porter"
+
+	baseURL := "https://porter-dev.github.io/chart-repo/"
+	resp, err := http.Get(baseURL + "index.yaml")
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	defer resp.Body.Close()
+	body, _ := ioutil.ReadAll(resp.Body)
+
+	form := models.IndexYAML{}
+	if err := yaml.Unmarshal([]byte(body), &form); err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	// Loop over charts in index.yaml
+	for k := range form.Entries {
+		indexChart := form.Entries[k][0]
+		tarURL := indexChart.Urls[0]
+		splits := strings.Split(tarURL, "-")
+
+		strAcc := splits[0]
+		for i := 1; i < len(splits)-1; i++ {
+			strAcc += "-" + splits[i]
+		}
+
+		// Unpack the target chart and retrieve values.yaml
+		if strAcc == tgt {
+			tgtURL := baseURL + tarURL
+			values, err := processValues(tgtURL)
+			if err != nil {
+				fmt.Println(err)
+				return
+			}
+
+			defaultValues := *values
+			defaultValues["replicaCount"] = 87
+			fmt.Println(defaultValues["replicaCount"])
+			for k := range *values {
+				fmt.Println(k)
+			}
+		}
+	}
+}
+
+func processValues(tgtURL string) (*map[string]interface{}, error) {
+	resp, err := http.Get(tgtURL)
+	if err != nil {
+		fmt.Println(err)
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+	body, _ := ioutil.ReadAll(resp.Body)
+	buf := bytes.NewBuffer(body)
+
+	gzf, err := gzip.NewReader(buf)
+	if err != nil {
+		fmt.Println(err)
+		return nil, err
+	}
+
+	// Process tarball to generate FormYAML and retrieve markdown
+	tarReader := tar.NewReader(gzf)
+	for {
+		header, err := tarReader.Next()
+		if err == io.EOF {
+			break
+		} else if err != nil {
+			fmt.Println(err)
+			return nil, err
+		}
+
+		name := header.Name
+		switch header.Typeflag {
+		case tar.TypeDir:
+			continue
+		case tar.TypeReg:
+
+			// Handle values.yaml located in archive
+			if strings.Contains(name, "values.yaml") {
+				bufForm := new(bytes.Buffer)
+
+				_, err := io.Copy(bufForm, tarReader)
+				if err != nil {
+					fmt.Println(err)
+					return nil, err
+				}
+
+				// Unmarshal yaml byte buffer
+				form := make(map[string]interface{})
+				if err := yaml.Unmarshal(bufForm.Bytes(), &form); err != nil {
+					fmt.Println(err)
+					return nil, err
+				}
+				return &form, nil
+			}
+		default:
+			fmt.Printf("%s : %c %s %s\n",
+				"Unknown type",
+				header.Typeflag,
+				"in file",
+				name,
+			)
+		}
+	}
+	return nil, errors.New("no values.yaml found")
+}

+ 113 - 0
server/api/deploy_handler_test.go

@@ -0,0 +1,113 @@
+package api_test
+
+import (
+	"encoding/json"
+	"net/http"
+	"strings"
+	"testing"
+
+	"github.com/porter-dev/porter/internal/kubernetes"
+)
+
+// ------------------------- TEST TYPES AND MAIN LOOP ------------------------- //
+
+type deployTest struct {
+	initializers []func(tester *tester)
+	msg          string
+	method       string
+	endpoint     string
+	body         string
+	expStatus    int
+	expBody      string
+	useCookie    bool
+	validators   []func(c *deployTest, tester *tester, t *testing.T)
+}
+
+func testDeployRequests(t *testing.T, tests []*deployTest, canQuery bool) {
+	for _, c := range tests {
+		// create a new tester
+		tester := newTester(canQuery)
+
+		// if there's an initializer, call it
+		for _, init := range c.initializers {
+			init(tester)
+		}
+
+		req, err := http.NewRequest(
+			c.method,
+			c.endpoint,
+			strings.NewReader(c.body),
+		)
+
+		tester.req = req
+
+		if c.useCookie {
+			req.AddCookie(tester.cookie)
+		}
+
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		tester.execute()
+		rr := tester.rr
+
+		// first, check that the status matches
+		if status := rr.Code; status != c.expStatus {
+			t.Errorf("%s, handler returned wrong status code: got %v want %v",
+				c.msg, status, c.expStatus)
+		}
+
+		// if there's a validator, call it
+		for _, validate := range c.validators {
+			validate(c, tester, t)
+		}
+	}
+}
+
+// ------------------------- TEST FIXTURES AND FUNCTIONS  ------------------------- //
+
+var newDeployTests = []*deployTest{
+	&deployTest{
+		initializers: []func(tester *tester){
+			initDefaultDeploy,
+		},
+		msg:       "Deploy template",
+		method:    "POST",
+		endpoint:  "/api/projects/1/deploy",
+		body:      "",
+		expStatus: http.StatusOK,
+		expBody:   "unimplemented",
+		useCookie: true,
+		validators: []func(c *deployTest, tester *tester, t *testing.T){
+			deployValidator,
+		},
+	},
+}
+
+func TestHandleDeployTemplate(t *testing.T) {
+	testDeployRequests(t, newDeployTests, true)
+}
+
+// ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
+
+func initDefaultDeploy(tester *tester) {
+	initUserDefault(tester)
+
+	agent := kubernetes.GetAgentTesting(defaultObjects...)
+
+	// overwrite the test agent with new resources
+	tester.app.TestAgents.K8sAgent = agent
+}
+
+func deployValidator(c *deployTest, tester *tester, t *testing.T) {
+	var gotBody map[string]interface{}
+	var expBody map[string]interface{}
+
+	json.Unmarshal(tester.rr.Body.Bytes(), &gotBody)
+	json.Unmarshal([]byte(c.expBody), &expBody)
+
+	if string(tester.rr.Body.Bytes()) != c.expBody {
+		t.Errorf("Mismatch")
+	}
+}

+ 7 - 61
server/api/template_handler.go

@@ -12,70 +12,16 @@ import (
 	"net/http"
 	"strings"
 
+	"github.com/porter-dev/porter/internal/models"
+
 	"gopkg.in/yaml.v2"
 )
 
-// IndexYAML represents a chart repo's index.yaml
-type IndexYAML struct {
-	APIVersion string                    `yaml:"apiVersion"`
-	Generated  string                    `yaml:"generated"`
-	Entries    map[interface{}]ChartYAML `yaml:"entries"`
-}
-
-// ChartYAML represents the data for chart in index.yaml
-type ChartYAML []struct {
-	APIVersion  string   `yaml:"apiVersion"`
-	AppVersion  string   `yaml:"appVersion"`
-	Created     string   `yaml:"created"`
-	Description string   `yaml:"description"`
-	Digest      string   `yaml:"digest"`
-	Icon        string   `yaml:"icon"`
-	Name        string   `yaml:"name"`
-	Type        string   `yaml:"type"`
-	Urls        []string `yaml:"urls"`
-	Version     string   `yaml:"version"`
-}
-
-// PorterChart represents a bundled Porter template
-type PorterChart struct {
-	Name        string
-	Description string
-	Icon        string
-	Form        FormYAML
-	Markdown    string
-}
-
-// FormYAML represents a chart's values.yaml form abstraction
-type FormYAML struct {
-	Name        string   `yaml:"name"`
-	Icon        string   `yaml:"icon"`
-	Description string   `yaml:"description"`
-	Tags        []string `yaml:"tags"`
-	Tabs        []struct {
-		Name     string `yaml:"name"`
-		Label    string `yaml:"label"`
-		Sections []struct {
-			Name     string `yaml:"name"`
-			ShowIf   string `yaml:"show_if"`
-			Contents []struct {
-				Type     string `yaml:"type"`
-				Label    string `yaml:"label"`
-				Name     string `yaml:"name,omitempty"`
-				Variable string `yaml:"variable,omitempty"`
-				Settings struct {
-					Default interface{}
-				} `yaml:"settings,omitempty"`
-			} `yaml:"contents"`
-		} `yaml:"sections"`
-	} `yaml:"tabs"`
-}
-
 // 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) {
 	baseURL := "https://porter-dev.github.io/chart-repo/"
-	fmt.Println("and i oop!")
 	resp, err := http.Get(baseURL + "index.yaml")
 	if err != nil {
 		fmt.Println(err)
@@ -85,14 +31,14 @@ func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
 	defer resp.Body.Close()
 	body, _ := ioutil.ReadAll(resp.Body)
 
-	form := IndexYAML{}
+	form := models.IndexYAML{}
 	if err := yaml.Unmarshal([]byte(body), &form); err != nil {
 		fmt.Println(err)
 		return
 	}
 
 	// Loop over charts in index.yaml
-	porterCharts := []PorterChart{}
+	porterCharts := []models.PorterChart{}
 	for k := range form.Entries {
 		indexChart := form.Entries[k][0]
 		tarURL := indexChart.Urls[0]
@@ -106,7 +52,7 @@ func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 
-		porterChart := PorterChart{}
+		porterChart := models.PorterChart{}
 		porterChart.Name = indexChart.Name
 		porterChart.Description = indexChart.Description
 		porterChart.Icon = indexChart.Icon
@@ -121,7 +67,7 @@ func (app *App) HandleListTemplates(w http.ResponseWriter, r *http.Request) {
 	json.NewEncoder(w).Encode(porterCharts)
 }
 
-func processTarball(tarURL string) (*FormYAML, string, error) {
+func processTarball(tarURL string) (*models.FormYAML, string, error) {
 	resp, err := http.Get(tarURL)
 	if err != nil {
 		fmt.Println(err)
@@ -180,7 +126,7 @@ func processTarball(tarURL string) (*FormYAML, string, error) {
 				}
 
 				// Unmarshal yaml byte buffer
-				form := FormYAML{}
+				form := models.FormYAML{}
 				if err := yaml.Unmarshal(bufForm.Bytes(), &form); err != nil {
 					fmt.Println(err)
 					return nil, "", err

+ 6 - 0
server/router/router.go

@@ -258,6 +258,12 @@ func New(
 			auth.BasicAuthenticate(requestlog.NewHandler(a.HandleListImages, l)),
 		)
 
+		r.Method(
+			"POST",
+			"/projects/{project_id}/deploy",
+			auth.BasicAuthenticate(requestlog.NewHandler(a.HandleDeployTemplate, l)),
+		)
+
 		// /api/templates routes
 		r.Method(
 			"GET",