Kaynağa Gözat

eks auto user creation

Alexander Belanger 5 yıl önce
ebeveyn
işleme
575652e111

+ 58 - 6
cli/cmd/connect/kubeconfig.go

@@ -12,6 +12,7 @@ import (
 	"github.com/fatih/color"
 	"github.com/porter-dev/porter/cli/cmd/utils"
 	"github.com/porter-dev/porter/internal/kubernetes/local"
+	awsLocal "github.com/porter-dev/porter/internal/providers/aws/local"
 	gcpLocal "github.com/porter-dev/porter/internal/providers/gcp/local"
 
 	"github.com/porter-dev/porter/cli/cmd/api"
@@ -116,6 +117,8 @@ func Kubeconfig(
 						saCandidate.ClusterEndpoint,
 						saCandidate.ClusterName,
 						saCandidate.AWSClusterIDGuess,
+						kubeconfigPath,
+						saCandidate.ContextName,
 					)
 
 					if err != nil {
@@ -260,11 +263,18 @@ Would you like to proceed? %s `,
 	}
 
 	if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
-		agent, _ := gcpLocal.NewDefaultAgent()
+		agent, err := gcpLocal.NewDefaultAgent()
+
+		if err != nil {
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return resolveGCPKeyActionManual(endpoint, clusterName)
+		}
+
 		projID, err := agent.GetProjectIDForGKECluster(endpoint)
 
 		if err != nil {
-			return nil, err
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return resolveGCPKeyActionManual(endpoint, clusterName)
 		}
 
 		agent.ProjectID = projID
@@ -275,21 +285,23 @@ Would you like to proceed? %s `,
 		resp, err := agent.CreateServiceAccount(name)
 
 		if err != nil {
-			color.New(color.FgRed).Println("Automatic creation failed, manual input required.")
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
 			return resolveGCPKeyActionManual(endpoint, clusterName)
 		}
 
 		err = agent.SetServiceAccountIAMPolicy(resp)
 
 		if err != nil {
-			return nil, err
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return resolveGCPKeyActionManual(endpoint, clusterName)
 		}
 
 		// get the service account key data to send to the server
 		bytes, err := agent.CreateServiceAccountKey(resp)
 
 		if err != nil {
-			return nil, err
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return resolveGCPKeyActionManual(endpoint, clusterName)
 		}
 
 		return &models.ServiceAccountAllActions{
@@ -332,8 +344,48 @@ func resolveAWSAction(
 	endpoint string,
 	clusterName string,
 	awsClusterIDGuess string,
+	kubeconfigPath string,
+	contextName string,
 ) (*models.ServiceAccountAllActions, error) {
-	// just support manual for now
+	userResp, err := utils.PromptPlaintext(
+		fmt.Sprintf(
+			`Detected AWS cluster in kubeconfig for the endpoint %s (%s). 
+Porter can set up an IAM user in your AWS account to connect to this cluster automatically.
+Would you like to proceed? %s `,
+			color.New(color.FgCyan).Sprintf("%s", endpoint),
+			clusterName,
+			color.New(color.FgCyan).Sprintf("[y/n]"),
+		),
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
+		agent, err := awsLocal.NewDefaultAgent(kubeconfigPath, contextName)
+
+		if err != nil {
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess)
+		}
+
+		creds, err := agent.CreateIAMKubernetesMapping(awsClusterIDGuess)
+
+		if err != nil {
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess)
+		}
+
+		return &models.ServiceAccountAllActions{
+			Name:               models.AWSDataAction,
+			AWSAccessKeyID:     creds.AWSAccessKeyID,
+			AWSSecretAccessKey: creds.AWSSecretAccessKey,
+			AWSClusterID:       creds.AWSClusterID,
+		}, nil
+	}
+
+	// fallback to manual
 	return resolveAWSActionManual(endpoint, clusterName, awsClusterIDGuess)
 }
 

+ 29 - 0
go.mod

@@ -35,6 +35,8 @@ require (
 	github.com/joeshaw/envdecode v0.0.0-20200121155833-099f1fc765bd
 	github.com/json-iterator/go v1.1.10 // indirect
 	github.com/kr/pretty v0.2.0 // indirect
+	github.com/kris-nova/logger v0.0.0-20181127235838-fd0d87064b06
+	github.com/kris-nova/lolgopher v0.0.0-20180921204813-313b3abb0d9b // indirect
 	github.com/mattn/go-colorable v0.1.7 // indirect
 	github.com/pelletier/go-toml v1.8.1 // indirect
 	github.com/pkg/errors v0.9.1
@@ -68,4 +70,31 @@ require (
 	k8s.io/utils v0.0.0-20200912215256-4140de9c8800 // indirect
 	sigs.k8s.io/aws-iam-authenticator v0.5.2
 	sigs.k8s.io/structured-merge-diff/v4 v4.0.1 // indirect
+	sigs.k8s.io/yaml v1.2.0
 )
+
+// Used to pin the k8s library versions regardless of what other dependencies enforce
+// replace (
+// 	k8s.io/api => k8s.io/api v0.18.8
+// 	k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.18.8
+// 	k8s.io/apimachinery => k8s.io/apimachinery v0.18.8
+// 	k8s.io/apiserver => k8s.io/apiserver v0.18.8
+// 	k8s.io/cli-runtime => k8s.io/cli-runtime v0.18.8
+// 	k8s.io/client-go => k8s.io/client-go v0.18.8
+// 	k8s.io/cloud-provider => k8s.io/cloud-provider v0.18.8
+// 	k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.18.8
+// 	k8s.io/code-generator => k8s.io/code-generator v0.18.8
+// 	k8s.io/component-base => k8s.io/component-base v0.18.8
+// 	k8s.io/cri-api => k8s.io/cri-api v0.18.8
+// 	k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.18.8
+// 	k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.18.8
+// 	k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.18.8
+// 	k8s.io/kube-proxy => k8s.io/kube-proxy v0.18.8
+// 	k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.18.8
+// 	k8s.io/kubectl => k8s.io/kubectl v0.18.8
+// 	k8s.io/kubelet => k8s.io/kubelet v0.18.8
+// 	k8s.io/kubernetes => k8s.io/kubernetes v1.16.8
+// 	k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.18.8
+// 	k8s.io/metrics => k8s.io/metrics v0.18.8
+// 	k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.18.8
+// )

Dosya farkı çok büyük olduğundan ihmal edildi
+ 342 - 42
go.sum


+ 1 - 0
internal/kubernetes/kubeconfig.go

@@ -69,6 +69,7 @@ func GetServiceAccountCandidates(kubeconfig []byte) ([]*models.ServiceAccountCan
 			res = append(res, &models.ServiceAccountCandidate{
 				Actions:           actions,
 				Kind:              "connector",
+				ContextName:       contextName,
 				ClusterName:       clusterName,
 				ClusterEndpoint:   rawConf.Clusters[clusterName].Server,
 				AuthMechanism:     authMechanism,

+ 3 - 0
internal/models/serviceaccount.go

@@ -25,6 +25,7 @@ type ServiceAccountCandidate struct {
 
 	Actions []ServiceAccountAction `json:"actions"`
 
+	ContextName     string `json:"context_name"`
 	ClusterName     string `json:"cluster_name"`
 	ClusterEndpoint string `json:"cluster_endpoint"`
 	AuthMechanism   string `json:"auth_mechanism"`
@@ -51,6 +52,7 @@ type ServiceAccountCandidateExternal struct {
 	Actions                 []ServiceAccountActionExternal `json:"actions"`
 	ProjectID               uint                           `json:"project_id"`
 	Kind                    string                         `json:"kind"`
+	ContextName             string                         `json:"context_name"`
 	ClusterName             string                         `json:"cluster_name"`
 	ClusterEndpoint         string                         `json:"cluster_endpoint"`
 	AuthMechanism           string                         `json:"auth_mechanism"`
@@ -71,6 +73,7 @@ func (s *ServiceAccountCandidate) Externalize() *ServiceAccountCandidateExternal
 		Actions:                 actions,
 		ProjectID:               s.ProjectID,
 		Kind:                    s.Kind,
+		ContextName:             s.ContextName,
 		ClusterName:             s.ClusterName,
 		ClusterEndpoint:         s.ClusterEndpoint,
 		AuthMechanism:           s.AuthMechanism,

+ 93 - 0
internal/providers/aws/agent.go

@@ -0,0 +1,93 @@
+package aws
+
+import (
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/iam"
+	"github.com/porter-dev/porter/cli/cmd/utils"
+	"k8s.io/client-go/kubernetes"
+)
+
+type Agent struct {
+	Session    *session.Session
+	IAMService *iam.IAM
+	Clientset  kubernetes.Interface
+}
+
+type PorterAWSCredentials struct {
+	AWSAccessKeyID     string `json:"aws_access_key_id"`
+	AWSSecretAccessKey string `json:"aws_secret_access_key"`
+	AWSClusterID       string `json:"aws_cluster_id"`
+}
+
+func (a *Agent) CreateIAMKubernetesMapping(clusterIDGuess string) (*PorterAWSCredentials, error) {
+	// (1) Create a new IAM user called porter-dashboard-[random_string], and attach the policy:
+	//
+	// arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
+	name := "porter-dashboard-" + utils.StringWithCharset(6, "abcdefghijklmnopqrstuvwxyz1234567890")
+
+	user, err := a.IAMService.CreateUser(&iam.CreateUserInput{
+		UserName: &name,
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	policyArn := "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
+
+	_, err = a.IAMService.AttachUserPolicy(&iam.AttachUserPolicyInput{
+		PolicyArn: &policyArn,
+		UserName:  &name,
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	// (2) Create an access key for the porter-dashboard-[random_string] user and return the
+	// access key and secret. Use the guessed cluster ID.
+	resp, err := a.IAMService.CreateAccessKey(&iam.CreateAccessKeyInput{
+		UserName: &name,
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	porterCreds := &PorterAWSCredentials{
+		AWSAccessKeyID:     *resp.AccessKey.AccessKeyId,
+		AWSSecretAccessKey: *resp.AccessKey.SecretAccessKey,
+		AWSClusterID:       clusterIDGuess,
+	}
+
+	// (3) Use the eksctl authconfigmap package to map this user to a cluster identity.
+	authCm, err := NewFromClientSet(a.Clientset)
+
+	if err != nil {
+		return nil, err
+	}
+
+	identity, err := NewIdentity(
+		*user.User.Arn,
+		"admin",
+		[]string{"system:masters"},
+	)
+
+	if err != nil {
+		return nil, err
+	}
+
+	err = authCm.AddIdentity(identity)
+
+	if err != nil {
+		return nil, err
+	}
+
+	err = authCm.Save()
+
+	if err != nil {
+		return nil, err
+	}
+
+	return porterCreds, nil
+}

+ 311 - 0
internal/providers/aws/authconfigmap.go

@@ -0,0 +1,311 @@
+package aws
+
+// Copy of eksctl that uses authconfigmap
+// https://github.com/weaveworks/eksctl/blob/35d0d6dc169e13b1fb655aea19d3aa2e7691dc81/pkg/authconfigmap/authconfigmap.go#L1
+//
+// eksctl is still pinned on v0.16.8 of Kubernetes, while we use v0.18.8
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"github.com/kris-nova/logger"
+	"github.com/pkg/errors"
+	"k8s.io/client-go/kubernetes"
+	"sigs.k8s.io/yaml"
+
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	v1 "k8s.io/client-go/kubernetes/typed/core/v1"
+
+	"github.com/aws/aws-sdk-go/aws/arn"
+	"github.com/aws/aws-sdk-go/aws/awsutil"
+)
+
+const (
+	// ObjectName is the Kubernetes resource name of the auth ConfigMap
+	ObjectName = "aws-auth"
+	// ObjectNamespace is the namespace the object can be found
+	ObjectNamespace = metav1.NamespaceSystem
+
+	rolesData = "mapRoles"
+
+	usersData = "mapUsers"
+
+	accountsData = "mapAccounts"
+
+	// GroupMasters is the admin group which is also automatically
+	// granted to the IAM role that creates the cluster.
+	GroupMasters = "system:masters"
+
+	// RoleNodeGroupUsername is the default username for a nodegroup
+	// role mapping.
+	RoleNodeGroupUsername = "system:node:{{EC2PrivateDNSName}}"
+)
+
+// AuthConfigMap allows modifying the auth ConfigMap.
+type AuthConfigMap struct {
+	client v1.ConfigMapInterface
+	cm     *corev1.ConfigMap
+}
+
+// New creates an AuthConfigMap instance that manipulates
+// a ConfigMap. If it is nil, one is created.
+func New(client v1.ConfigMapInterface, cm *corev1.ConfigMap) *AuthConfigMap {
+	if cm == nil {
+		cm = &corev1.ConfigMap{
+			ObjectMeta: ObjectMeta(),
+			Data:       map[string]string{},
+		}
+	}
+	if cm.Data == nil {
+		cm.ObjectMeta = ObjectMeta()
+		cm.Data = map[string]string{}
+	}
+	return &AuthConfigMap{client: client, cm: cm}
+}
+
+// ObjectMeta constructs metadata for the ConfigMap.
+func ObjectMeta() metav1.ObjectMeta {
+	return metav1.ObjectMeta{
+		Name:      ObjectName,
+		Namespace: ObjectNamespace,
+	}
+}
+
+// NewFromClientSet fetches the auth ConfigMap.
+func NewFromClientSet(clientSet kubernetes.Interface) (*AuthConfigMap, error) {
+	client := clientSet.CoreV1().ConfigMaps(ObjectNamespace)
+
+	cm, err := client.Get(context.TODO(), ObjectName, metav1.GetOptions{})
+	// It is fine for the configmap not to exist. Any other error is fatal.
+	if err != nil && !apierrors.IsNotFound(err) {
+		return nil, errors.Wrapf(err, "getting auth ConfigMap")
+	}
+	logger.Debug("aws-auth = %s", awsutil.Prettify(cm))
+	return New(client, cm), nil
+}
+
+// AddIdentity maps an IAM role or user ARN to a k8s group dynamically. It modifies the
+// role or user with given groups. If you are calling
+// this as part of node creation you should use DefaultNodeGroups.
+func (a *AuthConfigMap) AddIdentity(identity Identity) error {
+	identities, err := a.Identities()
+	if err != nil {
+		return err
+	}
+
+	identities = append(identities, identity)
+
+	logger.Info("adding identity %q to auth ConfigMap", identity.ARN())
+	return a.setIdentities(identities)
+}
+
+// Identities returns a list of iam users and roles that are currently in the (cached) configmap.
+func (a *AuthConfigMap) Identities() ([]Identity, error) {
+	var roles []RoleIdentity
+	if err := yaml.Unmarshal([]byte(a.cm.Data[rolesData]), &roles); err != nil {
+		return nil, errors.Wrapf(err, "unmarshalling %q", rolesData)
+	}
+
+	var users []UserIdentity
+	if err := yaml.Unmarshal([]byte(a.cm.Data[usersData]), &users); err != nil {
+		return nil, errors.Wrapf(err, "unmarshalling %q", usersData)
+	}
+
+	all := make([]Identity, len(users)+len(roles))
+	for i, r := range roles {
+		all[i] = r
+	}
+	for i, u := range users {
+		all[i+len(roles)] = u
+	}
+	return all, nil
+}
+
+func (a *AuthConfigMap) setIdentities(identities []Identity) error {
+	// Split identities into list of roles and list of users
+	users, roles := []Identity{}, []Identity{}
+	for _, identity := range identities {
+		switch identity.Type() {
+		case ResourceTypeRole:
+			roles = append(roles, identity)
+		case ResourceTypeUser:
+			users = append(users, identity)
+		default:
+			return errors.Errorf("cannot determine if %q refers to a user or role during setIdentities preprocessing", identity.ARN())
+		}
+	}
+
+	// Update the corresponding keys
+	_roles, err := yaml.Marshal(roles)
+	if err != nil {
+		return errors.Wrapf(err, "marshalling %q", rolesData)
+	}
+	a.cm.Data[rolesData] = string(_roles)
+
+	_users, err := yaml.Marshal(users)
+	if err != nil {
+		return errors.Wrapf(err, "marshalling %q", usersData)
+	}
+	a.cm.Data[usersData] = string(_users)
+
+	return nil
+}
+
+// Save persists the ConfigMap to the cluster. It determines
+// whether to create or update by looking at the ConfigMap's UID.
+func (a *AuthConfigMap) Save() (err error) {
+	if a.cm.UID == "" {
+		a.cm, err = a.client.Create(context.TODO(), a.cm, metav1.CreateOptions{})
+		return err
+	}
+
+	a.cm, err = a.client.Update(context.TODO(), a.cm, metav1.UpdateOptions{})
+	return err
+}
+
+var (
+	// ErrNeitherUserNorRole is the error returned when an identity is missing both UserARN
+	// and RoleARN.
+	ErrNeitherUserNorRole = errors.New("arn is neither user nor role")
+
+	// ErrNoKubernetesIdentity is the error returned when an identity has neither a Kubernetes
+	// username nor a list of groups.
+	ErrNoKubernetesIdentity = errors.New("neither username nor group are set for iam identity")
+)
+
+// Identity represents an IAM identity and its corresponding Kubernetes identity
+type Identity interface {
+	ARN() string
+	Type() string
+	Username() string
+	Groups() []string
+}
+
+// KubernetesIdentity represents a kubernetes identity to be used in iam mappings
+type KubernetesIdentity struct {
+	KubernetesUsername string   `json:"username,omitempty"`
+	KubernetesGroups   []string `json:"groups,omitempty"`
+}
+
+// UserIdentity represents a mapping from an IAM user to a kubernetes identity
+type UserIdentity struct {
+	UserARN string `json:"userarn,omitempty"`
+	KubernetesIdentity
+}
+
+// RoleIdentity represents a mapping from an IAM role to a kubernetes identity
+type RoleIdentity struct {
+	RoleARN string `json:"rolearn,omitempty"`
+	KubernetesIdentity
+}
+
+// Username returns the Kubernetes username
+func (k KubernetesIdentity) Username() string {
+	return k.KubernetesUsername
+}
+
+// Groups returns the Kubernetes groups
+func (k KubernetesIdentity) Groups() []string {
+	return k.KubernetesGroups
+}
+
+// ARN returns the ARN of the iam mapping
+func (u UserIdentity) ARN() string {
+	return u.UserARN
+}
+
+// Type returns the resource type of the iam mapping
+func (u UserIdentity) Type() string {
+	return ResourceTypeUser
+}
+
+// ARN returns the ARN of the iam mapping
+func (r RoleIdentity) ARN() string {
+	return r.RoleARN
+}
+
+// Type returns the resource type of the iam mapping
+func (r RoleIdentity) Type() string {
+	return ResourceTypeRole
+}
+
+// NewIdentity determines into which field the given arn goes and returns the new identity
+// alongside any error resulting for checking its validity.
+func NewIdentity(arn string, username string, groups []string) (Identity, error) {
+	if arn == "" {
+		return nil, fmt.Errorf("expected a valid arn but got empty string")
+	}
+	if username == "" && len(groups) == 0 {
+		return nil, ErrNoKubernetesIdentity
+	}
+
+	parsedARN, err := Parse(arn)
+	if err != nil {
+		return nil, err
+	}
+
+	switch {
+	case parsedARN.IsUser():
+		return &UserIdentity{
+			UserARN: arn,
+			KubernetesIdentity: KubernetesIdentity{
+				KubernetesUsername: username,
+				KubernetesGroups:   groups,
+			},
+		}, nil
+	case parsedARN.IsRole():
+		return &RoleIdentity{
+			RoleARN: arn,
+			KubernetesIdentity: KubernetesIdentity{
+				KubernetesUsername: username,
+				KubernetesGroups:   groups,
+			},
+		}, nil
+	default:
+		return nil, ErrNeitherUserNorRole
+	}
+}
+
+const (
+	// ResourceTypeRole is the resource type of the role ARN
+	ResourceTypeRole = "role"
+	// ResourceTypeUser is the resource type of the user ARN
+	ResourceTypeUser = "user"
+)
+
+// ARN implements the pflag.Value interface for aws-sdk-go/aws/arn.ARN
+type ARN struct {
+	arn.ARN
+}
+
+// Parse wraps the aws-sdk-go/aws/arn.Parse function and instead returns a
+// iam.ARN
+func Parse(s string) (ARN, error) {
+	a, err := arn.Parse(s)
+	return ARN{a}, err
+}
+
+// ResourceType returns the type of the resource specified in the ARN.
+// Typically, in the case of IAM, it is a role or a user
+func (a *ARN) ResourceType() string {
+	t := a.Resource
+	if idx := strings.Index(t, "/"); idx >= 0 {
+		t = t[:idx] // remove everything following the forward slash
+	}
+
+	return t
+}
+
+// IsUser returns whether the arn represents a IAM user or not
+func (a *ARN) IsUser() bool {
+	return a.ResourceType() == ResourceTypeUser
+}
+
+// IsRole returns whether the arn represents a IAM role or not
+func (a *ARN) IsRole() bool {
+	return a.ResourceType() == ResourceTypeRole
+}

+ 56 - 0
internal/providers/aws/local/config.go

@@ -0,0 +1,56 @@
+package local
+
+import (
+	"github.com/aws/aws-sdk-go/service/iam"
+	"github.com/porter-dev/porter/internal/kubernetes/local"
+	"github.com/porter-dev/porter/internal/providers/aws"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/tools/clientcmd"
+
+	"github.com/aws/aws-sdk-go/aws/session"
+)
+
+// NewDefaultAgent returns an agent using Application Default Credentials. If these are not
+// set and the gcloud utility is installed on the machine, this will spawn a setup process
+// to link these credentials.
+func NewDefaultAgent(kubeconfigPath string, contextName string) (*aws.Agent, error) {
+	// (1) Construct a local clientset from the AWS context, and use the eksctl authconfigmap package
+	// to read the current identities of the config map, to make sure user has access. Save the created
+	// clientset.
+	rawBytes, err := local.GetKubeconfigFromHost(kubeconfigPath, []string{contextName})
+
+	if err != nil {
+		return nil, err
+	}
+
+	conf, err := clientcmd.NewClientConfigFromBytes(rawBytes)
+
+	rawConf, err := conf.RawConfig()
+
+	conf = clientcmd.NewDefaultClientConfig(rawConf, &clientcmd.ConfigOverrides{
+		CurrentContext: contextName,
+	})
+
+	restConf, err := conf.ClientConfig()
+
+	if err != nil {
+		return nil, err
+	}
+
+	clientset, err := kubernetes.NewForConfig(restConf)
+
+	if err != nil {
+		return nil, err
+	}
+
+	sess := session.Must(session.NewSession())
+
+	iamSvc := iam.New(sess)
+
+	// Return a new agent with AWS session and clientset
+	return &aws.Agent{
+		Session:    sess,
+		IAMService: iamSvc,
+		Clientset:  clientset,
+	}, nil
+}

+ 1 - 1
server/api/k8s_handler.go

@@ -61,7 +61,7 @@ func (app *App) HandleListNamespaces(w http.ResponseWriter, r *http.Request) {
 	namespaces, err := agent.ListNamespaces()
 
 	if err != nil {
-		app.handleErrorFormValidation(err, ErrK8sValidate, w)
+		app.handleErrorDataRead(err, w)
 		return
 	}
 

+ 3 - 3
server/api/project_handler_test.go

@@ -176,7 +176,7 @@ var createProjectSACandidatesTests = []*projTest{
 		endpoint:  "/api/projects/1/candidates",
 		body:      `{"kubeconfig":"` + OIDCAuthWithDataForJSON + `"}`,
 		expStatus: http.StatusCreated,
-		expBody:   `[{"id":1,"actions":[],"created_sa_id":1,"project_id":1,"kind":"connector","cluster_name":"cluster-test","cluster_endpoint":"https://localhost","auth_mechanism":"oidc"}]`,
+		expBody:   `[{"id":1,"actions":[],"created_sa_id":1,"project_id":1,"kind":"connector","context_name":"context-test","cluster_name":"cluster-test","cluster_endpoint":"https://localhost","auth_mechanism":"oidc"}]`,
 		useCookie: true,
 		validators: []func(c *projTest, tester *tester, t *testing.T){
 			projectSACandidateBodyValidator,
@@ -231,7 +231,7 @@ var createProjectSACandidatesTests = []*projTest{
 		endpoint:  "/api/projects/1/candidates",
 		body:      `{"kubeconfig":"` + OIDCAuthWithoutDataForJSON + `"}`,
 		expStatus: http.StatusCreated,
-		expBody:   `[{"id":1,"actions":[{"name":"upload-oidc-idp-issuer-ca-data","filename":"/fake/path/to/ca.pem","docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"project_id":1,"kind":"connector","cluster_name":"cluster-test","cluster_endpoint":"https://localhost","auth_mechanism":"oidc"}]`,
+		expBody:   `[{"id":1,"actions":[{"name":"upload-oidc-idp-issuer-ca-data","filename":"/fake/path/to/ca.pem","docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"project_id":1,"kind":"connector","context_name":"context-test","cluster_name":"cluster-test","cluster_endpoint":"https://localhost","auth_mechanism":"oidc"}]`,
 		useCookie: true,
 		validators: []func(c *projTest, tester *tester, t *testing.T){
 			projectSACandidateBodyValidator,
@@ -255,7 +255,7 @@ var listProjectSACandidatesTests = []*projTest{
 		endpoint:  "/api/projects/1/candidates",
 		body:      ``,
 		expStatus: http.StatusOK,
-		expBody:   `[{"id":1,"actions":[{"name":"upload-oidc-idp-issuer-ca-data","filename":"/fake/path/to/ca.pem","docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"project_id":1,"kind":"connector","cluster_name":"cluster-test","cluster_endpoint":"https://localhost","auth_mechanism":"oidc"}]`,
+		expBody:   `[{"id":1,"actions":[{"name":"upload-oidc-idp-issuer-ca-data","filename":"/fake/path/to/ca.pem","docs":"https://github.com/porter-dev/porter","resolved":false,"fields":"oidc_idp_issuer_ca_data"}],"project_id":1,"kind":"connector","context_name":"context-test","cluster_name":"cluster-test","cluster_endpoint":"https://localhost","auth_mechanism":"oidc"}]`,
 		useCookie: true,
 		validators: []func(c *projTest, tester *tester, t *testing.T){
 			projectSACandidateBodyValidator,

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor