Jelajahi Sumber

initial helm list agent

Alexander Belanger 5 tahun lalu
induk
melakukan
ad338c62f7

+ 18 - 4
go.mod

@@ -3,37 +3,51 @@ module github.com/porter-dev/porter
 go 1.14
 
 require (
+	github.com/Azure/go-autorest/autorest v0.11.1 // indirect
 	github.com/DATA-DOG/go-sqlmock v1.5.0
 	github.com/cosmtrek/air v1.21.2 // indirect
 	github.com/creack/pty v1.1.11 // indirect
+	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 // indirect
+	github.com/evanphx/json-patch v4.9.0+incompatible // indirect
 	github.com/fatih/color v1.9.0 // indirect
 	github.com/go-chi/chi v4.1.2+incompatible
 	github.com/go-playground/locales v0.13.0
 	github.com/go-playground/universal-translator v0.17.0
 	github.com/go-playground/validator/v10 v10.3.0
 	github.com/go-test/deep v1.0.7
+	github.com/google/go-cmp v0.5.1
 	github.com/gorilla/securecookie v1.1.1
 	github.com/gorilla/sessions v1.2.1
 	github.com/imdario/mergo v0.3.11 // indirect
 	github.com/jinzhu/gorm v1.9.16
 	github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd
+	github.com/json-iterator/go v1.1.10 // indirect
+	github.com/kr/pretty v0.2.0 // indirect
 	github.com/leodido/go-urn v1.2.0 // indirect
 	github.com/mattn/go-colorable v0.1.7 // indirect
 	github.com/pelletier/go-toml v1.8.1 // indirect
 	github.com/pkg/errors v0.9.1
 	github.com/rs/zerolog v1.20.0
-	github.com/stretchr/testify v1.5.1
+	github.com/sirupsen/logrus v1.6.0
+	github.com/stretchr/testify v1.6.1
 	golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
 	golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
 	golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect
-	golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d // indirect
+	golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6 // indirect
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
+	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 	gopkg.in/go-playground/validator.v9 v9.31.0
 	gopkg.in/yaml.v2 v2.3.0
 	gorm.io/driver/postgres v1.0.2
 	gorm.io/gorm v1.20.2
-	k8s.io/apimachinery v0.19.2
-	k8s.io/client-go v0.0.0-20200917000235-cba7285b7f29
+	helm.sh/helm/v3 v3.3.4
+	k8s.io/api v0.18.8
+	k8s.io/apimachinery v0.18.8
+	k8s.io/cli-runtime v0.18.8
+	k8s.io/client-go v0.18.8
+	k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac // indirect
 	k8s.io/klog v1.0.0 // indirect
+	k8s.io/klog/v2 v2.2.0 // indirect
 	k8s.io/utils v0.0.0-20200912215256-4140de9c8800 // indirect
+	sigs.k8s.io/structured-merge-diff/v4 v4.0.1 // indirect
 )

File diff ditekan karena terlalu besar
+ 591 - 0
go.sum


+ 50 - 0
internal/helm/action_config.go

@@ -0,0 +1,50 @@
+package helm
+
+import (
+	"github.com/porter-dev/porter/internal/logger"
+
+	"helm.sh/helm/v3/pkg/action"
+	"helm.sh/helm/v3/pkg/kube"
+	"k8s.io/cli-runtime/pkg/genericclioptions"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/rest"
+)
+
+// NewActionConfig creates an action.Configuration, which can then be used to create Helm 3 actions.
+// Among other things, the action.Configuration controls which namespace the command is run against.
+func NewActionConfig(
+	l *logger.Logger,
+	newStorageDriver NewStorageDriver,
+	config *rest.Config,
+	clientset *kubernetes.Clientset,
+	namespace string,
+) (*action.Configuration, error) {
+	actionConfig := &action.Configuration{}
+	store := newStorageDriver(l, namespace, clientset)
+	restClientGetter := NewConfigFlagsFromCluster(namespace, config)
+	actionConfig.RESTClientGetter = restClientGetter
+	actionConfig.KubeClient = kube.New(restClientGetter)
+	actionConfig.Releases = store
+	actionConfig.Log = l.Printf
+	return actionConfig, nil
+}
+
+// NewConfigFlagsFromCluster returns ConfigFlags with default values set from within cluster.
+func NewConfigFlagsFromCluster(namespace string, clusterConfig *rest.Config) genericclioptions.RESTClientGetter {
+	impersonateGroup := []string{}
+
+	// CertFile and KeyFile must be nil for the BearerToken to be used for authentication and authorization instead of the pod's service account.
+	return &genericclioptions.ConfigFlags{
+		Insecure:         &clusterConfig.TLSClientConfig.Insecure,
+		Timeout:          stringptr("0"),
+		Namespace:        stringptr(namespace),
+		APIServer:        stringptr(clusterConfig.Host),
+		CAFile:           stringptr(clusterConfig.CAFile),
+		BearerToken:      stringptr(clusterConfig.BearerToken),
+		ImpersonateGroup: &impersonateGroup,
+	}
+}
+
+func stringptr(val string) *string {
+	return &val
+}

