Skip to content

Optimize query timing for the table aws_ecr_image_scan_finding #2492

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
89 changes: 9 additions & 80 deletions aws/table_aws_ecr_image_scan_finding.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ func tableAwsEcrImageScanFinding(_ context.Context) *plugin.Table {
Name: "aws_ecr_image_scan_finding",
Description: "AWS ECR Image Scan Finding",
List: &plugin.ListConfig{
ParentHydrate: listAwsEcrImageTags,
Hydrate: listAwsEcrImageScanFindings,
Tags: map[string]string{"service": "ecr", "action": "DescribeImageScanFindings"},
Hydrate: listAwsEcrImageScanFindings,
Tags: map[string]string{"service": "ecr", "action": "DescribeImageScanFindings"},
IgnoreConfig: &plugin.IgnoreConfig{
ShouldIgnoreErrorFunc: shouldIgnoreErrors([]string{"RepositoryNotFoundException", "ImageNotFoundException", "ScanNotFoundException"}),
},
Expand All @@ -36,8 +35,8 @@ func tableAwsEcrImageScanFinding(_ context.Context) *plugin.Table {
// image_digest as it's more common/friendly to use.
KeyColumns: []*plugin.KeyColumn{
{Name: "repository_name", Require: plugin.Required},
{Name: "image_tag", Require: plugin.Optional},
{Name: "image_digest", Require: plugin.Optional},
{Name: "image_tag", Require: plugin.AnyOf},
{Name: "image_digest", Require: plugin.AnyOf},
},
},
GetMatrixItemFunc: SupportedRegionMatrix(ecrv1.EndpointsID),
Expand Down Expand Up @@ -122,7 +121,6 @@ func tableAwsEcrImageScanFinding(_ context.Context) *plugin.Table {

// // LIST FUNCTION
func listAwsEcrImageScanFindings(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {
repositoryTag := h.Item.(*RepositoryImage)

// Create Session
svc, err := ECRClient(ctx, d)
Expand All @@ -134,13 +132,10 @@ func listAwsEcrImageScanFindings(ctx context.Context, d *plugin.QueryData, h *pl
imageDigest := d.EqualsQuals["image_digest"]
repositoryName := d.EqualsQuals["repository_name"]

if imageTag != nil && imageTag.GetStringValue() != *repositoryTag.ImageTag {
return nil, nil
if imageTag == nil && imageDigest == nil {
return nil, errors.New("image_tag or image_digest must be provided")
}
if repositoryName != nil && repositoryName.GetStringValue() != *repositoryTag.RepositoryName {
return nil, nil
}


// Limiting the results
maxLimit := int32(1000)
if d.QueryContext.Limit != nil {
Expand All @@ -152,11 +147,11 @@ func listAwsEcrImageScanFindings(ctx context.Context, d *plugin.QueryData, h *pl

input := &ecr.DescribeImageScanFindingsInput{
MaxResults: aws.Int32(maxLimit),
RepositoryName: repositoryTag.RepositoryName,
RepositoryName: aws.String(repositoryName.GetStringValue()),
}

imageInfo := &types.ImageIdentifier{
ImageTag: repositoryTag.ImageTag,
ImageTag: aws.String(imageTag.GetStringValue()),
Comment on lines 153 to +154
Copy link
Preview

Copilot AI May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If only 'image_digest' is provided and 'image_tag' is nil, calling GetStringValue() on a nil imageTag will cause a runtime panic. Consider adding a nil check or using the 'image_digest' value as a fallback.

Copilot uses AI. Check for mistakes.

}

// Ideally, both image_tag and image_digest could be used.
Expand Down Expand Up @@ -238,69 +233,3 @@ func listAwsEcrImageScanFindings(ctx context.Context, d *plugin.QueryData, h *pl

return nil, err
}

//// Parent Hydrate

type RepositoryImage struct {
RepositoryName *string
ImageTag *string
}

// To preserve the existing table behavior (fetching findings based on image tags), we have retained the parent function.
// Making `image_tags` an optional or `anyof` qualifier alters the query plan for complex join queries, causing query execution errors.
// This issue is detailed here: https://github.com/turbot/steampipe-plugin-aws/issues/2367
func listAwsEcrImageTags(ctx context.Context, d *plugin.QueryData, h *plugin.HydrateData) (interface{}, error) {

repoName := d.EqualsQuals["repository_name"].GetStringValue()

// Limiting the results
maxLimit := int32(100)

// Create Session
svc, err := ECRClient(ctx, d)
if err != nil {
plugin.Logger(ctx).Error("aws_ecr_image.listAwsEcrImageTags", "connection_error", err)
return nil, err
}

// Build the params
params := &ecr.DescribeImagesInput{
RepositoryName: &repoName,
MaxResults: aws.Int32(maxLimit),
}

paginator := ecr.NewDescribeImagesPaginator(svc, params, func(o *ecr.DescribeImagesPaginatorOptions) {
o.Limit = maxLimit
o.StopOnDuplicateToken = true
})

// List call
for paginator.HasMorePages() {
// apply rate limiting
d.WaitForListRateLimit(ctx)

output, err := paginator.NextPage(ctx)
if err != nil {
var ae smithy.APIError
if errors.As(err, &ae) {
if ae.ErrorCode() == "RepositoryNotFoundException" {
return nil, nil
}
}
plugin.Logger(ctx).Error("aws_ecr_image.listAwsEcrImageTags", "api_error", err)
return nil, err
}

for _, items := range output.ImageDetails {
for _, tags := range items.ImageTags {
imageDetails := &RepositoryImage{
RepositoryName: items.RepositoryName,
ImageTag: &tags,
}
d.StreamListItem(ctx, imageDetails)
}
}
}

return nil, nil
}
Loading