Explorar el Código

ecr automatic connection

Alexander Belanger hace 5 años
padre
commit
8af2137ba4

+ 67 - 7
cli/cmd/connect/ecr.go

@@ -3,9 +3,11 @@ package connect
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	"github.com/fatih/color"
 	"github.com/porter-dev/porter/cli/cmd/api"
+	awsLocal "github.com/porter-dev/porter/cli/cmd/providers/aws/local"
 	"github.com/porter-dev/porter/cli/cmd/utils"
 )
 
@@ -19,22 +21,76 @@ func ECR(
 		return 0, fmt.Errorf("no project set, please run porter project set [id]")
 	}
 
-	// query for the access key id
-	accessKeyID, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Access Key ID: `))
+	// query for the region
+	region, err := utils.PromptPlaintext(fmt.Sprintf(`Please provide the AWS region where the ECR instance is located.
+AWS Region: `))
 
 	if err != nil {
 		return 0, err
 	}
 
-	// query for the secret access key
-	secretKey, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Secret Access Key: `))
+	userResp, err := utils.PromptPlaintext(
+		fmt.Sprintf(`Porter can set up an IAM user in your AWS account to connect to this ECR instance automatically.
+Would you like to proceed? %s `,
+			color.New(color.FgCyan).Sprintf("[y/n]"),
+		),
+	)
 
 	if err != nil {
 		return 0, err
 	}
 
