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

Add capacity provider strategy support #1111

Open
wants to merge 4 commits into
base: mainline
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ ecs-cli/vendor/pkg
ecs-cli/.vscode/*
ecs-cli/.idea
.idea/*
.DS_Store
37 changes: 27 additions & 10 deletions ecs-cli/modules/cli/compose/entity/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,16 @@ import (
// Service type is placeholder for a single task definition and its cache
// and it performs operations on ECS Service level
type Service struct {
taskDef *ecs.TaskDefinition
cache cache.Cache
ecsContext *context.ECSContext
deploymentConfig *ecs.DeploymentConfiguration
loadBalancers []*ecs.LoadBalancer
role string
healthCheckGP *int64
serviceRegistries []*ecs.ServiceRegistry
tags []*ecs.Tag
taskDef *ecs.TaskDefinition
cache cache.Cache
ecsContext *context.ECSContext
deploymentConfig *ecs.DeploymentConfiguration
loadBalancers []*ecs.LoadBalancer
capacityProviderStrategy []*ecs.CapacityProviderStrategyItem
role string
healthCheckGP *int64
serviceRegistries []*ecs.ServiceRegistry
tags []*ecs.Tag
}

const (
Expand Down Expand Up @@ -105,6 +106,9 @@ func (s *Service) LoadContext() error {
return err
}

// Capacity Provider Strategy
capacityProviders := s.Context().CLIContext.StringSlice(flags.CapacityProviderStrategyFlag)

// Health Check Grace Period
healthCheckGP, err := getInt64FromCLIContext(s.Context(), flags.HealthCheckGracePeriodFlag)
if err != nil {
Expand Down Expand Up @@ -153,6 +157,15 @@ func (s *Service) LoadContext() error {
}
s.loadBalancers = append(s.loadBalancers, loadBalancers...)
}

if len(capacityProviders) != 0 {
capacityProviderStrategy, err := utils.ParseCapacityProviders(capacityProviders)
if err != nil {
return err
}
s.capacityProviderStrategy = append(s.capacityProviderStrategy, capacityProviderStrategy...)
}

s.role = role
return nil
}
Expand Down Expand Up @@ -569,7 +582,11 @@ func (s *Service) buildCreateServiceInput(serviceName, taskDefName string, desir
createServiceInput.PlacementStrategy = placementStrategy
}

if launchType != "" {
// just let capacity provider take precedence if it is set
// otherwise, fall back on the launch type
if len(s.capacityProviderStrategy) > 0 {
createServiceInput.CapacityProviderStrategy = s.capacityProviderStrategy
} else if launchType != "" {
createServiceInput.LaunchType = aws.String(launchType)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func createServiceCommand(factory composeFactory.ProjectFactory) cli.Command {
Name: "create",
Usage: usage.ServiceCreate,
Action: compose.WithProject(factory, compose.ProjectCreate, true),
Flags: flags.AppendFlags(deploymentConfigFlags(true), loadBalancerFlags(), flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), serviceDiscoveryFlags(), flags.OptionalSchedulingStrategyFlag(), taggingFlags()),
Flags: flags.AppendFlags(deploymentConfigFlags(true), loadBalancerFlags(), capacityProviderStrategyFlags(), flags.OptionalConfigFlags(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), serviceDiscoveryFlags(), flags.OptionalSchedulingStrategyFlag(), taggingFlags()),
OnUsageError: flags.UsageErrorFactory("create"),
}
}
Expand All @@ -88,7 +88,7 @@ func upServiceCommand(factory composeFactory.ProjectFactory) cli.Command {
Name: "up",
Usage: usage.ServiceUp,
Action: compose.WithProject(factory, compose.ProjectUp, true),
Flags: flags.AppendFlags(deploymentConfigFlags(true), loadBalancerFlags(), flags.OptionalConfigFlags(), ComposeServiceTimeoutFlag(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), ForceNewDeploymentFlag(), serviceDiscoveryFlags(), updateServiceDiscoveryFlags(), flags.OptionalSchedulingStrategyFlag(), taggingFlags()),
Flags: flags.AppendFlags(deploymentConfigFlags(true), loadBalancerFlags(), capacityProviderStrategyFlags(), flags.OptionalConfigFlags(), ComposeServiceTimeoutFlag(), flags.OptionalLaunchTypeFlag(), flags.OptionalCreateLogsFlag(), ForceNewDeploymentFlag(), serviceDiscoveryFlags(), updateServiceDiscoveryFlags(), flags.OptionalSchedulingStrategyFlag(), taggingFlags()),
OnUsageError: flags.UsageErrorFactory("up"),
}
}
Expand Down Expand Up @@ -226,7 +226,7 @@ func loadBalancerFlags() []cli.Flag {
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. Can't be used with --%s flag or --%s at the same time. To specify multiple target groups, add multiple seperate --%s flags Example: ecs-cli compose service create --target-groups targetGroupArn=arn,containerName=nginx,containerPort=80 --target-groups targetGroupArn=arn,containerName=database,containerPort=3306", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag, flags.TargetGroupsFlag)
targetGroupsUsageString := fmt.Sprintf("[Optional] Specifies multiple target groups to register with a service. Can't be used with --%s flag or --%s at the same time. To specify multiple target groups, add multiple separate --%s flags Example: ecs-cli compose service create --target-groups targetGroupArn=arn,containerName=nginx,containerPort=80 --target-groups targetGroupArn=arn,containerName=database,containerPort=3306", flags.LoadBalancerNameFlag, flags.TargetGroupArnFlag, flags.TargetGroupsFlag)
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 @@ -263,6 +263,18 @@ func loadBalancerFlags() []cli.Flag {
}
}

func capacityProviderStrategyFlags() []cli.Flag {
capacityProviderStrategyUsageString := fmt.Sprintf("[Optional] Specifies multiple capacity providers to register with a service. Can't be used with --%s flag at the same time. To specify multiple capacity providers, add multiple separate --%s flags. Only one capacity provider can have a base > 0, set the base of all others to zero. Example: ecs-cli compose service create --capacity-provider capacityProviderName=t3-large-capacity-provider,base=1,weight=1 --capacity-provider capacityProviderName=t3-nano-capacity-provider,base=0,weight=5", flags.LaunchTypeFlag, flags.CapacityProviderStrategyFlag)

return []cli.Flag{
cli.StringSliceFlag{
Name: flags.CapacityProviderStrategyFlag,
Usage: capacityProviderStrategyUsageString,
Value: &cli.StringSlice{},
},
}
}

// ComposeServiceTimeoutFlag allows user to specify a custom timeout
func ComposeServiceTimeoutFlag() []cli.Flag {
return []cli.Flag{
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 @@ -135,6 +135,7 @@ const (
ComposeServiceTimeOutFlag = "timeout"
ForceDeploymentFlag = "force-deployment"
TargetGroupsFlag = "target-groups"
CapacityProviderStrategyFlag = "capacity-provider"

// Registry Creds
UpdateExistingSecretsFlag = "update-existing-secrets"
Expand Down
74 changes: 69 additions & 5 deletions ecs-cli/modules/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ import (

const (
// ECSCLIResourcePrefix is prepended to the names of resources created through the ecs-cli
ECSCLIResourcePrefix = "amazon-ecs-cli-setup-"
containerNameParamKey = "containerName"
containerPortParamKey = "containerPort"
loadBalancerNameParamKey = "loadBalancerName"
targetGroupArnParamKey = "targetGroupArn"
ECSCLIResourcePrefix = "amazon-ecs-cli-setup-"
containerNameParamKey = "containerName"
containerPortParamKey = "containerPort"
loadBalancerNameParamKey = "loadBalancerName"
targetGroupArnParamKey = "targetGroupArn"
capacityProviderNameParamKey = "capacityProviderName"
capacityProviderBaseParamKey = "base"
capacityProviderWeightParamKey = "weight"
)

// InSlice checks if the given string exists in the given slice:
Expand Down Expand Up @@ -112,6 +115,67 @@ func GetPartition(region string) string {
}
}

// ParseCapacityProviders parses a StringSlice array into an array of CapacityProviderItem
// When you specify a capacity provider strategy, the number of capacity providers that can be specified is limited to six.
// Input: ["capacityProviderName="...",base="...",weight=80","capacityProviderName="...",base="...",weight=40"]
func ParseCapacityProviders(flagValues []string) ([]*ecs.CapacityProviderStrategyItem, error) {
var list []*ecs.CapacityProviderStrategyItem

if len(flagValues) > 6 {
return nil, fmt.Errorf("ECS only permits 6 Capacity providers, you provided %s", len(flagValues))
}

for _, flagValue := range flagValues {
m := make(map[string]string)

validFlags := []string{capacityProviderNameParamKey, capacityProviderBaseParamKey, capacityProviderWeightParamKey}
currentFlags := map[string]bool{
"capacityProviderName": false,
}

keyValPairs := strings.Split(flagValue, ",")

for _, kv := range keyValPairs {
pair := strings.SplitN(kv, "=", -1)

if len(pair) != 2 {
return nil, fmt.Errorf("There is an (key=value) initialization error, please check to see if you are using = accordingly on %s", pair[0])
}
key, val := pair[0], pair[1]

if ok := contains(validFlags, key); !ok {
return nil, fmt.Errorf("[--%s] is an invalid flag", key)
}
m[key] = val
if currentFlags[key] {
return nil, fmt.Errorf("%s already exists", key)
}
currentFlags[key] = true
}
for key, value := range currentFlags {
if value == false {
return nil, fmt.Errorf("--%s must be specified", key)
}
}

base, err := strconv.ParseInt(m["base"], 10, 64)
if err != nil {
return nil, fmt.Errorf("Failed to parse capacity provider base for capacity provider %s; set base to zero on all providers except one", m["capacityProviderName"])
}
weight, err := strconv.ParseInt(m["weight"], 10, 64)
if err != nil {
return nil, fmt.Errorf("Failed to parse capacity provider weight for capacity provider %s", m["capacityProviderName"])
}

list = append(list, &ecs.CapacityProviderStrategyItem{
CapacityProvider: aws.String(m["capacityProviderName"]),
Base: aws.Int64((base)),
Weight: aws.Int64((weight)),
})
}
return list, nil
}

// 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) {
Expand Down