Alexander Belanger il y a 5 ans
Parent
commit
3c34791e28

+ 26 - 0
internal/forms/chart.go

@@ -0,0 +1,26 @@
+package forms
+
+import (
+	"github.com/porter-dev/porter/internal/helm"
+	"github.com/porter-dev/porter/internal/repository"
+)
+
+// ListChartForm represents the accepted values for listing Helm charts
+type ListChartForm struct {
+	HelmOptions *helm.HelmForm   `json:"helm" form:"required"`
+	ListFilter  *helm.ListFilter `json:"filter" 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)
+
+	if err != nil {
+		return err
+	}
+
+	lcf.HelmOptions.AllowedContexts = user.Contexts
+	lcf.HelmOptions.KubeConfig = user.RawKubeConfig
+	return nil
+}

+ 61 - 24
internal/helm/agent.go

@@ -1,51 +1,88 @@
 package helm
 
 import (
+	"io/ioutil"
+
+	"github.com/porter-dev/porter/internal/kubernetes"
 	"github.com/porter-dev/porter/internal/logger"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/release"
-	"k8s.io/client-go/kubernetes"
-	"k8s.io/client-go/rest"
+
+	"helm.sh/helm/v3/pkg/chartutil"
+	kubefake "helm.sh/helm/v3/pkg/kube/fake"
 )
 
 // Agent is a Helm agent for performing helm operations
-type Agent interface {
-	ListReleases(namespace string, filter *ListFilter) ([]*release.Release, error)
-	GetRelease(name string) (*release.Release, error)
+type Agent struct {
+	ActionConfig *action.Configuration
 }
 
-// DefaultAgent implements Agent using Helm's action.Configuration
-type DefaultAgent struct {
-	actionConfig *action.Configuration
+type HelmForm struct {
+	KubeConfig      []byte   `form:"required"`
+	AllowedContexts []string `form:"required"`
+	Context         string   `json:"context" form:"required"`
+	Storage         string   `json:"storage" form:"oneof=secret configmap memory"`
+	Namespace       string   `json:"namespace"`
 }
 
-// NewDefaultAgent creates a new implementation of Agent
-func NewDefaultAgent(
+func (h *HelmForm) ToAgent(
 	l *logger.Logger,
-	storage string,
-	namespace string,
-	config *rest.Config,
-	clientset *kubernetes.Clientset,
-) (*DefaultAgent, error) {
-	// get the storage driver
-	storageDriver := StorageMap[storage]
+	testing bool,
+) (*Agent, error) {
+	if testing {
+		return &Agent{&action.Configuration{
+			Releases: StorageMap["memory"](l, h.Namespace, nil),
+			KubeClient: &kubefake.FailingKubeClient{
+				PrintingKubeClient: kubefake.PrintingKubeClient{
+					Out: ioutil.Discard,
+				},
+			},
+			Capabilities: chartutil.DefaultCapabilities,
+			Log:          l.Printf,
+		}}, nil
+	}
+
+	// create a client config using the app's helm/kubernetes agents
+	conf, err := kubernetes.GetRestrictedClientConfigFromBytes(
+		h.KubeConfig,
+		h.Context,
+		h.AllowedContexts,
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	restConf, err := conf.ClientConfig()
+
+	clientset, err := kubernetes.GetClientsetFromConfig(conf)
+
+	if err != nil {
+		return nil, err
+	}
 
-	// create the action config
-	actionConfig, err := NewActionConfig(l, storageDriver, config, clientset, namespace)
+	// create a new agent
+	actionConfig, err := NewActionConfig(
+		l,
+		StorageMap[h.Storage],
+		restConf,
+		clientset,
+		h.Namespace,
+	)
 
 	if err != nil {
 		return nil, err
 	}
 
-	return &DefaultAgent{actionConfig}, nil
+	return &Agent{actionConfig}, nil
 }
 
 // ListReleases lists releases based on a ListFilter
-func (a *DefaultAgent) ListReleases(
+func (a *Agent) ListReleases(
 	namespace string,
 	filter *ListFilter,
 ) ([]*release.Release, error) {
-	cmd := action.NewList(a.actionConfig)
+	cmd := action.NewList(a.ActionConfig)
 
 	filter.apply(cmd)
 
@@ -53,11 +90,11 @@ func (a *DefaultAgent) ListReleases(
 }
 
 // GetRelease returns the info of a release.
-func (a *DefaultAgent) GetRelease(
+func (a *Agent) GetRelease(
 	name string,
 ) (*release.Release, error) {
 	// Namespace is already known by the RESTClientGetter.
-	cmd := action.NewGet(a.actionConfig)
+	cmd := action.NewGet(a.ActionConfig)
 
 	return cmd.Run(name)
 }

+ 40 - 51
internal/helm/agent_test.go

@@ -1,36 +1,26 @@
 package helm_test
 
 import (
-	"io/ioutil"
 	"testing"
 
 	"github.com/porter-dev/porter/internal/helm"
 	"github.com/porter-dev/porter/internal/logger"
 
-	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart"
-	"helm.sh/helm/v3/pkg/chartutil"
-	kubefake "helm.sh/helm/v3/pkg/kube/fake"
 	"helm.sh/helm/v3/pkg/release"
-	"helm.sh/helm/v3/pkg/storage"
-	"helm.sh/helm/v3/pkg/storage/driver"
 )
 
-func newActionConfigFixture(t *testing.T) *action.Configuration {
+func newAgentFixture(t *testing.T, namespace string) *helm.Agent {
 	t.Helper()
 
 	l := logger.NewConsole(true)
-
-	return &action.Configuration{
-		Releases: storage.Init(driver.NewMemory()),
-		KubeClient: &kubefake.FailingKubeClient{
-			PrintingKubeClient: kubefake.PrintingKubeClient{
-				Out: ioutil.Discard,
-			},
-		},
-		Capabilities: chartutil.DefaultCapabilities,
-		Log:          l.Printf,
+	opts := &helm.HelmForm{
+		Namespace: namespace,
 	}
+
+	agent, _ := opts.ToAgent(l, true)
+
+	return agent
 }
 
 type releaseStub struct {
@@ -42,9 +32,9 @@ type releaseStub struct {
 }
 
 // makeReleases adds a slice of releases to the configured storage.
-func makeReleases(t *testing.T, actionConfig *action.Configuration, rels []releaseStub) {
+func makeReleases(t *testing.T, agent *helm.Agent, rels []releaseStub) {
 	t.Helper()
-	storage := actionConfig.Releases
+	storage := agent.ActionConfig.Releases
 	for _, r := range rels {
 		rel := &release.Release{
 			Name:      r.name,
@@ -135,39 +125,38 @@ var listReleaseTests = []listReleaseTest{
 			releaseStub{"wordpress", "default", 1, "1.0.1", release.StatusDeployed},
 		},
 	},
-	listReleaseTest{
-		name:      "simple test limit",
-		namespace: "",
-		filter: &helm.ListFilter{
-			Namespace:    "",
-			Limit:        2,
-			Skip:         0,
-			ByDate:       false,
-			StatusFilter: []string{"deployed"},
-		},
-		releases: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
-			releaseStub{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
-		},
-		expRes: []releaseStub{
-			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
-			releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
-		},
-	},
+	// listReleaseTest{
+	// 	name:      "simple test limit",
+	// 	namespace: "",
+	// 	filter: &helm.ListFilter{
+	// 		Namespace:    "",
+	// 		Limit:        2,
+	// 		Skip:         0,
+	// 		ByDate:       false,
+	// 		StatusFilter: []string{"deployed"},
+	// 	},
+	// 	releases: []releaseStub{
+	// 		releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+	// 		releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
+	// 		releaseStub{"wordpress", "default", 1, "1.0.2", release.StatusDeployed},
+	// 	},
+	// 	expRes: []releaseStub{
+	// 		releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+	// 		releaseStub{"not-in-default-namespace", "other", 1, "1.0.1", release.StatusDeployed},
+	// 	},
+	// },
 }
 
-// func TestListReleases(t *testing.T) {
-// 	for _, tc := range listReleaseTests {
-// 		actionConfig := newActionConfigFixture(t)
-// 		makeReleases(t, actionConfig, tc.releases)
-// 		actionConfig.Releases.Driver.(*driver.Memory).SetNamespace(tc.namespace)
+func TestListReleases(t *testing.T) {
+	for _, tc := range listReleaseTests {
+		agent := newAgentFixture(t, tc.namespace)
+		makeReleases(t, agent, tc.releases)
 
-// 		releases, err := helm.ListReleases(actionConfig, tc.namespace, tc.filter)
-// 		if err != nil {
-// 			t.Errorf("%v", err)
-// 		}
+		releases, err := agent.ListReleases(tc.namespace, tc.filter)
+		if err != nil {
+			t.Errorf("%v", err)
+		}
 
-// 		compareReleaseToStubs(t, releases, tc.expRes)
-// 	}
-// }
+		compareReleaseToStubs(t, releases, tc.expRes)
+	}
+}

+ 6 - 3
internal/helm/driver.go

@@ -54,10 +54,13 @@ func newConfigMapsStorageDriver(
 
 // NewMemoryStorageDriver returns a storage using the In-Memory driver.
 func newMemoryStorageDriver(
-	l *logger.Logger,
+	_ *logger.Logger,
 	namespace string,
-	clientset *kubernetes.Clientset,
+	_ *kubernetes.Clientset,
 ) *storage.Storage {
 	d := driver.NewMemory()
-	return storage.Init(d)
+	store := storage.Init(d)
+
+	store.Driver.(*driver.Memory).SetNamespace(namespace)
+	return store
 }

+ 42 - 5
server/api/chart_handler.go

@@ -1,12 +1,49 @@
 package api
 
-import "net/http"
+import (
+	"encoding/json"
+	"net/http"
 
-// TODO -- IMPLEMENT
+	"github.com/porter-dev/porter/internal/forms"
+)
+
+// Enumeration of chart API error codes, represented as int64
+const (
+	ErrChartDecode ErrorCode = iota + 600
+	ErrChartValidateFields
+)
+
+// HandleListCharts retrieves a list of charts with various filter options
 func (app *App) HandleListCharts(w http.ResponseWriter, r *http.Request) {
-	// get the user id
+	// get the filter options
+	form := &forms.ListChartForm{}
+
+	// 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, false)
+
+	releases, err := agent.ListReleases(form.HelmOptions.Namespace, form.ListFilter)
 
-	// create a client config using the app's helm/kubernetes agents
+	if err != nil {
+		app.handleErrorFormValidation(err, ErrChartValidateFields, w)
+		return
+	}
 
-	// call the list charts method
+	if err := json.NewEncoder(w).Encode(releases); err != nil {
+		app.handleErrorFormDecoding(err, ErrChartDecode, w)
+		return
+	}
 }