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

Signed-off-by: aiordache <anca.iordache@docker.com>
This commit is contained in:
aiordache 2020-10-09 19:24:37 +02:00
parent 5b54816a1a
commit c2af0a136a
4 changed files with 167 additions and 99 deletions

View File

@ -56,8 +56,6 @@ func createEcsCommand() *cobra.Command {
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.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
}

View File

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

View File

@ -20,13 +20,13 @@ import (
"context"
"fmt"
"os"
"reflect"
"strings"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/pkg/errors"
"gopkg.in/ini.v1"
"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
ecsCtx := store.EcsContext{
Profile: opts.Profile,
Region: opts.Region,
}
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
accessKey, secretKey, err = h.askCredentials()
if err != nil {
return nil, "", err
}
func (h contextCreateAWSHelper) createProfile(name string) error {
accessKey, secretKey, err := h.askCredentials()
if err != nil {
return err
}
if accessKey != "" && secretKey != "" {
if err := h.saveCredentials(ecsCtx.Profile, accessKey, secretKey); err != nil {
return h.saveCredentials(name, accessKey, secretKey)
}
return nil
}
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
}
func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) {
profile := opts.Profile
region := opts.Region
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 {
return nil, "", err
}
}
description := ecsCtx.Region
if opts.Description != "" {
description = fmt.Sprintf("%s (%s)", opts.Description, description)
if region == "" {
region, err = h.chooseRegion(region, profile)
if err != nil {
return nil, "", err
}
}
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 {
@ -132,75 +129,151 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri
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
}
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 == terminal.InterruptErr {
return "", errdefs.ErrCanceled
}
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
}
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
}
return result, nil
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 {
return "", err
}
return region, configIni.SaveTo(awsConfig)
}
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 {
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
}
// validate password
if len(secretAccessKey) < 3 {
return "", "", fmt.Errorf("AWS Secret Access Key must have more than 3 characters")
}
return accessKeyID, secretAccessKey, nil
if !confirm {
return "", "", nil
}
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
}
}
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 profiles, nil
}

View File

@ -152,16 +152,16 @@ func setupTest(t *testing.T) (*E2eCLI, string) {
if localTestProfile != "" {
region := os.Getenv("TEST_AWS_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 {
profile := contextName
profile := "default"
region := os.Getenv("AWS_DEFAULT_REGION")
secretKey := os.Getenv("AWS_SECRET_ACCESS_KEY")
keyID := os.Getenv("AWS_ACCESS_KEY_ID")
assert.Check(t, keyID != "")
assert.Check(t, secretKey != "")
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 = c.RunDockerCmd("context", "use", contextName)