|
@@ -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
|
|
|
}
|
|
}
|