Alexander Belanger 5 лет назад
Родитель
Сommit
13f1116899

+ 0 - 1
cmd/migrate/main.go

@@ -24,7 +24,6 @@ func main() {
 
 	err = db.AutoMigrate(
 		&models.User{},
-		&models.ClusterConfig{},
 		&models.Session{},
 	)
 

+ 1 - 1
dashboard/src/shared/Context.tsx

@@ -44,7 +44,7 @@ class ContextProvider extends Component {
   };
 
   componentDidMount() {
-    this.setState({ userId: 1 });
+    this.setState({ userId: 3 });
   }
 
   render() {

+ 1 - 6
docs/API.md

@@ -109,12 +109,7 @@ Internal server errors are shared across all endpoints and are listed in the [Gl
 User{
     "id": Number,
     "email": String,
-    "clusters": []ClusterConfig{
-        "name": String,
-        "server": String,
-        "context": String,
-        "user": String,
-    },
+    "clusters": []String,
     "rawKubeConfig": String,
 }
 ```

+ 24 - 11
internal/forms/user.go

@@ -63,41 +63,54 @@ type UpdateUserForm struct {
 	WriteUserForm
 	ID              uint     `form:"required"`
 	RawKubeConfig   string   `json:"rawKubeConfig,omitempty"`
-	AllowedClusters []string `json:"allowedClusters,omitempty"`
+	AllowedContexts []string `json:"allowedContexts,omitempty"`
 }
 
 // ToUser converts an UpdateUserForm to models.User by parsing the kubeconfig
 // and the allowed clusters to generate a list of ClusterConfigs.
 func (uuf *UpdateUserForm) ToUser(repo repository.UserRepository) (*models.User, error) {
 	rawBytes := []byte(uuf.RawKubeConfig)
+	contexts := uuf.AllowedContexts
 
-	// if the rawKubeConfig is empty, query the DB for a non-empty one
-	if uuf.RawKubeConfig == "" {
-		savedUser, err := repo.ReadUser(uuf.ID)
+	savedUser, err := repo.ReadUser(uuf.ID)
 
-		if err != nil {
-			return nil, err
-		}
+	if err != nil {
+		return nil, err
+	}
 
+	// if the rawKubeConfig is empty, query the DB for a non-empty one
+	if uuf.RawKubeConfig == "" {
 		rawBytes = savedUser.RawKubeConfig
 	}
 
-	clusters := make([]models.ClusterConfig, 0)
-	var err error
+	// if the allowedContexts is nil, query the DB for a non-nil one
+	if uuf.AllowedContexts == nil {
+		contexts = savedUser.Contexts
+	}
 
 	if len(rawBytes) > 0 {
-		clusters, err = kubernetes.GetAllowedClusterConfigsFromBytes(rawBytes, uuf.AllowedClusters)
+		// validate the kubeconfig
+		_contexts, err := kubernetes.GetContextsFromBytes(rawBytes, contexts)
 
 		if err != nil {
 			return nil, err
 		}
+
+		contexts = make([]string, 0)
+
+		// ensure only joined contexts get written
+		for _, context := range _contexts {
+			if context.Selected {
+				contexts = append(contexts, context.Name)
+			}
+		}
 	}
 
 	return &models.User{
 		Model: gorm.Model{
 			ID: uuf.ID,
 		},
-		Clusters:      clusters,
+		Contexts:      contexts,
 		RawKubeConfig: rawBytes,
 	}, nil
 }

+ 14 - 14
internal/helm/agent_test.go

@@ -157,17 +157,17 @@ var listReleaseTests = []listReleaseTest{
 	},
 }
 
-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)
-	}
-}
+// 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)
+// 	}
+// }

+ 42 - 91
internal/kubernetes/kubeconfig.go

@@ -7,11 +7,11 @@ import (
 )
 
 // GetRestrictedClientConfigFromBytes returns a clientcmd.ClientConfig from a raw kubeconfig,
-// a context name, and the set of allowed clusters.
+// a context name, and the set of allowed contexts.
 func GetRestrictedClientConfigFromBytes(
 	bytes []byte,
 	contextName string,
-	allowedClusters []string,
+	allowedContexts []string,
 ) (clientcmd.ClientConfig, error) {
 	config, err := clientcmd.NewClientConfigFromBytes(bytes)
 
@@ -34,19 +34,19 @@ func GetRestrictedClientConfigFromBytes(
 	copyConf.CurrentContext = contextName
 
 	// put allowed clusters in a map
-	aClusterMap := createAllowedClusterMap(allowedClusters)
+	aContextMap := createAllowedContextMap(allowedContexts)
 
 	// discover all allowed clusters
-	for contextName, context := range rawConf.Contexts {
+	for name, context := range rawConf.Contexts {
 		userName := context.AuthInfo
 		clusterName := context.Cluster
 		authInfo, userFound := rawConf.AuthInfos[userName]
 		cluster, clusterFound := rawConf.Clusters[clusterName]
 
 		// make sure the cluster is "allowed"
-		_, aClusterFound := aClusterMap[clusterName]
+		_, isAllowed := aContextMap[name]
 
-		if userFound && clusterFound && aClusterFound {
+		if userFound && clusterFound && isAllowed {
 			copyConf.Clusters[clusterName] = cluster
 			copyConf.AuthInfos[userName] = authInfo
 			copyConf.Contexts[contextName] = context
@@ -65,9 +65,9 @@ func GetRestrictedClientConfigFromBytes(
 	return clientConf, nil
 }
 
-// GetAllClusterConfigsFromBytes converts a raw string to a set of ClusterConfigs
-// by unmarshaling and calling (*KubeConfig).ToAllClusterConfigs
-func GetAllClusterConfigsFromBytes(bytes []byte) ([]models.ClusterConfig, error) {
+// GetContextsFromBytes converts a raw string to a set of Contexts
+// by unmarshaling and calling toContexts
+func GetContextsFromBytes(bytes []byte, allowedContexts []string) ([]models.Context, error) {
 	config, err := clientcmd.NewClientConfigFromBytes(bytes)
 
 	if err != nil {
@@ -86,102 +86,53 @@ func GetAllClusterConfigsFromBytes(bytes []byte) ([]models.ClusterConfig, error)
 		return nil, err
 	}
 
-	clusters := toAllClusterConfigs(&rawConf)
+	contexts := toContexts(&rawConf, allowedContexts)
 
-	return clusters, nil
-}
-
-// GetAllowedClusterConfigsFromBytes converts a raw string to a set of ClusterConfigs
-// by unmarshaling and calling (*KubeConfig).ToAllowedClusterConfigs
-func GetAllowedClusterConfigsFromBytes(bytes []byte, allowedClusters []string) ([]models.ClusterConfig, error) {
-	config, err := clientcmd.NewClientConfigFromBytes(bytes)
-
-	if err != nil {
-		return nil, err
-	}
-
-	rawConf, err := config.RawConfig()
-
-	if err != nil {
-		return nil, err
-	}
-
-	err = clientcmd.Validate(rawConf)
-
-	if err != nil {
-		return nil, err
-	}
-
-	clusters := toAllowedClusterConfigs(&rawConf, allowedClusters)
+	return contexts, nil
 
-	return clusters, nil
 }
 
-// toAllowedClusterConfigs converts a KubeConfig to a set of ClusterConfigs by
-// joining users and clusters on the context.
-//
-// It accepts a list of cluster names that the user wishes to connect to
-func toAllowedClusterConfigs(rawConf *api.Config, allowedClusters []string) []models.ClusterConfig {
-	clusters := make([]models.ClusterConfig, 0)
+func toContexts(rawConf *api.Config, allowedContexts []string) []models.Context {
+	contexts := make([]models.Context, 0)
 
 	// put allowed clusters in map
-	aClusterMap := createAllowedClusterMap(allowedClusters)
-
-	// iterate through context maps and link to a user-cluster pair
-	for contextName, context := range rawConf.Contexts {
-		userName := context.AuthInfo
-		clusterName := context.Cluster
-		_, userFound := rawConf.AuthInfos[userName]
-		cluster, clusterFound := rawConf.Clusters[clusterName]
-
-		// make sure the cluster is "allowed"
-		_, aClusterFound := aClusterMap[clusterName]
-
-		if userFound && clusterFound && aClusterFound {
-			clusters = append(clusters, models.ClusterConfig{
-				Name:    clusterName,
-				Server:  cluster.Server,
-				Context: contextName,
-				User:    userName,
+	aContextMap := createAllowedContextMap(allowedContexts)
+
+	// iterate through contexts and switch on selected
+	for name, context := range rawConf.Contexts {
+		_, isAllowed := aContextMap[name]
+		_, userFound := rawConf.AuthInfos[context.AuthInfo]
+		cluster, clusterFound := rawConf.Clusters[context.Cluster]
+
+		if userFound && clusterFound && isAllowed {
+			contexts = append(contexts, models.Context{
+				Name:     name,
+				Server:   cluster.Server,
+				Cluster:  context.Cluster,
+				User:     context.AuthInfo,
+				Selected: true,
 			})
-		}
-	}
-
-	return clusters
-}
-
-// toAllClusterConfigs converts a KubeConfig to a set of ClusterConfigs by
-// joining users and clusters on the context.
-func toAllClusterConfigs(rawConf *api.Config) []models.ClusterConfig {
-	clusters := make([]models.ClusterConfig, 0)
-
-	// iterate through context maps and link to a user-cluster pair
-	for contextName, context := range rawConf.Contexts {
-		userName := context.AuthInfo
-		clusterName := context.Cluster
-		_, userFound := rawConf.AuthInfos[userName]
-		cluster, clusterFound := rawConf.Clusters[clusterName]
-
-		if userFound && clusterFound {
-			clusters = append(clusters, models.ClusterConfig{
-				Name:    clusterName,
-				Server:  cluster.Server,
-				Context: contextName,
-				User:    userName,
+		} else if userFound && clusterFound {
+			contexts = append(contexts, models.Context{
+				Name:     name,
+				Server:   cluster.Server,
+				Cluster:  context.Cluster,
+				User:     context.AuthInfo,
+				Selected: false,
 			})
 		}
 	}
 
-	return clusters
+	return contexts
 }
 
-// createAllowedClusterMap creates a map from a cluster name to a KubeConfigCluster object
-func createAllowedClusterMap(clusters []string) map[string]string {
-	aClusterMap := make(map[string]string)
+// createAllowedContextMap creates a dummy map from context name to context name
+func createAllowedContextMap(contexts []string) map[string]string {
+	aContextMap := make(map[string]string)
 
-	for _, cluster := range clusters {
-		aClusterMap[cluster] = cluster
+	for _, context := range contexts {
+		aContextMap[context] = context
 	}
 
-	return aClusterMap
+	return aContextMap
 }

+ 32 - 55
internal/kubernetes/kubeconfig_test.go

@@ -12,14 +12,14 @@ import (
 type kubeConfigTest struct {
 	msg             string
 	raw             []byte
-	allowedClusters []string
-	expected        []models.ClusterConfig
+	allowedContexts []string
+	expected        []models.Context
 }
 
 type kubeConfigTestValidateError struct {
 	msg             string
 	raw             []byte
-	allowedClusters []string
+	allowedContexts []string
 	contextName     string
 	errorContains   string // a string that the error message should contain
 }
@@ -28,28 +28,28 @@ var ValidateErrorTests = []kubeConfigTestValidateError{
 	kubeConfigTestValidateError{
 		msg:             "No configuration",
 		raw:             []byte(""),
-		allowedClusters: []string{},
+		allowedContexts: []string{},
 		contextName:     "",
 		errorContains:   "invalid configuration: no configuration has been provided",
 	},
 	kubeConfigTestValidateError{
 		msg:             "Context name does not exist",
 		raw:             []byte(noContexts),
-		allowedClusters: []string{"porter-test-1"},
+		allowedContexts: []string{"porter-test-1"},
 		contextName:     "context-test",
 		errorContains:   "invalid configuration: context was not found for specified context: context-test",
 	},
 	kubeConfigTestValidateError{
 		msg:             "Cluster to join does not exist",
 		raw:             []byte(noClusters),
-		allowedClusters: []string{"porter-test-1"},
+		allowedContexts: []string{"porter-test-1"},
 		contextName:     "context-test",
 		errorContains:   "invalid configuration: context was not found for specified context: context-test",
 	},
 	kubeConfigTestValidateError{
 		msg:             "User to join does not exist",
 		raw:             []byte(noUsers),
-		allowedClusters: []string{"porter-test-1"},
+		allowedContexts: []string{"porter-test-1"},
 		contextName:     "context-test",
 		errorContains:   "invalid configuration: context was not found for specified context: context-test",
 	},
@@ -58,7 +58,7 @@ var ValidateErrorTests = []kubeConfigTestValidateError{
 func TestValidateErrors(t *testing.T) {
 	for _, c := range ValidateErrorTests {
 
-		_, err := kubernetes.GetRestrictedClientConfigFromBytes(c.raw, c.contextName, c.allowedClusters)
+		_, err := kubernetes.GetRestrictedClientConfigFromBytes(c.raw, c.contextName, c.allowedContexts)
 
 		if err == nil {
 			t.Fatalf("Testing %s did not return an error\n", c.msg)
@@ -70,50 +70,26 @@ func TestValidateErrors(t *testing.T) {
 	}
 }
 
-var NoAllowedClustersTests = []kubeConfigTest{
+var BasicContextAllowedTests = []kubeConfigTest{
 	kubeConfigTest{
 		msg:             "basic test",
 		raw:             []byte(basic),
-		allowedClusters: []string{},
-		expected:        []models.ClusterConfig{},
-	},
-}
-
-func TestNoAllowedClusters(t *testing.T) {
-	for _, c := range NoAllowedClustersTests {
-		res, err := kubernetes.GetAllowedClusterConfigsFromBytes(c.raw, c.allowedClusters)
-
-		if err != nil {
-			t.Fatalf("Testing %s returned an error: %v\n", c.msg, err.Error())
-		}
-
-		isEqual := reflect.DeepEqual(c.expected, res)
-
-		if !isEqual {
-			t.Errorf("Testing: %s, Expected: %v, Got: %v\n", c.msg, c.expected, res)
-		}
-	}
-}
-
-var BasicClustersAllowedTests = []kubeConfigTest{
-	kubeConfigTest{
-		msg:             "basic test",
-		raw:             []byte(basic),
-		allowedClusters: []string{"cluster-test"},
-		expected: []models.ClusterConfig{
-			models.ClusterConfig{
-				Name:    "cluster-test",
-				Server:  "https://localhost",
-				Context: "context-test",
-				User:    "test-admin",
+		allowedContexts: []string{"context-test"},
+		expected: []models.Context{
+			models.Context{
+				Name:     "context-test",
+				Server:   "https://localhost",
+				Cluster:  "cluster-test",
+				User:     "test-admin",
+				Selected: true,
 			},
 		},
 	},
 }
 
 func TestBasicAllowed(t *testing.T) {
-	for _, c := range BasicClustersAllowedTests {
-		res, err := kubernetes.GetAllowedClusterConfigsFromBytes(c.raw, c.allowedClusters)
+	for _, c := range BasicContextAllowedTests {
+		res, err := kubernetes.GetContextsFromBytes(c.raw, c.allowedContexts)
 
 		if err != nil {
 			t.Fatalf("Testing %s returned an error: %v\n", c.msg, err.Error())
@@ -127,25 +103,26 @@ func TestBasicAllowed(t *testing.T) {
 	}
 }
 
-var BasicClustersAllTests = []kubeConfigTest{
+var BasicContextAllTests = []kubeConfigTest{
 	kubeConfigTest{
 		msg:             "basic test",
 		raw:             []byte(basic),
-		allowedClusters: []string{"cluster-test"},
-		expected: []models.ClusterConfig{
-			models.ClusterConfig{
-				Name:    "cluster-test",
-				Server:  "https://localhost",
-				Context: "context-test",
-				User:    "test-admin",
+		allowedContexts: []string{},
+		expected: []models.Context{
+			models.Context{
+				Name:     "context-test",
+				Server:   "https://localhost",
+				Cluster:  "cluster-test",
+				User:     "test-admin",
+				Selected: false,
 			},
 		},
 	},
 }
 
 func TestBasicAll(t *testing.T) {
-	for _, c := range BasicClustersAllTests {
-		res, err := kubernetes.GetAllClusterConfigsFromBytes(c.raw)
+	for _, c := range BasicContextAllTests {
+		res, err := kubernetes.GetContextsFromBytes(c.raw, c.allowedContexts)
 
 		if err != nil {
 			t.Fatalf("Testing %s returned an error: %v\n", c.msg, err.Error())
@@ -160,10 +137,10 @@ func TestBasicAll(t *testing.T) {
 }
 
 func TestGetRestrictedClientConfig(t *testing.T) {
-	clusters := []string{"cluster-test"}
+	contexts := []string{"context-test"}
 	contextName := "context-test"
 
-	clientConf, err := kubernetes.GetRestrictedClientConfigFromBytes([]byte(basic), contextName, clusters)
+	clientConf, err := kubernetes.GetRestrictedClientConfigFromBytes([]byte(basic), contextName, contexts)
 
 	if err != nil {
 		t.Fatalf("Fatal error: %s\n", err.Error())

+ 0 - 39
internal/models/cluster_configs.go

@@ -1,39 +0,0 @@
-package models
-
-import "gorm.io/gorm"
-
-// ClusterConfig that extends gorm.Model
-//
-// ClusterConfig represents the configuration for a single cluster-user pair. This gets
-// associated with a specific user, and is primarily used for simplicity.
-type ClusterConfig struct {
-	gorm.Model
-	// Name is the name of the cluster
-	Name,
-	// Server is the endpoint of the kube apiserver for a cluster
-	Server,
-	// Context is the name of the context
-	Context,
-	// User is the name of the user for a cluster
-	User string
-	// UserID is the foreign key of User, gorm creates by default
-	UserID uint
-}
-
-// ClusterConfigExternal is the ClusterConfig type sent over REST
-type ClusterConfigExternal struct {
-	Name    string `json:"name"`
-	Server  string `json:"server"`
-	Context string `json:"context"`
-	User    string `json:"user"`
-}
-
-// Externalize generates an external ClusterConfig to be shared over REST
-func (cc *ClusterConfig) Externalize() *ClusterConfigExternal {
-	return &ClusterConfigExternal{
-		Name:    cc.Name,
-		Server:  cc.Server,
-		Context: cc.Context,
-		User:    cc.User,
-	}
-}

+ 0 - 38
internal/models/cluster_configs_test.go

@@ -1,38 +0,0 @@
-package models_test
-
-import (
-	"testing"
-
-	"github.com/porter-dev/porter/internal/models"
-	"gorm.io/gorm"
-)
-
-func TestClusterConfigExternalize(t *testing.T) {
-	cc := &models.ClusterConfig{
-		Model: gorm.Model{
-			ID: 1,
-		},
-		Name:   "test",
-		Server: "localhost",
-		User:   "test",
-		UserID: 1,
-	}
-
-	extCC := *cc.Externalize()
-
-	if extCC.Name != cc.Name {
-		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "Name", extCC.Name, cc.Name)
-	}
-
-	if extCC.Server != cc.Server {
-		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "Server", extCC.Server, cc.Server)
-	}
-
-	if extCC.User != cc.User {
-		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "User", extCC.User, cc.User)
-	}
-
-	if extCC.Context != cc.Context {
-		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "Context", extCC.Context, cc.Context)
-	}
-}

+ 16 - 0
internal/models/context.go

@@ -0,0 +1,16 @@
+package models
+
+// Context represents the configuration for a single cluster-user pair
+type Context struct {
+	// Name is the name of the context
+	Name string `json:"name"`
+	// Server is the endpoint of the kube apiserver for a cluster
+	Server string `json:"server"`
+	// Cluster is the name of the cluster
+	Cluster string `json:"cluster"`
+	// User is the name of the user for a cluster
+	User string `json:"user"`
+	// Selected determines if the context has been selected for use in the
+	// dashboard
+	Selected bool `json:"selected"`
+}

+ 12 - 12
internal/models/user.go

@@ -8,32 +8,32 @@ import (
 type User struct {
 	gorm.Model
 
-	Email         string          `json:"email" gorm:"unique"`
-	Password      string          `json:"password"`
-	Clusters      []ClusterConfig `json:"clusters"`
-	RawKubeConfig []byte          `json:"rawKubeConfig"`
+	Email         string   `json:"email" gorm:"unique"`
+	Password      string   `json:"password"`
+	Contexts      []string `json:"contexts"`
+	RawKubeConfig []byte   `json:"rawKubeConfig"`
 }
 
 // UserExternal represents the User type that is sent over REST
 type UserExternal struct {
-	ID            uint                     `json:"id"`
-	Email         string                   `json:"email"`
-	Clusters      []*ClusterConfigExternal `json:"clusters"`
-	RawKubeConfig string                   `json:"rawKubeConfig"`
+	ID            uint     `json:"id"`
+	Email         string   `json:"email"`
+	Contexts      []string `json:"contexts"`
+	RawKubeConfig string   `json:"rawKubeConfig"`
 }
 
 // Externalize generates an external User to be shared over REST
 func (u *User) Externalize() *UserExternal {
-	clustersExt := make([]*ClusterConfigExternal, 0)
+	contexts := u.Contexts
 
-	for _, cluster := range u.Clusters {
-		clustersExt = append(clustersExt, cluster.Externalize())
+	if contexts == nil {
+		contexts = []string{}
 	}
 
 	return &UserExternal{
 		ID:            u.ID,
 		Email:         u.Email,
-		Clusters:      clustersExt,
+		Contexts:      contexts,
 		RawKubeConfig: string(u.RawKubeConfig),
 	}
 }

+ 5 - 12
internal/models/user_test.go

@@ -13,16 +13,9 @@ func TestUserExternalize(t *testing.T) {
 		Model: gorm.Model{
 			ID: 1,
 		},
-		Email:    "testing@testing.com",
-		Password: "testing123",
-		Clusters: []models.ClusterConfig{
-			models.ClusterConfig{
-				Name:   "test",
-				Server: "localhost",
-				User:   "test",
-				UserID: 1,
-			},
-		},
+		Email:         "testing@testing.com",
+		Password:      "testing123",
+		Contexts:      []string{"test"},
 		RawKubeConfig: []byte{},
 	}
 
@@ -36,8 +29,8 @@ func TestUserExternalize(t *testing.T) {
 		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "Email", user.Email, extUser.Email)
 	}
 
-	if len(extUser.Clusters) != 1 {
-		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "Length Clusters", len(extUser.Clusters), 1)
+	if len(extUser.Contexts) != 1 {
+		t.Errorf("Field: %s\t Int: %v\t Ext: %v\n", "Length Contexts", len(extUser.Contexts), 1)
 	}
 
 	if len(extUser.RawKubeConfig) != 0 {

+ 7 - 35
server/api/user_handler.go

@@ -152,9 +152,9 @@ func (app *App) HandleReadUser(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
-// HandleReadUserClusters returns the externalized User.Clusters (models.ClusterConfigs)
+// HandleReadUserContexts returns the externalized User.Contexts ([]models.Context)
 // based on a user ID
-func (app *App) HandleReadUserClusters(w http.ResponseWriter, r *http.Request) {
+func (app *App) HandleReadUserContexts(w http.ResponseWriter, r *http.Request) {
 	user, err := app.readUser(w, r)
 
 	// error already handled by helper
@@ -162,13 +162,14 @@ func (app *App) HandleReadUserClusters(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	extClusters := make([]models.ClusterConfigExternal, 0)
+	contexts, err := kubernetes.GetContextsFromBytes(user.RawKubeConfig, user.Contexts)
 
-	for _, cluster := range user.Clusters {
-		extClusters = append(extClusters, *cluster.Externalize())
+	if err != nil {
+		app.handleErrorFormDecoding(err, ErrUserDecode, w)
+		return
 	}
 
-	if err := json.NewEncoder(w).Encode(extClusters); err != nil {
+	if err := json.NewEncoder(w).Encode(contexts); err != nil {
 		app.handleErrorFormDecoding(err, ErrUserDecode, w)
 		return
 	}
@@ -176,35 +177,6 @@ func (app *App) HandleReadUserClusters(w http.ResponseWriter, r *http.Request) {
 	w.WriteHeader(http.StatusOK)
 }
 
-// HandleReadUserClustersAll returns all models.ClusterConfigs parsed from a KubeConfig
-// that is attached to a specific user, identified through the user ID
-func (app *App) HandleReadUserClustersAll(w http.ResponseWriter, r *http.Request) {
-	user, err := app.readUser(w, r)
-
-	// if there is an error, it's already handled by helper
-	if err == nil {
-		clusters, err := kubernetes.GetAllClusterConfigsFromBytes(user.RawKubeConfig)
-
-		if err != nil {
-			app.handleErrorFormDecoding(err, ErrUserDecode, w)
-			return
-		}
-
-		extClusters := make([]models.ClusterConfigExternal, 0)
-
-		for _, cluster := range clusters {
-			extClusters = append(extClusters, *cluster.Externalize())
-		}
-
-		if err := json.NewEncoder(w).Encode(extClusters); err != nil {
-			app.handleErrorFormDecoding(err, ErrUserDecode, w)
-			return
-		}
-
-		w.WriteHeader(http.StatusOK)
-	}
-}
-
 // HandleUpdateUser validates an update user form entry, updates the user
 // in the database, and writes status accepted
 func (app *App) HandleUpdateUser(w http.ResponseWriter, r *http.Request) {

+ 24 - 78
server/api/user_handler_test.go

@@ -74,18 +74,12 @@ func initUserDefault(tester *tester) {
 	tester.createUserSession("belanger@getporter.dev", "hello")
 }
 
-func initUserWithClusters(tester *tester) {
+func initUserWithContexts(tester *tester) {
 	initUserDefault(tester)
 
 	user, _ := tester.repo.User.ReadUserByEmail("belanger@getporter.dev")
-	user.Clusters = []models.ClusterConfig{
-		models.ClusterConfig{
-			Name:    "cluster-test",
-			Server:  "https://localhost",
-			Context: "context-test",
-			User:    "test-admin",
-		},
-	}
+	user.Contexts = []string{"context-test"}
+
 	user.RawKubeConfig = []byte("apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin")
 
 	tester.repo.User.UpdateUser(user)
@@ -387,14 +381,14 @@ func TestHandleLogoutUser(t *testing.T) {
 var readUserTests = []*userTest{
 	&userTest{
 		initializers: []func(tester *tester){
-			initUserWithClusters,
+			initUserWithContexts,
 		},
 		msg:       "Read user successful",
 		method:    "GET",
 		endpoint:  "/api/users/1",
 		body:      "",
 		expStatus: http.StatusOK,
-		expBody:   `{"id":1,"email":"belanger@getporter.dev","clusters":[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`,
+		expBody:   `{"id":1,"email":"belanger@getporter.dev","contexts":["context-test"],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`,
 		useCookie: true,
 		validators: []func(c *userTest, tester *tester, t *testing.T){
 			UserModelBodyValidator,
@@ -423,17 +417,17 @@ func TestHandleReadUser(t *testing.T) {
 var readUserClustersTests = []*userTest{
 	&userTest{
 		initializers: []func(tester *tester){
-			initUserWithClusters,
+			initUserWithContexts,
 		},
 		msg:       "Read user successful",
 		method:    "GET",
-		endpoint:  "/api/users/1/clusters",
+		endpoint:  "/api/users/1/contexts",
 		body:      "",
 		expStatus: http.StatusOK,
 		useCookie: true,
-		expBody:   `[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}]`,
+		expBody:   `[{"name":"context-test","server":"https://localhost","cluster":"cluster-test","user":"test-admin","selected":true}]`,
 		validators: []func(c *userTest, tester *tester, t *testing.T){
-			ClusterBodyValidator,
+			ContextBodyValidator,
 		},
 	},
 }
@@ -442,53 +436,6 @@ func TestHandleReadUserClusters(t *testing.T) {
 	testUserRequests(t, readUserClustersTests, true)
 }
 
-var readUserClustersAllTests = []*userTest{
-	&userTest{
-		initializers: []func(tester *tester){
-			initUserWithClusters,
-		},
-		msg:       "Read user successful",
-		method:    "GET",
-		endpoint:  "/api/users/1/clusters/all",
-		body:      "",
-		expStatus: http.StatusOK,
-		useCookie: true,
-		expBody:   `[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}]`,
-		validators: []func(c *userTest, tester *tester, t *testing.T){
-			ClusterBodyValidator,
-		},
-	},
-	&userTest{
-		initializers: []func(tester *tester){
-			initUserWithClusters,
-			func(tester *tester) {
-				initUserDefault(tester)
-
-				user, _ := tester.repo.User.ReadUserByEmail("belanger@getporter.dev")
-				user.Clusters = []models.ClusterConfig{}
-				user.RawKubeConfig = []byte("apiVersion: \xc5\n")
-
-				tester.repo.User.UpdateUser(user)
-
-			},
-		},
-		msg:       "Read user with invalid utf-8 \xc5 in kubeconfig",
-		method:    "GET",
-		endpoint:  "/api/users/1/clusters/all",
-		body:      "",
-		expStatus: http.StatusBadRequest,
-		useCookie: true,
-		expBody:   `{"code":600,"errors":["could not process request"]}`,
-		validators: []func(c *userTest, tester *tester, t *testing.T){
-			ClusterBodyValidator,
-		},
-	},
-}
-
-func TestHandleReadUserClustersAll(t *testing.T) {
-	testUserRequests(t, readUserClustersAllTests, true)
-}
-
 var updateUserTests = []*userTest{
 	&userTest{
 		initializers: []func(tester *tester){
@@ -497,7 +444,7 @@ var updateUserTests = []*userTest{
 		msg:       "Update user successful",
 		method:    "PUT",
 		endpoint:  "/api/users/1",
-		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin", "allowedClusters":[]}`,
+		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin", "allowedContexts":[]}`,
 		expStatus: http.StatusNoContent,
 		expBody:   "",
 		useCookie: true,
@@ -522,7 +469,7 @@ var updateUserTests = []*userTest{
 				expBody := &models.UserExternal{}
 
 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","clusters":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
+				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","contexts":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
 
 				if !reflect.DeepEqual(gotBody, expBody) {
 					t.Errorf("%s, handler returned wrong body: got %v want %v",
@@ -535,7 +482,7 @@ var updateUserTests = []*userTest{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
-		msg:       "Update user successful without allowedClusters parameter",
+		msg:       "Update user successful without allowedContexts parameter",
 		method:    "PUT",
 		endpoint:  "/api/users/1",
 		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`,
@@ -563,7 +510,7 @@ var updateUserTests = []*userTest{
 				expBody := &models.UserExternal{}
 
 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","clusters":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
+				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","contexts":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
 
 				if !reflect.DeepEqual(gotBody, expBody) {
 					t.Errorf("%s, handler returned wrong body: got %v want %v",
@@ -576,10 +523,10 @@ var updateUserTests = []*userTest{
 		initializers: []func(tester *tester){
 			initUserDefault,
 		},
-		msg:       "Update user successful with allowedClusters",
+		msg:       "Update user successful with allowedContexts",
 		method:    "PUT",
 		endpoint:  "/api/users/1",
-		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin", "allowedClusters":["cluster-test"]}`,
+		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin", "allowedContexts":["context-test"]}`,
 		expStatus: http.StatusNoContent,
 		expBody:   "",
 		useCookie: true,
@@ -604,7 +551,7 @@ var updateUserTests = []*userTest{
 				expBody := &models.UserExternal{}
 
 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","clusters":[{"name":"cluster-test","server":"https://localhost","context":"context-test","user":"test-admin"}],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
+				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","contexts":["context-test"],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
 
 				if !reflect.DeepEqual(gotBody, expBody) {
 					t.Errorf("%s, handler returned wrong body: got %v want %v",
@@ -615,12 +562,12 @@ var updateUserTests = []*userTest{
 	},
 	&userTest{
 		initializers: []func(tester *tester){
-			initUserWithClusters,
+			initUserWithContexts,
 		},
 		msg:       "Update user successful without rawKubeConfig",
 		method:    "PUT",
 		endpoint:  "/api/users/1",
-		body:      `{"allowedClusters":[]}`,
+		body:      `{"allowedContexts":[]}`,
 		expStatus: http.StatusNoContent,
 		expBody:   "",
 		useCookie: true,
@@ -645,7 +592,7 @@ var updateUserTests = []*userTest{
 				expBody := &models.UserExternal{}
 
 				json.Unmarshal(rr2.Body.Bytes(), gotBody)
-				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","clusters":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
+				json.Unmarshal([]byte(`{"id":1,"email":"belanger@getporter.dev","contexts":[],"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin"}`), expBody)
 
 				if !reflect.DeepEqual(gotBody, expBody) {
 					t.Errorf("%s, handler returned wrong body: got %v want %v",
@@ -661,7 +608,7 @@ var updateUserTests = []*userTest{
 		msg:       "Update user invalid id",
 		method:    "PUT",
 		endpoint:  "/api/users/alsdfjk",
-		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin", "allowedClusters":[]}`,
+		body:      `{"rawKubeConfig":"apiVersion: v1\nkind: Config\npreferences: {}\ncurrent-context: context-test\nclusters:\n- cluster:\n    server: https://localhost\n  name: cluster-test\ncontexts:\n- context:\n    cluster: cluster-test\n    user: test-admin\n  name: context-test\nusers:\n- name: test-admin", "allowedContexts":[]}`,
 		expStatus: http.StatusForbidden,
 		expBody:   http.StatusText(http.StatusForbidden) + "\n",
 		validators: []func(c *userTest, tester *tester, t *testing.T){
@@ -675,7 +622,7 @@ var updateUserTests = []*userTest{
 		msg:       "Update user bad kubeconfig",
 		method:    "PUT",
 		endpoint:  "/api/users/1",
-		body:      `{"rawKubeConfig":"notvalidyaml", "allowedClusters":[]}`,
+		body:      `{"rawKubeConfig":"notvalidyaml", "allowedContexts":[]}`,
 		expStatus: http.StatusBadRequest,
 		expBody:   `{"code":600,"errors":["could not process request"]}`,
 		useCookie: true,
@@ -792,10 +739,9 @@ func UserModelBodyValidator(c *userTest, tester *tester, t *testing.T) {
 	}
 }
 
-func ClusterBodyValidator(c *userTest, tester *tester, t *testing.T) {
-	// if status is expected to be 200, parse the body for UserExternal
-	gotBody := &[]models.ClusterConfigExternal{}
-	expBody := &[]models.ClusterConfigExternal{}
+func ContextBodyValidator(c *userTest, tester *tester, t *testing.T) {
+	gotBody := &[]models.Context{}
+	expBody := &[]models.Context{}
 
 	json.Unmarshal(tester.rr.Body.Bytes(), gotBody)
 	json.Unmarshal([]byte(c.expBody), expBody)

+ 1 - 2
server/router/router.go

@@ -20,8 +20,7 @@ func New(a *api.App, store *sessionstore.PGStore, cookieName string) *chi.Mux {
 
 		// /api/users routes
 		r.Method("GET", "/users/{id}", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleReadUser, l)))
-		r.Method("GET", "/users/{id}/clusters", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleReadUserClusters, l)))
-		r.Method("GET", "/users/{id}/clusters/all", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleReadUserClustersAll, l)))
+		r.Method("GET", "/users/{id}/contexts", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleReadUserContexts, l)))
 		r.Method("POST", "/users", requestlog.NewHandler(a.HandleCreateUser, l))
 		r.Method("PUT", "/users/{id}", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleUpdateUser, l)))
 		r.Method("DELETE", "/users/{id}", auth.DoesUserIDMatch(requestlog.NewHandler(a.HandleDeleteUser, l)))