diff --git a/provider/ecs/ecs.go b/provider/ecs/ecs.go index fb35e0372..89959512d 100644 --- a/provider/ecs/ecs.go +++ b/provider/ecs/ecs.go @@ -347,43 +347,47 @@ func (p *Provider) lookupEc2Instances(ctx context.Context, client *awsClient, cl } } - resp, err := client.ecs.DescribeContainerInstancesWithContext(ctx, &ecs.DescribeContainerInstancesInput{ - ContainerInstances: containerInstancesArns, - Cluster: clusterName, - }) - - if err != nil { - log.Errorf("Unable to describe container instances: %s", err) - return nil, err - } - - for _, container := range resp.ContainerInstances { - instanceIds[aws.StringValue(container.Ec2InstanceId)] = aws.StringValue(container.ContainerInstanceArn) - instanceArns = append(instanceArns, container.Ec2InstanceId) - } - - if len(instanceArns) > 0 { - input := &ec2.DescribeInstancesInput{ - InstanceIds: instanceArns, - } - - err = client.ec2.DescribeInstancesPagesWithContext(ctx, input, func(page *ec2.DescribeInstancesOutput, lastPage bool) bool { - if len(page.Reservations) > 0 { - for _, r := range page.Reservations { - for _, i := range r.Instances { - if i.InstanceId != nil { - ec2Instances[instanceIds[aws.StringValue(i.InstanceId)]] = i - } - } - } - } - return !lastPage + for _, arns := range p.chunkIDs(containerInstancesArns) { + resp, err := client.ecs.DescribeContainerInstancesWithContext(ctx, &ecs.DescribeContainerInstancesInput{ + ContainerInstances: arns, + Cluster: clusterName, }) if err != nil { - log.Errorf("Unable to describe instances: %s", err) + log.Errorf("Unable to describe container instances: %v", err) return nil, err } + + for _, container := range resp.ContainerInstances { + instanceIds[aws.StringValue(container.Ec2InstanceId)] = aws.StringValue(container.ContainerInstanceArn) + instanceArns = append(instanceArns, container.Ec2InstanceId) + } + } + + if len(instanceArns) > 0 { + for _, ids := range p.chunkIDs(instanceArns) { + input := &ec2.DescribeInstancesInput{ + InstanceIds: ids, + } + + err := client.ec2.DescribeInstancesPagesWithContext(ctx, input, func(page *ec2.DescribeInstancesOutput, lastPage bool) bool { + if len(page.Reservations) > 0 { + for _, r := range page.Reservations { + for _, i := range r.Instances { + if i.InstanceId != nil { + ec2Instances[instanceIds[aws.StringValue(i.InstanceId)]] = i + } + } + } + } + return !lastPage + }) + + if err != nil { + log.Errorf("Unable to describe instances [%s]: %v", err) + return nil, err + } + } } return ec2Instances, nil @@ -414,3 +418,19 @@ func (p *Provider) loadECSConfig(ctx context.Context, client *awsClient) (*types return p.buildConfiguration(instances) } + +// chunkIDs ECS expects no more than 100 parameters be passed to a API call; +// thus, pack each string into an array capped at 100 elements +func (p *Provider) chunkIDs(ids []*string) [][]*string { + var chuncked [][]*string + for i := 0; i < len(ids); i += 100 { + sliceEnd := -1 + if i+100 < len(ids) { + sliceEnd = i + 100 + } else { + sliceEnd = len(ids) + } + chuncked = append(chuncked, ids[i:sliceEnd]) + } + return chuncked +} diff --git a/provider/ecs/ecs_test.go b/provider/ecs/ecs_test.go new file mode 100644 index 000000000..f55510305 --- /dev/null +++ b/provider/ecs/ecs_test.go @@ -0,0 +1,88 @@ +package ecs + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/stretchr/testify/assert" +) + +func TestChunkIDs(t *testing.T) { + provider := &Provider{} + + testCases := []struct { + desc string + count int + expected []int + }{ + { + desc: "0 element", + count: 0, + expected: []int(nil), + }, + { + desc: "1 element", + count: 1, + expected: []int{1}, + }, + { + desc: "99 elements, 1 chunk", + count: 99, + expected: []int{99}, + }, + { + desc: "100 elements, 1 chunk", + count: 100, + expected: []int{100}, + }, + { + desc: "101 elements, 2 chunks", + count: 101, + expected: []int{100, 1}, + }, + { + desc: "199 elements, 2 chunks", + count: 199, + expected: []int{100, 99}, + }, + { + desc: "200 elements, 2 chunks", + count: 200, + expected: []int{100, 100}, + }, + { + desc: "201 elements, 3 chunks", + count: 201, + expected: []int{100, 100, 1}, + }, + { + desc: "555 elements, 5 chunks", + count: 555, + expected: []int{100, 100, 100, 100, 100, 55}, + }, + { + desc: "1001 elements, 11 chunks", + count: 1001, + expected: []int{100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 1}, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var IDs []*string + for v := 0; v < test.count; v++ { + IDs = append(IDs, aws.String("a")) + } + + var outCount []int + for _, el := range provider.chunkIDs(IDs) { + outCount = append(outCount, len(el)) + } + + assert.Equal(t, test.expected, outCount) + }) + } +}