Merge pull request #846 from docker/ecs_context

This commit is contained in:
Nicolas De loof 2020-11-03 11:38:06 +01:00 committed by GitHub
commit f66123b34a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 595 additions and 127 deletions

View File

@ -18,6 +18,7 @@ package context
import ( import (
"context" "context"
"fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -45,6 +46,10 @@ func createEcsCommand() *cobra.Command {
Short: "Create a context for Amazon ECS", Short: "Create a context for Amazon ECS",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { 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 { if localSimulation {
return runCreateLocalSimulation(cmd.Context(), args[0], opts) return runCreateLocalSimulation(cmd.Context(), args[0], opts)
} }
@ -54,8 +59,8 @@ func createEcsCommand() *cobra.Command {
addDescriptionFlag(cmd, &opts.Description) addDescriptionFlag(cmd, &opts.Description)
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", "", "Use an existing AWS profile")
cmd.Flags().StringVar(&opts.Region, "region", "", "Region") cmd.Flags().BoolVar(&opts.CredsFromEnv, "from-env", false, "Use AWS environment variables for profile, or credentials and region")
return cmd return cmd
} }

View File

@ -51,8 +51,8 @@ type AciContext struct {
// EcsContext is the context for the AWS backend // EcsContext is the context for the AWS backend
type EcsContext struct { type EcsContext struct {
Profile string `json:",omitempty"` CredentialsFromEnv bool `json:",omitempty"`
Region string `json:",omitempty"` Profile string `json:",omitempty"`
} }
// AwsContext is the context for the ecs plugin // AwsContext is the context for the ecs plugin

View File

@ -18,6 +18,7 @@ package ecs
import ( import (
"context" "context"
"fmt"
"github.com/docker/compose-cli/api/compose" "github.com/docker/compose-cli/api/compose"
"github.com/docker/compose-cli/api/containers" "github.com/docker/compose-cli/api/containers"
@ -38,9 +39,23 @@ const backendType = store.EcsContextType
// ContextParams options for creating AWS context // ContextParams options for creating AWS context
type ContextParams struct { type ContextParams struct {
Description string Name string
Region string Description string
Profile 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() { func init() {
@ -60,11 +75,31 @@ func service(ctx context.Context) (backend.Service, error) {
} }
func getEcsAPIService(ecsCtx store.EcsContext) (*ecsAPIService, 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{ 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 {
@ -74,7 +109,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

@ -20,100 +20,186 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath"
"sort"
"strings" "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/context/store"
"github.com/docker/compose-cli/errdefs" "github.com/docker/compose-cli/errdefs"
"github.com/docker/compose-cli/prompt" "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 { type contextCreateAWSHelper struct {
user prompt.UI user prompt.UI
availableRegions func(opts *ContextParams) ([]string, error)
} }
func newContextCreateHelper() contextCreateAWSHelper { func newContextCreateHelper() contextCreateAWSHelper {
return 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) { func (h contextCreateAWSHelper) createContextData(_ context.Context, opts ContextParams) (interface{}, string, error) {
profile := opts.Profile if opts.CredsFromEnv {
region := opts.Region // Explicit creation from ENV variables
ecsCtx, descr := h.createContext(&opts)
profilesList, err := h.getProfiles() return ecsCtx, descr, nil
if err != nil { } else if opts.AccessKey != "" && opts.SecretKey != "" {
return nil, "", err // Explicit creation using keys
} err := h.createProfileFromCredentials(&opts)
if profile != "" { if err != nil {
// validate profile return nil, "", err
if profile != "default" && !contains(profilesList, profile) { }
return nil, "", errors.Wrapf(errdefs.ErrNotFound, "profile %q", profile) } 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 { } else {
// choose profile // interactive
profile, err = h.chooseProfile(profilesList) 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 { if err != nil {
return nil, "", err return nil, "", err
} }
} }
if region == "" {
region, err = h.chooseRegion(region, profile) ecsCtx, descr := h.createContext(&opts)
if err != nil {
return nil, "", err
}
}
ecsCtx, descr := h.createContext(profile, region, opts.Description)
return ecsCtx, descr, nil return ecsCtx, descr, nil
} }
func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID string, secretAccessKey string) error { func (h contextCreateAWSHelper) createContext(c *ContextParams) (interface{}, string) {
p := credentials.SharedCredentialsProvider{Profile: profile} var description string
_, err := p.Retrieve()
if err == nil { if c.CredsFromEnv {
return fmt.Errorf("credentials already exist") 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" { if c.Region != "" {
_, err := os.Create(p.Filename) 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 { if err != nil {
return err 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 { if err != nil {
return err 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) section, err := credIni.NewSection(profile)
if err != nil { if err != nil {
return err return err
@ -126,15 +212,47 @@ func (h contextCreateAWSHelper) saveCredentials(profile string, accessKeyID stri
if err != nil { if err != nil {
return err 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{} 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{
defaults.SharedCredentialsFilename(): false, getAWSCredentialsFile(): false,
defaults.SharedConfigFilename(): true, getAWSConfigFile(): true,
} }
for f, prefix := range configFiles { for f, prefix := range configFiles {
sections, err := loadIniFile(f, prefix) 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 return profiles, nil
} }
func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error) { func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error) {
options := []string{"new profile"} options := []string{}
options = append(options, profiles...) options = append(options, profiles...)
selected, err := h.user.Select("Select AWS Profile", options) selected, err := h.user.Select("Select AWS Profile", options)
@ -166,78 +288,86 @@ func (h contextCreateAWSHelper) chooseProfile(profiles []string) (string, error)
return "", err return "", err
} }
profile := options[selected] 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, profile string) (string, error) { func getRegion(profile string) (string, error) {
suggestion := region if profile == "" {
profile = "default"
}
// only load ~/.aws/config // only load ~/.aws/config
awsConfig := defaults.SharedConfigFilename() awsConfig := defaults.SharedConfigFilename()
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 "", err
} }
configIni = ini.Empty() 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" { if profile != "default" {
profile = fmt.Sprintf("profile %s", profile) profile = fmt.Sprintf("profile %s", profile)
} }
section, err := configIni.GetSection(profile) region := getProfileRegion(profile)
if err != nil { if region == "" {
if !strings.Contains(err.Error(), "does not exist") { region = getProfileRegion("default")
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
} }
if region == "" { 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 return region, nil
_, err = section.NewKey("region", region) }
func (h contextCreateAWSHelper) chooseRegion(opts *ContextParams) error {
regions, err := h.availableRegions(opts)
if err != nil { 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) { 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
@ -277,3 +407,19 @@ func loadIniFile(path string, prefix bool) (map[string]ini.Section, error) {
} }
return profiles, nil 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
}

173
ecs/context_test.go Normal file
View File

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

View File

@ -0,0 +1,3 @@
[profile default]
region = eu-west-3

View File

@ -0,0 +1,4 @@
[default]
aws_access_key_id = ABCD
aws_secret_access_key = X&123

View File

@ -0,0 +1,3 @@
[profile foo]
region = eu-west-3

View File

@ -0,0 +1,4 @@
[foo]
aws_access_key_id = ABCD
aws_secret_access_key = X&123

1
go.sum
View File

@ -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-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-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-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/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-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -20,6 +20,8 @@ import (
"github.com/AlecAivazis/survey/v2" "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 // UI - prompt user input
type UI interface { type UI interface {
Select(message string, options []string) (int, error) Select(message string, options []string) (int, error)

93
prompt/prompt_mock.go Normal file
View File

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

View File

@ -168,16 +168,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)