+ 30 - 0
internal/helm/agent.go

@@ -0,0 +1,30 @@
+package helm
+
+import (
+	"helm.sh/helm/v3/pkg/action"
+	"helm.sh/helm/v3/pkg/release"
+)
+
+// ListReleases lists releases based on a ListFilter
+func ListReleases(
+	actionConfig *action.Configuration,
+	namespace string,
+	filter *ListFilter,
+) ([]*release.Release, error) {
+	cmd := action.NewList(actionConfig)
+
+	filter.apply(cmd)
+
+	return cmd.Run()
+}
+
+// GetRelease returns the info of a release.
+func GetRelease(
+	actionConfig *action.Configuration,
+	name string,
+) (*release.Release, error) {
+	// Namespace is already known by the RESTClientGetter.
+	cmd := action.NewGet(actionConfig)
+
+	return cmd.Run(name)
+}

+ 173 - 0
internal/helm/agent_test.go

@@ -0,0 +1,173 @@
+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 {
+	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,
+	}
+}
+
+type releaseStub struct {
+	name         string
+	namespace    string
+	version      int
+	chartVersion string
+	status       release.Status
+}
+
+// makeReleases adds a slice of releases to the configured storage.
+func makeReleases(t *testing.T, actionConfig *action.Configuration, rels []releaseStub) {
+	t.Helper()
+	storage := actionConfig.Releases
+	for _, r := range rels {
+		rel := &release.Release{
+			Name:      r.name,
+			Namespace: r.namespace,
+			Version:   r.version,
+			Info: &release.Info{
+				Status: r.status,
+			},
+			Chart: &chart.Chart{
+				Metadata: &chart.Metadata{
+					Version: r.chartVersion,
+					Icon:    "https://example.com/icon.png",
+				},
+			},
+		}
+		err := storage.Create(rel)
+		if err != nil {
+			t.Fatal(err)
+		}
+	}
+}
+
+func compareReleaseToStubs(t *testing.T, releases []*release.Release, stubs []releaseStub) {
+	t.Helper()
+
+	if len(releases) != len(stubs) {
+		t.Fatalf("length of release %v doesn't match length of stub %v\n",
+			len(releases), len(stubs))
+	}
+
+	for i, r := range releases {
+		if r.Name != stubs[i].name {
+			t.Errorf("Release name %v doesn't match stub name %v\n",
+				r.Name, stubs[i].name)
+		}
+
+		if r.Namespace != stubs[i].namespace {
+			t.Errorf("Release namespace %v doesn't match stub namespace %v\n",
+				r.Namespace, stubs[i].namespace)
+		}
+
+		if r.Info.Status != stubs[i].status {
+			t.Errorf("Release namespace %v doesn't match stub namespace %v\n",
+				r.Info.Status, stubs[i].status)
+		}
+
+		if r.Version != stubs[i].version {
+			t.Errorf("Release version %v doesn't match stub version %v\n",
+				r.Version, stubs[i].version)
+		}
+
+		if r.Chart.Metadata.Version != stubs[i].chartVersion {
+			t.Errorf("Release metadata version %v doesn't match stub chart version %v\n",
+				r.Chart.Metadata.Version, stubs[i].chartVersion)
+		}
+	}
+
+	return
+}
+
+type listReleaseTest struct {
+	name      string
+	namespace string
+	filter    *helm.ListFilter
+	releases  []releaseStub
+	expRes    []releaseStub
+}
+
+var listReleaseTests = []listReleaseTest{
+	listReleaseTest{
+		name:      "simple test across namespaces, should sort by name",
+		namespace: "",
+		filter: &helm.ListFilter{
+			Namespace:    "",
+			Limit:        20,
+			Skip:         0,
+			ByDate:       false,
+			StatusFilter: []string{"deployed"},
+		},
+		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},
+		},
+		expRes: []releaseStub{
+			releaseStub{"airwatch", "default", 1, "1.0.0", release.StatusDeployed},
+			releaseStub{"not-in-default-namespace", "other", 1, "1.0.2", release.StatusDeployed},
+			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},
+		},
+	},
+}
+
+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)
+
+		releases, err := helm.ListReleases(actionConfig, tc.namespace, tc.filter)
+		if err != nil {
+			t.Errorf("%v", err)
+		}
+
+		compareReleaseToStubs(t, releases, tc.expRes)
+	}
+}

+ 55 - 0
internal/helm/driver.go

