Explorar el Código

get chart + docs

Alexander Belanger hace 5 años
padre
commit
9d8719e5ce

+ 85 - 0
docs/API.md

@@ -429,8 +429,93 @@ User{
 
 **Successful Response Body**: the full body is determined by the [release specification](https://pkg.go.dev/helm.sh/helm/v3@v3.3.4/pkg/release#Release): listed here is a subset of fields deemed to be most relevant. Note that all of the top-level fields are optional.
 
+```js
+[]Chart{
+  // Name is the name of the release
+  "name": String,
+  "info": Info{
+    // LastDeployed is when the release was last deployed.
+    "last_deployed": String,
+    // Deleted tracks when this object was deleted.
+    "deleted": String,
+    // Description is human-friendly "log entry" about this release.
+    "description": String,
+    // Status is the current state of the release
+    "status": String("unknown"|"deployed"|"uninstalled"|"superseded"|"failed"|"uninstalling"|"pending-install"|"pending-upgrade"|"pending-rollback")
+  },
+  "chart": Chart{
+    "metadata": Metadata{
+      // The name of the chart
+      "name": String,
+      // The URL to a relevant project page, git repo, or contact person
+      "home": String,
+      // Sources is a list of URLs to the source code of this chart
+      "sources": []String,
+      // A SemVer 2 conformant version string of the chart
+      "version": String,
+      // A one-sentence description of the chart
+      "description": String,
+      // The URL to an icon file.
+      "icon": String,
+      // The API Version of this chart.
+      "apiVersion": String,
+    },
+    "templates": []File{
+      // Name is the path-like name of the template.
+      "name": String,
+      // Data is the template as byte data.
+      "data": String
+    },
+    // Values are default config for this chart.
+    "values": Map[String]{}
+  },
+  // The set of extra Values added to the chart, which override the 
+  // default values inside of the chart
+  "config": Map[String]{},
+  // Manifest is the string representation of the rendered template
+  "manifest": String,
+  // Version is an int which represents the revision of the release.
+  "version": Number,
+  // Namespace is the kubernetes namespace of the release.
+  "namespace": String
+}
+```
+
+**Successful Status Code**: `200`
+
+**Errors:** TBD
+
+#### `GET /api/charts/{name}/{revision}`
+
+**Description:** Gets a single chart for a current context and a kubeconfig retrieved from the user's ID based on a **name** and **revision**. To retrieve the latest deployed chart, set **revision** to 0. 
+
+**URL parameters:** 
+
+- `name` The name of the release.
+- `revision` The number of the release (set to `0` for the latest deployed release).
+
+**Query parameters:** N/A
+
+**Request Body**:
+
 ```js
 {
+  "user_id": Number,
+  "helm": {
+    // 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**: the full body is determined by the [release specification](https://pkg.go.dev/helm.sh/helm/v3@v3.3.4/pkg/release#Release): listed here is a subset of fields deemed to be most relevant. Note that all of the top-level fields are optional.
+
+```js
+Chart{
   // Name is the name of the release
   "name": String,
   "info": Info{

+ 21 - 9
internal/forms/chart.go

@@ -5,23 +5,35 @@ import (
 	"github.com/porter-dev/porter/internal/repository"
 )
 
-// ListChartForm represents the accepted values for listing Helm charts
-type ListChartForm struct {
-	HelmOptions *helm.Form       `json:"helm" form:"required"`
-	ListFilter  *helm.ListFilter `json:"filter" form:"required"`
-	UserID      uint             `json:"user_id"`
+// ChartForm is the generic base type for CRUD operations on charts
+type ChartForm struct {
+	HelmOptions *helm.Form `json:"helm" form:"required"`
+	UserID      uint       `json:"user_id"`
 }
 
 // PopulateHelmOptions uses the passed user ID to populate the HelmOptions object
-func (lcf *ListChartForm) PopulateHelmOptions(repo repository.UserRepository) error {
-	user, err := repo.ReadUser(lcf.UserID)
+func (cf *ChartForm) PopulateHelmOptions(repo repository.UserRepository) error {
+	user, err := repo.ReadUser(cf.UserID)
 
 	if err != nil {
 		return err
 	}
 
-	lcf.HelmOptions.AllowedContexts = user.ContextToSlice()
+	cf.HelmOptions.AllowedContexts = user.ContextToSlice()
+	cf.HelmOptions.KubeConfig = user.RawKubeConfig
 
-	lcf.HelmOptions.KubeConfig = user.RawKubeConfig
 	return nil
 }
+
+// ListChartForm represents the accepted values for listing Helm charts
+type ListChartForm struct {
+	ChartForm
+	ListFilter *helm.ListFilter `json:"filter" form:"required"`
+}
+
+// GetChartForm represents the accepted values for getting a single Helm chart
+type GetChartForm struct {
+	ChartForm
+	Name     string `json:"name" form:"required"`
+	Revision int    `json:"release"`
+}

+ 3 - 0
internal/helm/agent.go

@@ -105,9 +105,12 @@ func (a *Agent) ListReleases(
 // GetRelease returns the info of a release.
 func (a *Agent) GetRelease(
 	name string,
+	version int,
 ) (*release.Release, error) {
 	// Namespace is already known by the RESTClientGetter.
 	cmd := action.NewGet(a.ActionConfig)
 
+	cmd.Version = version
+
 	return cmd.Run(name)
 }

+ 43 - 0
internal/helm/agent_test.go

@@ -173,3 +173,46 @@ func TestListReleases(t *testing.T) {
 		compareReleaseToStubs(t, releases, tc.expRes)
 	}
 }
+
+type getReleaseTest struct {
+	name       string
+	namespace  string
+	releases   []releaseStub
+	getName    string
+	getVersion int
+	expRes     releaseStub
+}
+
+var getReleaseTests = []getReleaseTest{
+	getReleaseTest{
+		name:      "simple get with revision 0 (latest)",
+		namespace: "default",
+		releases: []releaseStub{
+			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
+			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
+		},
+		getName:    "airwatch",
+		getVersion: 0,
+		expRes:     releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+	},
+}
+
+func TestGetReleases(t *testing.T) {
+	for _, tc := range getReleaseTests {
+		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)
+
+		rel, err := agent.GetRelease(tc.getName, tc.getVersion)
+
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+
+		compareReleaseToStubs(t, []*release.Release{rel}, []releaseStub{tc.expRes})
+	}
+}

+ 49 - 0
server/api/chart_handler.go

@@ -3,7 +3,9 @@ package api
 import (
 	"encoding/json"
 	"net/http"
+	"strconv"
 
+	"github.com/go-chi/chi"
 	"github.com/porter-dev/porter/internal/forms"
 )
 
@@ -47,3 +49,50 @@ func (app *App) HandleListCharts(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 }
+
+// HandleGetChart retrieves a single chart based on a name and revision
+func (app *App) HandleGetChart(w http.ResponseWriter, r *http.Request) {
+	name := chi.URLParam(r, "name")
+	revision, err := strconv.ParseUint(chi.URLParam(r, "revision"), 0, 64)
+
+	// decode from JSON to form value
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrChartDecode, w)
+		return
+	}
+
+	// get the filter options
+	form := &forms.GetChartForm{
+		Name:     name,
+		Revision: int(revision),
+	}
+
+	// decode from JSON to form value
+	if err := json.NewDecoder(r.Body).Decode(form); err != nil {
+		app.handleErrorFormDecoding(err, ErrChartDecode, w)
+		return
+	}
+
+	form.PopulateHelmOptions(app.repo.User)
+
+	// validate the form
+	if err := app.validator.Struct(form); err != nil {
+		app.handleErrorFormValidation(err, ErrChartValidateFields, w)
+		return
+	}
+
+	// create a new agent
+	agent, err := form.HelmOptions.ToAgent(app.logger, app.helmConf, app.HelmTestStorageDriver)
+
+	release, err := agent.GetRelease(form.Name, form.Revision)
+
+	if err != nil {
+		app.handleErrorFormValidation(err, ErrChartValidateFields, w)
+		return
+	}
+
+	if err := json.NewEncoder(w).Encode(release); err != nil {
+		app.handleErrorFormDecoding(err, ErrChartDecode, w)
+		return
+	}
+}

+ 37 - 0
server/api/chart_handler_test.go

@@ -142,6 +142,35 @@ func TestHandleListCharts(t *testing.T) {
 	testChartRequests(t, listChartsTests, true)
 }
 
+var getChartTests = []*chartTest{
+	&chartTest{
+		initializers: []func(tester *tester){
+			initDefaultCharts,
+		},
+		msg:      "Get charts",
+		method:   "GET",
+		endpoint: "/api/charts/airwatch/0",
+		body: `{
+			"user_id": 1,
+			"helm": {
+				"namespace": "",
+				"context": "context-test",
+				"storage": "memory"
+			}
+		}`,
+		expStatus: http.StatusOK,
+		expBody:   releaseStubToChartJSON(sampleReleaseStubs[0]),
+		useCookie: true,
+		validators: []func(c *chartTest, tester *tester, t *testing.T){
+			chartReleaseBodyValidator,
+		},
+	},
+}
+
+func TestHandleGetChart(t *testing.T) {
+	testChartRequests(t, getChartTests, true)
+}
+
 // ------------------------- INITIALIZERS AND VALIDATORS ------------------------- //
 
 func initDefaultCharts(tester *tester) {
@@ -189,6 +218,14 @@ func releaseStubsToChartJSON(rels []releaseStub) string {
 	return string(str)
 }
 
+func releaseStubToChartJSON(r releaseStub) string {
+	rel := releaseStubToRelease(r)
+
+	str, _ := json.Marshal(rel)
+
+	return string(str)
+}
+
 func releaseStubToRelease(r releaseStub) *release.Release {
 	return &release.Release{
 		Name:      r.name,

+ 1 - 0
server/router/middleware/auth.go

@@ -66,6 +66,7 @@ func (auth *Auth) DoesUserIDMatch(next http.Handler, loc IDLocation) http.Handle
 			form := &bodyID{}
 			body, _ := ioutil.ReadAll(r.Body)
 			err = json.Unmarshal(body, form)
+
 			id = form.UserID
 
 			// need to create a new stream for the body

+ 1 - 0
server/router/router.go

@@ -30,6 +30,7 @@ func New(a *api.App, store *sessionstore.PGStore, cookieName string) *chi.Mux {
 
 		// /api/charts routes
 		r.Method("GET", "/charts", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleListCharts, l), mw.BodyParam))
+		r.Method("GET", "/charts/{name}/{revision}", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleGetChart, l), mw.BodyParam))
 	})
 
 	return r