Parcourir la source

Merge pull request #58 from porter-dev/api-charts-cleanup

Api charts cleanup
sunguroku il y a 5 ans
Parent
commit
e0110b672e

+ 0 - 15
INT_TEST.md

@@ -1,15 +0,0 @@
-Just storing the command-line "integration tests" I've done, which we can use later:
-
-```sh
-# should fail on form decoding
-curl -X POST localhost:8080/api/users 
-
-# should fail on email validation
-curl -d "{\"email\":\"hello\",\"password\":\"hello\"}" -H 'Content-Type: application/json' -X POST localhost:8080/api/users
-
-# should pass (without authentication)
-curl -d "{\"email\":\"belanger@getporter.dev\",\"password\":\"hello\"}" -H 'Content-Type: application/json' -X POST localhost:8080/api/users
-
-# should pass
-curl -X DELETE localhost:8080/api/users/1 -d "{\"password\":\"hello\"}"
-```

+ 1 - 0
dashboard/.dockerignore

@@ -0,0 +1 @@
+node_modules

+ 1 - 0
docker-compose.dev.yaml

@@ -7,6 +7,7 @@ services:
     restart: on-failure
     volumes:
       - ./dashboard:/webpack:rw,cached
+      - /webpack/node_modules
   porter:
     build:
       context: .

+ 90 - 37
docs/API.md

@@ -499,7 +499,17 @@ User object with only the id field. Other fields are empty - with values in para
 
 **Successful Status Code**: `200`
 
-**Errors:** TBD
+**Errors:** 
+
+- Missing required field
+  - Status Code: `422`
+  - Request Body:
+    ```json
+    {
+      "code":601,
+      "errors":["required validation failed"]
+    }
+    ```
 
 #### `GET /api/releases/{name}/history`
 
@@ -580,7 +590,27 @@ User object with only the id field. Other fields are empty - with values in para
 
 **Successful Status Code**: `200`
 
-**Errors:** TBD
+**Errors:** 
+
+- Release not found
+  - Status Code: `404`
+  - Request Body:
+    ```json
+    {
+      "code":602,
+      "errors":["release not found"]
+    }
+    ```
+- Missing required field
+  - Status Code: `422`
+  - Request Body:
+    ```json
+    {
+      "code":601,
+      "errors":["required validation failed"]
+    }
+    ```
+
 
 #### `GET /api/releases/{name}/{revision}`
 
@@ -662,7 +692,26 @@ User object with only the id field. Other fields are empty - with values in para
 
 **Successful Status Code**: `200`
 
-**Errors:** TBD
+**Errors:** 
+
+- Release not found
+  - Status Code: `404`
+  - Request Body:
+    ```json
+    {
+      "code":602,
+      "errors":["release not found"]
+    }
+    ```
+- Missing required field
+  - Status Code: `422`
+  - Request Body:
+    ```json
+    {
+      "code":601,
+      "errors":["required validation failed"]
+    }
+    ```
 
 #### `POST /api/releases/{name}/rollback`
 
@@ -694,41 +743,26 @@ User object with only the id field. Other fields are empty - with values in para
 
 **Successful Status Code**: `200`
 
-**Errors:** TBD
-
-### `/api/k8s`
-
-#### `GET /api/k8s/namespaces`
-
-**Description:** 
-
-**URL parameters:** N/A
-
-**Query parameters:** N/A
-
-```js
-// The name of the context in the kubeconfig being used
-"context": String,
-```
-
-**Request Body**: N/A
-
-**Successful Response Body**: the full body is determined by the [namespace specification](https://pkg.go.dev/k8s.io/api/core/v1#NamespaceList), but we're primarily only interested in namespace `name`:
+**Errors:**
 
-```js
-{
-  "metadata": {},
-  "items": []Namespace{
-    "metadata": {
-      "name": String
+- Rollback failed
+  - Status Code: `500`
+  - Request Body:
+    ```json
+    {
+      "code":603,
+      "errors":["rollback failed: <error>"]
     }
-  }
-}
-```
-
-**Successful Status Code**: `200`
-
-**Errors:** TBD
+    ```
+- Missing required field
+  - Status Code: `422`
+  - Request Body:
+    ```json
+    {
+      "code":601,
+      "errors":["required validation failed"]
+    }
+    ```
 
 #### `POST /api/releases/{name}/upgrade`
 
