Browse Source

Populate ~/.aws/config(credentials) on ecs context create

Signed-off-by: aiordache <[email protected]>
aiordache 5 years ago
parent
commit
c2af0a136a
4 changed files with 160 additions and 92 deletions
  1. 0 2
      cli/cmd/context/create_ecs.go
  2. 0 3
      ecs/backend.go
  3. 157 84
      ecs/context.go
  4. 3 3
      tests/ecs-e2e/e2e-ecs_test.go

+ 0 - 2
cli/cmd/context/create_ecs.go

@@ -56,8 +56,6 @@ func createEcsCommand() *cobra.Command {
 	cmd.Flags().BoolVar(&localSimulation, "local-simulation", false, "Create context for ECS local simulation endpoints")
 	cmd.Flags().BoolVar(&localSimulation, "local-simulation", false, "Create context for ECS local simulation endpoints")
 	cmd.Flags().StringVar(&opts.Profile, "profile", "", "Profile")
 	cmd.Flags().StringVar(&opts.Profile, "profile", "", "Profile")
 	cmd.Flags().StringVar(&opts.Region, "region", "", "Region")
 	cmd.Flags().StringVar(&opts.Region, "region", "", "Region")
-	cmd.Flags().StringVar(&opts.AwsID, "key-id", "", "AWS Access Key ID")
-	cmd.Flags().StringVar(&opts.AwsSecret, "secret-key", "", "AWS Secret Access Key")
 	return cmd
 	return cmd
 }
 }
 
 

+ 0 - 3
ecs/backend.go

@@ -40,9 +40,6 @@ type ContextParams struct {
 	Description string
 	Description string
 	Region      string
 	Region      string
 	Profile     string
 	Profile     string
-
-	AwsID     string
-	AwsSecret string
 }
 }
 
 
 func init() {
 func init() {

+ 157 - 84
ecs/context.go

@@ -20,13 +20,13 @@ import (
 	"context"
 	"context"
 	"fmt"
 	"fmt"
 	"os"
 	"os"
-	"reflect"
 	"strings"
 	"strings"
 
 
 	"github.com/AlecAivazis/survey/v2/terminal"
 	"github.com/AlecAivazis/survey/v2/terminal"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/awserr"
 	"github.com/aws/aws-sdk-go/aws/credentials"
 	"github.com/aws/aws-sdk-go/aws/credentials"
 	"github.com/aws/aws-sdk-go/aws/defaults"
 	"github.com/aws/aws-sdk-go/aws/defaults"
+	"github.com/pkg/errors"
 	"gopkg.in/ini.v1"
 	"gopkg.in/ini.v1"
 
 
 	"github.com/docker/compose-cli/context/store"
 	"github.com/docker/compose-cli/context/store"
@@ -44,60 +44,57 @@ func newContextCreateHelper() contextCreateAWSHelper {
 	}
 	}
 }
 }
 
 
-func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) {
-	accessKey := opts.AwsID
-	secretKey := opts.AwsSecret
+func (h contextCreateAWSHelper) createProfile(name string) error {
+	accessKey, secretKey, err := h.askCredentials()
+	if err != nil {
+		return err
+	}
+	if accessKey != "" && secretKey != "" {
+		return h.saveCredentials(name, accessKey, secretKey)
+	}
+	return nil
+}
 
 
-	ecsCtx := store.EcsContext{
-		Profile: opts.Profile,
-		Region:  opts.Region,
+func (h contextCreateAWSHelper) createContext(profile, region, description string) (interface{}, string) {
+	if profile == "default" {
+		profile = ""
 	}
 	}
+	description = strings.TrimSpace(
+		fmt.Sprintf("%s (%s)", description, region))
+	return store.EcsContext{
+		Profile: profile,
+		Region:  region,
+	}, description
+}
 
 
-	if h.missingRequiredFlags(ecsCtx) {
-		profilesList, err := h.getProfiles()
-		if err != nil {
-			return nil, "", err
-		}
-		// get profile
-		_, ok := profilesList[ecsCtx.Profile]
-		if !ok {
-			profile, err := h.chooseProfile(profilesList)
-			if err != nil {
-				return nil, "", err
-			}
-			ecsCtx.Profile = profile
-		}
-		// set region
-		region, err := h.chooseRegion(ecsCtx.Region, profilesList[ecsCtx.Profile])
-		if err != nil {
-			return nil, "", err
-		}
-		ecsCtx.Region = region
+func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) {
+	profile := opts.Profile
+	region := opts.Region
 
 
-		accessKey, secretKey, err = h.askCredentials()
+	profilesList, err := h.getProfiles()
+	if err != nil {
+		return nil, "", err
+	}
+	if profile != "" {
+		// validate profile
+		if profile != "default" && !contains(profilesList, profile) {
+			return nil, "", errors.Wrapf(errdefs.ErrNotFound, "profile %q", profile)
+		}
+	} else {
+		// choose profile
+		profile, err = h.chooseProfile(profilesList)
 		if err != nil {
 		if err != nil {
 			return nil, "", err
 			return nil, "", err
 		}
 		}
 	}
 	}
-	if accessKey != "" && secretKey != "" {
-		if err := h.saveCredentials(ecsCtx.Profile, accessKey, secretKey); err != nil {
+	if region == "" {
+		region, err = h.chooseRegion(region, profile)
+		if err != nil {
 			return nil, "", err
 			return nil, "", err
 		}
 		}
 	}
 	}
-
-	description := ecsCtx.Region
-	if opts.Description != "" {
-		description = fmt.Sprintf("%s (%s)", opts.Description, description)
-	}
-
-	return ecsCtx, description, nil
-}
-
-func (h contextCreateAWSHelper) missingRequiredFlags(ctx store.EcsContext) bool {
-	if ctx.Profile == "" || ctx.Region == "" {
-		return true
-	}
-	return false
+	ecsCtx, descr := h.createContext(profile, region, opts.Description)
+	return ecsCtx, descr, nil
 }
 }
 
 
 func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID string, secretAccessKey string) error {
 func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID string, secretAccessKey string) error {
@@ -132,75 +129,151 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri
 	return credIni.SaveTo(p.Filename)
 	return credIni.SaveTo(p.Filename)
 }
 }
 
 