-	// query for the region
-	region, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Region: `))
+	if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
+		agent := awsLocal.NewDefaultAgent()
+
+		creds, err := agent.CreateIAMECRUser(region)
+
+		if err != nil {
+			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)
+			return ecrManual(client, projectID, region)
+		}
+
+		integration, err := client.CreateAWSIntegration(
+			context.Background(),
+			projectID,
+			&api.CreateAWSIntegrationRequest{
+				AWSAccessKeyID:     creds.AWSAccessKeyID,
+				AWSSecretAccessKey: creds.AWSSecretAccessKey,
+				AWSRegion:          region,
+			},
+		)
+
+		if err != nil {
+			return 0, err
+		}
+
+		color.New(color.FgGreen).Printf("created aws integration with id %d\n", integration.ID)
+
+		return linkRegistry(client, projectID, integration.ID)
+	}
+
+	return ecrManual(client, projectID, region)
+}
+
+func ecrManual(
+	client *api.Client,
+	projectID uint,
+	region string,
+) (uint, error) {
+	// if project ID is 0, ask the user to set the project ID or create a project
+	if projectID == 0 {
+		return 0, fmt.Errorf("no project set, please run porter project set [id]")
+	}
+
+	// query for the access key id
+	accessKeyID, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Access Key ID: `))
+
+	if err != nil {
+		return 0, err
+	}
+
+	// query for the secret access key
+	secretKey, err := utils.PromptPlaintext(fmt.Sprintf(`AWS Secret Access Key: `))
 
 	if err != nil {
 		return 0, err
@@ -57,6 +113,10 @@ func ECR(
 
 	color.New(color.FgGreen).Printf("created aws integration with id %d\n", integration.ID)
 
+	return linkRegistry(client, projectID, integration.ID)
+}
+
+func linkRegistry(client *api.Client, projectID uint, intID uint) (uint, error) {
 	// create the registry
 	// query for registry name
 	regName, err := utils.PromptPlaintext(fmt.Sprintf(`Give this registry a name: `))
@@ -70,7 +130,7 @@ func ECR(
 		projectID,
 		&api.CreateECRRequest{
 			Name:             regName,
-			AWSIntegrationID: integration.ID,
+			AWSIntegrationID: intID,
 		},
 	)
 

+ 1 - 1
cli/cmd/connect/kubeconfig.go

@@ -448,7 +448,7 @@ Would you like to proceed? %s `,
 	}
 
 	if userResp := strings.ToLower(userResp); userResp == "y" || userResp == "yes" {
-		agent, err := awsLocal.NewDefaultAgent(kubeconfigPath, contextName)
+		agent, err := awsLocal.NewDefaultKubernetesAgent(kubeconfigPath, contextName)
 
 		if err != nil {
 			color.New(color.FgRed).Printf("Automatic creation failed, manual input required. Error was: %v\n", err)

+ 100 - 9
cli/cmd/providers/aws/agent.go

@@ -1,6 +1,8 @@
 package aws
 
 import (
+	"regexp"
+
 	"github.com/aws/aws-sdk-go/aws/session"
 	"github.com/aws/aws-sdk-go/service/iam"
 	"github.com/porter-dev/porter/cli/cmd/utils"
@@ -20,19 +22,31 @@ type PorterAWSCredentials struct {
 }
 
 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,
-	})
+	user, err := a.getIAMUserIfExists()
 
 	if err != nil {
 		return nil, err
 	}
 
+	var name string
+
+	if user == nil {
+		// (1) Create a new IAM user called porter-dashboard-[random_string], and attach the policy:
+		name = "porter-dashboard-" + utils.StringWithCharset(6, "abcdefghijklmnopqrstuvwxyz1234567890")
+
+		resp, err := a.IAMService.CreateUser(&iam.CreateUserInput{
+			UserName: &name,
+		})
+
+		if err != nil {
+			return nil, err
+		}
+
+		user = resp.User
+	} else {
+		name = *user.UserName
+	}
+
 	policyArn := "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
 
 	_, err = a.IAMService.AttachUserPolicy(&iam.AttachUserPolicyInput{
@@ -68,7 +82,7 @@ func (a *Agent) CreateIAMKubernetesMapping(clusterIDGuess string) (*PorterAWSCre
 	}
 
 	identity, err := NewIdentity(
-		*user.User.Arn,
+		*user.Arn,
 		"admin",
 		[]string{"system:masters"},
 	)
@@ -91,3 +105,80 @@ func (a *Agent) CreateIAMKubernetesMapping(clusterIDGuess string) (*PorterAWSCre
 
 	return porterCreds, nil
 }
+
+// CreateIAMECRUser creates an IAM user if it does not exist, and attaches a ECR-read policy
+// to the user
+func (a *Agent) CreateIAMECRUser(region string) (*PorterAWSCredentials, error) {
+	user, err := a.getIAMUserIfExists()
+
+	if err != nil {
+		return nil, err
+	}
+
+	var name string
+
+	if user == nil {
+		// (1) Create a new IAM user called porter-dashboard-[random_string], and attach the policy:
+		//
+		// arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
+		name = "porter-dashboard-" + utils.StringWithCharset(6, "abcdefghijklmnopqrstuvwxyz1234567890")
+
+		resp, err := a.IAMService.CreateUser(&iam.CreateUserInput{
+			UserName: &name,
+		})
+
+		if err != nil {
+			return nil, err
+		}
+
+		user = resp.User
+	} else {
+		name = *user.UserName
+	}
+
+	policyArn := "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
+
+	_, 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,
+	}
+
+	return porterCreds, nil
+}
+
+func (a *Agent) getIAMUserIfExists() (*iam.User, error) {
+	resp, err := a.IAMService.ListUsers(&iam.ListUsersInput{})
+
+	if err != nil {
+		return nil, err
+	}
+
+	re := regexp.MustCompile(`porter-dashboard-[a-z1-9]{6}`)
+
+	for _, user := range resp.Users {
+		if re.MatchString(*user.UserName) {
+			return user, nil
+		}
+	}
+
+	return nil, nil
+}

+ 16 - 4
cli/cmd/providers/aws/local/config.go

@@ -10,10 +10,22 @@ import (
 	"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) {
+// NewDefaultAgent returns an AWS agent without a k8s clientset
+func NewDefaultAgent() *aws.Agent {
+	sess := session.Must(session.NewSession())
+
+	iamSvc := iam.New(sess)
+
+	// Return a new agent with AWS session and iam service
+	return &aws.Agent{
+		Session:    sess,
+		IAMService: iamSvc,
+		Clientset:  nil,
+	}
+}
+
+// NewDefaultKubernetesAgent returns an AWS agent using local credentials.
+func NewDefaultKubernetesAgent(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.