@@ -759,7 +793,26 @@ User object with only the id field. Other fields are empty - with values in para
 
 **Successful Status Code**: `200`
 
-**Errors:** TBD
+**Errors:** 
+
+- Upgrade failed
+  - Status Code: `500`
+  - Request Body:
+    ```json
+    {
+      "code":603,
+      "errors":["upgrade failed: <error>"]
+    }
+    ```
+- Missing required field
+  - Status Code: `422`
+  - Request Body:
+    ```json
+    {
+      "code":601,
+      "errors":["required validation failed"]
+    }
+    ```
 
 ### `/api/k8s`
 

+ 2 - 0
go.sum

@@ -1273,6 +1273,8 @@ k8s.io/cli-runtime v0.18.8 h1:ycmbN3hs7CfkJIYxJAOB10iW7BVPmXGXkfEyiV9NJ+k=
 k8s.io/cli-runtime v0.18.8/go.mod h1:7EzWiDbS9PFd0hamHHVoCY4GrokSTPSL32MA4rzIu0M=
 k8s.io/client-go v0.18.8 h1:SdbLpIxk5j5YbFr1b7fq8S7mDgDjYmUxSbszyoesoDM=
 k8s.io/client-go v0.18.8/go.mod h1:HqFqMllQ5NnQJNwjro9k5zMyfhZlOwpuTLVrxjkYSxU=
+k8s.io/client-go v1.5.1 h1:XaX/lo2/u3/pmFau8HN+sB5C/b4dc4Dmm2eXjBH4p1E=
+k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o=
 k8s.io/code-generator v0.18.8/go.mod h1:TgNEVx9hCyPGpdtCWA34olQYLkh3ok9ar7XfSsr8b6c=
 k8s.io/component-base v0.18.8 h1:BW5CORobxb6q5mb+YvdwQlyXXS6NVH5fDXWbU7tf2L8=
 k8s.io/component-base v0.18.8/go.mod h1:00frPRDas29rx58pPCxNkhUfPbwajlyyvu8ruNgSErU=

+ 3 - 3
internal/helm/agent.go

@@ -56,7 +56,7 @@ func (a *Agent) UpgradeRelease(
 	rel, err := a.GetRelease(name, 0)
 
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("Could not get release to be upgraded: %v", err)
 	}
 
 	ch := rel.Chart