-func (h contextCreateAWSHelper) getProfiles() (map[string]ini.Section, error) {
-	profiles := map[string]ini.Section{"new profile": {}}
-	credIni, err := ini.Load(defaults.SharedConfigFilename())
-	if err != nil {
-		return nil, err
+func (h contextCreateAWSHelper) getProfiles() ([]string, error) {
+	profiles := []string{}
+	// parse both .aws/credentials and .aws/config for profiles
+	configFiles := map[string]bool{
+		defaults.SharedCredentialsFilename(): false,
+		defaults.SharedConfigFilename():      true,
 	}
 	}
-	for _, section := range credIni.Sections() {
-		if strings.HasPrefix(section.Name(), "profile") {
-			profiles[section.Name()[len("profile "):]] = *section
+	for f, prefix := range configFiles {
+		sections, err := loadIniFile(f, prefix)
+		if err != nil {
+			if os.IsNotExist(err) {
+				continue
+			}
+			return nil, err
+		}
+		for key := range sections {
+			name := strings.ToLower(key)
+			if !contains(profiles, name) {
+				profiles = append(profiles, name)
+			}
 		}
 		}
 	}
 	}
 	return profiles, nil
 	return profiles, nil
 }
 }
 
 
-func (h contextCreateAWSHelper) chooseProfile(section map[string]ini.Section) (string, error) {
-	keys := reflect.ValueOf(section).MapKeys()
-	profiles := make([]string, len(keys))
-	for i := 0; i < len(keys); i++ {
-		profiles[i] = keys[i].String()
-	}
+func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error) {
+	options := []string{"new profile"}
+	options = append(options, profiles...)
 
 
-	selected, err := h.user.Select("Select AWS Profile", profiles)
+	selected, err := h.user.Select("Select AWS Profile", options)
 	if err != nil {
 	if err != nil {
 		if err == terminal.InterruptErr {
 		if err == terminal.InterruptErr {
 			return "", errdefs.ErrCanceled
 			return "", errdefs.ErrCanceled
 		}
 		}
 		return "", err
 		return "", err
 	}
 	}
-	profile := profiles[selected]
-	if profiles[selected] == "new profile" {
-		return h.user.Input("profile name", "")
+	profile := options[selected]
+	if options[selected] == "new profile" {
+		suggestion := ""
+		if !contains(profiles, "default") {
+			suggestion = "default"
+		}
+		name, err := h.user.Input("profile name", suggestion)
+		if err != nil {
+			return "", err
+		}
+		if name == "" {
+			return "", fmt.Errorf("profile name cannot be empty")
+		}
+		return name, h.createProfile(name)
 	}
 	}
 	return profile, nil
 	return profile, nil
 }
 }
 
 
-func (h contextCreateAWSHelper) chooseRegion(region string, section ini.Section) (string, error) {
-	defaultRegion := region
-	if defaultRegion == "" && section.Name() != "" {
-		reg, err := section.GetKey("region")
-		if err == nil {
-			defaultRegion = reg.Value()
+func (h contextCreateAWSHelper) chooseRegion(region string, profile string) (string, error) {
+	suggestion := region
+
+	// only load ~/.aws/config
+	awsConfig := defaults.SharedConfigFilename()
+	configIni, err := ini.Load(awsConfig)
+
+	if err != nil {
+		if !os.IsNotExist(err) {
+			return "", err
+		}
+		configIni = ini.Empty()
+	}
+	if profile != "default" {
+		profile = fmt.Sprintf("profile %s", profile)
+	}
+	section, err := configIni.GetSection(profile)
+	if err != nil {
+		if !strings.Contains(err.Error(), "does not exist") {
+			return "", err
+		}
+		section, err = configIni.NewSection(profile)
+		if err != nil {
+			return "", err
 		}
 		}
 	}
 	}
-	result, err := h.user.Input("Region", defaultRegion)
+	reg, err := section.GetKey("region")
+	if err == nil {
+		suggestion = reg.Value()
+	}
+	// promp user for region
+	region, err = h.user.Input("Region", suggestion)
+	if err != nil {
+		return "", err
+	}
+	if region == "" {
+		return "", fmt.Errorf("region cannot be empty")
+	}
+	// save selected/typed region under profile in ~/.aws/config
+	_, err = section.NewKey("region", region)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	}
 	}
