diff --git a/.changelog/44795.txt b/.changelog/44795.txt new file mode 100644 index 000000000000..a61c86afe7de --- /dev/null +++ b/.changelog/44795.txt @@ -0,0 +1,3 @@ +```release-note:new-data-source +aws_ecrpublic_images +``` \ No newline at end of file diff --git a/internal/service/ecrpublic/images_data_source.go b/internal/service/ecrpublic/images_data_source.go new file mode 100644 index 000000000000..d6ac720ebb27 --- /dev/null +++ b/internal/service/ecrpublic/images_data_source.go @@ -0,0 +1,134 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ecrpublic + +import ( + "context" + + "github.com/YakDriver/regexache" + "github.com/aws/aws-sdk-go-v2/service/ecrpublic" + awstypes "github.com/aws/aws-sdk-go-v2/service/ecrpublic/types" + "github.com/hashicorp/terraform-plugin-framework-timetypes/timetypes" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + fwflex "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types" + "github.com/hashicorp/terraform-provider-aws/internal/smerr" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkDataSource("aws_ecrpublic_images", name="Images") +func newDataSourceImages(_ context.Context) (datasource.DataSourceWithConfigure, error) { + return &dataSourceImages{}, nil +} + +type dataSourceImages struct { + framework.DataSourceWithModel[dataSourceImagesModel] +} + +func (d *dataSourceImages) Schema(ctx context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Provides details about AWS ECR Public Images in a public repository.", + Attributes: map[string]schema.Attribute{ + names.AttrRepositoryName: schema.StringAttribute{ + Description: "Name of the public repository.", + Required: true, + Validators: []validator.String{ + stringvalidator.LengthBetween(2, 205), + }, + }, + "registry_id": schema.StringAttribute{ + Description: "AWS account ID associated with the public registry that contains the repository. If not specified, the default public registry is assumed.", + Optional: true, + Validators: []validator.String{ + stringvalidator.RegexMatches(regexache.MustCompile(`^[0-9]{12}$`), "must be a 12-digit AWS account ID"), + }, + }, + "images": framework.DataSourceComputedListOfObjectAttribute[imageItemModel](ctx), + }, + Blocks: map[string]schema.Block{ + "image_ids": schema.ListNestedBlock{ + Description: "List of image IDs to filter. Each image ID can use either a tag or digest.", + CustomType: fwtypes.NewListNestedObjectTypeOf[imagesIDsModel](ctx), + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "image_tag": schema.StringAttribute{ + Description: "Image tag.", + Optional: true, + }, + "image_digest": schema.StringAttribute{ + Description: "Image digest.", + Optional: true, + }, + }, + }, + }, + }, + } +} + +func (d *dataSourceImages) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data dataSourceImagesModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + conn := d.Meta().ECRPublicClient(ctx) + + var input ecrpublic.DescribeImagesInput + resp.Diagnostics.Append(fwflex.Expand(ctx, &data, &input)...) + if resp.Diagnostics.HasError() { + return + } + + var images []awstypes.ImageDetail + + paginator := ecrpublic.NewDescribeImagesPaginator(conn, &input) + for paginator.HasMorePages() { + output, err := paginator.NextPage(ctx) + + if err != nil { + smerr.AddError(ctx, &resp.Diagnostics, err, smerr.ID, data.RepositoryName.String()) + return + } + + images = append(images, output.ImageDetails...) + } + + resp.Diagnostics.Append(fwflex.Flatten(ctx, images, &data.Images)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +type dataSourceImagesModel struct { + framework.WithRegionModel + RepositoryName types.String `tfsdk:"repository_name"` + RegistryId types.String `tfsdk:"registry_id"` + ImageIds fwtypes.ListNestedObjectValueOf[imagesIDsModel] `tfsdk:"image_ids"` + Images fwtypes.ListNestedObjectValueOf[imageItemModel] `tfsdk:"images"` +} + +type imagesIDsModel struct { + ImageTag types.String `tfsdk:"image_tag"` + ImageDigest types.String `tfsdk:"image_digest"` +} + +type imageItemModel struct { + ArtifactMediaType types.String `tfsdk:"artifact_media_type"` + ImageDigest types.String `tfsdk:"image_digest"` + ImageManifestMediaType types.String `tfsdk:"image_manifest_media_type"` + ImagePushedAt timetypes.RFC3339 `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"` +} diff --git a/internal/service/ecrpublic/images_data_source_test.go b/internal/service/ecrpublic/images_data_source_test.go new file mode 100644 index 000000000000..0f1d35dd7b86 --- /dev/null +++ b/internal/service/ecrpublic/images_data_source_test.go @@ -0,0 +1,116 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ecrpublic_test + +import ( + "fmt" + "testing" + + "github.com/YakDriver/regexache" + "github.com/hashicorp/aws-sdk-go-base/v2/endpoints" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccECRPublicImagesDataSource_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ecrpublic_images.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckRegion(t, endpoints.UsEast1RegionID) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECRPublicServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccImagesDataSourceConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttr(dataSourceName, "images.#", "0"), + ), + }, + }, + }) +} + +func TestAccECRPublicImagesDataSource_registryID(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + dataSourceName := "data.aws_ecrpublic_images.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckRegion(t, endpoints.UsEast1RegionID) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECRPublicServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccImagesDataSourceConfig_registryID(rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, names.AttrRepositoryName, rName), + resource.TestCheckResourceAttrSet(dataSourceName, "registry_id"), + resource.TestCheckResourceAttr(dataSourceName, "images.#", "0"), + ), + }, + }, + }) +} + +func TestAccECRPublicImagesDataSource_registryIDValidation(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t); acctest.PreCheckRegion(t, endpoints.UsEast1RegionID) }, + ErrorCheck: acctest.ErrorCheck(t, names.ECRPublicServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccImagesDataSourceConfig_registryIDInvalid(rName), + ExpectError: regexache.MustCompile(`must be a 12-digit AWS account ID`), + }, + }, + }) +} + +func testAccImagesDataSourceConfig_basic(rName string) string { + return fmt.Sprintf(` +resource "aws_ecrpublic_repository" "test" { + repository_name = %[1]q +} + +data "aws_ecrpublic_images" "test" { + repository_name = aws_ecrpublic_repository.test.repository_name +} +`, rName) +} + +func testAccImagesDataSourceConfig_registryID(rName string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_ecrpublic_repository" "test" { + repository_name = %[1]q +} + +data "aws_ecrpublic_images" "test" { + repository_name = aws_ecrpublic_repository.test.repository_name + registry_id = data.aws_caller_identity.current.account_id +} +`, rName) +} + +func testAccImagesDataSourceConfig_registryIDInvalid(rName string) string { + return fmt.Sprintf(` +resource "aws_ecrpublic_repository" "test" { + repository_name = %[1]q +} + +data "aws_ecrpublic_images" "test" { + repository_name = aws_ecrpublic_repository.test.repository_name + registry_id = "invalid" +} +`, rName) +} diff --git a/internal/service/ecrpublic/service_package_gen.go b/internal/service/ecrpublic/service_package_gen.go index 32f6e94d39f7..b0f813d6019d 100644 --- a/internal/service/ecrpublic/service_package_gen.go +++ b/internal/service/ecrpublic/service_package_gen.go @@ -18,7 +18,14 @@ import ( type servicePackage struct{} func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*inttypes.ServicePackageFrameworkDataSource { - return []*inttypes.ServicePackageFrameworkDataSource{} + return []*inttypes.ServicePackageFrameworkDataSource{ + { + Factory: newDataSourceImages, + TypeName: "aws_ecrpublic_images", + Name: "Images", + Region: unique.Make(inttypes.ResourceRegionDefault()), + }, + } } func (p *servicePackage) FrameworkResources(ctx context.Context) []*inttypes.ServicePackageFrameworkResource { diff --git a/website/docs/d/ecrpublic_images.html.markdown b/website/docs/d/ecrpublic_images.html.markdown new file mode 100644 index 000000000000..ff36f8f82aad --- /dev/null +++ b/website/docs/d/ecrpublic_images.html.markdown @@ -0,0 +1,52 @@ +--- +subcategory: "ECR Public" +layout: "aws" +page_title: "AWS: aws_ecrpublic_images" +description: |- + Provides a list of images for a specified AWS ECR Public Repository. +--- + +# Data Source: aws_ecrpublic_images + +The ECR Public Images data source allows the list of images in a specified public repository to be retrieved. + +## Example Usage + +```terraform +data "aws_ecrpublic_images" "example" { + repository_name = "my-public-repository" +} + +output "image_digests" { + value = [for img in data.aws_ecrpublic_images.example.images : img.digest if img.digest != null] +} + +output "image_tags" { + value = distinct(flatten([for img in data.aws_ecrpublic_images.example.images : img.tags])) +} +``` + +## Argument Reference + +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). +* `repository_name` - (Required) Name of the public repository. +* `registry_id` - (Optional) AWS account ID associated with the public registry that contains the repository. If not specified, the default public registry is assumed. +* `image_ids` - (Optional) One or more image ID filters. Each image ID can use either a tag or digest (or both). Each object has the following attributes: + * `image_tag` - (Optional) Tag used for the image. + * `image_digest` - (Optional) Digest of the image manifest. + +## Attribute Reference + +This data source exports the following attributes in addition to the arguments above: + +* `images` - List of images returned. Each image contains: + * `digest` - Image digest. + * `tags` - List of image tags. + * `size_in_bytes` - Image size in bytes. + * `pushed_at` - Timestamp when image was pushed. + * `artifact_media_type` - Media type of the artifact. + * `image_manifest_media_type` - Media type of the image manifest. + * `registry_id` - AWS account ID associated with the public registry. + * `repository_name` - Name of the repository.