Ver código fonte

add private images support

Signed-off-by: aiordache <[email protected]>
Signed-off-by: Nicolas De Loof <[email protected]>
aiordache 5 anos atrás
pai
commit
d09c8c7236

+ 12 - 5
ecs/cmd/commands/secret.go

@@ -16,7 +16,10 @@ import (
 )
 
 type createSecretOptions struct {
-	Label string
+	Label       string
+	Username    string
+	Password    string
+	Description string
 }
 
 type deleteSecretOptions struct {
@@ -39,9 +42,9 @@ func SecretCommand(dockerCli command.Cli) *cobra.Command {
 }
 
 func CreateSecret(dockerCli command.Cli) *cobra.Command {
-	//opts := createSecretOptions{}
+	opts := createSecretOptions{}
 	cmd := &cobra.Command{
-		Use:   "create NAME SECRET",
+		Use:   "create NAME",
 		Short: "Creates a secret.",
 		RunE: docker.WithAwsContext(dockerCli, func(clusteropts docker.AwsContext, args []string) error {
 			client, err := amazon.NewClient(clusteropts.Profile, clusteropts.Cluster, clusteropts.Region)
@@ -52,12 +55,16 @@ func CreateSecret(dockerCli command.Cli) *cobra.Command {
 				return errors.New("Missing mandatory parameter: NAME")
 			}
 			name := args[0]
-			secret := args[1]
-			id, err := client.CreateSecret(context.Background(), name, secret)
+
+			secret := docker.NewSecret(name, opts.Username, opts.Password, opts.Description)
+			id, err := client.CreateSecret(context.Background(), secret)
 			fmt.Println(id)
 			return err
 		}),
 	}
+	cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "username")
+	cmd.Flags().StringVarP(&opts.Password, "password", "p", "", "password")
+	cmd.Flags().StringVarP(&opts.Description, "description", "d", "", "Secret description")
 	return cmd
 }
 

+ 43 - 5
ecs/pkg/amazon/cloudformation.go

@@ -55,17 +55,28 @@ func (c client) Convert(ctx context.Context, project *compose.Project) (*cloudfo
 		}
 
 		taskExecutionRole := fmt.Sprintf("%sTaskExecutionRole", service.Name)
+		policy, err := c.getPolicy(ctx, definition)
+		if err != nil {
+			return nil, err
+		}
+		rolePolicies := []iam.Role_Policy{}
+		if policy != nil {
+			rolePolicies = append(rolePolicies, iam.Role_Policy{
+				PolicyDocument: policy,
+				PolicyName:     taskExecutionRole,
+			})
+
+		}
+		definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
+
+		taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
 		template.Resources[taskExecutionRole] = &iam.Role{
 			AssumeRolePolicyDocument: assumeRolePolicyDocument,
-			// Here we can grant access to secrets/configs using a Policy { Allow,ssm:GetParameters,secret|config ARN}
+			Policies:                 rolePolicies,
 			ManagedPolicyArns: []string{
 				ECSTaskExecutionPolicy,
 			},
 		}
-		definition.ExecutionRoleArn = cloudformation.Ref(taskExecutionRole)
-		// FIXME definition.TaskRoleArn = ?
-
-		taskDefinition := fmt.Sprintf("%sTaskDefinition", service.Name)
 		template.Resources[taskDefinition] = definition
 
 		var healthCheck *cloudmap.Service_HealthCheckConfig
@@ -182,6 +193,33 @@ func (c client) GetVPC(ctx context.Context, project *compose.Project) (string, e
 	return defaultVPC, nil
 }
 