-	return result, nil
+	return region, configIni.SaveTo(awsConfig)
 }
 }
 
 
 func (h contextCreateAWSHelper) askCredentials() (string, string, error) {
 func (h contextCreateAWSHelper) askCredentials() (string, string, error) {
-	confirm, err := h.user.Confirm("Enter credentials", false)
+	confirm, err := h.user.Confirm("Enter AWS credentials", false)
 	if err != nil {
 	if err != nil {
 		return "", "", err
 		return "", "", err
 	}
 	}
-	if confirm {
-		accessKeyID, err := h.user.Input("AWS Access Key ID", "")
-		if err != nil {
-			return "", "", err
-		}
-		secretAccessKey, err := h.user.Password("Enter AWS Secret Access Key")
-		if err != nil {
-			return "", "", err
+	if !confirm {
+		return "", "", nil
+	}
+
+	accessKeyID, err := h.user.Input("AWS Access Key ID", "")
+	if err != nil {
+		return "", "", err
+	}
+	secretAccessKey, err := h.user.Password("Enter AWS Secret Access Key")
+	if err != nil {
+		return "", "", err
+	}
+	// validate access ID and password
+	if len(accessKeyID) < 3 || len(secretAccessKey) < 3 {
+		return "", "", fmt.Errorf("AWS Access/Secret Access Key must have more than 3 characters")
+	}
+	return accessKeyID, secretAccessKey, nil
+}
+
+func contains(values []string, value string) bool {
+	for _, v := range values {
+		if v == value {
+			return true
 		}
 		}
-		// validate password
-		if len(secretAccessKey) < 3 {
-			return "", "", fmt.Errorf("AWS Secret Access Key must have more than 3 characters")
+	}
+	return false
+}
+
+func loadIniFile(path string, prefix bool) (map[string]ini.Section, error) {
+	profiles := map[string]ini.Section{}
+	credIni, err := ini.Load(path)
+	if err != nil {
+		return nil, err
+	}
+	for _, section := range credIni.Sections() {
+		if prefix && strings.HasPrefix(section.Name(), "profile ") {
+			profiles[section.Name()[len("profile "):]] = *section
+		} else if !prefix || section.Name() == "default" {
+			profiles[section.Name()] = *section
 		}
 		}
-		return accessKeyID, secretAccessKey, nil
 	}
 	}
-	return "", "", nil
+	return profiles, nil
 }
 }

+ 3 - 3
tests/ecs-e2e/e2e-ecs_test.go

@@ -152,16 +152,16 @@ func setupTest(t *testing.T) (*E2eCLI, string) {
 		if localTestProfile != "" {
 		if localTestProfile != "" {
 			region := os.Getenv("TEST_AWS_REGION")
 			region := os.Getenv("TEST_AWS_REGION")
 			assert.Check(t, region != "")
 			assert.Check(t, region != "")
-			res = c.RunDockerCmd("context", "create", "ecs", contextName, "--profile", localTestProfile, "--region", region)
+			res = c.RunDockerCmd("context", "create", "ecs", contextName, "--profile", "default", "--region", region)
 		} else {
 		} else {
-			profile := contextName
+			profile := "default"
 			region := os.Getenv("AWS_DEFAULT_REGION")
 			region := os.Getenv("AWS_DEFAULT_REGION")
 			secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
 			secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
 			keyID := os.Getenv("AWS_ACCESS_KEY_ID")
 			keyID := os.Getenv("AWS_ACCESS_KEY_ID")
 			assert.Check(t, keyID != "")
 			assert.Check(t, keyID != "")
 			assert.Check(t, secretKey != "")
 			assert.Check(t, secretKey != "")
 			assert.Check(t, region != "")
 			assert.Check(t, region != "")
-			res = c.RunDockerCmd("context", "create", "ecs", contextName, "--profile", profile, "--region", region, "--secret-key", secretKey, "--key-id", keyID)
+			res = c.RunDockerCmd("context", "create", "ecs", contextName, "--profile", profile, "--region", region)
 		}
 		}
 		res.Assert(t, icmd.Expected{Out: "Successfully created ecs context \"" + contextName + "\""})
 		res.Assert(t, icmd.Expected{Out: "Successfully created ecs context \"" + contextName + "\""})
 		res = c.RunDockerCmd("context", "use", contextName)
 		res = c.RunDockerCmd("context", "use", contextName)