From 5bda54c1e72ef4783db9d3cdbbe0699e98f14b63 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Fri, 10 Oct 2025 23:49:53 +0200 Subject: [PATCH 01/13] feat(ecr): add tag_status filter to images data source - Add tag_status attribute to schema (TAGGED/UNTAGGED/ANY) - Update data model and Read method to support ListImagesFilter - Add comprehensive tests for all tag_status values - Update documentation with examples --- internal/service/ecr/images_data_source.go | 13 +++++ .../service/ecr/images_data_source_test.go | 48 +++++++++++++++++++ website/docs/d/ecr_images.html.markdown | 10 ++++ 3 files changed, 71 insertions(+) diff --git a/internal/service/ecr/images_data_source.go b/internal/service/ecr/images_data_source.go index afa3cc9cb6f2..7029db35e0bb 100644 --- a/internal/service/ecr/images_data_source.go +++ b/internal/service/ecr/images_data_source.go @@ -40,6 +40,10 @@ func (d *imagesDataSource) Schema(ctx context.Context, request datasource.Schema Required: true, Description: "Name of the repository", }, + "tag_status": schema.StringAttribute{ + Optional: true, + Description: "Filter images by tag status. Valid values: TAGGED, UNTAGGED, ANY", + }, }, } } @@ -59,6 +63,14 @@ func (d *imagesDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } + // Set tag status filter if provided + if !data.TagStatus.IsNull() && !data.TagStatus.IsUnknown() { + tagStatus := awstypes.TagStatus(data.TagStatus.ValueString()) + input.Filter = &awstypes.ListImagesFilter{ + TagStatus: tagStatus, + } + } + output, err := findImages(ctx, conn, &input) if err != nil { resp.Diagnostics.AddError("reading ECR Images", err.Error()) @@ -102,6 +114,7 @@ type imagesDataSourceModel struct { ImageIDs fwtypes.ListNestedObjectValueOf[imagesIDsModel] `tfsdk:"image_ids"` RegistryID types.String `tfsdk:"registry_id"` RepositoryName types.String `tfsdk:"repository_name"` + TagStatus types.String `tfsdk:"tag_status"` } type imagesIDsModel struct { diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index d97f2cd2efbb..2c0f532a65a4 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -102,6 +102,54 @@ data "aws_ecr_images" "test" { `, rName) } +func TestAccECRImagesDataSource_tagStatus(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ecr_images.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECRServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccImagesDataSourceConfig_tagStatus(rName, "TAGGED"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, "tag_status", "TAGGED"), + ), + }, + { + Config: testAccImagesDataSourceConfig_tagStatus(rName, "UNTAGGED"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, "tag_status", "UNTAGGED"), + ), + }, + { + Config: testAccImagesDataSourceConfig_tagStatus(rName, "ANY"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, "tag_status", "ANY"), + ), + }, + }, + }) +} + +func testAccImagesDataSourceConfig_tagStatus(rName, tagStatus string) string { + return fmt.Sprintf(` +resource "aws_ecr_repository" "test" { + name = %[1]q +} + +data "aws_ecr_images" "test" { + repository_name = aws_ecr_repository.test.name + tag_status = %[2]q +} +`, rName, tagStatus) +} + func testAccImagesDataSourceConfig_registryID(registryID, repositoryName string) string { return fmt.Sprintf(` data "aws_ecr_images" "test" { diff --git a/website/docs/d/ecr_images.html.markdown b/website/docs/d/ecr_images.html.markdown index d6e2db4ab312..73c467703c3f 100644 --- a/website/docs/d/ecr_images.html.markdown +++ b/website/docs/d/ecr_images.html.markdown @@ -26,6 +26,15 @@ output "image_tags" { } ``` +### Filter by Tag Status + +```terraform +data "aws_ecr_images" "tagged_only" { + repository_name = "my-repository" + tag_status = "TAGGED" +} +``` + ## Argument Reference This data source supports the following arguments: @@ -33,6 +42,7 @@ This data source supports the following arguments: * `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference). * `registry_id` - (Optional) ID of the Registry where the repository resides. * `repository_name` - (Required) Name of the ECR Repository. +* `tag_status` - (Optional) Filter images by tag status. Valid values: `TAGGED`, `UNTAGGED`, `ANY`. Defaults to `ANY`. ## Attribute Reference From 0d0037d25651293a111d88a3afea5edd59dc16ab Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Fri, 10 Oct 2025 23:52:38 +0200 Subject: [PATCH 02/13] feat(ecr): add max_results parameter to images data source - Add max_results attribute to schema with int64 type - Update data model and Read method to set MaxResults on ListImagesInput - Add test for max_results functionality - Update documentation with example usage --- internal/service/ecr/images_data_source.go | 11 ++++++ .../service/ecr/images_data_source_test.go | 34 +++++++++++++++++++ website/docs/d/ecr_images.html.markdown | 10 ++++++ 3 files changed, 55 insertions(+) diff --git a/internal/service/ecr/images_data_source.go b/internal/service/ecr/images_data_source.go index 7029db35e0bb..ca270aeede43 100644 --- a/internal/service/ecr/images_data_source.go +++ b/internal/service/ecr/images_data_source.go @@ -32,6 +32,10 @@ func (d *imagesDataSource) Schema(ctx context.Context, request datasource.Schema response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "image_ids": framework.DataSourceComputedListOfObjectAttribute[imagesIDsModel](ctx), + "max_results": schema.Int64Attribute{ + Optional: true, + Description: "Maximum number of images to return", + }, "registry_id": schema.StringAttribute{ Optional: true, Description: "ID of the registry (AWS account ID)", @@ -71,6 +75,12 @@ func (d *imagesDataSource) Read(ctx context.Context, req datasource.ReadRequest, } } + // Set max results if provided + if !data.MaxResults.IsNull() && !data.MaxResults.IsUnknown() { + maxResults := int32(data.MaxResults.ValueInt64()) + input.MaxResults = &maxResults + } + output, err := findImages(ctx, conn, &input) if err != nil { resp.Diagnostics.AddError("reading ECR Images", err.Error()) @@ -112,6 +122,7 @@ func findImages(ctx context.Context, conn *ecr.Client, input *ecr.ListImagesInpu type imagesDataSourceModel struct { framework.WithRegionModel ImageIDs fwtypes.ListNestedObjectValueOf[imagesIDsModel] `tfsdk:"image_ids"` + MaxResults types.Int64 `tfsdk:"max_results"` RegistryID types.String `tfsdk:"registry_id"` RepositoryName types.String `tfsdk:"repository_name"` TagStatus types.String `tfsdk:"tag_status"` diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index 2c0f532a65a4..e09c6ca715c1 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -102,6 +102,27 @@ data "aws_ecr_images" "test" { `, rName) } +func TestAccECRImagesDataSource_maxResults(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ecr_images.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECRServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccImagesDataSourceConfig_maxResults(rName, 5), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, "max_results", "5"), + ), + }, + }, + }) +} + func TestAccECRImagesDataSource_tagStatus(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -137,6 +158,19 @@ func TestAccECRImagesDataSource_tagStatus(t *testing.T) { }) } +func testAccImagesDataSourceConfig_maxResults(rName string, maxResults int) string { + return fmt.Sprintf(` +resource "aws_ecr_repository" "test" { + name = %[1]q +} + +data "aws_ecr_images" "test" { + repository_name = aws_ecr_repository.test.name + max_results = %[2]d +} +`, rName, maxResults) +} + func testAccImagesDataSourceConfig_tagStatus(rName, tagStatus string) string { return fmt.Sprintf(` resource "aws_ecr_repository" "test" { diff --git a/website/docs/d/ecr_images.html.markdown b/website/docs/d/ecr_images.html.markdown index 73c467703c3f..fe10b89460e4 100644 --- a/website/docs/d/ecr_images.html.markdown +++ b/website/docs/d/ecr_images.html.markdown @@ -35,6 +35,15 @@ data "aws_ecr_images" "tagged_only" { } ``` +### Limit Results + +```terraform +data "aws_ecr_images" "limited" { + repository_name = "my-repository" + max_results = 10 +} +``` + ## Argument Reference This data source supports the following arguments: @@ -43,6 +52,7 @@ This data source supports the following arguments: * `registry_id` - (Optional) ID of the Registry where the repository resides. * `repository_name` - (Required) Name of the ECR Repository. * `tag_status` - (Optional) Filter images by tag status. Valid values: `TAGGED`, `UNTAGGED`, `ANY`. Defaults to `ANY`. +* `max_results` - (Optional) Maximum number of images to return. ## Attribute Reference From 7629d132dc0853a25893e7b3f91fb14e35275d86 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Fri, 10 Oct 2025 23:56:42 +0200 Subject: [PATCH 03/13] feat(ecr): add describe_images functionality to images data source - Add describe_images boolean attribute to schema (defaults to false) - Add image_details output attribute with detailed image information - Create imageDetailsModel struct for DescribeImages API response - Add findImagesDetails function with batching (max 100 images per request) - Update Read method to conditionally call DescribeImages when describe_images=true - Add comprehensive test for describe_images functionality - Update documentation with examples and detailed attribute descriptions --- internal/service/ecr/images_data_source.go | 65 +++++++++++++++++++ .../service/ecr/images_data_source_test.go | 34 ++++++++++ website/docs/d/ecr_images.html.markdown | 21 ++++++ 3 files changed, 120 insertions(+) diff --git a/internal/service/ecr/images_data_source.go b/internal/service/ecr/images_data_source.go index ca270aeede43..b28cb2cca1a9 100644 --- a/internal/service/ecr/images_data_source.go +++ b/internal/service/ecr/images_data_source.go @@ -31,6 +31,11 @@ type imagesDataSource struct { func (d *imagesDataSource) Schema(ctx context.Context, request datasource.SchemaRequest, response *datasource.SchemaResponse) { response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ + "describe_images": schema.BoolAttribute{ + Optional: true, + Description: "Whether to call DescribeImages API to get detailed image information", + }, + "image_details": framework.DataSourceComputedListOfObjectAttribute[imageDetailsModel](ctx), "image_ids": framework.DataSourceComputedListOfObjectAttribute[imagesIDsModel](ctx), "max_results": schema.Int64Attribute{ Optional: true, @@ -92,9 +97,58 @@ func (d *imagesDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } + // If describe_images is true, call DescribeImages API + if !data.DescribeImages.IsNull() && data.DescribeImages.ValueBool() { + registryID := "" + if !data.RegistryID.IsNull() { + registryID = data.RegistryID.ValueString() + } + + imageDetails, err := findImagesDetails(ctx, conn, data.RepositoryName.ValueString(), registryID, output) + if err != nil { + resp.Diagnostics.AddError("describing ECR Images", err.Error()) + return + } + + resp.Diagnostics.Append(fwflex.Flatten(ctx, imageDetails, &data.ImageDetails)...) + if resp.Diagnostics.HasError() { + return + } + } + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } +func findImagesDetails(ctx context.Context, conn *ecr.Client, repositoryName, registryID string, imageIds []awstypes.ImageIdentifier) ([]awstypes.ImageDetail, error) { + var output []awstypes.ImageDetail + + // DescribeImages has a limit of 100 images per request + const batchSize = 100 + for i := 0; i < len(imageIds); i += batchSize { + end := i + batchSize + if end > len(imageIds) { + end = len(imageIds) + } + + input := &ecr.DescribeImagesInput{ + RepositoryName: &repositoryName, + ImageIds: imageIds[i:end], + } + if registryID != "" { + input.RegistryId = ®istryID + } + + result, err := conn.DescribeImages(ctx, input) + if err != nil { + return nil, err + } + + output = append(output, result.ImageDetails...) + } + + return output, nil +} + func findImages(ctx context.Context, conn *ecr.Client, input *ecr.ListImagesInput) ([]awstypes.ImageIdentifier, error) { var output []awstypes.ImageIdentifier @@ -121,6 +175,8 @@ func findImages(ctx context.Context, conn *ecr.Client, input *ecr.ListImagesInpu type imagesDataSourceModel struct { framework.WithRegionModel + DescribeImages types.Bool `tfsdk:"describe_images"` + ImageDetails fwtypes.ListNestedObjectValueOf[imageDetailsModel] `tfsdk:"image_details"` ImageIDs fwtypes.ListNestedObjectValueOf[imagesIDsModel] `tfsdk:"image_ids"` MaxResults types.Int64 `tfsdk:"max_results"` RegistryID types.String `tfsdk:"registry_id"` @@ -128,6 +184,15 @@ type imagesDataSourceModel struct { TagStatus types.String `tfsdk:"tag_status"` } +type imageDetailsModel struct { + ImageDigest types.String `tfsdk:"image_digest"` + ImagePushedAt types.String `tfsdk:"image_pushed_at"` + ImageSizeInBytes types.Int64 `tfsdk:"image_size_in_bytes"` + ImageTags fwtypes.ListValueOf[types.String] `tfsdk:"image_tags"` + RegistryID types.String `tfsdk:"registry_id"` + RepositoryName types.String `tfsdk:"repository_name"` +} + type imagesIDsModel struct { ImageDigest types.String `tfsdk:"image_digest"` ImageTag types.String `tfsdk:"image_tag"` diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index e09c6ca715c1..1ff24c1f7780 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -102,6 +102,27 @@ data "aws_ecr_images" "test" { `, rName) } +func TestAccECRImagesDataSource_describeImages(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ecr_images.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECRServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccImagesDataSourceConfig_describeImages(rName, true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, "describe_images", "true"), + ), + }, + }, + }) +} + func TestAccECRImagesDataSource_maxResults(t *testing.T) { ctx := acctest.Context(t) rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -158,6 +179,19 @@ func TestAccECRImagesDataSource_tagStatus(t *testing.T) { }) } +func testAccImagesDataSourceConfig_describeImages(rName string, describeImages bool) string { + return fmt.Sprintf(` +resource "aws_ecr_repository" "test" { + name = %[1]q +} + +data "aws_ecr_images" "test" { + repository_name = aws_ecr_repository.test.name + describe_images = %[2]t +} +`, rName, describeImages) +} + func testAccImagesDataSourceConfig_maxResults(rName string, maxResults int) string { return fmt.Sprintf(` resource "aws_ecr_repository" "test" { diff --git a/website/docs/d/ecr_images.html.markdown b/website/docs/d/ecr_images.html.markdown index fe10b89460e4..2fbe4273e7cd 100644 --- a/website/docs/d/ecr_images.html.markdown +++ b/website/docs/d/ecr_images.html.markdown @@ -44,6 +44,19 @@ data "aws_ecr_images" "limited" { } ``` +### Get Detailed Image Information + +```terraform +data "aws_ecr_images" "detailed" { + repository_name = "my-repository" + describe_images = true +} + +output "image_details" { + value = data.aws_ecr_images.detailed.image_details +} +``` + ## Argument Reference This data source supports the following arguments: @@ -53,6 +66,7 @@ This data source supports the following arguments: * `repository_name` - (Required) Name of the ECR Repository. * `tag_status` - (Optional) Filter images by tag status. Valid values: `TAGGED`, `UNTAGGED`, `ANY`. Defaults to `ANY`. * `max_results` - (Optional) Maximum number of images to return. +* `describe_images` - (Optional) Whether to call DescribeImages API to get detailed image information. Defaults to `false`. ## Attribute Reference @@ -61,3 +75,10 @@ This data source exports the following attributes in addition to the arguments a * `image_ids` - List of image objects containing image digest and tags. Each object has the following attributes: * `image_digest` - The sha256 digest of the image manifest. * `image_tag` - The tag associated with the image. +* `image_details` - List of detailed image information (only populated when `describe_images` is `true`). Each object has the following attributes: + * `image_digest` - The sha256 digest of the image manifest. + * `image_pushed_at` - The date and time when the image was pushed to the repository. + * `image_size_in_bytes` - The size of the image in bytes. + * `image_tags` - List of tags associated with the image. + * `registry_id` - The AWS account ID associated with the registry. + * `repository_name` - The name of the repository. From b1e634737e0730f1bb5ca0b4e7afd947fa8d516a Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:01:22 +0200 Subject: [PATCH 04/13] style: fix Go formatting in images_data_source.go - Align struct field tags for consistency --- internal/service/ecr/images_data_source.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/service/ecr/images_data_source.go b/internal/service/ecr/images_data_source.go index b28cb2cca1a9..798e739dbee7 100644 --- a/internal/service/ecr/images_data_source.go +++ b/internal/service/ecr/images_data_source.go @@ -36,7 +36,7 @@ func (d *imagesDataSource) Schema(ctx context.Context, request datasource.Schema Description: "Whether to call DescribeImages API to get detailed image information", }, "image_details": framework.DataSourceComputedListOfObjectAttribute[imageDetailsModel](ctx), - "image_ids": framework.DataSourceComputedListOfObjectAttribute[imagesIDsModel](ctx), + "image_ids": framework.DataSourceComputedListOfObjectAttribute[imagesIDsModel](ctx), "max_results": schema.Int64Attribute{ Optional: true, Description: "Maximum number of images to return", @@ -175,13 +175,13 @@ func findImages(ctx context.Context, conn *ecr.Client, input *ecr.ListImagesInpu type imagesDataSourceModel struct { framework.WithRegionModel - DescribeImages types.Bool `tfsdk:"describe_images"` + DescribeImages types.Bool `tfsdk:"describe_images"` ImageDetails fwtypes.ListNestedObjectValueOf[imageDetailsModel] `tfsdk:"image_details"` - ImageIDs fwtypes.ListNestedObjectValueOf[imagesIDsModel] `tfsdk:"image_ids"` - MaxResults types.Int64 `tfsdk:"max_results"` - RegistryID types.String `tfsdk:"registry_id"` - RepositoryName types.String `tfsdk:"repository_name"` - TagStatus types.String `tfsdk:"tag_status"` + ImageIDs fwtypes.ListNestedObjectValueOf[imagesIDsModel] `tfsdk:"image_ids"` + MaxResults types.Int64 `tfsdk:"max_results"` + RegistryID types.String `tfsdk:"registry_id"` + RepositoryName types.String `tfsdk:"repository_name"` + TagStatus types.String `tfsdk:"tag_status"` } type imageDetailsModel struct { From 1c2de28b087ba70d000c38b8fc3ab0cbd936f4df Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:15:16 +0200 Subject: [PATCH 05/13] docs: add changelog entry for PR #44621 Add changelog entry for ECR images data source enhancements: - tag_status filter (TAGGED/UNTAGGED/ANY) - max_results parameter - describe_images functionality with image_details output --- .changelog/44621.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .changelog/44621.txt diff --git a/.changelog/44621.txt b/.changelog/44621.txt new file mode 100644 index 000000000000..bc2acb0416b6 --- /dev/null +++ b/.changelog/44621.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +data-source/aws_ecr_images: Add `tag_status`, `max_results`, and `describe_images` arguments with `image_details` attribute +``` From cc4ed1181fa22a50eb7f7b5228d24dc7bba98dfe Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:18:12 +0200 Subject: [PATCH 06/13] docs: remove incorrect default value for tag_status Remove 'Defaults to ANY' from tag_status documentation as there is no default value set in the implementation. The parameter is optional and when not specified, the AWS API returns all images regardless of tag status. --- website/docs/d/ecr_images.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/d/ecr_images.html.markdown b/website/docs/d/ecr_images.html.markdown index 2fbe4273e7cd..e71efb282d95 100644 --- a/website/docs/d/ecr_images.html.markdown +++ b/website/docs/d/ecr_images.html.markdown @@ -64,7 +64,7 @@ This data source supports the following arguments: * `region` - (Optional) Region where this resource will be [managed](https://docs.aws.amazon.com/general/latest/gr/rande.html#regional-endpoints). Defaults to the Region set in the [provider configuration](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#aws-configuration-reference). * `registry_id` - (Optional) ID of the Registry where the repository resides. * `repository_name` - (Required) Name of the ECR Repository. -* `tag_status` - (Optional) Filter images by tag status. Valid values: `TAGGED`, `UNTAGGED`, `ANY`. Defaults to `ANY`. +* `tag_status` - (Optional) Filter images by tag status. Valid values: `TAGGED`, `UNTAGGED`, `ANY`. * `max_results` - (Optional) Maximum number of images to return. * `describe_images` - (Optional) Whether to call DescribeImages API to get detailed image information. Defaults to `false`. From eb321ff03b0975aee528b787fca1adbdae8c83ed Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:22:03 +0200 Subject: [PATCH 07/13] test: enhance tag_status filtering validation - Use public ECR registry (amazonlinux) with real images for testing - Add testAccCheckECRImagesAllHaveTags validation function - Verify TAGGED filter returns only images with tags - Test now validates actual filtering behavior, not just parameter acceptance Addresses review feedback about test coverage for tag_status filtering. --- .../service/ecr/images_data_source_test.go | 58 +++++++++++++++---- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index 1ff24c1f7780..05009d26c99a 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -5,10 +5,12 @@ package ecr_test import ( "fmt" + "strconv" "testing" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -146,7 +148,8 @@ func TestAccECRImagesDataSource_maxResults(t *testing.T) { func TestAccECRImagesDataSource_tagStatus(t *testing.T) { ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + registryID := "137112412989" + repositoryName := "amazonlinux" dataSourceName := "data.aws_ecr_images.test" resource.ParallelTest(t, resource.TestCase{ @@ -155,24 +158,21 @@ func TestAccECRImagesDataSource_tagStatus(t *testing.T) { ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, Steps: []resource.TestStep{ { - Config: testAccImagesDataSourceConfig_tagStatus(rName, "TAGGED"), + Config: testAccImagesDataSourceConfig_tagStatusPublic(registryID, repositoryName, "TAGGED"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, repositoryName), resource.TestCheckResourceAttr(dataSourceName, "tag_status", "TAGGED"), + resource.TestCheckResourceAttrSet(dataSourceName, "image_ids.#"), + // Verify all returned images have tags + testAccCheckECRImagesAllHaveTags(dataSourceName), ), }, { - Config: testAccImagesDataSourceConfig_tagStatus(rName, "UNTAGGED"), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), - resource.TestCheckResourceAttr(dataSourceName, "tag_status", "UNTAGGED"), - ), - }, - { - Config: testAccImagesDataSourceConfig_tagStatus(rName, "ANY"), + Config: testAccImagesDataSourceConfig_tagStatusPublic(registryID, repositoryName, "ANY"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, repositoryName), resource.TestCheckResourceAttr(dataSourceName, "tag_status", "ANY"), + resource.TestCheckResourceAttrSet(dataSourceName, "image_ids.#"), ), }, }, @@ -205,6 +205,40 @@ data "aws_ecr_images" "test" { `, rName, maxResults) } +func testAccCheckECRImagesAllHaveTags(resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + imageCount := rs.Primary.Attributes["image_ids.#"] + count, err := strconv.Atoi(imageCount) + if err != nil { + return err + } + + for i := 0; i < count; i++ { + tagKey := fmt.Sprintf("image_ids.%d.image_tag", i) + if tag := rs.Primary.Attributes[tagKey]; tag == "" { + return fmt.Errorf("Image at index %d has no tag when TAGGED filter was used", i) + } + } + + return nil + } +} + +func testAccImagesDataSourceConfig_tagStatusPublic(registryID, repositoryName, tagStatus string) string { + return fmt.Sprintf(` +data "aws_ecr_images" "test" { + registry_id = %[1]q + repository_name = %[2]q + tag_status = %[3]q +} +`, registryID, repositoryName, tagStatus) +} + func testAccImagesDataSourceConfig_tagStatus(rName, tagStatus string) string { return fmt.Sprintf(` resource "aws_ecr_repository" "test" { From 066498c526c41a9bac498a472d5b81665bf1f4bd Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:35:01 +0200 Subject: [PATCH 08/13] fix: use acctest.CtTrue constant instead of string literal Replace 'true' string literal with acctest.CtTrue constant in test to comply with semgrep rule ci.literal-True-string-test-constant --- internal/service/ecr/images_data_source_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index 05009d26c99a..f70471467746 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -118,7 +118,7 @@ func TestAccECRImagesDataSource_describeImages(t *testing.T) { Config: testAccImagesDataSourceConfig_describeImages(rName, true), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), - resource.TestCheckResourceAttr(dataSourceName, "describe_images", "true"), + resource.TestCheckResourceAttr(dataSourceName, "describe_images", acctest.CtTrue), ), }, }, From 6ef335445fecc7c90d1782e579ed4a8e079477a4 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:35:26 +0200 Subject: [PATCH 09/13] fix: correct terraform configuration formatting Remove extra spaces in attribute alignment to match terrafmt standards --- internal/service/ecr/images_data_source_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index f70471467746..1f09fdec7514 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -186,8 +186,8 @@ resource "aws_ecr_repository" "test" { } data "aws_ecr_images" "test" { - repository_name = aws_ecr_repository.test.name - describe_images = %[2]t + repository_name = aws_ecr_repository.test.name + describe_images = %[2]t } `, rName, describeImages) } From b0efc6085c6e16202a330afab2cf54d9c1e899f6 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:37:04 +0200 Subject: [PATCH 10/13] refactor: modernize code using min function Replace if statement with modern min() function for calculating batch end index in findImagesDetails function --- internal/service/ecr/images_data_source.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/service/ecr/images_data_source.go b/internal/service/ecr/images_data_source.go index 798e739dbee7..78cd75d3b72c 100644 --- a/internal/service/ecr/images_data_source.go +++ b/internal/service/ecr/images_data_source.go @@ -125,10 +125,7 @@ func findImagesDetails(ctx context.Context, conn *ecr.Client, repositoryName, re // DescribeImages has a limit of 100 images per request const batchSize = 100 for i := 0; i < len(imageIds); i += batchSize { - end := i + batchSize - if end > len(imageIds) { - end = len(imageIds) - } + end := min(i+batchSize, len(imageIds)) input := &ecr.DescribeImagesInput{ RepositoryName: &repositoryName, From deb537320523b99d12d4a7fbb4ce2861a13281ad Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:40:34 +0200 Subject: [PATCH 11/13] fix: remove ECR from function name in ecr package Rename testAccCheckECRImagesAllHaveTags to testAccCheckImagesAllHaveTags to comply with semgrep rule ci.ecr-in-func-name --- internal/service/ecr/images_data_source_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index 1f09fdec7514..4dc0a97337b8 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -164,7 +164,7 @@ func TestAccECRImagesDataSource_tagStatus(t *testing.T) { resource.TestCheckResourceAttr(dataSourceName, "tag_status", "TAGGED"), resource.TestCheckResourceAttrSet(dataSourceName, "image_ids.#"), // Verify all returned images have tags - testAccCheckECRImagesAllHaveTags(dataSourceName), + testAccCheckImagesAllHaveTags(dataSourceName), ), }, { @@ -205,7 +205,7 @@ data "aws_ecr_images" "test" { `, rName, maxResults) } -func testAccCheckECRImagesAllHaveTags(resourceName string) resource.TestCheckFunc { +func testAccCheckImagesAllHaveTags(resourceName string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[resourceName] if !ok { From 16778d5bc4d73d3b91cbb40c334bc1239c2e1294 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:49:53 +0200 Subject: [PATCH 12/13] fix: remove unused testAccImagesDataSourceConfig_tagStatus function Function is no longer used after updating test to use public ECR registry --- internal/service/ecr/images_data_source_test.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index 4dc0a97337b8..547a6933bdab 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -239,19 +239,6 @@ data "aws_ecr_images" "test" { `, registryID, repositoryName, tagStatus) } -func testAccImagesDataSourceConfig_tagStatus(rName, tagStatus string) string { - return fmt.Sprintf(` -resource "aws_ecr_repository" "test" { - name = %[1]q -} - -data "aws_ecr_images" "test" { - repository_name = aws_ecr_repository.test.name - tag_status = %[2]q -} -`, rName, tagStatus) -} - func testAccImagesDataSourceConfig_registryID(registryID, repositoryName string) string { return fmt.Sprintf(` data "aws_ecr_images" "test" { From d7ed7daa7443f6c0e5aa49ea5a7a1cb121fffa41 Mon Sep 17 00:00:00 2001 From: Paul Santus Date: Sat, 11 Oct 2025 00:58:57 +0200 Subject: [PATCH 13/13] refactor: modernize for loop using range over int Replace traditional for loop with modern range over int syntax --- internal/service/ecr/images_data_source_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/ecr/images_data_source_test.go b/internal/service/ecr/images_data_source_test.go index 547a6933bdab..037fea6006d4 100644 --- a/internal/service/ecr/images_data_source_test.go +++ b/internal/service/ecr/images_data_source_test.go @@ -218,7 +218,7 @@ func testAccCheckImagesAllHaveTags(resourceName string) resource.TestCheckFunc { return err } - for i := 0; i < count; i++ { + for i := range count { tagKey := fmt.Sprintf("image_ids.%d.image_tag", i) if tag := rs.Primary.Attributes[tagKey]; tag == "" { return fmt.Errorf("Image at index %d has no tag when TAGGED filter was used", i)