+func (c client) getPolicy(ctx context.Context, taskDef *ecs.TaskDefinition) (*PolicyDocument, error) {
+
+	arns := []string{}
+	for _, container := range taskDef.ContainerDefinitions {
+		if container.RepositoryCredentials != nil {
+			arns = append(arns, container.RepositoryCredentials.CredentialsParameter)
+		}
+		if len(container.Secrets) > 0 {
+			for _, s := range container.Secrets {
+				arns = append(arns, s.ValueFrom)
+			}
+		}
+
+	}
+	if len(arns) > 0 {
+		return &PolicyDocument{
+			Statement: []PolicyStatement{
+				{
+					Effect:   "Allow",
+					Action:   []string{"secretsmanager:GetSecretValue", "ssm:GetParameters", "kms:Decrypt"},
+					Resource: arns,
+				}},
+		}, nil
+	}
+	return nil, nil
+}
+
 type convertAPI interface {
 	GetDefaultVPC(ctx context.Context) (string, error)
 	VpcExists(ctx context.Context, vpcID string) (bool, error)

+ 19 - 14
ecs/pkg/amazon/convert.go

@@ -44,7 +44,7 @@ func Convert(project *compose.Project, service types.ServiceConfig) (*ecs.TaskDe
 				FirelensConfiguration: nil,
 				HealthCheck:           toHealthCheck(service.HealthCheck),
 				Hostname:              service.Hostname,
-				Image:                 service.Image,
+				Image:                 getImage(service.Image),
 				Interactive:           false,
 				Links:                 nil,
 				LinuxParameters:       toLinuxParameters(service),
@@ -282,22 +282,27 @@ func toKeyValuePair(environment types.MappingWithEquals) []ecs.TaskDefinition_Ke
 	return pairs
 }
 
+func getImage(image string) string {
+	switch f := strings.Split(image, "/"); len(f) {
+	case 1:
+		return "docker.io/library/" + image
+	case 2:
+		return "docker.io/" + image
+	default:
+		return image
+	}
+}
+
 func getRepoCredentials(service types.ServiceConfig) (*ecs.TaskDefinition_RepositoryCredentials, error) {
 	// extract registry and namespace string from image name
-	fields := strings.Split(service.Image, "/")
-	regPath := ""
-	for i, field := range fields {
-		if i < len(fields)-1 {
-			regPath = regPath + field
+	credential := ""
+	for key, value := range service.Extras {
+		if strings.HasPrefix(key, "x-aws-pull_credentials") {
+			credential = value.(string)
 		}
 	}
-	if regPath == "" || len(service.Secrets) == 0 {
-		return nil, nil
-	}
-	for _, secret := range service.Secrets {
-		if secret.Source == regPath {
-			return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: secret.Target}, nil
-		}
+	if credential != "" {
+		return &ecs.TaskDefinition_RepositoryCredentials{CredentialsParameter: credential}, nil
 	}
 	return nil, nil
 }
@@ -306,7 +311,7 @@ func getSecrets(service types.ServiceConfig) ([]ecs.TaskDefinition_Secret, error
 	secrets := []ecs.TaskDefinition_Secret{}
 
 	for _, secret := range service.Secrets {
-		secrets = append(secrets, ecs.TaskDefinition_Secret{Name: secret.Target})
+		secrets = append(secrets, ecs.TaskDefinition_Secret{Name: secret.Target, ValueFrom: secret.Source})
 	}
 	return secrets, nil
 }

+ 6 - 5
ecs/pkg/amazon/mock/api.go

@@ -6,11 +6,12 @@ package mock
 
 import (
 	context "context"
+	reflect "reflect"
+
 	cloudformation "github.com/aws/aws-sdk-go/service/cloudformation"
 	cloudformation0 "github.com/awslabs/goformation/v4/cloudformation"
 	docker "github.com/docker/ecs-plugin/pkg/docker"
 	gomock "github.com/golang/mock/gomock"
-	reflect "reflect"
 )
 
 // MockAPI is a mock of API interface
@@ -67,18 +68,18 @@ func (mr *MockAPIMockRecorder) CreateCluster(arg0, arg1 interface{}) *gomock.Cal
 }
 
 // CreateSecret mocks base method
-func (m *MockAPI) CreateSecret(arg0 context.Context, arg1, arg2 string) (string, error) {
+func (m *MockAPI) CreateSecret(arg0 context.Context, arg1 docker.Secret) (string, error) {
 	m.ctrl.T.Helper()
-	ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1, arg2)
+	ret := m.ctrl.Call(m, "CreateSecret", arg0, arg1)
 	ret0, _ := ret[0].(string)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
 
 // CreateSecret indicates an expected call of CreateSecret
-func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1, arg2 interface{}) *gomock.Call {
+func (mr *MockAPIMockRecorder) CreateSecret(arg0, arg1 docker.Secret) *gomock.Call {
 	mr.mock.ctrl.T.Helper()
-	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1, arg2)
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSecret", reflect.TypeOf((*MockAPI)(nil).CreateSecret), arg0, arg1)
 }
 
 // CreateStack mocks base method

+ 12 - 3
ecs/pkg/amazon/sdk.go

@@ -223,9 +223,18 @@ func (s sdk) DeleteStack(ctx context.Context, name string) error {
 	return err
 }
 
-func (s sdk) CreateSecret(ctx context.Context, name string, secret string) (string, error) {
-	logrus.Debug("Create secret " + name)
-	response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{Name: &name, SecretString: &secret})
+func (s sdk) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
+	logrus.Debug("Create secret " + secret.Name)
+	secretStr, err := secret.GetCredString()
+	if err != nil {
+		return "", err
+	}
+
+	response, err := s.SM.CreateSecret(&secretsmanager.CreateSecretInput{
+		Name:         &secret.Name,
+		SecretString: &secretStr,
+		Description:  &secret.Description,
+	})
 	if err != nil {
 		return "", err
 	}

+ 3 - 3
ecs/pkg/amazon/secrets.go

@@ -7,14 +7,14 @@ import (
 )
 
 type secretsAPI interface {
-	CreateSecret(ctx context.Context, name string, content string) (string, error)
+	CreateSecret(ctx context.Context, secret docker.Secret) (string, error)
 	InspectSecret(ctx context.Context, id string) (docker.Secret, error)
 	ListSecrets(ctx context.Context) ([]docker.Secret, error)
 	DeleteSecret(ctx context.Context, id string, recover bool) error
 }
 
-func (c client) CreateSecret(ctx context.Context, name string, content string) (string, error) {
-	return c.api.CreateSecret(ctx, name, content)
+func (c client) CreateSecret(ctx context.Context, secret docker.Secret) (string, error) {
+	return c.api.CreateSecret(ctx, secret)
 }
 
 func (c client) InspectSecret(ctx context.Context, id string) (docker.Secret, error) {

+ 1 - 1
ecs/pkg/compose/api.go

@@ -12,7 +12,7 @@ type API interface {
 	ComposeUp(ctx context.Context, project *Project) error
 	ComposeDown(ctx context.Context, projectName string, deleteCluster bool) error
 
-	CreateSecret(ctx context.Context, name string, secret string) (string, error)
+	CreateSecret(ctx context.Context, secret docker.Secret) (string, error)
 	InspectSecret(ctx context.Context, id string) (docker.Secret, error)
 	ListSecrets(ctx context.Context) ([]docker.Secret, error)
 	DeleteSecret(ctx context.Context, id string, recover bool) error

+ 23 - 0
ecs/pkg/docker/secret.go

@@ -9,6 +9,17 @@ type Secret struct {
 	Name        string            `json:"Name"`
 	Labels      map[string]string `json:"Labels"`
 	Description string            `json:"Description"`
+	username    string
+	password    string
+}
+
+func NewSecret(name, username, password, description string) Secret {
+	return Secret{
+		Name:        name,
+		username:    username,
+		password:    password,
+		Description: description,
+	}
 }
 
 func (s Secret) ToJSON() (string, error) {
@@ -18,3 +29,15 @@ func (s Secret) ToJSON() (string, error) {
 	}
 	return string(b), nil
 }
+
+func (s Secret) GetCredString() (string, error) {
+	creds := map[string]string{
+		"username": s.username,
+		"password": s.password,
+	}
+	b, err := json.Marshal(&creds)
+	if err != nil {
+		return "", err
+	}
+	return string(b), nil
+}