@@ -0,0 +1,55 @@
+package helm
+
+// Helm contains support for several different storage drivers.
+//
+// This includes (as of October 2020):
+// - configmap
+// - secret
+// - memory
+// - postgres
+//
+// This file implements first-class support for each driver type, and integrates with the
+// logger.
+
+import (
+	"github.com/porter-dev/porter/internal/logger"
+
+	"helm.sh/helm/v3/pkg/storage"
+	"helm.sh/helm/v3/pkg/storage/driver"
+	"k8s.io/client-go/kubernetes"
+)
+
+// NewStorageDriver is a function type for returning a new storage driver
+type NewStorageDriver func(l *logger.Logger, namespace string, clientset *kubernetes.Clientset) *storage.Storage
+
+// NewSecretStorageDriver returns a storage using the Secret driver.
+func NewSecretStorageDriver(
+	l *logger.Logger,
+	namespace string,
+	clientset *kubernetes.Clientset,
+) *storage.Storage {
+	d := driver.NewSecrets(clientset.CoreV1().Secrets(namespace))
+	d.Log = l.Printf
+	return storage.Init(d)
+}
+
+// NewConfigMapsStorageDriver returns a storage using the ConfigMap driver.
+func NewConfigMapsStorageDriver(
+	l *logger.Logger,
+	namespace string,
+	clientset *kubernetes.Clientset,
+) *storage.Storage {
+	d := driver.NewConfigMaps(clientset.CoreV1().ConfigMaps(namespace))
+	d.Log = l.Printf
+	return storage.Init(d)
+}
+
+// NewMemoryStorageDriver returns a storage using the In-Memory driver.
+func NewMemoryStorageDriver(
+	l *logger.Logger,
+	namespace string,
+	clientset *kubernetes.Clientset,
+) *storage.Storage {
+	d := driver.NewMemory()
+	return storage.Init(d)
+}

+ 48 - 0
internal/helm/filter.go

@@ -0,0 +1,48 @@
+package helm
+
+import (
+	"helm.sh/helm/v3/pkg/action"
+)
+
+// ListFilter is a struct that represents the various filter options used for
+// retrieving the releases
+type ListFilter struct {
+	Namespace    string   `json:"namespace"`
+	Limit        int      `json:"limit"`
+	Skip         int      `json:"skip"`
+	ByDate       bool     `json:"byDate"`
+	StatusFilter []string `json:"statusFilter"`
+}
+
+// listStatesFromNames accepts the following list of names:
+//
+// "deployed", "uninstalled", "uninstalling", "pending", "pending_upgrade",
+// "pending_rollback", "superseded", "failed"
+//
+// It returns an action.ListStates to be used in an action.List as filters for
+// releases in a certain state.
+func (h *ListFilter) listStatesFromNames() action.ListStates {
+	var res action.ListStates = 0
+
+	for _, name := range h.StatusFilter {
+		res = res | res.FromName(name)
+	}
+
+	return res
+}
+
+// apply sets the ListFilter options for an action.List
+func (h *ListFilter) apply(list *action.List) {
+	if h.Namespace == "" {
+		list.AllNamespaces = true
+	}
+
+	list.Limit = h.Limit
+	list.Offset = h.Skip
+
+	list.StateMask = h.listStatesFromNames()
+
+	if h.ByDate {
+		list.ByDate = true
+	}
+}

+ 19 - 0
internal/kubernetes/agent.go

@@ -0,0 +1,19 @@
+package kubernetes
+
+import (
+	"context"
+
+	v1 "k8s.io/api/core/v1"
+	v1Machinery "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes"
+)
+
+// ListNamespaces simply lists namespaces
+func ListNamespaces(clientset *kubernetes.Clientset) *v1.NamespaceList {
+	namespaces, _ := clientset.CoreV1().Namespaces().List(
+		context.TODO(),
+		v1Machinery.ListOptions{},
+	)
+
+	return namespaces
+}

+ 18 - 0
internal/kubernetes/client.go

@@ -0,0 +1,18 @@
+package kubernetes
+
+import (
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/clientcmd"
+)
+
+// GetClientsetFromConfig is a simple wrapper that returns a *kubernetes.Clientset based on
+// a clientcmd.ClientConfig
+func GetClientsetFromConfig(conf clientcmd.ClientConfig) (*kubernetes.Clientset, error) {
+	clientConf, err := conf.ClientConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return kubernetes.NewForConfig(clientConf)
+}

+ 1 - 1
internal/models/cluster_configs_test.go

@@ -3,8 +3,8 @@ package models_test
 import (
 	"testing"
 
-	"gorm.io/gorm"
 	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
 )
 
 func TestClusterConfigExternalize(t *testing.T) {

+ 1 - 1
internal/models/user_test.go

@@ -3,8 +3,8 @@ package models_test
 import (
 	"testing"
 
-	"gorm.io/gorm"
 	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
 )
 
 func TestUserExternalize(t *testing.T) {

+ 10 - 0
server/api/chart_handler.go

@@ -0,0 +1,10 @@
+package api
+
+import "net/http"
+
+// TODO -- IMPLEMENT
+func (app *App) HandleListCharts(w http.ResponseWriter, r *http.Request) {
+	// get the user id
+
+	// create a client config using the app's helm/kubernetes agents
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini