Kaynağa Gözat

Merge branch 'api-charts-rollback' of https://github.com/porter-dev/porter into frontend-integration

jusrhee 5 yıl önce
ebeveyn
işleme
0c3da984ff

+ 32 - 0
docs/API.md

@@ -17,6 +17,7 @@
   - [`GET /api/charts`](#get-apicharts)
   - [`GET /api/charts/{name}/history`](#get-apichartsnamehistory)
   - [`GET /api/charts/{name}/{revision}`](#get-apichartsnamerevision)
+  - [`POST /api/charts/rollback/{name}/{revision}`](#post-apichartsrollbacknamerevision)
 - [`/api/k8s`](#apik8s)
   - [`GET /api/k8s/namespaces`](#get-apik8snamespaces)
 
@@ -662,6 +663,37 @@ Chart{
 
 **Errors:** TBD
 
+#### `POST /api/charts/rollback/{name}/{revision}`
+
+**Description:** Rolls a release back to a specified revision. 
+
+**URL parameters:** 
+
+- `name` The name of the release.
+- `revision` The number of the release. 
+
+**Query parameters:** N/A
+
+**Request Body**:
+
+```js
+{
+  // The namespace of the cluster to be used
+  "namespace": String,
+  // The name of the context in the kubeconfig being used
+  "context": String,
+  // The Helm storage option to use
+  "storage": String("secret"|"configmap"|"memory")
+}
+```
+
+
+**Successful Response Body**: N/A
+
+**Successful Status Code**: `200`
+
+**Errors:** TBD
+
 ### `/api/k8s`
 
 #### `GET /api/k8s/namespaces`

+ 1 - 0
go.sum

@@ -1247,6 +1247,7 @@ gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gorm.io/gorm v1.20.2 h1:bZzSEnq7NDGsrd+n3evOOedDrY5oLM5QPlCjZJUK2ro=
 gorm.io/gorm v1.20.2/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
+helm.sh/helm v1.2.1 h1:Jrn7kKQqQ/hnFWZEX+9pMFvYqFexkzrBnGqYBmIph7c=
 helm.sh/helm v2.16.12+incompatible h1:nQfifk10KcpAGD1RJaNZVW/fWiqluV0JMuuDwdba4rw=
 helm.sh/helm v2.16.12+incompatible/go.mod h1:0Xbc6ErzwWH9qC55X1+hE3ZwhM3atbhCm/NbFZw5i+4=
 helm.sh/helm/v3 v3.3.4 h1:tbad6WQVMxEw1HlVBvI2rQqOblmI5lgXOrWAMwJ198M=

+ 10 - 0
internal/helm/agent.go

@@ -43,3 +43,13 @@ func (a *Agent) GetReleaseHistory(
 
 	return cmd.Run(name)
 }
+
+// RollbackRelease rolls a release back to a specified revision/version
+func (a *Agent) RollbackRelease(
+	name string,
+	version int,
+) error {
+	cmd := action.NewRollback(a.ActionConfig)
+	cmd.Version = version
+	return cmd.Run(name)
+}

+ 40 - 1
internal/helm/agent_test.go

@@ -209,7 +209,7 @@ var getReleaseTests = []getReleaseTest{
 			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
 		},
 		getName:    "airwatch",
-		getVersion: 0,
+		getVersion: 1,
 		expRes:     releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
 	},
 }
@@ -266,3 +266,42 @@ func TestListReleaseHistory(t *testing.T) {
 		compareReleaseToStubs(t, releases, tc.expRes)
 	}
 }
+
+var rollbackReleaseTests = []getReleaseTest{
+	getReleaseTest{
+		name:      "simple rollback test",
+		namespace: "default",
+		releases: []releaseStub{
+			releaseStub{"wordpress", "default", 2, "1.0.2", release.StatusDeployed},
+			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusSuperseded},
+		},
+		getName:    "wordpress",
+		getVersion: 3,
+		expRes:     releaseStub{"wordpress", "default", 3, "1.0.1", release.StatusDeployed},
+	},
+}
+
+func TestRollbackRelease(t *testing.T) {
+	for _, tc := range rollbackReleaseTests {
+		agent := newAgentFixture(t, tc.namespace)
+		makeReleases(t, agent, tc.releases)
+
+		// calling agent.ActionConfig.Releases.Create in makeReleases will automatically set the
+		// namespace, so we have to reset the namespace of the storage driver
+		agent.ActionConfig.Releases.Driver.(*driver.Memory).SetNamespace(tc.namespace)
+
+		err := agent.RollbackRelease("wordpress", 1)
+
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+
+		rel, err := agent.GetRelease(tc.getName, tc.getVersion)
+
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+
+		compareReleaseToStubs(t, []*release.Release{rel}, []releaseStub{tc.expRes})
+	}
+}

+ 61 - 0
server/api/chart_handler.go

@@ -2,6 +2,7 @@ package api
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
 	"net/url"
 	"strconv"
@@ -195,3 +196,63 @@ func (app *App) HandleListChartHistory(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 }
+
+// HandleRollbackChart rolls a release back to a specified revision
+func (app *App) HandleRollbackChart(w http.ResponseWriter, r *http.Request) {
+	session, err := app.store.Get(r, app.cookieName)
+
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrChartDecode, w)
+		return
+	}
+
+	name := chi.URLParam(r, "name")
+	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
+
+	// get the filter options
+	form := &forms.GetChartForm{
+		ChartForm: &forms.ChartForm{
+			Form: &helm.Form{},
+		},
+		Name:     name,
+		Revision: int(revision),
+	}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrUserDecode, w)
+		return
+	}
+
+	if sessID, ok := session.Values["user_id"].(uint); ok {
+		form.PopulateHelmOptionsFromUserID(sessID, app.repo.User)
+	}
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrChartValidateFields, w)
+		return
+	}
+
+	// create a new agent
+	var agent *helm.Agent
+
+	if app.testing {
+		agent = app.TestAgents.HelmAgent
+	} else {
+		agent, err = helm.GetAgentOutOfClusterConfig(form.ChartForm.Form, app.logger)
+	}
+
+	err = agent.RollbackRelease(form.Name, form.Revision)
+
+	if err != nil {
+		app.handleErrorInternal(err, w)
+		return
+	}
+
+	release, err := agent.GetRelease("wordpress", 3)
+
+	fmt.Println("RELEASE IS", release)
+
+	w.WriteHeader(http.StatusOK)
+}

