Repository navigation
Add more data fetchers for ECS - #80
donghwikim2 wants to merge 22 commits into
Conversation
5110b09 to
8cdb56b
Compare
8cdb56b to
4cfdbec
Compare
…tion query Co-authored-by: donghwikim2 <126684759+donghwikim2@users.noreply.github.com>
Address feedback: revert ClusterDataFetcher imports, add missing AwsContainerDefinition query
Co-authored-by: donghwikim2 <126684759+donghwikim2@users.noreply.github.com>
Add AwsTaskSet query to example application
Co-authored-by: donghwikim2 <126684759+donghwikim2@users.noreply.github.com>
Register AwsContainerDefinition and AwsTaskSet as schema object aliases
Co-authored-by: donghwikim2 <126684759+donghwikim2@users.noreply.github.com>
Address formatting and ordering feedback on ECS data fetchers
| * @author Donghwi Kim | ||
| * @since 1.0.0 | ||
| */ | ||
| public class AwsService extends AwsWrapper<Service> { |
There was a problem hiding this comment.
The name is rather generic, can you maybe use a more concrete name on what it is?
There was a problem hiding this comment.
Maybe you can prefix all of the resources with the service type, e.g. AwsEcsService, AwsEcsTaskSet etc. That way we know to which service it belongs. Some are rather generic.
There was a problem hiding this comment.
Pull Request Overview
This PR expands AWS ECS connector functionality by introducing new data fetchers and model classes for ECS resources. The changes rename AwsCluster to AwsEcsCluster for consistency and add support for querying ECS services, task definitions, container definitions, and task sets.
- Renamed
AwsClustertoAwsEcsClusterto follow theAwsEcs*naming convention - Added new ECS data fetchers:
ServiceDataFetcher,TaskDefinitionDataFetcher,ContainerDefinitionDataFetcher, andTaskSetDataFetcher - Registered new ECS model classes and updated example application to demonstrate the new query capabilities
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Main.java | Updated imports and registrations to use renamed AwsEcsCluster and added registrations/queries for new ECS entities |
| TaskSetDataFetcher.java | New data fetcher to retrieve ECS task sets from services |
| TaskDefinitionDataFetcher.java | New data fetcher to retrieve ECS task definitions |
| ServiceDataFetcher.java | New data fetcher to retrieve ECS services from clusters |
| ContainerDefinitionDataFetcher.java | New data fetcher to retrieve container definitions from task definitions |
| ClusterDataFetcher.java | Updated to use AwsEcsCluster naming and added additional cluster fields |
| AwsEcsTaskSet.java | New model class wrapping ECS TaskSet |
| AwsEcsTaskDefinition.java | New model class wrapping ECS task definition response |
| AwsEcsService.java | New model class wrapping ECS Service |
| AwsEcsContainerDefinition.java | New record class for container definitions |
| AwsECSSchemaProvider.java | Registered new data fetchers in the schema provider |
| AwsCluster.java | Renamed class from AwsCluster to AwsEcsCluster |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| List<String> serviceArns = client.listServices( r -> r.cluster( clusterArn ) ) | ||
| .serviceArns(); | ||
| if ( !serviceArns.isEmpty() ) { | ||
| DescribeServicesRequest request = DescribeServicesRequest.builder() | ||
| .cluster( clusterArn ) | ||
| .services( serviceArns ) | ||
| .include( ServiceField.TAGS ) | ||
| .build(); | ||
| DescribeServicesResponse response = client.describeServices( request ); | ||
| for ( Service service : response.services() ) { |
There was a problem hiding this comment.
The nested loops with API calls create an N+1 query pattern. Each cluster triggers a separate listServices call. Consider batching or using pagination tokens to optimize the number of API requests.
| List<String> serviceArns = client.listServices( r -> r.cluster( clusterArn ) ) | |
| .serviceArns(); | |
| if ( !serviceArns.isEmpty() ) { | |
| DescribeServicesRequest request = DescribeServicesRequest.builder() | |
| .cluster( clusterArn ) | |
| .services( serviceArns ) | |
| .include( ServiceField.TAGS ) | |
| .build(); | |
| DescribeServicesResponse response = client.describeServices( request ); | |
| for ( Service service : response.services() ) { | |
| List<String> serviceArns = new ArrayList<>(); | |
| String nextToken = null; | |
| do { | |
| var response = client.listServices(r -> r.cluster(clusterArn).nextToken(nextToken)); | |
| serviceArns.addAll(response.serviceArns()); | |
| nextToken = response.nextToken(); | |
| } while (nextToken != null && !nextToken.isEmpty()); | |
| if ( !serviceArns.isEmpty() ) { | |
| DescribeServicesRequest request = DescribeServicesRequest.builder() | |
| .cluster( clusterArn ) | |
| .services( serviceArns ) | |
| .include( ServiceField.TAGS ) | |
| .build(); | |
| DescribeServicesResponse describeResponse = client.describeServices( request ); | |
| for ( Service service : describeResponse.services() ) { |
There was a problem hiding this comment.
/**
* <p>
* A list of services to describe. You may specify up to 10 services to describe in a single operation.
* </p>
*
* @param services
* A list of services to describe. You may specify up to 10 services to describe in a single operation.
* @return Returns a reference to this object so that method calls can be chained together.
*/
Builder services(Collection<String> services);
I would not batch for now, but I noticed that maximum number of services that it accept is 10 so I modified like below now
for ( String clusterArn : clusterArns ) {
List<String> serviceArns = client.listServices( r -> r.cluster( clusterArn ) )
.serviceArns();
if ( !serviceArns.isEmpty() ) {
// AWS DescribeServices API accepts up to 10 services per request
int batchSize = 10;
for ( int i = 0; i < serviceArns.size(); i += batchSize ) {
int end = Math.min( i + batchSize, serviceArns.size() );
List<String> batch = serviceArns.subList( i, end );
DescribeServicesRequest request = DescribeServicesRequest.builder()
.cluster( clusterArn )
.services( batch )
.include( ServiceField.TAGS )
.build();
DescribeServicesResponse response = client.describeServices( request );
for ( Service service : response.services() ) {
list.add( new AwsEcsService( service.serviceArn(), service ) );
}
}
}
}
There was a problem hiding this comment.
Pull Request Overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| List<? extends AwsEcsService> services = context.getSession().getOrFetch( AwsEcsService.class ); | ||
| List<AwsEcsTaskSet> list = new ArrayList<>(); | ||
| for ( AwsConnectorConfig.Account account : accounts ) { | ||
| for ( Region region : account.getRegions() ) { | ||
| EcsClientBuilder ecsClientBuilder = EcsClient.builder() | ||
| .region( region ) | ||
| .credentialsProvider( account.getCredentialsProvider() ); | ||
| if ( sdkHttpClient != null ) { | ||
| ecsClientBuilder.httpClient( sdkHttpClient ); | ||
| } | ||
| try (EcsClient client = ecsClientBuilder.build()) { | ||
| for ( AwsEcsService service : services ) { | ||
| if ( service.getAccountId().equals( account.getAccountId() ) | ||
| && service.getRegionId().equals( region.id() ) ) { | ||
| DescribeTaskSetsResponse response = client.describeTaskSets( r -> r | ||
| .cluster( service.getPayload().clusterArn() ) | ||
| .service( service.getPayload().serviceArn() ) | ||
| .include( TaskSetField.TAGS ) | ||
| ); | ||
| for ( TaskSet taskSet : response.taskSets() ) { | ||
| list.add( new AwsEcsTaskSet( taskSet.taskSetArn(), taskSet ) ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The TaskSetDataFetcher fetches all services via context.getSession().getOrFetch(AwsEcsService.class) but only processes task sets for services that match specific account and region criteria. This creates an N+1 query pattern where services are fetched globally first, then filtered locally. Additionally, fetching all services upfront may have performance implications when only a subset is needed. Consider
10BC8
fetching services per account/region or document this dependency relationship if it's intentional.
| try (EcsClient client = ecsClientBuilder.build()) { | ||
| List<String> taskDefinitionArns = client.listTaskDefinitions().taskDefinitionArns(); | ||
| for ( String taskDefinitionArn : taskDefinitionArns ) { | ||
| DescribeTaskDefinitionRequest request = DescribeTaskDefinitionRequest.builder() | ||
| .taskDefinition( taskDefinitionArn ) | ||
| .build(); | ||
| DescribeTaskDefinitionResponse response = client.describeTaskDefinition( request ); | ||
| TaskDefinition taskDefinition = response.taskDefinition(); | ||
| List<ContainerDefinition> containerDefinitions = taskDefinition.containerDefinitions(); | ||
| for ( ContainerDefinition containerDefinition : containerDefinitions ) { | ||
| list.add( new AwsEcsContainerDefinition( taskDefinition.taskDefinitionArn(), | ||
| containerDefinition.name(), containerDefinition ) ); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The ContainerDefinitionDataFetcher and TaskDefinitionDataFetcher both independently fetch the complete list of task definitions and make describeTaskDefinition calls for each. This results in duplicated API calls and unnecessary work. Consider either:
- Having
ContainerDefinitionDataFetcherdepend on cachedAwsEcsTaskDefinitionobjects viacontext.getSession().getOrFetch() - Documenting why this duplication is necessary if there's a specific reason
|
Merged via 91e75a3 |
No description provided.