diff --git a/cli/cmd/context/create_ecs.go b/cli/cmd/context/create_ecs.go index 8dde7b04..5fb68a10 100644 --- a/cli/cmd/context/create_ecs.go +++ b/cli/cmd/context/create_ecs.go @@ -18,6 +18,7 @@ package context import ( "context" + "fmt" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -45,6 +46,10 @@ func createEcsCommand() *cobra.Command { Short: "Create a context for Amazon ECS", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + opts.Name = args[0] + if opts.CredsFromEnv && opts.Profile != "" { + return fmt.Errorf("--profile and --from-env flags cannot be set at the same time") + } if localSimulation { return runCreateLocalSimulation(cmd.Context(), args[0], opts) } @@ -54,8 +59,8 @@ func createEcsCommand() *cobra.Command { addDescriptionFlag(cmd, &opts.Description) 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.Profile, "profile", "", "Use an existing AWS profile") + cmd.Flags().BoolVar(&opts.CredsFromEnv, "from-env", false, "Use AWS environment variables for profile, or credentials and region") return cmd } diff --git a/context/store/contextmetadata.go b/context/store/contextmetadata.go index 771c074d..a34c8313 100644 --- a/context/store/contextmetadata.go +++ b/context/store/contextmetadata.go @@ -51,8 +51,8 @@ type AciContext struct { // EcsContext is the context for the AWS backend type EcsContext struct { - Profile string `json:",omitempty"` - Region string `json:",omitempty"` + CredentialsFromEnv bool `json:",omitempty"` + Profile string `json:",omitempty"` } // AwsContext is the context for the ecs plugin diff --git a/ecs/backend.go b/ecs/backend.go index 5d18bbc8..3831ac45 100644 --- a/ecs/backend.go +++ b/ecs/backend.go @@ -18,6 +18,7 @@ package ecs import ( "context" + "fmt" "github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/containers" @@ -38,9 +39,23 @@ const backendType = store.EcsContextType // ContextParams options for creating AWS context type ContextParams struct { - Description string - Region string - Profile string + Name string + Description string + AccessKey string + SecretKey string + Profile string + Region string + CredsFromEnv bool +} + +func (c ContextParams) haveRequiredEnvVars() bool { + if c.Profile != "" { + return true + } + if c.AccessKey != "" && c.SecretKey != "" { + return true + } + return false } func init() { @@ -60,11 +75,31 @@ func service(ctx context.Context) (backend.Service, error) { } func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) { + region := "" + profile := ecsCtx.Profile + + if ecsCtx.CredentialsFromEnv { + env := getEnvVars() + if !env.haveRequiredEnvVars() { + return nil, fmt.Errorf("context requires credentials to be passed as environment variables") + } + profile = env.Profile + region = env.Region + } + + if region == "" { + r, err := getRegion(profile) + if err != nil { + return nil, err + } + region = r + } + sess, err := session.NewSessionWithOptions(session.Options{ - Profile: ecsCtx.Profile, + Profile: profile, SharedConfigState: session.SharedConfigEnable, Config: aws.Config{ - Region: aws.String(ecsCtx.Region), + Region: aws.String(region), }, }) if err != nil { @@ -74,7 +109,7 @@ func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, error) { sdk := newSDK(sess) return &ecsAPIService{ ctx: ecsCtx, - Region: ecsCtx.Region, + Region: region, aws: sdk, }, nil } diff --git a/ecs/context.go b/ecs/context.go index ce91a853..5d636ac3 100644 --- a/ecs/context.go +++ b/ecs/context.go @@ -20,100 +20,186 @@ import ( "context" "fmt" "os" + "path/filepath" + "sort" "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" "github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/prompt" + + "github.com/AlecAivazis/survey/v2/terminal" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/defaults" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/pkg/errors" + "gopkg.in/ini.v1" ) +func getEnvVars() ContextParams { + c := ContextParams{ + Profile: os.Getenv("AWS_PROFILE"), + Region: os.Getenv("AWS_REGION"), + } + if c.Region == "" { + defaultRegion := os.Getenv("AWS_DEFAULT_REGION") + if defaultRegion == "" { + defaultRegion = "us-east-1" + } + c.Region = defaultRegion + } + + p := credentials.EnvProvider{} + creds, err := p.Retrieve() + if err != nil { + return c + } + c.AccessKey = creds.AccessKeyID + c.SecretKey = creds.SecretAccessKey + return c +} + type contextCreateAWSHelper struct { - user prompt.UI + user prompt.UI + availableRegions func(opts *ContextParams) ([]string, error) } func newContextCreateHelper() contextCreateAWSHelper { return contextCreateAWSHelper{ - user: prompt.User{}, + user: prompt.User{}, + availableRegions: listAvailableRegions, } } -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 -} - -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) + if opts.CredsFromEnv { + // Explicit creation from ENV variables + ecsCtx, descr := h.createContext(&opts) + return ecsCtx, descr, nil + } else if opts.AccessKey != "" && opts.SecretKey != "" { + // Explicit creation using keys + err := h.createProfileFromCredentials(&opts) + if err != nil { + return nil, "", err + } + } else if opts.Profile != "" { + // Excplicit creation by selecting a profile + // check profile exists + profilesList, err := getProfiles() + if err != nil { + return nil, "", err + } + if !contains(profilesList, opts.Profile) { + return nil, "", errors.Wrapf(errdefs.ErrNotFound, "profile %q not found", opts.Profile) } } else { - // choose profile - profile, err = h.chooseProfile(profilesList) + // interactive + var options []string + var actions []func(params *ContextParams) error + + if _, err := os.Stat(getAWSConfigFile()); err == nil { + // User has .aws/config file, so we can offer to select one of his profiles + options = append(options, "An existing AWS profile") + actions = append(actions, h.selectFromLocalProfile) + } + + options = append(options, "AWS secret and token credentials") + actions = append(actions, h.createProfileFromCredentials) + + options = append(options, "AWS environment variables") + actions = append(actions, func(params *ContextParams) error { + opts.CredsFromEnv = true + return nil + }) + + selected, err := h.user.Select("Create a Docker context using:", options) + if err != nil { + if err == terminal.InterruptErr { + return nil, "", errdefs.ErrCanceled + } + return nil, "", err + } + + err = actions[selected](&opts) if err != nil { return nil, "", err } } - if region == "" { - region, err = h.chooseRegion(region, profile) - if err != nil { - return nil, "", err - } - } - ecsCtx, descr := h.createContext(profile, region, opts.Description) + + ecsCtx, descr := h.createContext(&opts) return ecsCtx, descr, nil } -func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID string, secretAccessKey string) error { - p := credentials.SharedCredentialsProvider{Profile: profile} - _, err := p.Retrieve() - if err == nil { - return fmt.Errorf("credentials already exist") +func (h contextCreateAWSHelper) createContext(c *ContextParams) (interface{}, string) { + var description string + + if c.CredsFromEnv { + if c.Description == "" { + description = "credentials read from environment" + } + return store.EcsContext{ + CredentialsFromEnv: c.CredsFromEnv, + Profile: c.Profile, + }, description } - if err.(awserr.Error).Code() == "SharedCredsLoad" && err.(awserr.Error).Message() == "failed to load shared credentials file" { - _, err := os.Create(p.Filename) + if c.Region != "" { + description = strings.TrimSpace( + fmt.Sprintf("%s (%s)", c.Description, c.Region)) + } + return store.EcsContext{ + Profile: c.Profile, + }, description +} + +func (h contextCreateAWSHelper) selectFromLocalProfile(opts *ContextParams) error { + profilesList, err := getProfiles() + if err != nil { + return err + } + opts.Profile, err = h.chooseProfile(profilesList) + return err +} + +func (h contextCreateAWSHelper) createProfileFromCredentials(opts *ContextParams) error { + if opts.AccessKey == "" || opts.SecretKey == "" { + fmt.Println("Retrieve or create AWS Access Key and Secret on https://console.aws.amazon.com/iam/home?#security_credential") + accessKey, secretKey, err := h.askCredentials() + if err != nil { + return err + } + opts.AccessKey = accessKey + opts.SecretKey = secretKey + } + + if opts.Region == "" { + err := h.chooseRegion(opts) if err != nil { return err } } - credIni, err := ini.Load(p.Filename) + // save as a profile + if opts.Profile == "" { + opts.Profile = "default" + } + // context name used as profile name + err := h.saveCredentials(opts.Profile, opts.AccessKey, opts.SecretKey) if err != nil { return err } + return h.saveRegion(opts.Profile, opts.Region) +} + +func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID string, secretAccessKey string) error { + file := getAWSCredentialsFile() + err := os.MkdirAll(filepath.Dir(file), 0700) + if err != nil { + return err + } + + credIni := ini.Empty() section, err := credIni.NewSection(profile) if err != nil { return err @@ -126,15 +212,47 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri if err != nil { return err } - return credIni.SaveTo(p.Filename) + return credIni.SaveTo(file) } -func (h contextCreateAWSHelper) getProfiles() ([]string, error) { +func (h contextCreateAWSHelper) saveRegion(profile, region string) error { + if region == "" { + return nil + } + // loads ~/.aws/config + awsConfig := getAWSConfigFile() + configIni, err := ini.Load(awsConfig) + if err != nil { + if !os.IsNotExist(err) { + return err + } + configIni = ini.Empty() + } + 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 + } + } + // save region under profile section in ~/.aws/config + _, err = section.NewKey("region", region) + if err != nil { + return err + } + return configIni.SaveTo(awsConfig) +} + +func getProfiles() ([]string, error) { profiles := []string{} // parse both .aws/credentials and .aws/config for profiles configFiles := map[string]bool{ - defaults.SharedCredentialsFilename(): false, - defaults.SharedConfigFilename(): true, + getAWSCredentialsFile(): false, + getAWSConfigFile(): true, } for f, prefix := range configFiles { sections, err := loadIniFile(f, prefix) @@ -151,11 +269,15 @@ func (h contextCreateAWSHelper) getProfiles() ([]string, error) { } } } + sort.Slice(profiles, func(i, j int) bool { + return profiles[i] < profiles[j] + }) + return profiles, nil } func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error) { - options := []string{"new profile"} + options := []string{} options = append(options, profiles...) selected, err := h.user.Select("Select AWS Profile", options) @@ -166,78 +288,86 @@ func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error) return "", err } 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, profile string) (string, error) { - suggestion := region - +func getRegion(profile string) (string, error) { + if profile == "" { + profile = "default" + } // only load ~/.aws/config awsConfig := defaults.SharedConfigFilename() configIni, err := ini.Load(awsConfig) - if err != nil { if !os.IsNotExist(err) { return "", err } configIni = ini.Empty() } + + getProfileRegion := func(p string) string { + r := "" + section, err := configIni.GetSection(p) + if err == nil { + reg, err := section.GetKey("region") + if err == nil { + r = reg.Value() + } + } + return r + } 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 - } - } - 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 + region := getProfileRegion(profile) + if region == "" { + region = getProfileRegion("default") } if region == "" { - return "", fmt.Errorf("region cannot be empty") + // fallback to AWS default + region = "us-east-1" } - // save selected/typed region under profile in ~/.aws/config - _, err = section.NewKey("region", region) + return region, nil +} + +func (h contextCreateAWSHelper) chooseRegion(opts *ContextParams) error { + regions, err := h.availableRegions(opts) if err != nil { - return "", err + return err } - return region, configIni.SaveTo(awsConfig) + // promp user for region + selected, err := h.user.Select("Region", regions) + if err != nil { + return err + } + opts.Region = regions[selected] + return nil +} + +func listAvailableRegions(opts *ContextParams) ([]string, error) { + // Setup SDK with credentials, will also validate those + session, err := session.NewSessionWithOptions(session.Options{ + Config: aws.Config{ + Credentials: credentials.NewStaticCredentials(opts.AccessKey, opts.SecretKey, ""), + Region: aws.String("us-east-1"), + }, + }) + if err != nil { + return nil, err + } + + desc, err := ec2.New(session).DescribeRegions(&ec2.DescribeRegionsInput{}) + if err != nil { + return nil, err + } + var regions []string + for _, r := range desc.Regions { + regions = append(regions, aws.StringValue(r.RegionName)) + } + return regions, nil } 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", "") if err != nil { return "", "", err @@ -277,3 +407,19 @@ func loadIniFile(path string, prefix bool) (map[string]ini.Section, error) { } return profiles, nil } + +func getAWSConfigFile() string { + awsConfig, ok := os.LookupEnv("AWS_CONFIG_FILE") + if !ok { + awsConfig = defaults.SharedConfigFilename() + } + return awsConfig +} + +func getAWSCredentialsFile() string { + awsConfig, ok := os.LookupEnv("AWS_SHARED_CREDENTIALS_FILE") + if !ok { + awsConfig = defaults.SharedCredentialsFilename() + } + return awsConfig +} diff --git a/ecs/context_test.go b/ecs/context_test.go new file mode 100644 index 00000000..a69385e7 --- /dev/null +++ b/ecs/context_test.go @@ -0,0 +1,173 @@ +/* + Copyright 2020 Docker Compose CLI authors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package ecs + +import ( + "context" + "os" + "testing" + + "github.com/docker/compose-cli/context/store" + "github.com/docker/compose-cli/prompt" + + "github.com/golang/mock/gomock" + "gotest.tools/v3/assert" + "gotest.tools/v3/fs" + "gotest.tools/v3/golden" +) + +func TestCreateContextDataFromEnv(t *testing.T) { + c := contextCreateAWSHelper{ + user: nil, + } + data, desc, err := c.createContextData(context.TODO(), ContextParams{ + Name: "test", + CredsFromEnv: true, + }) + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).CredentialsFromEnv, true) + assert.Equal(t, desc, "credentials read from environment") +} + +func TestCreateContextDataByKeys(t *testing.T) { + dir := fs.NewDir(t, "aws") + os.Setenv("AWS_CONFIG_FILE", dir.Join("config")) // nolint:errcheck + os.Setenv("AWS_SHARED_CREDENTIALS_FILE", dir.Join("credentials")) // nolint:errcheck + + defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck + defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck + + c := contextCreateAWSHelper{ + user: nil, + } + + data, _, err := c.createContextData(context.TODO(), ContextParams{ + Name: "test", + AccessKey: "ABCD", + SecretKey: "X&123", + Region: "eu-west-3", + }) + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).Profile, "default") + + s := golden.Get(t, dir.Join("config")) + golden.Assert(t, string(s), "context/by-keys/config.golden") + + s = golden.Get(t, dir.Join("credentials")) + golden.Assert(t, string(s), "context/by-keys/credentials.golden") +} + +func TestCreateContextDataFromProfile(t *testing.T) { + os.Setenv("AWS_CONFIG_FILE", "testdata/context/by-profile/config.golden") // nolint:errcheck + os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/context/by-profile/credentials.golden") // nolint:errcheck + + defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck + defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck + + c := contextCreateAWSHelper{ + user: nil, + } + + data, _, err := c.createContextData(context.TODO(), ContextParams{ + Name: "test", + Profile: "foo", + }) + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).Profile, "foo") +} + +func TestCreateContextDataFromEnvInteractive(t *testing.T) { + dir := fs.NewDir(t, "aws") + os.Setenv("AWS_CONFIG_FILE", dir.Join("config")) // nolint:errcheck + os.Setenv("AWS_SHARED_CREDENTIALS_FILE", dir.Join("credentials")) // nolint:errcheck + + defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck + defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ui := prompt.NewMockUI(ctrl) + c := contextCreateAWSHelper{ + user: ui, + } + + ui.EXPECT().Select("Create a Docker context using:", gomock.Any()).Return(1, nil) + data, _, err := c.createContextData(context.TODO(), ContextParams{}) + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).CredentialsFromEnv, true) +} + +func TestCreateContextDataByKeysInteractive(t *testing.T) { + dir := fs.NewDir(t, "aws") + os.Setenv("AWS_CONFIG_FILE", dir.Join("config")) // nolint:errcheck + os.Setenv("AWS_SHARED_CREDENTIALS_FILE", dir.Join("credentials")) // nolint:errcheck + + defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck + defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ui := prompt.NewMockUI(ctrl) + c := contextCreateAWSHelper{ + user: ui, + availableRegions: func(opts *ContextParams) ([]string, error) { + return []string{"us-east-1", "eu-west-3"}, nil + }, + } + + ui.EXPECT().Select("Create a Docker context using:", gomock.Any()).Return(0, nil) + ui.EXPECT().Input("AWS Access Key ID", gomock.Any()).Return("ABCD", nil) + ui.EXPECT().Password("Enter AWS Secret Access Key").Return("X&123", nil) + ui.EXPECT().Select("Region", []string{"us-east-1", "eu-west-3"}).Return(1, nil) + + data, _, err := c.createContextData(context.TODO(), ContextParams{}) + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).Profile, "default") + + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).Profile, "default") + + s := golden.Get(t, dir.Join("config")) + golden.Assert(t, string(s), "context/by-keys/config.golden") + + s = golden.Get(t, dir.Join("credentials")) + golden.Assert(t, string(s), "context/by-keys/credentials.golden") +} + +func TestCreateContextDataByProfileInteractive(t *testing.T) { + os.Setenv("AWS_CONFIG_FILE", "testdata/context/by-profile/config.golden") // nolint:errcheck + os.Setenv("AWS_SHARED_CREDENTIALS_FILE", "testdata/context/by-profile/credentials.golden") // nolint:errcheck + + defer os.Unsetenv("AWS_CONFIG_FILE") // nolint:errcheck + defer os.Unsetenv("AWS_SHARED_CREDENTIALS_FILE") // nolint:errcheck + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + ui := prompt.NewMockUI(ctrl) + c := contextCreateAWSHelper{ + user: ui, + } + ui.EXPECT().Select("Create a Docker context using:", gomock.Any()).Return(0, nil) + ui.EXPECT().Select("Select AWS Profile", []string{"default", "foo"}).Return(1, nil) + + data, _, err := c.createContextData(context.TODO(), ContextParams{}) + assert.NilError(t, err) + assert.Equal(t, data.(store.EcsContext).Profile, "foo") +} diff --git a/ecs/testdata/context/by-keys/config.golden b/ecs/testdata/context/by-keys/config.golden new file mode 100644 index 00000000..6d231832 --- /dev/null +++ b/ecs/testdata/context/by-keys/config.golden @@ -0,0 +1,3 @@ +[profile default] +region = eu-west-3 + diff --git a/ecs/testdata/context/by-keys/credentials.golden b/ecs/testdata/context/by-keys/credentials.golden new file mode 100644 index 00000000..2c69e47e --- /dev/null +++ b/ecs/testdata/context/by-keys/credentials.golden @@ -0,0 +1,4 @@ +[default] +aws_access_key_id = ABCD +aws_secret_access_key = X&123 + diff --git a/ecs/testdata/context/by-profile/config.golden b/ecs/testdata/context/by-profile/config.golden new file mode 100644 index 00000000..e388f9b0 --- /dev/null +++ b/ecs/testdata/context/by-profile/config.golden @@ -0,0 +1,3 @@ +[profile foo] +region = eu-west-3 + diff --git a/ecs/testdata/context/by-profile/credentials.golden b/ecs/testdata/context/by-profile/credentials.golden new file mode 100644 index 00000000..7146be5f --- /dev/null +++ b/ecs/testdata/context/by-profile/credentials.golden @@ -0,0 +1,4 @@ +[foo] +aws_access_key_id = ABCD +aws_secret_access_key = X&123 + diff --git a/go.sum b/go.sum index bb00d44a..d5c3e992 100644 --- a/go.sum +++ b/go.sum @@ -748,6 +748,7 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200717024301-6ddee64345a6/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d h1:W07d4xkoAUSNOkOzdzXCdFGxT7o2rW4q8M34tB2i//k= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/prompt/prompt.go b/prompt/prompt.go index ac0ff942..f30fbce3 100644 --- a/prompt/prompt.go +++ b/prompt/prompt.go @@ -20,6 +20,8 @@ import ( "github.com/AlecAivazis/survey/v2" ) +//go:generate mockgen -destination=./prompt_mock.go -self_package "github.com/docker/compose-cli/prompt" -package=prompt . UI + // UI - prompt user input type UI interface { Select(message string, options []string) (int, error) diff --git a/prompt/prompt_mock.go b/prompt/prompt_mock.go new file mode 100644 index 00000000..06886e0f --- /dev/null +++ b/prompt/prompt_mock.go @@ -0,0 +1,93 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/docker/compose-cli/prompt (interfaces: UI) + +// Package prompt is a generated GoMock package. +package prompt + +import ( + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockUI is a mock of UI interface +type MockUI struct { + ctrl *gomock.Controller + recorder *MockUIMockRecorder +} + +// MockUIMockRecorder is the mock recorder for MockUI +type MockUIMockRecorder struct { + mock *MockUI +} + +// NewMockUI creates a new mock instance +func NewMockUI(ctrl *gomock.Controller) *MockUI { + mock := &MockUI{ctrl: ctrl} + mock.recorder = &MockUIMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockUI) EXPECT() *MockUIMockRecorder { + return m.recorder +} + +// Confirm mocks base method +func (m *MockUI) Confirm(arg0 string, arg1 bool) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Confirm", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Confirm indicates an expected call of Confirm +func (mr *MockUIMockRecorder) Confirm(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Confirm", reflect.TypeOf((*MockUI)(nil).Confirm), arg0, arg1) +} + +// Input mocks base method +func (m *MockUI) Input(arg0, arg1 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Input", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Input indicates an expected call of Input +func (mr *MockUIMockRecorder) Input(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Input", reflect.TypeOf((*MockUI)(nil).Input), arg0, arg1) +} + +// Password mocks base method +func (m *MockUI) Password(arg0 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Password", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Password indicates an expected call of Password +func (mr *MockUIMockRecorder) Password(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Password", reflect.TypeOf((*MockUI)(nil).Password), arg0) +} + +// Select mocks base method +func (m *MockUI) Select(arg0 string, arg1 []string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Select", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Select indicates an expected call of Select +func (mr *MockUIMockRecorder) Select(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Select", reflect.TypeOf((*MockUI)(nil).Select), arg0, arg1) +} diff --git a/tests/ecs-e2e/e2e-ecs_test.go b/tests/ecs-e2e/e2e-ecs_test.go index c780d1ee..f028ca9b 100644 --- a/tests/ecs-e2e/e2e-ecs_test.go +++ b/tests/ecs-e2e/e2e-ecs_test.go @@ -168,16 +168,15 @@ 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", "default", "--region", region) + res = c.RunDockerCmd("context", "create", "ecs", contextName, "--from-env") } else { - 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) + res = c.RunDockerCmd("context", "create", "ecs", contextName, "--from-env") } res.Assert(t, icmd.Expected{Out: "Successfully created ecs context \"" + contextName + "\""}) res = c.RunDockerCmd("context", "use", contextName)