Refactor context create options

Signed-off-by: aiordache <anca.iordache@docker.com>
This commit is contained in:
aiordache 2020-10-29 10:08:41 +01:00
parent e44d0b922a
commit 53efa312c8
4 changed files with 139 additions and 113 deletions

View File

@ -57,6 +57,7 @@ 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().BoolVar(&opts.CredsFromEnv, "from-env", false, "Use credentials and region from environment")
return cmd return cmd
} }

View File

@ -39,10 +39,24 @@ const backendType = store.EcsContextType
// ContextParams options for creating AWS context // ContextParams options for creating AWS context
type ContextParams struct { type ContextParams struct {
Name string Name string
Description string Description string
Region string AccessKey string
Profile string SecretKey string
SessionToken string
Profile string
Region string
CredsFromEnv bool
}
func (c ContextParams) HaveRequiredCredentials() bool {
if c.AccessKey == "" || c.SecretKey == "" {
return false
}
if c.Region == "" && c.Profile == "" {
return false
}
return true
} }
func init() { func init() {
@ -62,18 +76,34 @@ func service(ctx context.Context) (backend.Service, error) {
} }
func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) { func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) {
var region string
var profile string
if ecsCtx.CredentialsFromEnv { if ecsCtx.CredentialsFromEnv {
creds := getEnvVars() creds := getEnvVars()
if !creds.HaveRequiredCredentials() { if !creds.HaveRequiredCredentials() {
return nil, fmt.Errorf(`context requires credentials to be passed as environment variable.`) return nil, fmt.Errorf(`context requires credentials to be passed as environment variable.`)
} }
region = creds.Region
profile = creds.Profile
} else {
// get region
profile = ecsCtx.Profile
if ecsCtx.Region != "" {
region = ecsCtx.Region
} else {
r, _, err := getRegion(ecsCtx.Profile)
if err != nil {
return nil, err
}
region = r
}
} }
sess, err := session.NewSessionWithOptions(session.Options{ sess, err := session.NewSessionWithOptions(session.Options{
Profile: ecsCtx.Profile, Profile: profile,
SharedConfigState: session.SharedConfigEnable, SharedConfigState: session.SharedConfigEnable,
Config: aws.Config{ Config: aws.Config{
Region: aws.String(ecsCtx.Region), Region: aws.String(region),
}, },
}) })
if err != nil { if err != nil {
@ -83,7 +113,7 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) {
sdk := newSDK(sess) sdk := newSDK(sess)
return &ecsAPIService{ return &ecsAPIService{
ctx: ecsCtx, ctx: ecsCtx,
Region: ecsCtx.Region, Region: region,
aws: sdk, aws: sdk,
}, nil }, nil
} }

View File

@ -33,38 +33,23 @@ import (
"github.com/docker/compose-cli/prompt" "github.com/docker/compose-cli/prompt"
) )
type contextElements struct { func getEnvVars() ContextParams {
AccessKey string c := ContextParams{}
SecretKey string
SessionToken string
Profile string
Region string
CredsFromEnv bool
}
func (c contextElements) HaveRequiredCredentials() bool { //check profile env vars
if c.AccessKey != "" && c.SecretKey != "" {
return true
}
return false
}
type contextCreateAWSHelper struct {
user prompt.UI
}
func newContextCreateHelper() contextCreateAWSHelper {
return contextCreateAWSHelper{
user: prompt.User{},
}
}
func getEnvVars() contextElements {
c := contextElements{}
profile := os.Getenv("AWS_PROFILE") profile := os.Getenv("AWS_PROFILE")
if profile != "" { if profile != "" {
c.Profile = profile c.Profile = profile
} }
// check REGION env vars
region := os.Getenv("AWS_REGION")
if region == "" {
region = os.Getenv("AWS_DEFAULT_REGION")
if region == "" {
region = "us-east-1"
}
c.Region = region
}
p := credentials.EnvProvider{} p := credentials.EnvProvider{}
creds, err := p.Retrieve() creds, err := p.Retrieve()
@ -77,7 +62,17 @@ func getEnvVars() contextElements {
return c return c
} }
func (h contextCreateAWSHelper) createProfile(name string, c *contextElements) error { type contextCreateAWSHelper struct {
user prompt.UI
}
func newContextCreateHelper() contextCreateAWSHelper {
return contextCreateAWSHelper{
user: prompt.User{},
}
}
func (h contextCreateAWSHelper) createProfile(name string, c *ContextParams) error {
if c != nil { if c != nil {
if c.AccessKey != "" && c.SecretKey != "" { if c.AccessKey != "" && c.SecretKey != "" {
return h.saveCredentials(name, c.AccessKey, c.SecretKey) return h.saveCredentials(name, c.AccessKey, c.SecretKey)
@ -102,19 +97,27 @@ func (h contextCreateAWSHelper) createProfile(name string, c *contextElements) e
return nil return nil
} }
func (h contextCreateAWSHelper) createContext(c *contextElements, description string) (interface{}, string) { func (h contextCreateAWSHelper) createContext(c *ContextParams) (interface{}, string) {
if c.Profile == "default" { if c.Profile == "default" {
c.Profile = "" c.Profile = ""
} }
description = strings.TrimSpace( var description string
fmt.Sprintf("%s (%s)", description, c.Region))
if c.CredsFromEnv { if c.CredsFromEnv {
if c.Description == "" {
description = "credentials read from environment"
}
return store.EcsContext{ return store.EcsContext{
CredentialsFromEnv: c.CredsFromEnv, CredentialsFromEnv: c.CredsFromEnv,
Profile: c.Profile, Profile: c.Profile,
Region: c.Region, Region: c.Region,
}, description }, description
} }
if c.Region != "" {
description = strings.TrimSpace(
fmt.Sprintf("%s (%s)", c.Description, c.Region))
}
return store.EcsContext{ return store.EcsContext{
Profile: c.Profile, Profile: c.Profile,
Region: c.Region, Region: c.Region,
@ -122,14 +125,16 @@ func (h contextCreateAWSHelper) createContext(c *contextElements, description st
} }
func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) { func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) {
creds := contextElements{} if opts.CredsFromEnv {
ecsCtx, descr := h.createContext(&opts)
options := []string{ return ecsCtx, descr, nil
"Use AWS credentials set via environment variables",
"Create a new profile with AWS credentials",
"Select from existing local AWS profiles",
} }
//if creds.HaveRequiredProps() { options := []string{
"Use AWS credentials from environment",
"Select from existing AWS profiles",
"Create new profile from AWS credentials",
}
selected, err := h.user.Select("Would you like to create your context based on", options) selected, err := h.user.Select("Would you like to create your context based on", options)
if err != nil { if err != nil {
if err == terminal.InterruptErr { if err == terminal.InterruptErr {
@ -137,63 +142,56 @@ func (h contextCreateAWSHelper) createContextData(_ context.Context, opts Contex
} }
return nil, "", err return nil, "", err
} }
if creds.Region == "" {
creds.Region = opts.Region
}
if creds.Profile == "" {
creds.Profile = opts.Profile
}
switch selected { switch selected {
case 0: case 0:
creds.CredsFromEnv = true opts.CredsFromEnv = true
// confirm region profile should target
if creds.Region == "" {
creds.Region, err = h.chooseRegion(creds.Region, creds.Profile)
if err != nil {
return nil, "", err
}
}
case 1: case 1:
accessKey, secretKey, err := h.askCredentials() profilesList, err := getProfiles()
if err != nil {
return nil, "", err
}
creds.AccessKey = accessKey
creds.SecretKey = secretKey
// we need a region set -- either read it from profile or prompt user
// prompt for the region to use with this context
creds.Region, err = h.chooseRegion(creds.Region, creds.Profile)
if err != nil {
return nil, "", err
}
// save as a profile
if creds.Profile == "" {
creds.Profile = opts.Name
}
fmt.Printf("Saving credentials under profile %s\n", creds.Profile)
h.createProfile(creds.Profile, &creds)
case 2:
profilesList, err := h.getProfiles()
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
// choose profile // choose profile
creds.Profile, err = h.chooseProfile(profilesList) opts.Profile, err = h.chooseProfile(profilesList)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
if creds.Region == "" {
creds.Region, err = h.chooseRegion(creds.Region, creds.Profile) if opts.Region == "" {
if err != nil { region, isDefinedInProfile, err := getRegion(opts.Profile)
return nil, "", err if isDefinedInProfile {
opts.Region = region
} else {
fmt.Println("No region defined in the profile. Choose the region to use.")
opts.Region, err = h.chooseRegion(opts.Region, opts.Profile)
if err != nil {
return nil, "", err
}
} }
} }
case 2:
accessKey, secretKey, err := h.askCredentials()
if err != nil {
return nil, "", err
}
opts.AccessKey = accessKey
opts.SecretKey = secretKey
// we need a region set -- either read it from profile or prompt user
// prompt for the region to use with this context
opts.Region, err = h.chooseRegion(opts.Region, opts.Profile)
if err != nil {
return nil, "", err
}
// save as a profile
if opts.Profile == "" {
opts.Profile = opts.Name
}
fmt.Printf("Saving credentials under profile %s\n", opts.Profile)
h.createProfile(opts.Profile, &opts)
} }
ecsCtx, descr := h.createContext(&creds, opts.Description) ecsCtx, descr := h.createContext(&opts)
return ecsCtx, descr, nil return ecsCtx, descr, nil
} }
@ -229,7 +227,7 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri
return credIni.SaveTo(p.Filename) return credIni.SaveTo(p.Filename)
} }
func (h contextCreateAWSHelper) getProfiles() ([]string, error) { func getProfiles() ([]string, error) {
profiles := []string{} profiles := []string{}
// parse both .aws/credentials and .aws/config for profiles // parse both .aws/credentials and .aws/config for profiles
configFiles := map[string]bool{ configFiles := map[string]bool{
@ -269,7 +267,7 @@ func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error)
return profile, nil return profile, nil
} }
func (h contextCreateAWSHelper) getRegionSuggestion(region string, profile string) (string, error) { func getRegion(profile string) (string, bool, error) {
if profile == "" { if profile == "" {
profile = "default" profile = "default"
} }
@ -278,13 +276,14 @@ func (h contextCreateAWSHelper) getRegionSuggestion(region string, profile strin
configIni, err := ini.Load(awsConfig) configIni, err := ini.Load(awsConfig)
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return "", err return "", false, err
} }
configIni = ini.Empty() configIni = ini.Empty()
} }
var f func(string, string) string var f func(string) (string, string)
f = func(r string, p string) string { f = func(p string) (string, string) {
r := ""
section, err := configIni.GetSection(p) section, err := configIni.GetSection(p)
if err == nil { if err == nil {
reg, err := section.GetKey("region") reg, err := section.GetKey("region")
@ -295,28 +294,33 @@ func (h contextCreateAWSHelper) getRegionSuggestion(region string, profile strin
if r == "" { if r == "" {
switch p { switch p {
case "": case "":
return "us-east-1" return "us-east-1", ""
case "default": case "default":
return f(r, "") return f("")
} }
return f(r, "default") return f("default")
} }
return r return r, p
} }
if profile != "default" { if profile != "default" {
profile = fmt.Sprintf("profile %s", profile) profile = fmt.Sprintf("profile %s", profile)
} }
return f(region, profile), nil region, p := f(profile)
return region, p == profile, nil
} }
func (h contextCreateAWSHelper) chooseRegion(region string, profile string) (string, error) { func (h contextCreateAWSHelper) chooseRegion(region string, profile string) (string, error) {
suggestion, err := h.getRegionSuggestion(region, profile) suggestion := region
if err != nil { if suggestion == "" {
return "", err region, _, err := getRegion(profile)
if err != nil {
return "", err
}
suggestion = region
} }
// promp user for region // promp user for region
region, err = h.user.Input("Region", suggestion) region, err := h.user.Input("Region", suggestion)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -327,14 +331,6 @@ func (h contextCreateAWSHelper) chooseRegion(region string, profile string) (str
} }
func (h contextCreateAWSHelper) askCredentials() (string, string, error) { func (h contextCreateAWSHelper) askCredentials() (string, string, error) {
/*confirm, err := h.user.Confirm("Enter AWS credentials", false)
if err != nil {
return "", "", err
}
if !confirm {
return "", "", nil
}*/
accessKeyID, err := h.user.Input("AWS Access Key ID", "") accessKeyID, err := h.user.Input("AWS Access Key ID", "")
if err != nil { if err != nil {
return "", "", err return "", "", err

View File

@ -153,16 +153,15 @@ 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", "default", "--region", region) res = c.RunDockerCmd("context", "create", "ecs", contextName, "--from-env")
} else { } else {
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) res = c.RunDockerCmd("context", "create", "ecs", contextName, "--from-env")
} }
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)