Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature for attaching multiple target groups in a service #1013

Merged
merged 12 commits into from
Apr 28, 2020
38 changes: 33 additions & 5 deletions ecs-cli/modules/cli/compose/entity/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Service struct {
ecsContext *context.ECSContext
deploymentConfig *ecs.DeploymentConfiguration
loadBalancer *ecs.LoadBalancer
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
loadBalancers []*ecs.LoadBalancer
role string
healthCheckGP *int64
serviceRegistries []*ecs.ServiceRegistry
Expand All @@ -71,8 +72,8 @@ var waitUntilSDSDeletable route53.WaitUntilSDSDeletableFunc = route53.WaitUntilS
// NewService creates an instance of a Service and also sets up a cache for task definition
func NewService(ecsContext *context.ECSContext) entity.ProjectEntity {
return &Service{
cache: entity.SetupTaskDefinitionCache(),
ecsContext: ecsContext,
cache: entity.SetupTaskDefinitionCache(),
ecsContext: ecsContext,
}
}

Expand All @@ -97,6 +98,7 @@ func (s *Service) LoadContext() error {
role := s.Context().CLIContext.String(flags.RoleFlag)
targetGroupArn := s.Context().CLIContext.String(flags.TargetGroupArnFlag)
loadBalancerName := s.Context().CLIContext.String(flags.LoadBalancerNameFlag)
targetGroupsList := s.Context().CLIContext.StringSlice(flags.TargetGroupsFlag)
containerName := s.Context().CLIContext.String(flags.ContainerNameFlag)
containerPort, err := getInt64FromCLIContext(s.Context(), flags.ContainerPortFlag)
if err != nil {
Expand All @@ -117,11 +119,22 @@ func (s *Service) LoadContext() error {
// specified do not exist.
// TODO: Add validation on targetGroupArn or loadBalancerName being
// present if containerName or containerPort are specified
if role != "" || targetGroupArn != "" || loadBalancerName != "" || containerName != "" || containerPort != nil {
if targetGroupArn != "" || loadBalancerName != "" || containerName != "" || containerPort != nil {
if role == "" {
return errors.Errorf("[--%s] is required when either when [--%s] or [--%s] flag is specified", flags.RoleFlag, flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
}
if containerName == "" {
return errors.Errorf("[--%s] is required if [--%s] or [--%s] is specified", flags.ContainerNameFlag, flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
}
if containerPort == nil {
return errors.Errorf("[--%s] is required if [--%s] or [--%s] is specified", flags.ContainerPortFlag, flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
}
if targetGroupArn != "" && loadBalancerName != "" {
return errors.Errorf("[--%s] and [--%s] flags cannot both be specified", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
}

if targetGroupArn == "" && loadBalancerName == "" {
return errors.Errorf("Must specify either [--%s] or [--%s] flag for your service", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
}
s.loadBalancer = &ecs.LoadBalancer{
ContainerName: aws.String(containerName),
ContainerPort: containerPort,
Expand All @@ -132,8 +145,23 @@ func (s *Service) LoadContext() error {
if loadBalancerName != "" {
s.loadBalancer.LoadBalancerName = aws.String(loadBalancerName)
}
s.loadBalancers = []*ecs.LoadBalancer{s.loadBalancer}
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
// s.loadBalancers = append(s.loadBalancers, s.loadBalancer)
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
s.role = role
}

if len(targetGroupsList) != 0 {
if role == "" {
return errors.Errorf("[--%s] is required when either when [--%s] flag is specified", flags.RoleFlag, flags.TargetGroupsFlag)
}
loadBalancers, err := utils.ParseLoadBalancers(targetGroupsList)
if err != nil {
return err
}
s.loadBalancers = append(s.loadBalancers, loadBalancers...)
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
s.role = role
}

return nil
}

Expand Down Expand Up @@ -511,7 +539,7 @@ func (s *Service) buildCreateServiceInput(serviceName, taskDefName string, desir
TaskDefinition: aws.String(taskDefName), // Required
Cluster: aws.String(cluster),
DeploymentConfiguration: s.deploymentConfig,
LoadBalancers: []*ecs.LoadBalancer{s.loadBalancer},
LoadBalancers: s.loadBalancers,
Role: aws.String(s.role),
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,10 +222,11 @@ func deploymentConfigFlags(specifyDefaults bool) []cli.Flag {
}

func loadBalancerFlags() []cli.Flag {
targetGroupArnUsageString := fmt.Sprintf("[Optional] Specifies the full Amazon Resource Name (ARN) of a previously configured target group for an Application Load Balancer or Network Load Balancer to associate with your service. NOTE: For Classic Load Balancers, use the --%s flag.", flags.LoadBalancerNameFlag)
containerNameUsageString := fmt.Sprintf("[Optional] Specifies the container name (as it appears in a container definition). This parameter is required if --%s or --%s is specified.", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
containerPortUsageString := fmt.Sprintf("[Optional] Specifies the port on the container to associate with the load balancer. This port must correspond to a containerPort in the service's task definition. This parameter is required if --%s or --%s is specified.", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
loadBalancerNameUsageString := fmt.Sprintf("[Optional] Specifies the name of a previously configured Classic Elastic Load Balancing load balancer to associate with your service. NOTE: For Application Load Balancers or Network Load Balancers, use the --%s flag.", flags.TargetGroupArnFlag)
targetGroupArnUsageString := fmt.Sprintf("[Deprecated] Specifies the full Amazon Resource Name (ARN) of a previously configured target group for an Application Load Balancer or Network Load Balancer to associate with your service. NOTE: For Classic Load Balancers, use the --%s flag.", flags.LoadBalancerNameFlag)
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
containerNameUsageString := fmt.Sprintf("[Deprecated] Specifies the container name (as it appears in a container definition). This parameter is required if --%s or --%s is specified.", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
containerPortUsageString := fmt.Sprintf("[Deprecated] Specifies the port on the container to associate with the load balancer. This port must correspond to a containerPort in the service's task definition. This parameter is required if --%s or --%s is specified.", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
loadBalancerNameUsageString := fmt.Sprintf("[Deprecated] Specifies the name of a previously configured Classic Elastic Load Balancing load balancer to associate with your service. NOTE: For Application Load Balancers or Network Load Balancers, use the --%s flag.", flags.TargetGroupArnFlag)
targetGroupsUsageString := fmt.Sprintf("[Optional] Specifies multiple target groups to register with a service. Cannot be used with --%s flag or --%s at the same time.", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
roleUsageString := fmt.Sprintf("[Optional] Specifies the name or full Amazon Resource Name (ARN) of the IAM role that allows Amazon ECS to make calls to your load balancer or target group on your behalf. This parameter requires either --%s or --%s to be specified.", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag)
healthCheckGracePeriodString := "[Optional] Specifies the period of time, in seconds, that the Amazon ECS service scheduler should ignore unhealthy Elastic Load Balancing target health checks after a task has first started."

Expand Down Expand Up @@ -254,6 +255,11 @@ func loadBalancerFlags() []cli.Flag {
Name: flags.HealthCheckGracePeriodFlag,
Usage: healthCheckGracePeriodString,
},
cli.StringSliceFlag{
Name: flags.TargetGroupsFlag,
Usage: targetGroupsUsageString,
Value: &cli.StringSlice{},
},
}
}

Expand Down
1 change: 1 addition & 0 deletions ecs-cli/modules/commands/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const (
RoleFlag = "role"
ComposeServiceTimeOutFlag = "timeout"
ForceDeploymentFlag = "force-deployment"
TargetGroupsFlag = "target-groups"

// Registry Creds
UpdateExistingSecretsFlag = "update-existing-secrets"
Expand Down
45 changes: 44 additions & 1 deletion ecs-cli/modules/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package utils
import (
"fmt"
"os"
"strconv"
"strings"

"github.com/aws/aws-sdk-go/aws"
Expand Down Expand Up @@ -79,7 +80,7 @@ func ParseTags(flagValue string, tags []*ecs.Tag) ([]*ecs.Tag, error) {
return tags, nil
}

// GetTags parses AWS Resource tags from the flag value
// GetTagsMap parses AWS Resource tags from the flag value
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
// users specify tags in this format: key1=value1,key2=value2,key3=value3
// Returns tags in the format used by the standalone resource tagging API
func GetTagsMap(flagValue string) (map[string]*string, error) {
Expand All @@ -106,3 +107,45 @@ func GetPartition(region string) string {
return "aws"
}
}

// ParseLoadBalancers parses a StringSlice array into an array of load balancers struct
// Input: ["targetGroupArn="...",containerName="...",containerPort=80","targetGroupArn="...",containerName="...",containerPort=40"]
func ParseLoadBalancers(flagValues []string) ([]*ecs.LoadBalancer, error) {
var list []*ecs.LoadBalancer

seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
for _, flagValue := range flagValues {
m := make(map[string]string)
elbv2 := false
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
keyValPairs := strings.Split(flagValue, ",")

for _, kv := range keyValPairs {
pair := strings.SplitN(kv, "=", -1)
if len(pair) > 2 {
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
return nil, fmt.Errorf("Only include one = to indicate your value in your %s", pair[0])
}

m[pair[0]] = pair[1]
if pair[0] == "targetGroupArn" {
elbv2 = true
}
}
containerPort, err := strconv.ParseInt(m["containerPort"], 10, 64)
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, fmt.Errorf("Fail to parse container port %s for container %s", m["containerPort"], m["containerName"])
}
if elbv2 {
list = append(list, &ecs.LoadBalancer{
seongm-1 marked this conversation as resolved.
Show resolved Hide resolved
TargetGroupArn: aws.String(m["targetGroupArn"]),
ContainerName: aws.String(m["containerName"]),
ContainerPort: aws.Int64((containerPort)),
})
} else {
list = append(list, &ecs.LoadBalancer{
LoadBalancerName: aws.String(m["loadBalancerName"]),
ContainerName: aws.String(m["containerName"]),
ContainerPort: aws.Int64((containerPort)),
})
}
}
return list, nil
}