Преглед изворни кода

rewrote kubernetes implementation

Alexander Belanger пре 5 година
родитељ
комит
c8b199f941

+ 0 - 0
internal/providers/aws/agent.go → cli/cmd/providers/aws/agent.go


+ 0 - 0
internal/providers/aws/authconfigmap.go → cli/cmd/providers/aws/authconfigmap.go


+ 0 - 0
internal/providers/aws/local/config.go → cli/cmd/providers/aws/local/config.go


+ 0 - 0
internal/providers/gcp/agent.go → cli/cmd/providers/gcp/agent.go


+ 0 - 0
internal/providers/gcp/local/config.go → cli/cmd/providers/gcp/local/config.go


+ 189 - 13
internal/kubernetes/config.go

@@ -1,12 +1,14 @@
 package kubernetes
 
 import (
+	"errors"
 	"path/filepath"
 	"regexp"
 	"strings"
 	"time"
 
 	"github.com/porter-dev/porter/internal/models"
+	"github.com/porter-dev/porter/internal/repository"
 	"k8s.io/apimachinery/pkg/api/meta"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
@@ -17,9 +19,12 @@ import (
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/restmapper"
 	"k8s.io/client-go/tools/clientcmd"
+	"k8s.io/client-go/tools/clientcmd/api"
 	"k8s.io/client-go/util/homedir"
 
-	// add oidc provider here
+	ints "github.com/porter-dev/porter/internal/models/integrations"
+
+	// this line will register plugins
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
 )
 
@@ -60,16 +65,11 @@ func GetAgentTesting(objects ...runtime.Object) *Agent {
 	return &Agent{&fakeRESTClientGetter{}, fake.NewSimpleClientset(objects...)}
 }
 
-// UpdateTokenCacheFunc is a function that updates the token cache
-// with a new token and expiry time
-type UpdateTokenCacheFunc func(token string, expiry time.Time) error
-
 // OutOfClusterConfig is the set of parameters required for an out-of-cluster connection.
 // This implements RESTClientGetter
 type OutOfClusterConfig struct {
-	ServiceAccount   *models.ServiceAccount `form:"required"`
-	ClusterID        uint                   `json:"cluster_id" form:"required"`
-	UpdateTokenCache UpdateTokenCacheFunc
+	Cluster *models.Cluster
+	Repo    *repository.Repository
 }
 
 // ToRESTConfig creates a kubernetes REST client factory -- it calls ClientConfig on
@@ -89,11 +89,7 @@ func (conf *OutOfClusterConfig) ToRESTConfig() (*rest.Config, error) {
 // ToRawKubeConfigLoader creates a clientcmd.ClientConfig from the raw kubeconfig found in
 // the OutOfClusterConfig. It does not implement loading rules or overrides.
 func (conf *OutOfClusterConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
-	cmdConf, _ := GetClientConfigFromServiceAccount(
-		conf.ServiceAccount,
-		conf.ClusterID,
-		conf.UpdateTokenCache,
-	)
+	cmdConf, _ := conf.GetClientConfigFromCluster()
 
 	return cmdConf
 }
@@ -135,6 +131,186 @@ func (conf *OutOfClusterConfig) ToRESTMapper() (meta.RESTMapper, error) {
 	return expander, nil
 }
 
+// GetClientConfigFromCluster will construct new clientcmd.ClientConfig using
+// the configuration saved within a Cluster model
+func (conf *OutOfClusterConfig) GetClientConfigFromCluster() (clientcmd.ClientConfig, error) {
+	cluster := conf.Cluster
+
+	if cluster.AuthMechanism == models.Local {
+		kubeAuth, err := conf.Repo.KubeIntegration.ReadKubeIntegration(
+			cluster.KubeIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		return clientcmd.NewClientConfigFromBytes(kubeAuth.Kubeconfig)
+	}
+
+	apiConfig, err := conf.createRawConfigFromCluster()
+
+	if err != nil {
+		return nil, err
+	}
+
+	config := clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{})
+
+	return config, nil
+}
+
+func (conf *OutOfClusterConfig) createRawConfigFromCluster() (*api.Config, error) {
+	cluster := conf.Cluster
+
+	apiConfig := &api.Config{}
+
+	clusterMap := make(map[string]*api.Cluster)
+
+	clusterMap[cluster.Name] = &api.Cluster{
+		Server:                   cluster.Server,
+		LocationOfOrigin:         cluster.ClusterLocationOfOrigin,
+		TLSServerName:            cluster.TLSServerName,
+		InsecureSkipTLSVerify:    cluster.InsecureSkipTLSVerify,
+		CertificateAuthorityData: cluster.CertificateAuthorityData,
+	}
+
+	// construct the auth infos
+	authInfoName := cluster.Name + "-" + string(cluster.AuthMechanism)
+
+	authInfoMap := make(map[string]*api.AuthInfo)
+
+	authInfoMap[authInfoName] = &api.AuthInfo{
+		LocationOfOrigin: cluster.UserLocationOfOrigin,
+		Impersonate:      cluster.UserImpersonate,
+	}
+
+	if groups := strings.Split(cluster.UserImpersonateGroups, ","); len(groups) > 0 && groups[0] != "" {
+		authInfoMap[authInfoName].ImpersonateGroups = groups
+	}
+
+	switch cluster.AuthMechanism {
+	case models.X509:
+		kubeAuth, err := conf.Repo.KubeIntegration.ReadKubeIntegration(
+			cluster.KubeIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		authInfoMap[authInfoName].ClientCertificateData = kubeAuth.ClientCertificateData
+		authInfoMap[authInfoName].ClientKeyData = kubeAuth.ClientKeyData
+	case models.Basic:
+		kubeAuth, err := conf.Repo.KubeIntegration.ReadKubeIntegration(
+			cluster.KubeIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		authInfoMap[authInfoName].Username = string(kubeAuth.Username)
+		authInfoMap[authInfoName].Password = string(kubeAuth.Password)
+	case models.Bearer:
+		kubeAuth, err := conf.Repo.KubeIntegration.ReadKubeIntegration(
+			cluster.KubeIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		authInfoMap[authInfoName].Token = string(kubeAuth.Token)
+	case models.OIDC:
+		oidcAuth, err := conf.Repo.OIDCIntegration.ReadOIDCIntegration(
+			cluster.OIDCIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		authInfoMap[authInfoName].AuthProvider = &api.AuthProviderConfig{
+			Name: "oidc",
+			Config: map[string]string{
+				"idp-issuer-url":                 string(oidcAuth.IssuerURL),
+				"client-id":                      string(oidcAuth.ClientID),
+				"client-secret":                  string(oidcAuth.ClientSecret),
+				"idp-certificate-authority-data": string(oidcAuth.CertificateAuthorityData),
+				"id-token":                       string(oidcAuth.IDToken),
+				"refresh-token":                  string(oidcAuth.RefreshToken),
+			},
+		}
+	case models.GCP:
+		gcpAuth, err := conf.Repo.GCPIntegration.ReadGCPIntegration(
+			cluster.GCPIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		tok, err := gcpAuth.GetBearerToken(conf.getTokenCache, conf.setTokenCache)
+
+		if err != nil {
+			return nil, err
+		}
+
+		// add this as a bearer token
+		authInfoMap[authInfoName].Token = tok
+	case models.AWS:
+		awsAuth, err := conf.Repo.AWSIntegration.ReadAWSIntegration(
+			cluster.AWSIntegrationID,
+		)
+
+		if err != nil {
+			return nil, err
+		}
+
+		tok, err := awsAuth.GetBearerToken(conf.getTokenCache, conf.setTokenCache)
+
+		if err != nil {
+			return nil, err
+		}
+
+		// add this as a bearer token
+		authInfoMap[authInfoName].Token = tok
+	default:
+		return nil, errors.New("not a supported auth mechanism")
+	}
+
+	// create a context of the cluster name
+	contextMap := make(map[string]*api.Context)
+
+	contextMap[cluster.Name] = &api.Context{
+		LocationOfOrigin: cluster.ClusterLocationOfOrigin,
+		Cluster:          cluster.Name,
+		AuthInfo:         authInfoName,
+	}
+
+	apiConfig.Clusters = clusterMap
+	apiConfig.AuthInfos = authInfoMap
+	apiConfig.Contexts = contextMap
+	apiConfig.CurrentContext = cluster.Name
+
+	return apiConfig, nil
+}
+
+func (conf *OutOfClusterConfig) getTokenCache() (tok *ints.TokenCache, err error) {
+	return &conf.Cluster.TokenCache, nil
+}
+
+func (conf *OutOfClusterConfig) setTokenCache(token string, expiry time.Time) error {
+	_, err := conf.Repo.Cluster.UpdateClusterTokenCache(
+		&ints.TokenCache{
+			Token:  []byte(token),
+			Expiry: expiry,
+		},
+	)
+
+	return err
+}
+
 // newRESTClientGetterFromInClusterConfig returns a RESTClientGetter using
 // default values set from the *rest.Config
 func newRESTClientGetterFromInClusterConfig(conf *rest.Config) genericclioptions.RESTClientGetter {

+ 161 - 407
internal/kubernetes/kubeconfig.go

@@ -1,30 +1,22 @@
 package kubernetes
 
 import (
-	"context"
+	"encoding/json"
 	"errors"
 	"net/url"
-	"strings"
 
 	"github.com/porter-dev/porter/internal/models"
-	"golang.org/x/oauth2/google"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/clientcmd/api"
-
-	"github.com/aws/aws-sdk-go/aws"
-
-	"github.com/aws/aws-sdk-go/aws/credentials"
-	"github.com/aws/aws-sdk-go/aws/session"
-	token "sigs.k8s.io/aws-iam-authenticator/pkg/token"
 )
 
-// GetServiceAccountCandidates parses a kubeconfig for a list of service account
+// GetClusterCandidatesFromKubeconfig parses a kubeconfig for a list of cluster
 // candidates.
 //
 // The local boolean represents whether the auth mechanism should be designated as
 // "local": if so, the auth mechanism uses local plugins/mechanisms purely from the
 // kubeconfig.
-func GetServiceAccountCandidates(kubeconfig []byte, local bool) ([]*models.ServiceAccountCandidate, error) {
+func GetClusterCandidatesFromKubeconfig(kubeconfig []byte, projectID uint) ([]*models.ClusterCandidate, error) {
 	config, err := clientcmd.NewClientConfigFromBytes(kubeconfig)
 
 	if err != nil {
@@ -37,32 +29,22 @@ func GetServiceAccountCandidates(kubeconfig []byte, local bool) ([]*models.Servi
 		return nil, err
 	}
 
-	res := make([]*models.ServiceAccountCandidate, 0)
+	res := make([]*models.ClusterCandidate, 0)
 
 	for contextName, context := range rawConf.Contexts {
 		clusterName := context.Cluster
 		awsClusterID := ""
 		authInfoName := context.AuthInfo
 
-		actions := make([]models.ServiceAccountAction, 0)
-		var integration string
+		// get the resolvers, if needed
+		authMechanism, resolvers := parseAuthInfoForResolvers(rawConf.AuthInfos[authInfoName])
+		clusterResolvers := parseClusterForResolvers(rawConf.Clusters[clusterName])
+		resolvers = append(resolvers, clusterResolvers...)
 
-		if local {
-			integration = models.Local
-		} else {
-			// get the auth mechanism and actions
-			integration, actions = parseAuthInfoForActions(rawConf.AuthInfos[authInfoName])
-			clusterActions := parseClusterForActions(rawConf.Clusters[clusterName])
-			actions = append(actions, clusterActions...)
-
-			// if auth mechanism is unsupported, we'll skip it
-			if integration == models.NotAvailable {
-				continue
-			} else if integration == models.AWS {
-				// if the auth mechanism is AWS, we need to parse more explicitly
-				// for the cluster id
-				awsClusterID = parseAuthInfoForAWSClusterID(rawConf.AuthInfos[authInfoName], clusterName)
-			}
+		if authMechanism == models.AWS {
+			// if the auth mechanism is AWS, we need to parse more explicitly
+			// for the cluster id
+			awsClusterID = parseAuthInfoForAWSClusterID(rawConf.AuthInfos[authInfoName], clusterName)
 		}
 
 		// construct the raw kubeconfig that's relevant for that context
@@ -75,15 +57,15 @@ func GetServiceAccountCandidates(kubeconfig []byte, local bool) ([]*models.Servi
 		rawBytes, err := clientcmd.Write(*contextConf)
 
 		if err == nil {
-			// create the candidate service account
-			res = append(res, &models.ServiceAccountCandidate{
-				Actions:           actions,
-				Kind:              "connector",
+			// create the candidate cluster
+			res = append(res, &models.ClusterCandidate{
+				AuthMechanism:     authMechanism,
+				ProjectID:         projectID,
+				Resolvers:         resolvers,
 				ContextName:       contextName,
-				ClusterName:       clusterName,
-				ClusterEndpoint:   rawConf.Clusters[clusterName].Server,
-				Integration:     integration,
-				AWSClusterIDGuess: awsClusterID,
+				Name:              clusterName,
+				Server:            rawConf.Clusters[clusterName].Server,
+				AWSClusterIDGuess: []byte(awsClusterID),
 				Kubeconfig:        rawBytes,
 			})
 		}
@@ -92,6 +74,80 @@ func GetServiceAccountCandidates(kubeconfig []byte, local bool) ([]*models.Servi
 	return res, nil
 }
 
+// GetServiceAccountCandidates parses a kubeconfig for a list of service account
+// candidates.
+//
+// The local boolean represents whether the auth mechanism should be designated as
+// "local": if so, the auth mechanism uses local plugins/mechanisms purely from the
+// kubeconfig.
+// func GetServiceAccountCandidates(kubeconfig []byte, local bool) ([]*models.ServiceAccountCandidate, error) {
+// 	config, err := clientcmd.NewClientConfigFromBytes(kubeconfig)
+
+// 	if err != nil {
+// 		return nil, err
+// 	}
+
+// 	rawConf, err := config.RawConfig()
+
+// 	if err != nil {
+// 		return nil, err
+// 	}
+
+// 	res := make([]*models.ServiceAccountCandidate, 0)
+
+// 	for contextName, context := range rawConf.Contexts {
+// 		clusterName := context.Cluster
+// 		awsClusterID := ""
+// 		authInfoName := context.AuthInfo
+
+// 		resolvers := make([]models.ServiceAccountResolver, 0)
+// 		var integration string
+
+// 		if local {
+// 			integration = models.Local
+// 		} else {
+// 			// get the auth mechanism and resolvers
+// 			integration, resolvers = parseAuthInfoForResolvers(rawConf.AuthInfos[authInfoName])
+// 			clusterResolvers := parseClusterForResolvers(rawConf.Clusters[clusterName])
+// 			resolvers = append(resolvers, clusterResolvers...)
+
+// 			// if auth mechanism is unsupported, we'll skip it
+// 			if integration == models.NotAvailable {
+// 				continue
+// 			} else if integration == models.AWS {
+// 				// if the auth mechanism is AWS, we need to parse more explicitly
+// 				// for the cluster id
+// 				awsClusterID = parseAuthInfoForAWSClusterID(rawConf.AuthInfos[authInfoName], clusterName)
+// 			}
+// 		}
+
+// 		// construct the raw kubeconfig that's relevant for that context
+// 		contextConf, err := getConfigForContext(&rawConf, contextName)
+
+// 		if err != nil {
+// 			continue
+// 		}
+
+// 		rawBytes, err := clientcmd.Write(*contextConf)
+
+// 		if err == nil {
+// 			// create the candidate service account
+// 			res = append(res, &models.ServiceAccountCandidate{
+// 				Resolvers:           resolvers,
+// 				Kind:              "connector",
+// 				ContextName:       contextName,
+// 				ClusterName:       clusterName,
+// 				ClusterEndpoint:   rawConf.Clusters[clusterName].Server,
+// 				Integration:     integration,
+// 				AWSClusterIDGuess: awsClusterID,
+// 				Kubeconfig:        rawBytes,
+// 			})
+// 		}
+// 	}
+
+// 	return res, nil
+// }
+
 // GetRawConfigFromBytes returns the clientcmdapi.Config from kubeconfig
 // bytes
 func GetRawConfigFromBytes(kubeconfig []byte) (*api.Config, error) {
@@ -118,28 +174,40 @@ func GetRawConfigFromBytes(kubeconfig []byte) (*api.Config, error) {
 // (4) If a username/password exist, uses basic auth mechanism
 // (5) Otherwise, the config gets skipped
 //
-func parseAuthInfoForActions(authInfo *api.AuthInfo) (integration string, actions []models.ServiceAccountAction) {
-	actions = make([]models.ServiceAccountAction, 0)
+func parseAuthInfoForResolvers(authInfo *api.AuthInfo) (authMechanism models.ClusterAuth, resolvers []models.ClusterResolver) {
+	resolvers = make([]models.ClusterResolver, 0)
 
 	if (authInfo.ClientCertificate != "" || len(authInfo.ClientCertificateData) != 0) &&
 		(authInfo.ClientKey != "" || len(authInfo.ClientKeyData) != 0) {
 		if len(authInfo.ClientCertificateData) == 0 {
-			actions = append(actions, models.ServiceAccountAction{
-				Name:     models.ClientCertDataAction,
+			fn := map[string]string{
+				"filename": authInfo.ClientCertificate,
+			}
+
+			fnBytes, _ := json.Marshal(&fn)
+
+			resolvers = append(resolvers, models.ClusterResolver{
+				Name:     models.ClientCertData,
 				Resolved: false,
-				Filename: authInfo.ClientCertificate,
+				Data:     fnBytes,
 			})
 		}
 
 		if len(authInfo.ClientKeyData) == 0 {
-			actions = append(actions, models.ServiceAccountAction{
-				Name:     models.ClientKeyDataAction,
+			fn := map[string]string{
+				"filename": authInfo.ClientKey,
+			}
+
+			fnBytes, _ := json.Marshal(&fn)
+
+			resolvers = append(resolvers, models.ClusterResolver{
+				Name:     models.ClientKeyData,
 				Resolved: false,
-				Filename: authInfo.ClientKey,
+				Data:     fnBytes,
 			})
 		}
 
-		return models.X509, actions
+		return models.X509, resolvers
 	}
 
 	if authInfo.AuthProvider != nil {
@@ -149,20 +217,26 @@ func parseAuthInfoForActions(authInfo *api.AuthInfo) (integration string, action
 			data, isData := authInfo.AuthProvider.Config["idp-certificate-authority-data"]
 
 			if isFile && (!isData || data == "") {
-				return models.OIDC, []models.ServiceAccountAction{
-					models.ServiceAccountAction{
-						Name:     models.OIDCIssuerDataAction,
+				fn := map[string]string{
+					"filename": filename,
+				}
+
+				fnBytes, _ := json.Marshal(&fn)
+
+				return models.OIDC, []models.ClusterResolver{
+					models.ClusterResolver{
+						Name:     models.OIDCIssuerData,
 						Resolved: false,
-						Filename: filename,
+						Data:     fnBytes,
 					},
 				}
 			}
 
-			return models.OIDC, actions
+			return models.OIDC, resolvers
 		case "gcp":
-			return models.GCP, []models.ServiceAccountAction{
-				models.ServiceAccountAction{
-					Name:     models.GCPKeyDataAction,
+			return models.GCP, []models.ClusterResolver{
+				models.ClusterResolver{
+					Name:     models.GCPKeyData,
 					Resolved: false,
 				},
 			}
@@ -171,9 +245,9 @@ func parseAuthInfoForActions(authInfo *api.AuthInfo) (integration string, action
 
 	if authInfo.Exec != nil {
 		if authInfo.Exec.Command == "aws" || authInfo.Exec.Command == "aws-iam-authenticator" {
-			return models.AWS, []models.ServiceAccountAction{
-				models.ServiceAccountAction{
-					Name:     models.AWSDataAction,
+			return models.AWS, []models.ClusterResolver{
+				models.ClusterResolver{
+					Name:     models.AWSData,
 					Resolved: false,
 				},
 			}
@@ -182,35 +256,47 @@ func parseAuthInfoForActions(authInfo *api.AuthInfo) (integration string, action
 
 	if authInfo.Token != "" || authInfo.TokenFile != "" {
 		if authInfo.Token == "" {
-			return models.Bearer, []models.ServiceAccountAction{
-				models.ServiceAccountAction{
-					Name:     models.TokenDataAction,
+			fn := map[string]string{
+				"filename": authInfo.TokenFile,
+			}
+
+			fnBytes, _ := json.Marshal(&fn)
+
+			return models.Bearer, []models.ClusterResolver{
+				models.ClusterResolver{
+					Name:     models.TokenData,
 					Resolved: false,
-					Filename: authInfo.TokenFile,
+					Data:     fnBytes,
 				},
 			}
 		}
 
-		return models.Bearer, actions
+		return models.Bearer, resolvers
 	}
 
 	if authInfo.Username != "" && authInfo.Password != "" {
-		return models.Basic, actions
+		return models.Basic, resolvers
 	}
 
-	return models.NotAvailable, actions
+	return models.X509, resolvers
 }
 
-// Parses the cluster object to determine actions -- only currently supported action is
+// Parses the cluster object to determine resolvers -- only currently supported resolver is
 // population of the cluster certificate authority data
-func parseClusterForActions(cluster *api.Cluster) (actions []models.ServiceAccountAction) {
-	actions = make([]models.ServiceAccountAction, 0)
+func parseClusterForResolvers(cluster *api.Cluster) (resolvers []models.ClusterResolver) {
+	resolvers = make([]models.ClusterResolver, 0)
 
 	if cluster.CertificateAuthority != "" && len(cluster.CertificateAuthorityData) == 0 {
-		actions = append(actions, models.ServiceAccountAction{
-			Name:     models.ClusterCADataAction,
+		fn := map[string]string{
+			"filename": cluster.CertificateAuthority,
+		}
+
+		fnBytes, _ := json.Marshal(&fn)
+
+		resolvers = append(resolvers, models.ClusterResolver{
+			Name:     models.ClusterCAData,
 			Resolved: false,
-			Filename: cluster.CertificateAuthority,
+			Data:     fnBytes,
 		})
 	}
 
@@ -218,14 +304,14 @@ func parseClusterForActions(cluster *api.Cluster) (actions []models.ServiceAccou
 
 	if err == nil {
 		if hostname := serverURL.Hostname(); hostname == "127.0.0.1" || hostname == "localhost" {
-			actions = append(actions, models.ServiceAccountAction{
-				Name:     models.ClusterLocalhostAction,
+			resolvers = append(resolvers, models.ClusterResolver{
+				Name:     models.ClusterLocalhost,
 				Resolved: false,
 			})
 		}
 	}
 
-	return actions
+	return resolvers
 }
 
 func parseAuthInfoForAWSClusterID(authInfo *api.AuthInfo, fallback string) string {
@@ -250,7 +336,7 @@ func parseAuthInfoForAWSClusterID(authInfo *api.AuthInfo, fallback string) strin
 	return fallback
 }
 
-// getKubeconfigForContext returns the raw kubeconfig associated with only a
+// getConfigForContext returns the raw kubeconfig associated with only a
 // single context of the raw config
 func getConfigForContext(
 	rawConf *api.Config,
@@ -284,335 +370,3 @@ func getConfigForContext(
 
 	return copyConf, nil
 }
-
-// GetClientConfigFromServiceAccount will construct new clientcmd.ClientConfig using
-// the configuration saved within a ServiceAccount model
-func GetClientConfigFromServiceAccount(
-	sa *models.ServiceAccount,
-	clusterID uint,
-	updateTokenCache UpdateTokenCacheFunc,
-) (clientcmd.ClientConfig, error) {
-	if sa.Integration == models.Local {
-		return clientcmd.NewClientConfigFromBytes(sa.Kubeconfig)
-	}
-
-	apiConfig, err := createRawConfigFromServiceAccount(sa, clusterID, updateTokenCache)
-
-	if err != nil {
-		return nil, err
-	}
-
-	config := clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{})
-
-	return config, nil
-}
-
-func createRawConfigFromServiceAccount(
-	sa *models.ServiceAccount,
-	clusterID uint,
-	updateTokenCache UpdateTokenCacheFunc,
-) (*api.Config, error) {
-	apiConfig := &api.Config{}
-
-	var cluster *models.Cluster = nil
-
-	// find the cluster within the ServiceAccount configuration
-	for _, _cluster := range sa.Clusters {
-		if _cluster.ID == clusterID {
-			cluster = &_cluster
-		}
-	}
-
-	if cluster == nil {
-		return nil, errors.New("cluster not found")
-	}
-
-	clusterMap := make(map[string]*api.Cluster)
-
-	clusterMap[cluster.Name] = &api.Cluster{
-		LocationOfOrigin:         cluster.LocationOfOrigin,
-		Server:                   cluster.Server,
-		TLSServerName:            cluster.TLSServerName,
-		InsecureSkipTLSVerify:    cluster.InsecureSkipTLSVerify,
-		CertificateAuthorityData: cluster.CertificateAuthorityData,
-	}
-
-	// construct the auth infos
-	authInfoName := cluster.Name + "-" + sa.Integration
-
-	authInfoMap := make(map[string]*api.AuthInfo)
-
-	authInfoMap[authInfoName] = &api.AuthInfo{
-		LocationOfOrigin: sa.LocationOfOrigin,
-		Impersonate:      sa.Impersonate,
-	}
-
-	if groups := strings.Split(sa.ImpersonateGroups, ","); len(groups) > 0 && groups[0] != "" {
-		authInfoMap[authInfoName].ImpersonateGroups = groups
-	}
-
-	switch sa.Integration {
-	case models.X509:
-		authInfoMap[authInfoName].ClientCertificateData = sa.ClientCertificateData
-		authInfoMap[authInfoName].ClientKeyData = sa.ClientKeyData
-	case models.Basic:
-		authInfoMap[authInfoName].Username = string(sa.Username)
-		authInfoMap[authInfoName].Password = string(sa.Password)
-	case models.Bearer:
-		authInfoMap[authInfoName].Token = string(sa.Token)
-	case models.OIDC:
-		authInfoMap[authInfoName].AuthProvider = &api.AuthProviderConfig{
-			Name: "oidc",
-			Config: map[string]string{
-				"idp-issuer-url":                 string(sa.OIDCIssuerURL),
-				"client-id":                      string(sa.OIDCClientID),
-				"client-secret":                  string(sa.OIDCClientSecret),
-				"idp-certificate-authority-data": string(sa.OIDCCertificateAuthorityData),
-				"id-token":                       string(sa.OIDCIDToken),
-				"refresh-token":                  string(sa.OIDCRefreshToken),
-			},
-		}
-	case models.GCP:
-		tok, err := getGCPToken(sa, updateTokenCache)
-
-		if err != nil {
-			return nil, err
-		}
-
-		// add this as a bearer token
-		authInfoMap[authInfoName].Token = tok
-	case models.AWS:
-		tok, err := getAWSToken(sa, updateTokenCache)
-
-		if err != nil {
-			return nil, err
-		}
-
-		// add this as a bearer token
-		authInfoMap[authInfoName].Token = tok
-	default:
-		return nil, errors.New("not a supported auth mechanism")
-	}
-
-	// create a context of the cluster name
-	contextMap := make(map[string]*api.Context)
-
-	contextMap[cluster.Name] = &api.Context{
-		LocationOfOrigin: cluster.LocationOfOrigin,
-		Cluster:          cluster.Name,
-		AuthInfo:         authInfoName,
-	}
-
-	apiConfig.Clusters = clusterMap
-	apiConfig.AuthInfos = authInfoMap
-	apiConfig.Contexts = contextMap
-	apiConfig.CurrentContext = cluster.Name
-
-	return apiConfig, nil
-}
-
-func getGCPToken(
-	sa *models.ServiceAccount,
-	updateTokenCache UpdateTokenCacheFunc,
-) (string, error) {
-	// check the token cache for a non-expired token
-	if tok := sa.TokenCache.Token; !sa.TokenCache.IsExpired() && len(tok) > 0 {
-		return string(tok), nil
-	}
-
-	creds, err := google.CredentialsFromJSON(
-		context.Background(),
-		sa.GCPKeyData,
-		"https://www.googleapis.com/auth/cloud-platform",
-	)
-
-	if err != nil {
-		return "", err
-	}
-
-	tok, err := creds.TokenSource.Token()
-
-	if err != nil {
-		return "", err
-	}
-
-	// update the token cache
-	updateTokenCache(tok.AccessToken, tok.Expiry)
-
-	return tok.AccessToken, nil
-}
-
-func getAWSToken(
-	sa *models.ServiceAccount,
-	updateTokenCache UpdateTokenCacheFunc,
-) (string, error) {
-	// check the token cache for a non-expired token
-	if tok := sa.TokenCache.Token; !sa.TokenCache.IsExpired() && len(tok) > 0 {
-		return string(tok), nil
-	}
-
-	generator, err := token.NewGenerator(false, false)
-
-	if err != nil {
-		return "", err
-	}
-
-	sess, err := session.NewSessionWithOptions(session.Options{
-		SharedConfigState: session.SharedConfigEnable,
-		Config: aws.Config{
-			Credentials: credentials.NewStaticCredentials(
-				string(sa.AWSAccessKeyID),
-				string(sa.AWSSecretAccessKey),
-				"",
-			),
-		},
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	tok, err := generator.GetWithOptions(&token.GetTokenOptions{
-		Session:   sess,
-		ClusterID: string(sa.AWSClusterID),
-	})
-
-	if err != nil {
-		return "", err
-	}
-
-	updateTokenCache(tok.Token, tok.Expiration)
-
-	return tok.Token, nil
-}
-
-// GetRestrictedClientConfigFromBytes returns a clientcmd.ClientConfig from a raw kubeconfig,
-// a context name, and the set of allowed contexts.
-func GetRestrictedClientConfigFromBytes(
-	bytes []byte,
-	contextName string,
-	allowedContexts []string,
-) (clientcmd.ClientConfig, error) {
-	config, err := clientcmd.NewClientConfigFromBytes(bytes)
-
-	if err != nil {
-		return nil, err
-	}
-
-	rawConf, err := config.RawConfig()
-
-	if err != nil {
-		return nil, err
-	}
-
-	// grab a copy to get the pointer and set clusters, authinfos, and contexts to empty
-	copyConf := rawConf.DeepCopy()
-
-	copyConf.Clusters = make(map[string]*api.Cluster)
-	copyConf.AuthInfos = make(map[string]*api.AuthInfo)
-	copyConf.Contexts = make(map[string]*api.Context)
-	copyConf.CurrentContext = contextName
-
-	// put allowed clusters in a map
-	aContextMap := CreateAllowedContextMap(allowedContexts)
-
-	context, ok := rawConf.Contexts[contextName]
-
-	if ok {
-		userName := context.AuthInfo
-		clusterName := context.Cluster
-		authInfo, userFound := rawConf.AuthInfos[userName]
-		cluster, clusterFound := rawConf.Clusters[clusterName]
-
-		// make sure the cluster is "allowed"
-		_, isAllowed := aContextMap[contextName]
-
-		if userFound && clusterFound && isAllowed {
-			copyConf.Clusters[clusterName] = cluster
-			copyConf.AuthInfos[userName] = authInfo
-			copyConf.Contexts[contextName] = context
-		}
-	}
-
-	// validate the copyConf and create a ClientConfig
-	err = clientcmd.Validate(*copyConf)
-
-	if err != nil {
-		return nil, err
-	}
-
-	clientConf := clientcmd.NewDefaultClientConfig(*copyConf, &clientcmd.ConfigOverrides{})
-
-	return clientConf, nil
-}
-
-// 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 {
-		return nil, err
-	}
-
-	rawConf, err := config.RawConfig()
-
-	if err != nil {
-		return nil, err
-	}
-
-	err = clientcmd.Validate(rawConf)
-
-	if err != nil {
-		return nil, err
-	}
-
-	contexts := toContexts(&rawConf, allowedContexts)
-
-	return contexts, nil
-}
-
-func toContexts(rawConf *api.Config, allowedContexts []string) []models.Context {
-	contexts := make([]models.Context, 0)
-
-	// put allowed clusters in map
-	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,
-			})
-		} else if userFound && clusterFound {
-			contexts = append(contexts, models.Context{
-				Name:     name,
-				Server:   cluster.Server,
-				Cluster:  context.Cluster,
-				User:     context.AuthInfo,
-				Selected: false,
-			})
-		}
-	}
-
-	return contexts
-}
-
-// CreateAllowedContextMap creates a dummy map from context name to context name
-func CreateAllowedContextMap(contexts []string) map[string]string {
-	aContextMap := make(map[string]string)
-
-	for _, context := range contexts {
-		aContextMap[context] = context
-	}
-
-	return aContextMap
-}

+ 196 - 479
internal/kubernetes/kubeconfig_test.go

@@ -1,491 +1,329 @@
 package kubernetes_test
 
 import (
-	"reflect"
-	"strings"
 	"testing"
 
+	"github.com/go-test/deep"
 	"github.com/porter-dev/porter/internal/kubernetes"
 	"github.com/porter-dev/porter/internal/models"
 	"k8s.io/client-go/tools/clientcmd"
 )
 
-type kubeConfigTest struct {
-	msg             string
-	raw             []byte
-	allowedContexts []string
-	expected        []models.Context
-}
-
-type kubeConfigTestValidateError struct {
-	msg             string
-	raw             []byte
-	allowedContexts []string
-	contextName     string
-	errorContains   string // a string that the error message should contain
-}
-
-var ValidateErrorTests = []kubeConfigTestValidateError{
-	kubeConfigTestValidateError{
-		msg:             "No configuration",
-		raw:             []byte(""),
-		allowedContexts: []string{},
-		contextName:     "",
-		errorContains:   "invalid configuration: no configuration has been provided",
-	},
-	kubeConfigTestValidateError{
-		msg:             "Context name does not exist",
-		raw:             []byte(noContexts),
-		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),
-		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),
-		allowedContexts: []string{"porter-test-1"},
-		contextName:     "context-test",
-		errorContains:   "invalid configuration: context was not found for specified context: context-test",
-	},
-}
-
-func TestValidateErrors(t *testing.T) {
-	for _, c := range ValidateErrorTests {
-
-		_, err := kubernetes.GetRestrictedClientConfigFromBytes(c.raw, c.contextName, c.allowedContexts)
-
-		if err == nil {
-			t.Fatalf("Testing %s did not return an error\n", c.msg)
-		}
-
-		if !strings.Contains(err.Error(), c.errorContains) {
-			t.Errorf("Testing %s -- Error was:\n \"%s\" \n It did not contain string \"%s\"\n", c.msg, err.Error(), c.errorContains)
-		}
-	}
-}
-
-var BasicContextAllowedTests = []kubeConfigTest{
-	kubeConfigTest{
-		msg:             "basic test",
-		raw:             []byte(basic),
-		allowedContexts: []string{"context-test"},
-		expected: []models.Context{
-			models.Context{
-				Name:     "context-test",
-				Server:   "https://10.10.10.10",
-				Cluster:  "cluster-test",
-				User:     "test-admin",
-				Selected: true,
-			},
-		},
-	},
-}
-
-func TestBasicAllowed(t *testing.T) {
-	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())
-		}
-
-		isEqual := reflect.DeepEqual(c.expected, res)
-
-		if !isEqual {
-			t.Errorf("Testing: %s, Expected: %v, Got: %v\n", c.msg, c.expected, res)
-		}
-	}
-}
-
-var BasicContextAllTests = []kubeConfigTest{
-	kubeConfigTest{
-		msg:             "basic test",
-		raw:             []byte(basic),
-		allowedContexts: []string{},
-		expected: []models.Context{
-			models.Context{
-				Name:     "context-test",
-				Server:   "https://10.10.10.10",
-				Cluster:  "cluster-test",
-				User:     "test-admin",
-				Selected: false,
-			},
-		},
-	},
-}
-
-func TestBasicAll(t *testing.T) {
-	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())
-		}
-
-		isEqual := reflect.DeepEqual(c.expected, res)
-
-		if !isEqual {
-			t.Errorf("Testing: %s, Expected: %v, Got: %v\n", c.msg, c.expected, res)
-		}
-	}
-}
-
-func TestGetRestrictedClientConfig(t *testing.T) {
-	contexts := []string{"context-test"}
-	contextName := "context-test"
-
-	clientConf, err := kubernetes.GetRestrictedClientConfigFromBytes([]byte(basic), contextName, contexts)
-
-	if err != nil {
-		t.Fatalf("Fatal error: %s\n", err.Error())
-	}
-
-	rawConf, err := clientConf.RawConfig()
-
-	if err != nil {
-		t.Fatalf("Fatal error: %s\n", err.Error())
-	}
-
-	if cluster, clusterFound := rawConf.Clusters["cluster-test"]; !clusterFound || cluster.Server != "https://10.10.10.10" {
-		t.Errorf("invalid cluster returned")
-	}
-
-	if _, contextFound := rawConf.Contexts["context-test"]; !contextFound {
-		t.Errorf("invalid context returned")
-	}
-
-	if _, authInfoFound := rawConf.AuthInfos["test-admin"]; !authInfoFound {
-		t.Errorf("invalid auth info returned")
-	}
-}
-
-type saCandidatesTest struct {
+type ccsTest struct {
 	name     string
 	raw      []byte
-	expected []*models.ServiceAccountCandidate
+	expected []*models.ClusterCandidate
 }
 
-var SACandidatesTests = []saCandidatesTest{
-	saCandidatesTest{
+var ClusterCandidatesTests = []ccsTest{
+	ccsTest{
 		name: "test without cluster ca data",
 		raw:  []byte(ClusterCAWithoutData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
-						Name:     "upload-cluster-ca-data",
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.X509,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
+						Name:     models.ClusterCAData,
 						Resolved: false,
-						Filename: "/fake/path/to/ca.pem",
+						Data:     []byte(`{"filename":"/fake/path/to/ca.pem"}`),
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.X509,
-				Kubeconfig:      []byte(ClusterCAWithoutData),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(ClusterCAWithoutData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "test cluster localhost",
 		raw:  []byte(ClusterLocalhost),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
-						Name:     "fix-cluster-localhost",
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.X509,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
+						Name:     models.ClusterLocalhost,
 						Resolved: false,
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://localhost",
-				Integration:   models.X509,
-				Kubeconfig:      []byte(ClusterLocalhost),
+				Name:              "cluster-test",
+				Server:            "https://localhost",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(ClusterLocalhost),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "x509 test with cert and key data",
 		raw:  []byte(x509WithData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions:         []models.ServiceAccountAction{},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.X509,
-				Kubeconfig:      []byte(x509WithData),
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism:     models.X509,
+				ProjectID:         1,
+				Resolvers:         []models.ClusterResolver{},
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(x509WithData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "x509 test without cert data",
 		raw:  []byte(x509WithoutCertData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.X509,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-client-cert-data",
 						Resolved: false,
-						Filename: "/fake/path/to/cert.pem",
+						Data:     []byte(`{"filename":"/fake/path/to/cert.pem"}`),
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.X509,
-				Kubeconfig:      []byte(x509WithoutCertData),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(x509WithoutCertData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "x509 test without key data",
 		raw:  []byte(x509WithoutKeyData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.X509,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-client-key-data",
 						Resolved: false,
-						Filename: "/fake/path/to/key.pem",
+						Data:     []byte(`{"filename":"/fake/path/to/key.pem"}`),
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.X509,
-				Kubeconfig:      []byte(x509WithoutKeyData),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(x509WithoutKeyData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "x509 test without cert and key data",
 		raw:  []byte(x509WithoutCertAndKeyData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.X509,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-client-cert-data",
 						Resolved: false,
-						Filename: "/fake/path/to/cert.pem",
+						Data:     []byte(`{"filename":"/fake/path/to/cert.pem"}`),
 					},
-					models.ServiceAccountAction{
+					models.ClusterResolver{
 						Name:     "upload-client-key-data",
 						Resolved: false,
-						Filename: "/fake/path/to/key.pem",
+						Data:     []byte(`{"filename":"/fake/path/to/key.pem"}`),
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.X509,
-				Kubeconfig:      []byte(x509WithoutCertAndKeyData),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(x509WithoutCertAndKeyData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "bearer token test with data",
 		raw:  []byte(BearerTokenWithData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions:         []models.ServiceAccountAction{},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.Bearer,
-				Kubeconfig:      []byte(BearerTokenWithData),
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism:     models.Bearer,
+				ProjectID:         1,
+				Resolvers:         []models.ClusterResolver{},
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(BearerTokenWithData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "bearer token test without data",
 		raw:  []byte(BearerTokenWithoutData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.Bearer,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-token-data",
 						Resolved: false,
-						Filename: "/path/to/token/file.txt",
+						Data:     []byte(`{"filename":"/path/to/token/file.txt"}`),
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.Bearer,
-				Kubeconfig:      []byte(BearerTokenWithoutData),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(BearerTokenWithoutData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "gcp test",
 		raw:  []byte(GCPPlugin),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.GCP,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-gcp-key-data",
 						Resolved: false,
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.GCP,
-				Kubeconfig:      []byte(GCPPlugin),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(GCPPlugin),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "aws iam authenticator test",
 		raw:  []byte(AWSIamAuthenticatorExec),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.AWS,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-aws-data",
 						Resolved: false,
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.AWS,
-				Kubeconfig:      []byte(AWSIamAuthenticatorExec),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(AWSIamAuthenticatorExec),
+				AWSClusterIDGuess: []byte("cluster-test-aws-id-guess"),
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "aws eks get-token test",
 		raw:  []byte(AWSEKSGetTokenExec),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.AWS,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-aws-data",
 						Resolved: false,
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.AWS,
-				Kubeconfig:      []byte(AWSEKSGetTokenExec),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(AWSEKSGetTokenExec),
+				AWSClusterIDGuess: []byte("cluster-test-aws-id-guess"),
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "oidc without ca data",
 		raw:  []byte(OIDCAuthWithoutData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions: []models.ServiceAccountAction{
-					models.ServiceAccountAction{
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism: models.OIDC,
+				ProjectID:     1,
+				Resolvers: []models.ClusterResolver{
+					models.ClusterResolver{
 						Name:     "upload-oidc-idp-issuer-ca-data",
 						Resolved: false,
-						Filename: "/fake/path/to/ca.pem",
+						Data:     []byte(`{"filename":"/fake/path/to/ca.pem"}`),
 					},
 				},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.OIDC,
-				Kubeconfig:      []byte(OIDCAuthWithoutData),
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(OIDCAuthWithoutData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "oidc with ca data",
 		raw:  []byte(OIDCAuthWithData),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions:         []models.ServiceAccountAction{},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.OIDC,
-				Kubeconfig:      []byte(OIDCAuthWithData),
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism:     models.OIDC,
+				ProjectID:         1,
+				Resolvers:         []models.ClusterResolver{},
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(OIDCAuthWithData),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
-	saCandidatesTest{
+	ccsTest{
 		name: "basic auth test",
 		raw:  []byte(BasicAuth),
-		expected: []*models.ServiceAccountCandidate{
-			&models.ServiceAccountCandidate{
-				Actions:         []models.ServiceAccountAction{},
-				Kind:            "connector",
-				ClusterName:     "cluster-test",
-				ClusterEndpoint: "https://10.10.10.10",
-				Integration:   models.Basic,
-				Kubeconfig:      []byte(BasicAuth),
+		expected: []*models.ClusterCandidate{
+			&models.ClusterCandidate{
+				AuthMechanism:     models.Basic,
+				ProjectID:         1,
+				Resolvers:         []models.ClusterResolver{},
+				Name:              "cluster-test",
+				Server:            "https://10.10.10.10",
+				ContextName:       "context-test",
+				Kubeconfig:        []byte(BasicAuth),
+				AWSClusterIDGuess: []byte{},
 			},
 		},
 	},
 }
 
-func TestGetServiceAccountCandidatesNonLocal(t *testing.T) {
-	for _, c := range SACandidatesTests {
-		result, err := kubernetes.GetServiceAccountCandidates(c.raw, false)
+func TestGetClusterCandidatesNonLocal(t *testing.T) {
+	for _, c := range ClusterCandidatesTests {
+		result, err := kubernetes.GetClusterCandidatesFromKubeconfig(c.raw, 1)
 
 		if err != nil {
 			t.Fatalf("error occurred %v\n", err)
 		}
 
 		// make result into a map so it's easier to compare
-		resMap := make(map[string]*models.ServiceAccountCandidate)
+		resMap := make(map[string]*models.ClusterCandidate)
 
 		for _, res := range result {
-			resMap[res.Kind+"-"+res.ClusterEndpoint+"-"+res.Integration] = res
+			resMap[res.Server+"-"+string(res.AuthMechanism)] = res
 		}
 
 		for _, exp := range c.expected {
-			res, ok := resMap[exp.Kind+"-"+exp.ClusterEndpoint+"-"+exp.Integration]
+			res, ok := resMap[exp.Server+"-"+string(exp.AuthMechanism)]
 
 			if !ok {
 				t.Fatalf("%s failed: no matching result for %s\n", c.name,
-					exp.Kind+"-"+exp.ClusterEndpoint+"-"+exp.Integration)
-			}
-
-			// compare basic string fields
-			if exp.Integration != res.Integration {
-				t.Errorf("%s failed on auth mechanism: expected %s, got %s\n",
-					c.name, exp.Integration, res.Integration)
-			}
-
-			if exp.ClusterName != res.ClusterName {
-				t.Errorf("%s failed on cluster name: expected %s, got %s\n",
-					c.name, exp.ClusterName, res.ClusterName)
-			}
-
-			if exp.ClusterEndpoint != res.ClusterEndpoint {
-				t.Errorf("%s failed on cluster endpoint: expected %s, got %s\n",
-					c.name, exp.ClusterEndpoint, res.ClusterEndpoint)
-			}
-
-			if len(res.Actions) != len(exp.Actions) {
-				t.Errorf("%s failed on action names: expected length %d, got length %d\n",
-					c.name, len(res.Actions), len(exp.Actions))
-			} else {
-				for i, action := range exp.Actions {
-					if res.Actions[i].Name != action.Name {
-						t.Errorf("%s failed on action names: expected res to contain %s, got %s\n",
-							c.name, action.Name, res.Actions[i].Name)
-					}
-
-					if res.Actions[i].Filename != action.Filename {
-						t.Errorf("%s failed on action file names: expected res to contain %s, got %s\n",
-							c.name, action.Filename, res.Actions[i].Filename)
-					}
-				}
+					exp.Server+"-"+string(exp.AuthMechanism))
 			}
 
 			// compare kubeconfig by transforming into a client config
@@ -503,144 +341,23 @@ func TestGetServiceAccountCandidatesNonLocal(t *testing.T) {
 				t.Fatalf("raw config conversion, error occurred %v\n", err)
 			}
 
-			if !reflect.DeepEqual(resRawConf, expRawConf) {
-				t.Errorf("%s failed: expected %v, got %v\n", c.name, expRawConf, resRawConf)
+			if diff := deep.Equal(expRawConf, resRawConf); diff != nil {
+				t.Errorf("incorrect kubeconfigs")
+				t.Error(diff)
 			}
-		}
-	}
-}
 
-func TestAWSClusterIDGuess(t *testing.T) {
-	result, err := kubernetes.GetServiceAccountCandidates([]byte(AWSIamAuthenticatorExec), false)
-
-	if err != nil {
-		t.Fatalf("error occurred %v\n", err)
-	}
+			// reset kubeconfigs since they won't be exactly "deep equal"
+			exp.Kubeconfig = []byte{}
+			res.Kubeconfig = []byte{}
 
-	if len(result) != 1 {
-		t.Fatalf("result length was not 1\n")
-	}
-
-	if result[0].AWSClusterIDGuess != "cluster-test-aws-id-guess" {
-		t.Errorf("Guess AWS cluster id failed: expected %s, got %s\n", "cluster-test-aws-id-guess", result[0].AWSClusterIDGuess)
-	}
-
-	result, err = kubernetes.GetServiceAccountCandidates([]byte(AWSEKSGetTokenExec), false)
-
-	if err != nil {
-		t.Fatalf("error occurred %v\n", err)
-	}
-
-	if len(result) != 1 {
-		t.Fatalf("result length was not 1\n")
-	}
-
-	if result[0].AWSClusterIDGuess != "cluster-test-aws-id-guess" {
-		t.Errorf("Guess AWS cluster id failed: expected %s, got %s\n", "cluster-test-aws-id-guess", result[0].AWSClusterIDGuess)
+			if diff := deep.Equal(exp, res); diff != nil {
+				t.Errorf("incorrect cluster candidate")
+				t.Error(diff)
+			}
+		}
 	}
 }
 
-const noContexts string = `
-apiVersion: v1
-kind: Config
-preferences: {}
-clusters:
-- cluster:
-    server: https://10.10.10.10
-  name: porter-test-1
-current-context: context-test
-users:
-- name: test-admin
-  user:
-`
-
-const noClusters string = `
-apiVersion: v1
-kind: Config
-preferences: {}
-current-context: context-test
-contexts:
-- context:
-    cluster: porter-test-1
-    user: test-admin
-  name: context-test
-users:
-- name: test-admin
-  user:
-`
-
-const noUsers string = `
-apiVersion: v1
-kind: Config
-preferences: {}
-current-context: default
-clusters:
-- cluster:
-    server: https://10.10.10.10
-  name: porter-test-1
-contexts:
-- context:
-    cluster: porter-test-1
-    user: test-admin
-  name: context-test
-`
-
-const noContextClusters string = `
-apiVersion: v1
-kind: Config
-preferences: {}
-current-context: default
-clusters:
-- cluster:
-    server: https://10.10.10.10
-  name: porter-test-1
-contexts:
-- context:
-    # cluster: porter-test-1
-    user: test-admin
-  name: context-test
-users:
-- name: test-admin
-  user:
-`
-
-const noContextUsers string = `
-apiVersion: v1
-kind: Config
-preferences: {}
-current-context: default
-clusters:
-- cluster:
-    server: https://10.10.10.10
-  name: porter-test-1
-contexts:
-- context:
-    cluster: porter-test-1
-    # user: test-admin
-  name: context-test
-users:
-- name: test-admin
-  user:
-`
-
-const basic string = `
-apiVersion: v1
-kind: Config
-preferences: {}
-current-context: context-test
-clusters:
-- cluster:
-    server: https://10.10.10.10
-  name: cluster-test
-contexts:
-- context:
-    cluster: cluster-test
-    user: test-admin
-  name: context-test
-users:
-  - name: test-admin
-`
-
 const ClusterCAWithoutData string = `
 apiVersion: v1
 kind: Config

+ 31 - 17
internal/models/cluster.go

@@ -7,11 +7,28 @@ import (
 	"gorm.io/gorm"
 )
 
+// ClusterAuth is an auth mechanism that a cluster candidate can resolve
+type ClusterAuth string
+
+// The support cluster candidate auth mechanisms
+const (
+	X509   ClusterAuth = "x509"
+	Basic              = "basic"
+	Bearer             = "bearerToken"
+	OIDC               = "oidc"
+	GCP                = "gcp-sa"
+	AWS                = "aws-sa"
+	Local              = "local"
+)
+
 // Cluster is an integration that can connect to a Kubernetes cluster via
 // a specific auth mechanism
 type Cluster struct {
 	gorm.Model
 
+	// The auth mechanism that this cluster will use
+	AuthMechanism ClusterAuth `json:"auth_mechanism"`
+
 	// The project that this integration belongs to
 	ProjectID uint `json:"project_id"`
 
@@ -76,6 +93,9 @@ func (c *Cluster) Externalize() *ClusterExternal {
 type ClusterCandidate struct {
 	gorm.Model
 
+	// The auth mechanism that this candidate will parse for
+	AuthMechanism ClusterAuth `json:"auth_mechanism"`
+
 	// The project that this integration belongs to
 	ProjectID uint `json:"project_id"`
 
@@ -184,36 +204,36 @@ type ClusterResolverInfo struct {
 
 // ClusterResolverInfos is a map of the information for actions to be
 // performed in order to initialize a cluster
-var ClusterResolverInfos = map[string]ClusterResolverInfo{
-	"upload-cluster-ca-data": ClusterResolverInfo{
+var ClusterResolverInfos = map[ClusterResolverName]ClusterResolverInfo{
+	ClusterCAData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "cluster_ca_data",
 	},
-	"rewrite-cluster-localhost": ClusterResolverInfo{
+	ClusterLocalhost: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "cluster_hostname",
 	},
-	"upload-client-cert-data": ClusterResolverInfo{
+	ClientCertData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "client_cert_data",
 	},
-	"upload-client-key-data": ClusterResolverInfo{
+	ClientKeyData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "client_key_data",
 	},
-	"upload-oidc-idp-issuer-ca-data": ClusterResolverInfo{
+	OIDCIssuerData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "oidc_idp_issuer_ca_data",
 	},
-	"upload-token-data": ClusterResolverInfo{
+	TokenData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "token_data",
 	},
-	"upload-gcp-key-data": ClusterResolverInfo{
+	GCPKeyData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "gcp_key_data",
 	},
-	"upload-aws-data": ClusterResolverInfo{
+	AWSData: ClusterResolverInfo{
 		Docs:   "https://github.com/porter-dev/porter",
 		Fields: "aws_access_key_id,aws_secret_access_key,aws_cluster_id",
 	},
@@ -246,17 +266,11 @@ type ClusterResolver struct {
 	ClusterCandidateID uint `json:"cluster_candidate_id"`
 
 	// One of the ClusterResolverNames
-	Name string `json:"name"`
+	Name ClusterResolverName `json:"name"`
 
 	// Resolved is true if this has been resolved, false otherwise
 	Resolved bool `json:"resolved"`
 
-	// Docs is a link to documentation that helps resolve this manually
-	Docs string `json:"docs"`
-
-	// Fields is a list of fields that must be sent with the resolving request
-	Fields string `json:"fields"`
-
 	// Data is additional data for resolving the action, for example a file name,
 	// context name, etc
 	Data []byte `json:"data,omitempty"`
@@ -274,7 +288,7 @@ type ClusterResolverExternal struct {
 	ClusterCandidateID uint `json:"cluster_candidate_id"`
 
 	// One of the ClusterResolverNames
-	Name string `json:"name"`
+	Name ClusterResolverName `json:"name"`
 
 	// Resolved is true if this has been resolved, false otherwise
 	Resolved bool `json:"resolved"`

+ 4 - 2
internal/repository/gorm/cluster_test.go

@@ -20,6 +20,7 @@ func TestCreateClusterCandidate(t *testing.T) {
 	defer cleanup(tester, t)
 
 	cc := &models.ClusterCandidate{
+		AuthMechanism:     models.AWS,
 		ProjectID:         tester.initProjects[0].ID,
 		CreatedClusterID:  0,
 		Resolvers:         []models.ClusterResolver{},
@@ -68,14 +69,13 @@ func TestCreateClusterCandidateWithResolvers(t *testing.T) {
 	defer cleanup(tester, t)
 
 	cc := &models.ClusterCandidate{
+		AuthMechanism:    models.AWS,
 		ProjectID:        tester.initProjects[0].ID,
 		CreatedClusterID: 0,
 		Resolvers: []models.ClusterResolver{
 			models.ClusterResolver{
 				Name:     models.ClusterLocalhost,
 				Resolved: false,
-				Docs:     models.ClusterResolverInfos[models.ClusterLocalhost].Docs,
-				Fields:   models.ClusterResolverInfos[models.ClusterLocalhost].Fields,
 			},
 		},
 		Name:              "cluster-test",
@@ -150,6 +150,7 @@ func TestListClusterCandidatesByProjectID(t *testing.T) {
 
 	// make sure data is correct
 	expCC := models.ClusterCandidate{
+		AuthMechanism:     models.AWS,
 		ProjectID:         tester.initProjects[0].ID,
 		CreatedClusterID:  0,
 		Resolvers:         []models.ClusterResolver{},
@@ -191,6 +192,7 @@ func TestUpdateClusterCandidateCreatedClusterID(t *testing.T) {
 	}
 
 	expCC := models.ClusterCandidate{
+		AuthMechanism:     models.AWS,
 		ProjectID:         tester.initProjects[0].ID,
 		CreatedClusterID:  tester.initClusters[0].ID,
 		Name:              "cluster-test",

+ 1 - 0
internal/repository/gorm/helpers_test.go

@@ -283,6 +283,7 @@ func initClusterCandidate(tester *tester, t *testing.T) {
 	}
 
 	cc := &models.ClusterCandidate{
+		AuthMechanism:     models.AWS,
 		ProjectID:         tester.initProjects[0].ID,
 		CreatedClusterID:  0,
 		Resolvers:         []models.ClusterResolver{},

+ 0 - 609
internal/repository/gorm/serviceaccount.go

@@ -1,609 +0,0 @@
-package gorm
-
-// import (
-// 	"github.com/porter-dev/porter/internal/models"
-// 	"github.com/porter-dev/porter/internal/repository"
-// 	"gorm.io/gorm"
-// )
-
-// // ClusterRepository uses gorm.DB for querying the database
-// type ClusterRepository struct {
-// 	db  *gorm.DB
-// 	key *[32]byte
-// }
-
-// // NewClusterRepository returns a ClusterRepository which uses
-// // gorm.DB for querying the database. It accepts an encryption key to encrypt
-// // sensitive data
-// func NewClusterRepository(db *gorm.DB, key *[32]byte) repository.ClusterRepository {
-// 	return &ClusterRepository{db, key}
-// }
-
-// // CreateClusterCandidate creates a new service account candidate
-// func (repo *ClusterRepository) CreateClusterCandidate(
-// 	saCandidate *models.ClusterCandidate,
-// ) (*models.ClusterCandidate, error) {
-// 	err := repo.EncryptClusterCandidateData(saCandidate, repo.key)
-
-// 	if err != nil {
-// 		return nil, err
-// 	}
-
-// 	project := &models.Project{}
-
-// 	if err := repo.db.Where("id = ?", saCandidate.ProjectID).First(&project).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	assoc := repo.db.Model(&project).Association("ClusterCandidates")
-
-// 	if assoc.Error != nil {
-// 		return nil, assoc.Error
-// 	}
-
-// 	if err := assoc.Append(saCandidate); err != nil {
-// 		return nil, err
-// 	}
-
-// 	return saCandidate, nil
-// }
-
-// // ReadClusterCandidate finds a service account candidate by id
-// func (repo *ClusterRepository) ReadClusterCandidate(
-// 	id uint,
-// ) (*models.ClusterCandidate, error) {
-// 	saCandidate := &models.ClusterCandidate{}
-
-// 	if err := repo.db.Preload("Actions").Where("id = ?", id).First(&saCandidate).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	repo.DecryptClusterCandidateData(saCandidate, repo.key)
-
-// 	return saCandidate, nil
-// }
-
-// // ListClusterCandidatesByProjectID finds all service account candidates
-// // for a given project id
-// func (repo *ClusterRepository) ListClusterCandidatesByProjectID(
-// 	projectID uint,
-// ) ([]*models.ClusterCandidate, error) {
-// 	saCandidates := []*models.ClusterCandidate{}
-
-// 	if err := repo.db.Preload("Actions").Where("project_id = ?", projectID).Find(&saCandidates).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	for _, saCandidate := range saCandidates {
-// 		repo.DecryptClusterCandidateData(saCandidate, repo.key)
-// 	}
-
-// 	return saCandidates, nil
-// }
-
-// // UpdateClusterCandidateCreatedSAID updates the CreatedClusterID for
-// // a candidate, after the candidate has been resolved.
-// func (repo *ClusterRepository) UpdateClusterCandidateCreatedSAID(
-// 	id uint,
-// 	createdSAID uint,
-// ) (*models.ClusterCandidate, error) {
-// 	saCandidate := &models.ClusterCandidate{}
-
-// 	if err := repo.db.Where("id = ?", id).First(&saCandidate).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	saCandidate.CreatedClusterID = createdSAID
-
-// 	if err := repo.db.Save(saCandidate).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	return saCandidate, nil
-// }
-
-// // CreateCluster creates a new servicea account
-// func (repo *ClusterRepository) CreateCluster(
-// 	sa *models.Cluster,
-// ) (*models.Cluster, error) {
-// 	err := repo.EncryptClusterData(sa, repo.key)
-
-// 	if err != nil {
-// 		return nil, err
-// 	}
-
-// 	project := &models.Project{}
-
-// 	if err := repo.db.Where("id = ?", sa.ProjectID).First(&project).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	assoc := repo.db.Model(&project).Association("Clusters")
-
-// 	if assoc.Error != nil {
-// 		return nil, assoc.Error
-// 	}
-
-// 	if err := assoc.Append(sa); err != nil {
-// 		return nil, err
-// 	}
-
-// 	// create a token cache by default
-// 	assoc = repo.db.Model(sa).Association("TokenCache")
-
-// 	if assoc.Error != nil {
-// 		return nil, assoc.Error
-// 	}
-
-// 	if err := assoc.Append(&sa.TokenCache); err != nil {
-// 		return nil, err
-// 	}
-
-// 	return sa, nil
-// }
-
-// // ReadCluster finds a service account by id
-// func (repo *ClusterRepository) ReadCluster(
-// 	id uint,
-// ) (*models.Cluster, error) {
-// 	sa := &models.Cluster{}
-
-// 	// preload Clusters association
-// 	if err := repo.db.Preload("Clusters").Preload("TokenCache").Where("id = ?", id).First(&sa).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	repo.DecryptClusterData(sa, repo.key)
-
-// 	return sa, nil
-// }
-
-// // ListClustersByProjectID finds all service accounts
-// // for a given project id
-// func (repo *ClusterRepository) ListClustersByProjectID(
-// 	projectID uint,
-// ) ([]*models.Cluster, error) {
-// 	sas := []*models.Cluster{}
-
-// 	if err := repo.db.Preload("Clusters").Where("project_id = ?", projectID).Find(&sas).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	for _, sa := range sas {
-// 		repo.DecryptClusterData(sa, repo.key)
-// 	}
-
-// 	return sas, nil
-// }
-
-// // UpdateClusterTokenCache updates the token cache for a service account
-// func (repo *ClusterRepository) UpdateClusterTokenCache(
-// 	tokenCache *models.TokenCache,
-// ) (*models.Cluster, error) {
-// 	if tok := tokenCache.Token; len(tok) > 0 {
-// 		cipherData, err := repository.Encrypt(tok, repo.key)
-
-// 		if err != nil {
-// 			return nil, err
-// 		}
-
-// 		tokenCache.Token = cipherData
-// 	}
-
-// 	sa := &models.Cluster{}
-
-// 	if err := repo.db.Where("id = ?", tokenCache.ClusterID).First(&sa).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	sa.TokenCache.Token = tokenCache.Token
-// 	sa.TokenCache.Expiry = tokenCache.Expiry
-
-// 	if err := repo.db.Save(sa).Error; err != nil {
-// 		return nil, err
-// 	}
-
-// 	return sa, nil
-// }
-
-// // EncryptClusterData will encrypt the user's service account data before writing
-// // to the DB
-// func (repo *ClusterRepository) EncryptClusterData(
-// 	sa *models.Cluster,
-// 	key *[32]byte,
-// ) error {
-// 	if len(sa.ClientCertificateData) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.ClientCertificateData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.ClientCertificateData = cipherData
-// 	}
-
-// 	if len(sa.ClientKeyData) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.ClientKeyData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.ClientKeyData = cipherData
-// 	}
-
-// 	if len(sa.Token) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.Token, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.Token = cipherData
-// 	}
-
-// 	if len(sa.Username) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.Username, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.Username = cipherData
-// 	}
-
-// 	if len(sa.Password) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.Password, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.Password = cipherData
-// 	}
-
-// 	if len(sa.GCPKeyData) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.GCPKeyData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.GCPKeyData = cipherData
-// 	}
-
-// 	if tok := sa.TokenCache.Token; len(tok) > 0 {
-// 		cipherData, err := repository.Encrypt(tok, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.TokenCache.Token = cipherData
-// 	}
-
-// 	if len(sa.AWSAccessKeyID) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.AWSAccessKeyID, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.AWSAccessKeyID = cipherData
-// 	}
-
-// 	if len(sa.AWSSecretAccessKey) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.AWSSecretAccessKey, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.AWSSecretAccessKey = cipherData
-// 	}
-
-// 	if len(sa.AWSClusterID) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.AWSClusterID, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.AWSClusterID = cipherData
-// 	}
-
-// 	if len(sa.OIDCIssuerURL) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.OIDCIssuerURL, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCIssuerURL = cipherData
-// 	}
-
-// 	if len(sa.OIDCClientID) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.OIDCClientID, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCClientID = cipherData
-// 	}
-
-// 	if len(sa.OIDCClientSecret) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.OIDCClientSecret, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCClientSecret = cipherData
-// 	}
-
-// 	if len(sa.OIDCCertificateAuthorityData) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.OIDCCertificateAuthorityData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCCertificateAuthorityData = cipherData
-// 	}
-
-// 	if len(sa.OIDCIDToken) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.OIDCIDToken, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCIDToken = cipherData
-// 	}
-
-// 	if len(sa.OIDCRefreshToken) > 0 {
-// 		cipherData, err := repository.Encrypt(sa.OIDCRefreshToken, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCRefreshToken = cipherData
-// 	}
-
-// 	for i, cluster := range sa.Clusters {
-// 		if len(cluster.CertificateAuthorityData) > 0 {
-// 			cipherData, err := repository.Encrypt(cluster.CertificateAuthorityData, key)
-
-// 			if err != nil {
-// 				return err
-// 			}
-
-// 			cluster.CertificateAuthorityData = cipherData
-// 			sa.Clusters[i] = cluster
-// 		}
-// 	}
-
-// 	return nil
-// }
-
-// // EncryptClusterCandidateData will encrypt the service account candidate data before
-// // writing to the DB
-// func (repo *ClusterRepository) EncryptClusterCandidateData(
-// 	saCandidate *models.ClusterCandidate,
-// 	key *[32]byte,
-// ) error {
-// 	if len(saCandidate.Kubeconfig) > 0 {
-// 		cipherData, err := repository.Encrypt(saCandidate.Kubeconfig, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		saCandidate.Kubeconfig = cipherData
-// 	}
-
-// 	return nil
-// }
-
-// // DecryptClusterData will decrypt the user's service account data before
-// // returning it from the DB
-// func (repo *ClusterRepository) DecryptClusterData(
-// 	sa *models.Cluster,
-// 	key *[32]byte,
-// ) error {
-// 	if len(sa.ClientCertificateData) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.ClientCertificateData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.ClientCertificateData = plaintext
-// 	}
-
-// 	if len(sa.ClientKeyData) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.ClientKeyData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.ClientKeyData = plaintext
-// 	}
-
-// 	if len(sa.Token) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.Token, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.Token = plaintext
-// 	}
-
-// 	if len(sa.Username) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.Username, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.Username = plaintext
-// 	}
-
-// 	if len(sa.Password) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.Password, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.Password = plaintext
-// 	}
-
-// 	if len(sa.GCPKeyData) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.GCPKeyData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.GCPKeyData = plaintext
-// 	}
-
-// 	if tok := sa.TokenCache.Token; len(tok) > 0 {
-// 		plaintext, err := repository.Decrypt(tok, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.TokenCache.Token = plaintext
-// 	}
-
-// 	if len(sa.AWSAccessKeyID) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.AWSAccessKeyID, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.AWSAccessKeyID = plaintext
-// 	}
-
-// 	if len(sa.AWSSecretAccessKey) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.AWSSecretAccessKey, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.AWSSecretAccessKey = plaintext
-// 	}
-
-// 	if len(sa.AWSClusterID) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.AWSClusterID, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.AWSClusterID = plaintext
-// 	}
-
-// 	if len(sa.OIDCIssuerURL) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.OIDCIssuerURL, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCIssuerURL = plaintext
-// 	}
-
-// 	if len(sa.OIDCClientID) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.OIDCClientID, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCClientID = plaintext
-// 	}
-
-// 	if len(sa.OIDCClientSecret) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.OIDCClientSecret, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCClientSecret = plaintext
-// 	}
-
-// 	if len(sa.OIDCCertificateAuthorityData) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.OIDCCertificateAuthorityData, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCCertificateAuthorityData = plaintext
-// 	}
-
-// 	if len(sa.OIDCIDToken) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.OIDCIDToken, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCIDToken = plaintext
-// 	}
-
-// 	if len(sa.OIDCRefreshToken) > 0 {
-// 		plaintext, err := repository.Decrypt(sa.OIDCRefreshToken, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		sa.OIDCRefreshToken = plaintext
-// 	}
-
-// 	for i, cluster := range sa.Clusters {
-// 		if len(cluster.CertificateAuthorityData) > 0 {
-// 			plaintext, err := repository.Decrypt(cluster.CertificateAuthorityData, key)
-
-// 			if err != nil {
-// 				return err
-// 			}
-
-// 			cluster.CertificateAuthorityData = plaintext
-// 			sa.Clusters[i] = cluster
-// 		}
-// 	}
-
-// 	return nil
-// }
-
-// // DecryptClusterCandidateData will decrypt the service account candidate data before
-// // returning it from the DB
-// func (repo *ClusterRepository) DecryptClusterCandidateData(
-// 	saCandidate *models.ClusterCandidate,
-// 	key *[32]byte,
-// ) error {
-// 	if len(saCandidate.Kubeconfig) > 0 {
-// 		plaintext, err := repository.Decrypt(saCandidate.Kubeconfig, key)
-
-// 		if err != nil {
-// 			return err
-// 		}
-
-// 		saCandidate.Kubeconfig = plaintext
-// 	}
-
-// 	return nil
-// }

+ 0 - 418
internal/repository/gorm/serviceaccount_test.go

@@ -1,418 +0,0 @@
-package gorm_test
-
-// import (
-// 	"testing"
-// 	"time"
-
-// 	"github.com/go-test/deep"
-// 	"github.com/porter-dev/porter/internal/models"
-// 	orm "gorm.io/gorm"
-// )
-
-// func TestCreateServiceAccountCandidate(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_create_sa_candidate.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	saCandidate := &models.ServiceAccountCandidate{
-// 		ProjectID:       1,
-// 		Kind:            "connector",
-// 		ClusterName:     "cluster-test",
-// 		ClusterEndpoint: "https://localhost",
-// 		Integration:   models.X509,
-// 		Kubeconfig:      []byte("current-context: testing\n"),
-// 	}
-
-// 	saCandidate, err := tester.repo.ServiceAccount.CreateServiceAccountCandidate(saCandidate)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	saCandidate, err = tester.repo.ServiceAccount.ReadServiceAccountCandidate(saCandidate.Model.ID)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	// make sure id is 1
-// 	if saCandidate.Model.ID != 1 {
-// 		t.Errorf("incorrect service accound candidate ID: expected %d, got %d\n", 1, saCandidate.Model.ID)
-// 	}
-
-// 	// make sure data is correct
-// 	expSACandidate := &models.ServiceAccountCandidate{
-// 		ProjectID:       1,
-// 		Kind:            "connector",
-// 		ClusterName:     "cluster-test",
-// 		ClusterEndpoint: "https://localhost",
-// 		Integration:   models.X509,
-// 		Kubeconfig:      []byte("current-context: testing\n"),
-// 		Actions:         []models.ServiceAccountAction{},
-// 	}
-
-// 	copySACandidate := saCandidate
-
-// 	// reset fields for reflect.DeepEqual
-// 	copySACandidate.Model = orm.Model{}
-
-// 	if diff := deep.Equal(copySACandidate, expSACandidate); diff != nil {
-// 		t.Errorf("incorrect sa candidate")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func TestCreateServiceAccountCandidateWithAction(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_create_sa_candidate_w_action.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	initServiceAccountCandidate(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	saCandidate := tester.initSACandidates[0]
-
-// 	saCandidate, err := tester.repo.ServiceAccount.ReadServiceAccountCandidate(saCandidate.Model.ID)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	// make sure IDs are correct
-// 	if saCandidate.Model.ID != 1 {
-// 		t.Errorf("incorrect service account candidate ID: expected %d, got %d\n", 1, saCandidate.Model.ID)
-// 	}
-
-// 	if len(saCandidate.Actions) != 1 {
-// 		t.Errorf("incorrect actions length: expected %d, got %d\n", 1, len(saCandidate.Actions))
-// 	}
-
-// 	if saCandidate.Actions[0].Model.ID != 1 {
-// 		t.Errorf("incorrect actions ID: expected %d, got %d\n", 1, saCandidate.Actions[0].Model.ID)
-// 	}
-
-// 	// make sure data is correct
-// 	expSACandidate := &models.ServiceAccountCandidate{
-// 		ProjectID:       1,
-// 		Kind:            "connector",
-// 		ClusterName:     "cluster-test",
-// 		ClusterEndpoint: "https://localhost",
-// 		Integration:   models.X509,
-// 		Kubeconfig:      []byte("current-context: testing\n"),
-// 		Actions: []models.ServiceAccountAction{
-// 			models.ServiceAccountAction{
-// 				ServiceAccountCandidateID: 1,
-// 				Name:                      models.TokenDataAction,
-// 				Resolved:                  false,
-// 			},
-// 		},
-// 	}
-
-// 	copySACandidate := saCandidate
-
-// 	// reset fields for reflect.DeepEqual
-// 	copySACandidate.Model = orm.Model{}
-
-// 	copySACandidate.Actions[0].Model = orm.Model{}
-
-// 	if diff := deep.Equal(copySACandidate, expSACandidate); diff != nil {
-// 		t.Errorf("incorrect sa candidate")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func TestListServiceAccountCandidatesByProjectID(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_list_sa_candidates.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	initServiceAccountCandidate(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	saCandidates, err := tester.repo.ServiceAccount.ListServiceAccountCandidatesByProjectID(
-// 		tester.initProjects[0].Model.ID,
-// 	)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	if len(saCandidates) != 1 {
-// 		t.Fatalf("length of sa candidates incorrect: expected %d, got %d\n", 1, len(saCandidates))
-// 	}
-
-// 	// make sure data is correct
-// 	expSACandidate := &models.ServiceAccountCandidate{
-// 		ProjectID:       1,
-// 		Kind:            "connector",
-// 		ClusterName:     "cluster-test",
-// 		ClusterEndpoint: "https://localhost",
-// 		Integration:   models.X509,
-// 		Kubeconfig:      []byte("current-context: testing\n"),
-// 		Actions: []models.ServiceAccountAction{
-// 			models.ServiceAccountAction{
-// 				ServiceAccountCandidateID: 1,
-// 				Name:                      models.TokenDataAction,
-// 				Resolved:                  false,
-// 			},
-// 		},
-// 	}
-
-// 	copySACandidate := saCandidates[0]
-
-// 	// reset fields for reflect.DeepEqual
-// 	copySACandidate.Model = orm.Model{}
-// 	copySACandidate.Actions[0].Model = orm.Model{}
-
-// 	if diff := deep.Equal(copySACandidate, expSACandidate); diff != nil {
-// 		t.Errorf("incorrect sa candidate")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func TestCreateServiceAccount(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_create_sa.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	sa := &models.ServiceAccount{
-// 		ProjectID:             1,
-// 		Kind:                  "connector",
-// 		Integration:         models.X509,
-// 		ClientCertificateData: []byte("-----BEGIN"),
-// 		ClientKeyData:         []byte("-----BEGIN"),
-// 	}
-
-// 	sa, err := tester.repo.ServiceAccount.CreateServiceAccount(sa)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err = tester.repo.ServiceAccount.ReadServiceAccount(sa.Model.ID)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	// make sure id is 1
-// 	if sa.Model.ID != 1 {
-// 		t.Errorf("incorrect service account ID: expected %d, got %d\n", 1, sa.Model.ID)
-// 	}
-
-// 	// make sure data is correct
-// 	expSA := &models.ServiceAccount{
-// 		ProjectID:             1,
-// 		Kind:                  "connector",
-// 		Integration:         models.X509,
-// 		ClientCertificateData: []byte("-----BEGIN"),
-// 		ClientKeyData:         []byte("-----BEGIN"),
-// 		Clusters:              []models.Cluster{},
-// 	}
-
-// 	copySA := sa
-
-// 	// reset fields for reflect.DeepEqual
-// 	copySA.Model = orm.Model{}
-
-// 	if diff := deep.Equal(copySA, expSA); diff != nil {
-// 		t.Errorf("incorrect service account")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func TestCreateServiceAccountWithCluster(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_create_sa_w_cluster.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	initServiceAccount(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	sa := tester.initSAs[0]
-
-// 	sa, err := tester.repo.ServiceAccount.ReadServiceAccount(sa.Model.ID)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	// make sure id is 1
-// 	if sa.Model.ID != 1 {
-// 		t.Errorf("incorrect service account ID: expected %d, got %d\n", 1, sa.Model.ID)
-// 	}
-
-// 	if len(sa.Clusters) != 1 {
-// 		t.Errorf("incorrect clusters length: expected %d, got %d\n", 1, len(sa.Clusters))
-// 	}
-
-// 	if sa.Clusters[0].Model.ID != 1 {
-// 		t.Errorf("incorrect clusters ID: expected %d, got %d\n", 1, sa.Clusters[0].Model.ID)
-// 	}
-
-// 	// make sure data is correct
-// 	expSA := &models.ServiceAccount{
-// 		ProjectID:             1,
-// 		Kind:                  "connector",
-// 		Integration:         models.X509,
-// 		ClientCertificateData: []byte("-----BEGIN"),
-// 		ClientKeyData:         []byte("-----BEGIN"),
-// 		Clusters: []models.Cluster{
-// 			models.Cluster{
-// 				ServiceAccountID:         1,
-// 				Name:                     "cluster-test",
-// 				Server:                   "https://localhost",
-// 				CertificateAuthorityData: []byte("-----BEGIN"),
-// 			},
-// 		},
-// 	}
-
-// 	copySA := sa
-
-// 	// reset fields for reflect.DeepEqual
-// 	copySA.Model = orm.Model{}
-// 	copySA.Clusters[0].Model = orm.Model{}
-
-// 	if diff := deep.Equal(copySA, expSA); diff != nil {
-// 		t.Errorf("incorrect service account")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func TestListServiceAccountsByProjectID(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_list_sas.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	initServiceAccount(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	sas, err := tester.repo.ServiceAccount.ListServiceAccountsByProjectID(
-// 		tester.initProjects[0].Model.ID,
-// 	)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	if len(sas) != 1 {
-// 		t.Fatalf("length of sas incorrect: expected %d, got %d\n", 1, len(sas))
-// 	}
-
-// 	// make sure data is correct
-// 	expSA := &models.ServiceAccount{
-// 		ProjectID:             1,
-// 		Kind:                  "connector",
-// 		Integration:         models.X509,
-// 		ClientCertificateData: []byte("-----BEGIN"),
-// 		ClientKeyData:         []byte("-----BEGIN"),
-// 		Clusters: []models.Cluster{
-// 			models.Cluster{
-// 				ServiceAccountID:         1,
-// 				Name:                     "cluster-test",
-// 				Server:                   "https://localhost",
-// 				CertificateAuthorityData: []byte("-----BEGIN"),
-// 			},
-// 		},
-// 	}
-
-// 	copySA := sas[0]
-
-// 	// reset fields for reflect.DeepEqual
-// 	copySA.Model = orm.Model{}
-// 	copySA.Clusters[0].Model = orm.Model{}
-
-// 	if diff := deep.Equal(copySA, expSA); diff != nil {
-// 		t.Errorf("incorrect service account")
-// 		t.Error(diff)
-// 	}
-// }
-
-// func TestUpdateServiceAccountToken(t *testing.T) {
-// 	tester := &tester{
-// 		dbFileName: "./porter_test_update_sa_token.db",
-// 	}
-
-// 	setupTestEnv(tester, t)
-// 	initProject(tester, t)
-// 	defer cleanup(tester, t)
-
-// 	sa := &models.ServiceAccount{
-// 		ProjectID:     1,
-// 		Kind:          "connector",
-// 		Integration: models.GCP,
-// 		GCPKeyData:    []byte(`{"key":"data"}`),
-// 		TokenCache: models.TokenCache{
-// 			Token:  []byte("token-1"),
-// 			Expiry: time.Now().Add(-1 * time.Hour),
-// 		},
-// 	}
-
-// 	sa, err := tester.repo.ServiceAccount.CreateServiceAccount(sa)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	sa, err = tester.repo.ServiceAccount.ReadServiceAccount(sa.Model.ID)
-
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	// make sure service account id of token is 1
-// 	if sa.TokenCache.ServiceAccountID != 1 {
-// 		t.Fatalf("incorrect service account ID in token cache: expected %d, got %d\n", 1, sa.TokenCache.ServiceAccountID)
-// 	}
-
-// 	// make sure old token is expired
-// 	if isExpired := sa.TokenCache.IsExpired(); !isExpired {
-// 		t.Fatalf("token was not expired\n")
-// 	}
-
-// 	sa.TokenCache.Token = []byte("token-2")
-// 	sa.TokenCache.Expiry = time.Now().Add(24 * time.Hour)
-
-// 	sa, err = tester.repo.ServiceAccount.UpdateServiceAccountTokenCache(&sa.TokenCache)
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-// 	sa, err = tester.repo.ServiceAccount.ReadServiceAccount(sa.Model.ID)
-// 	if err != nil {
-// 		t.Fatalf("%v\n", err)
-// 	}
-
-// 	// make sure id is 1
-// 	if sa.Model.ID != 1 {
-// 		t.Errorf("incorrect service account ID: expected %d, got %d\n", 1, sa.Model.ID)
-// 	}
-
-// 	// make sure new token is correct and not expired
-// 	if sa.TokenCache.ServiceAccountID != 1 {
-// 		t.Fatalf("incorrect service account ID in token cache: expected %d, got %d\n", 1, sa.TokenCache.ServiceAccountID)
-// 	}
-
-// 	if isExpired := sa.TokenCache.IsExpired(); isExpired {
-// 		t.Fatalf("token was expired\n")
-// 	}
-
-// 	if string(sa.TokenCache.Token) != "token-2" {
-// 		t.Errorf("incorrect token in cache: expected %s, got %s\n", "token-2", sa.TokenCache.Token)
-// 	}
-// }