+ 94 - 8
server/api/chart_handler_test.go

@@ -2,7 +2,9 @@ package api_test
 
 import (
 	"encoding/json"
+	"fmt"
 	"net/http"
+	"net/http/httptest"
 	"net/url"
 	"reflect"
 	"strings"
@@ -105,7 +107,7 @@ var listChartsTests = []*chartTest{
 		expBody:   releaseStubsToChartJSON(sampleReleaseStubs),
 		useCookie: true,
 		validators: []func(c *chartTest, tester *tester, t *testing.T){
-			chartReleaseBodyValidator,
+			chartReleaseArrBodyValidator,
 		},
 	},
 	&chartTest{
@@ -132,7 +134,7 @@ var listChartsTests = []*chartTest{
 		}),
 		useCookie: true,
 		validators: []func(c *chartTest, tester *tester, t *testing.T){
-			chartReleaseBodyValidator,
+			chartReleaseArrBodyValidator,
 		},
 	},
 }
@@ -146,9 +148,10 @@ var getChartTests = []*chartTest{
 		initializers: []func(tester *tester){
 			initDefaultCharts,
 		},
-		msg:    "Get charts",
-		method: "GET",
-		endpoint: "/api/charts/airwatch/0?" + url.Values{
+		msg:       "Get charts",
+		method:    "GET",
+		namespace: "default",
+		endpoint: "/api/charts/airwatch/1?" + url.Values{
 			"namespace": []string{""},
 			"context":   []string{"context-test"},
 			"storage":   []string{"memory"},
@@ -172,8 +175,9 @@ var listChartHistoryTests = []*chartTest{
 		initializers: []func(tester *tester){
 			initHistoryCharts,
 		},
-		msg:    "List chart history",
-		method: "GET",
+		msg:       "List chart history",
+		method:    "GET",
+		namespace: "default",
 		endpoint: "/api/charts/wordpress/history?" + url.Values{
 			"namespace": []string{""},
 			"context":   []string{"context-test"},
@@ -184,7 +188,7 @@ var listChartHistoryTests = []*chartTest{
 		expBody:   releaseStubsToChartJSON(historyReleaseStubs),
 		useCookie: true,
 		validators: []func(c *chartTest, tester *tester, t *testing.T){
-			chartReleaseBodyValidator,
+			chartReleaseArrBodyValidator,
 		},
 	},
 }
@@ -193,6 +197,75 @@ func TestHandleListChartHistory(t *testing.T) {
 	testChartRequests(t, listChartHistoryTests, true)
 }
 
+var rollbackChartTests = []*chartTest{
+	&chartTest{
+		initializers: []func(tester *tester){
+			initHistoryCharts,
+		},
+		msg:       "Rollback relase",
+		method:    "POST",
+		namespace: "default",
+		endpoint:  "/api/charts/rollback/wordpress/1",
+		body: `
+			{
+				"namespace": "default",
+				"context": "context-test",
+				"storage": "memory"
+			}
+		`,
+		expStatus: http.StatusOK,
+		expBody:   ``,
+		useCookie: true,
+		validators: []func(c *chartTest, tester *tester, t *testing.T){
+			func(c *chartTest, tester *tester, t *testing.T) {
+				req, err := http.NewRequest(
+					"GET",
+					"/api/charts/wordpress/3?"+url.Values{
+						"namespace": []string{"default"},
+						"context":   []string{"context-test"},
+						"storage":   []string{"memory"},
+					}.Encode(),
+					strings.NewReader(""),
+				)
+
+				req.AddCookie(tester.cookie)
+
+				if err != nil {
+					t.Fatal(err)
+				}
+
+				rr2 := httptest.NewRecorder()
+				tester.router.ServeHTTP(rr2, req)
+
+				gotBody := &release.Release{}
+				expBody := &release.Release{}
+
+				expBodyJSON := releaseStubToChartJSON(releaseStub{"wordpress", "default", 3, "1.0.1", release.StatusDeployed})
+
+				fmt.Println(rr2.Body.String())
+
+				json.Unmarshal(rr2.Body.Bytes(), gotBody)
+				json.Unmarshal([]byte(expBodyJSON), expBody)
+
+				// just check name and version match, other items will be different
+				if gotBody.Name != expBody.Name {
+					t.Errorf("%s, validation wrong body: got %v want %v",
+						c.msg, gotBody.Name, expBody.Name)
+				}
+
+				if gotBody.Version != expBody.Version {
+					t.Errorf("%s, validation wrong body: got %v want %v",
+						c.msg, gotBody.Version, expBody.Version)
+				}
+			},
+		},
+	},
+}
+
+func TestRollbackChart(t *testing.T) {
+	testChartRequests(t, rollbackChartTests, true)
+}
+
 // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
 
 func initDefaultCharts(tester *tester) {
@@ -280,6 +353,19 @@ func makeReleases(agent *helm.Agent, rels []releaseStub) {
 }
 
 func chartReleaseBodyValidator(c *chartTest, tester *tester, t *testing.T) {
+	gotBody := &release.Release{}
+	expBody := &release.Release{}
+
+	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
+	json.Unmarshal([]byte(c.expBody), expBody)
+
+	if !reflect.DeepEqual(gotBody, expBody) {
+		t.Errorf("%s, handler returned wrong body: got %v want %v",
+			c.msg, gotBody, expBody)
+	}
+}
+
+func chartReleaseArrBodyValidator(c *chartTest, tester *tester, t *testing.T) {
 	gotBody := &[]release.Release{}
 	expBody := &[]release.Release{}
 

+ 1 - 0
server/router/router.go

@@ -31,6 +31,7 @@ func New(a *api.App, store sessions.Store, cookieName string) *chi.Mux {
 		r.Method("GET", "/charts", auth.BasicAuthenticate(requestlog.NewHandler(a.HandleListCharts, l)))
 		r.Method("GET", "/charts/{name}/history", auth.BasicAuthenticate(requestlog.NewHandler(a.HandleListChartHistory, l)))
 		r.Method("GET", "/charts/{name}/{revision}", auth.BasicAuthenticate(requestlog.NewHandler(a.HandleGetChart, l)))
+		r.Method("POST", "/charts/rollback/{name}/{revision}", auth.BasicAuthenticate(requestlog.NewHandler(a.HandleRollbackChart, l)))
 
 		// /api/k8s routes
 		r.Method("GET", "/k8s/namespaces", auth.BasicAuthenticate(requestlog.NewHandler(a.HandleListNamespaces, l)))