@@ -65,13 +65,13 @@ func (a *Agent) UpgradeRelease(
 	valuesYaml, err := chartutil.ReadValues([]byte(values))
 
 	if err != nil {
-		return nil, fmt.Errorf("Unable to upgrade the release because values could not be parsed: %v", err)
+		return nil, fmt.Errorf("Values could not be parsed: %v", err)
 	}
 
 	res, err := cmd.Run(name, ch, valuesYaml)
 
 	if err != nil {
-		return nil, fmt.Errorf("Unable to upgrade the release: %v", err)
+		return nil, fmt.Errorf("Upgrade failed: %v", err)
 	}
 
 	return res, nil

+ 3 - 3
internal/helm/config.go

@@ -50,7 +50,7 @@ func GetAgentOutOfClusterConfig(form *Form, l *logger.Logger) (*Agent, error) {
 	return &Agent{&action.Configuration{
 		RESTClientGetter: k8sAgent.RESTClientGetter,
 		KubeClient:       kube.New(k8sAgent.RESTClientGetter),
-		Releases:         StorageMap[form.Storage](l, form.Namespace, clientset),
+		Releases:         StorageMap[form.Storage](l, clientset.CoreV1(), form.Namespace),
 		Log:              l.Printf,
 	}}, nil
 }
@@ -75,7 +75,7 @@ func GetAgentInClusterConfig(form *Form, l *logger.Logger) (*Agent, error) {
 	return &Agent{&action.Configuration{
 		RESTClientGetter: k8sAgent.RESTClientGetter,
 		KubeClient:       kube.New(k8sAgent.RESTClientGetter),
-		Releases:         StorageMap[form.Storage](l, form.Namespace, clientset),
+		Releases:         StorageMap[form.Storage](l, clientset.CoreV1(), form.Namespace),
 		Log:              l.Printf,
 	}}, nil
 }
@@ -85,7 +85,7 @@ func GetAgentTesting(form *Form, storage *storage.Storage, l *logger.Logger) *Ag
 	testStorage := storage
 
 	if testStorage == nil {
-		testStorage = StorageMap["memory"](nil, form.Namespace, nil)
+		testStorage = StorageMap["memory"](nil, nil, "")
 	}
 
 	return &Agent{&action.Configuration{

+ 13 - 9
internal/helm/storage.go

@@ -8,7 +8,7 @@ package helm
 // - memory
 // - postgres
 //
-// This file implements first-class support for the first three driver types,
+// This file implements first-class support for the first three driver types
 // and integrates with the logger.
 //
 // TODO -- include support for SQL storage...
@@ -18,11 +18,15 @@ import (
 
 	"helm.sh/helm/v3/pkg/storage"
 	"helm.sh/helm/v3/pkg/storage/driver"
-	"k8s.io/client-go/kubernetes"
+	corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
 )
 
 // NewStorageDriver is a function type for returning a new storage driver
-type NewStorageDriver func(l *logger.Logger, namespace string, clientset *kubernetes.Clientset) *storage.Storage
+type NewStorageDriver func(
+	l *logger.Logger,
+	v1Interface corev1.CoreV1Interface,
+	namespace string,
+) *storage.Storage
 
 // StorageMap is a map from storage configuration env variables to a function
 // that initializes that Helm storage driver.
@@ -35,10 +39,10 @@ var StorageMap map[string]NewStorageDriver = map[string]NewStorageDriver{
 // NewSecretStorageDriver returns a storage using the Secret driver.
 func newSecretStorageDriver(
 	l *logger.Logger,
+	v1Interface corev1.CoreV1Interface,
 	namespace string,
-	clientset *kubernetes.Clientset,
 ) *storage.Storage {
-	d := driver.NewSecrets(clientset.CoreV1().Secrets(namespace))
+	d := driver.NewSecrets(v1Interface.Secrets(namespace))
 	d.Log = l.Printf
 	return storage.Init(d)
 }
@@ -46,10 +50,10 @@ func newSecretStorageDriver(
 // NewConfigMapsStorageDriver returns a storage using the ConfigMap driver.
 func newConfigMapsStorageDriver(
 	l *logger.Logger,
+	v1Interface corev1.CoreV1Interface,
 	namespace string,
-	clientset *kubernetes.Clientset,
 ) *storage.Storage {
-	d := driver.NewConfigMaps(clientset.CoreV1().ConfigMaps(namespace))
+	d := driver.NewConfigMaps(v1Interface.ConfigMaps(namespace))
 	d.Log = l.Printf
 	return storage.Init(d)
 }
@@ -57,8 +61,8 @@ func newConfigMapsStorageDriver(
 // NewMemoryStorageDriver returns a storage using the In-Memory driver.
 func newMemoryStorageDriver(
 	_ *logger.Logger,
-	namespace string,
-	_ *kubernetes.Clientset,
+	_ corev1.CoreV1Interface,
+	_ string,
 ) *storage.Storage {
 	d := driver.NewMemory()
 	return storage.Init(d)

+ 82 - 0
internal/helm/storage_test.go

@@ -0,0 +1,82 @@
+package helm_test
+
+import (
+	"reflect"
+	"testing"
+
+	"helm.sh/helm/v3/pkg/chart"
+	"helm.sh/helm/v3/pkg/release"
+
+	"github.com/porter-dev/porter/internal/helm"
+	"github.com/porter-dev/porter/internal/kubernetes"
+	"github.com/porter-dev/porter/internal/logger"
+	"helm.sh/helm/v3/pkg/storage"
+	"k8s.io/client-go/kubernetes/fake"
+)
+
+func testDriver(t *testing.T, storage *storage.Storage) {
+	t.Helper()
+
+	rel := &release.Release{
+		Name:      "porter",
+		Namespace: "default",
+		Version:   1,
+		Info: &release.Info{
+			Status: release.StatusDeployed,
+		},
+		Chart: &chart.Chart{
+			Metadata: &chart.Metadata{
+				Version: "1.0.0",
+				Icon:    "https://example.com/icon.png",
+			},
+		},
+	}
+
+	err := storage.Create(rel)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	gotRel, err := storage.Get("porter", 1)
+
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if !reflect.DeepEqual(rel, gotRel) {
+		t.Fatalf("Objects not equal: expected %v, got %v\n", rel, gotRel)
+	}
+}
+
+func testStorageDriver(t *testing.T, name string) {
+	t.Helper()
+
+	k8sAgent := kubernetes.GetAgentTesting()
+
+	newDriver := helm.StorageMap[name]
+
+	l := logger.NewConsole(true)
+
+	clientset, ok := k8sAgent.Clientset.(*fake.Clientset)
+
+	if !ok {
+		t.Fatal("Agent Clientset was not of type *(k8s.io/client-go/kubernetes/fake).Clientset")
+	}
+
+	driver := newDriver(l, clientset.CoreV1(), "default")
+
+	testDriver(t, driver)
+}
+
+func TestNewSecretStorageDriver(t *testing.T) {
+	testStorageDriver(t, "secret")
+}
+
+func TestNewConfigMapStorageDriver(t *testing.T) {
+	testStorageDriver(t, "configmap")
+}
+
+func TestNewMemoryStorageDriver(t *testing.T) {
+	testStorageDriver(t, "memory")
+}

+ 1 - 1
server/api/api.go

@@ -51,7 +51,7 @@ func New(
 	var testAgents *TestAgents = nil
 
 	if testing {
-		memStorage := helm.StorageMap["memory"](nil, "", nil)
+		memStorage := helm.StorageMap["memory"](nil, nil, "")
 
 		testAgents = &TestAgents{
 			HelmAgent:             helm.GetAgentTesting(&helm.Form{}, nil, logger),

+ 21 - 4
server/api/release_handler.go

@@ -16,6 +16,7 @@ const (
 	ErrReleaseDecode ErrorCode = iota + 600
 	ErrReleaseValidateFields
 	ErrReleaseReadData
+	ErrReleaseDeploy
 )
 
 // HandleListReleases retrieves a list of releases for a cluster
@@ -82,7 +83,11 @@ func (app *App) HandleGetRelease(w http.ResponseWriter, r *http.Request) {
 	release, err := agent.GetRelease(form.Name, form.Revision)
 
 	if err != nil {
-		app.handleErrorRead(err, ErrReleaseReadData, w)
+		app.sendExternalError(err, http.StatusNotFound, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"release not found"},
+		}, w)
+
 		return
 	}
 
@@ -118,7 +123,11 @@ func (app *App) HandleListReleaseHistory(w http.ResponseWriter, r *http.Request)
 	release, err := agent.GetReleaseHistory(form.Name)
 
 	if err != nil {
-		app.handleErrorFormValidation(err, ErrReleaseValidateFields, w)
+		app.sendExternalError(err, http.StatusNotFound, HTTPError{
+			Code:   ErrReleaseReadData,
+			Errors: []string{"release not found"},
+		}, w)
+
 		return
 	}
 
@@ -158,7 +167,11 @@ func (app *App) HandleUpgradeRelease(w http.ResponseWriter, r *http.Request) {
 	_, err = agent.UpgradeRelease(form.Name, form.Values)
 
 	if err != nil {
-		app.handleErrorInternal(err, w)
+		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+			Code:   ErrReleaseDeploy,
+			Errors: []string{"error upgrading release " + err.Error()},
+		}, w)
+
 		return
 	}
 
@@ -195,7 +208,11 @@ func (app *App) HandleRollbackRelease(w http.ResponseWriter, r *http.Request) {
 	err = agent.RollbackRelease(form.Name, form.Revision)
 
 	if err != nil {
-		app.handleErrorInternal(err, w)
+		app.sendExternalError(err, http.StatusInternalServerError, HTTPError{
+			Code:   ErrReleaseDeploy,
+			Errors: []string{"error rolling back release " + err.Error()},
+		}, w)
+
 		return
 	}
 

+ 72 - 2
server/api/release_handler_test.go

@@ -91,7 +91,7 @@ var listReleasesTests = []*releaseTest{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
-		msg:    "List releases",
+		msg:    "List releases no namespace",
 		method: "GET",
 		endpoint: "/api/releases?" + url.Values{
 			"namespace":    []string{""},
@@ -114,7 +114,7 @@ var listReleasesTests = []*releaseTest{
 		initializers: []func(tester *tester){
 			initDefaultReleases,
 		},
-		msg:       "List releases",
+		msg:       "List releases with namespace",
 		method:    "GET",
 		namespace: "default",
 		endpoint: "/api/releases?" + url.Values{
@@ -137,6 +137,29 @@ var listReleasesTests = []*releaseTest{
 			releaseReleaseArrBodyValidator,
 		},
 	},
+	&releaseTest{
+		initializers: []func(tester *tester){
+			initDefaultReleases,
+		},
+		msg:       "List releases missing required",
+		method:    "GET",
+		namespace: "default",
+		endpoint: "/api/releases?" + url.Values{
+			"namespace":    []string{"default"},
+			"storage":      []string{"memory"},
+			"limit":        []string{"20"},
+			"skip":         []string{"0"},
+			"byDate":       []string{"false"},
+			"statusFilter": []string{"deployed"},
+		}.Encode(),
+		body:      "",
+		expStatus: http.StatusUnprocessableEntity,
+		expBody:   `{"code":601,"errors":["required validation failed"]}`,
+		useCookie: true,
+		validators: []func(c *releaseTest, tester *tester, t *testing.T){
+			releaseBasicBodyValidator,
+		},
+	},
 }
 
 func TestHandleListReleases(t *testing.T) {
@@ -164,6 +187,26 @@ var getReleaseTests = []*releaseTest{
 			releaseReleaseBodyValidator,
 		},
 	},
+	&releaseTest{
+		initializers: []func(tester *tester){
+			initDefaultReleases,
+		},
+		msg:       "Release not found",
+		method:    "GET",
+		namespace: "default",
+		endpoint: "/api/releases/airwatch/5?" + url.Values{
+			"namespace": []string{""},
+			"context":   []string{"context-test"},
+			"storage":   []string{"memory"},
+		}.Encode(),
+		body:      "",
+		expStatus: http.StatusNotFound,
+		expBody:   `{"code":602,"errors":["release not found"]}`,
+		useCookie: true,
+		validators: []func(c *releaseTest, tester *tester, t *testing.T){
+			releaseBasicBodyValidator,
+		},
+	},
 }
 
 func TestHandleGetRelease(t *testing.T) {
@@ -191,6 +234,26 @@ var listReleaseHistoryTests = []*releaseTest{
 			releaseReleaseArrBodyValidator,
 		},
 	},
+	&releaseTest{
+		initializers: []func(tester *tester){
+			initDefaultReleases,
+		},
+		msg:       "Release not found",
+		method:    "GET",
+		namespace: "default",
+		endpoint: "/api/releases/asldfkja/history?" + url.Values{
+			"namespace": []string{""},
+			"context":   []string{"context-test"},
+			"storage":   []string{"memory"},
+		}.Encode(),
+		body:      "",
+		expStatus: http.StatusNotFound,
+		expBody:   `{"code":602,"errors":["release not found"]}`,
+		useCookie: true,
+		validators: []func(c *releaseTest, tester *tester, t *testing.T){
+			releaseBasicBodyValidator,
+		},
+	},
 }
 
 func TestHandleListReleaseHistory(t *testing.T) {
@@ -430,6 +493,13 @@ func makeReleases(agent *helm.Agent, rels []releaseStub) {
 	}
 }
 
+func releaseBasicBodyValidator(c *releaseTest, tester *tester, t *testing.T) {
+	if body := tester.rr.Body.String(); strings.TrimSpace(body) != strings.TrimSpace(c.expBody) {
+		t.Errorf("%s, handler returned wrong body: got %v want %v",
+			c.msg, body, c.expBody)
+	}
+}
+
 func releaseReleaseBodyValidator(c *releaseTest, tester *tester, t *testing.T) {
 	gotBody := &release.Release{}
 	expBody := &release.Release{}