diff --git a/.changelog/45514.txt b/.changelog/45514.txt new file mode 100644 index 000000000000..25f54a579431 --- /dev/null +++ b/.changelog/45514.txt @@ -0,0 +1,3 @@ +```release-note:new-list-resource +aws_kms_key +``` \ No newline at end of file diff --git a/internal/errs/fwdiag/diags.go b/internal/errs/fwdiag/diags.go index 0e94e61975f5..4912b16bb434 100644 --- a/internal/errs/fwdiag/diags.go +++ b/internal/errs/fwdiag/diags.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/list" + sdkdiag "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" ) // DiagnosticsError returns an error containing all Diagnostic with SeverityError @@ -71,6 +73,32 @@ func NewListResultErrorDiagnostic(err error) list.ListResult { } } +func NewListResultSDKDiagnostics(diags sdkdiag.Diagnostics) list.ListResult { + return list.ListResult{ + Diagnostics: FromSDKDiagnostics(diags), + } +} + +func FromSDKDiagnostics(diags sdkdiag.Diagnostics) diag.Diagnostics { + return tfslices.ApplyToAll(diags, FromSDKDiagnostic) +} + +func FromSDKDiagnostic(d sdkdiag.Diagnostic) diag.Diagnostic { + switch d.Severity { + case sdkdiag.Error: + return diag.NewErrorDiagnostic( + d.Summary, + d.Detail, + ) + case sdkdiag.Warning: + return diag.NewWarningDiagnostic( + d.Summary, + d.Detail, + ) + } + return nil +} + func AsError[T any](x T, diags diag.Diagnostics) (T, error) { return x, DiagnosticsError(diags) } diff --git a/internal/service/kms/key.go b/internal/service/kms/key.go index 36295ce85a6d..696350ffb7e6 100644 --- a/internal/service/kms/key.go +++ b/internal/service/kms/key.go @@ -13,6 +13,8 @@ import ( "github.com/aws/aws-sdk-go-v2/service/kms" awstypes "github.com/aws/aws-sdk-go-v2/service/kms/types" awspolicy "github.com/hashicorp/awspolicyequivalence" + "github.com/hashicorp/terraform-plugin-framework/list" + listschema "github.com/hashicorp/terraform-plugin-framework/list/schema" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" sdkretry "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" @@ -22,12 +24,15 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/enum" "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/logging" "github.com/hashicorp/terraform-provider-aws/internal/retry" "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + inttypes "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -773,3 +778,93 @@ func waitKeyStatePropagated(ctx context.Context, conn *kms.Client, keyID string, return tfresource.WaitUntil(ctx, timeout, checkFunc, opts) } + +// @SDKListResource("aws_kms_key") +func keyResourceAsListResource() inttypes.ListResourceForSDK { + l := keyListResource{} + l.SetResourceSchema(resourceKey()) + return &l +} + +type keyListResource struct { + framework.ResourceWithConfigure + framework.ListResourceWithSDKv2Resource + framework.ListResourceWithSDKv2Tags +} + +type keyListResourceModel struct { + framework.WithRegionModel +} + +func (l *keyListResource) ListResourceConfigSchema(ctx context.Context, request list.ListResourceSchemaRequest, response *list.ListResourceSchemaResponse) { + response.Schema = listschema.Schema{ + Attributes: map[string]listschema.Attribute{}, + Blocks: map[string]listschema.Block{}, + } +} + +func (l *keyListResource) List(ctx context.Context, request list.ListRequest, stream *list.ListResultsStream) { + var query keyListResourceModel + if request.Config.Raw.IsKnown() && !request.Config.Raw.IsNull() { + if diags := request.Config.Get(ctx, &query); diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + } + + awsClient := l.Meta() + conn := awsClient.KMSClient(ctx) + + tflog.Info(ctx, "Listing KMS keys") + stream.Results = func(yield func(list.ListResult) bool) { + var input kms.ListKeysInput + pages := kms.NewListKeysPaginator(conn, &input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + if err != nil { + result := fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + for _, key := range page.Keys { + id := aws.ToString(key.KeyId) + ctx := tflog.SetField(ctx, logging.ResourceAttributeKey(names.AttrID), id) + + result := request.NewListResult(ctx) + + rd := l.ResourceData() + rd.SetId(id) + + diags := resourceKeyRead(ctx, rd, awsClient) + if diags.HasError() || rd.Id() == "" { + // Resource can't be read or is logically deleted. + // Log and continue. + tflog.Error(ctx, "Reading KMS key", map[string]any{ + names.AttrID: id, + "error": sdkdiag.DiagnosticsString(diags), + }) + continue + } + + if err := l.SetTags(ctx, awsClient, rd); err != nil { + result = fwdiag.NewListResultErrorDiagnostic(err) + yield(result) + return + } + + result.DisplayName = id + + l.SetResult(ctx, awsClient, request.IncludeResource, &result, rd) + if result.Diagnostics.HasError() { + yield(result) + return + } + + if !yield(result) { + return + } + } + } + } +} diff --git a/internal/service/kms/key_list_test.go b/internal/service/kms/key_list_test.go new file mode 100644 index 000000000000..bb249c390a1f --- /dev/null +++ b/internal/service/kms/key_list_test.go @@ -0,0 +1,66 @@ +// Copyright IBM Corp. 2014, 2025 +// SPDX-License-Identifier: MPL-2.0 + +package kms_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/config" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + tfquerycheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/querycheck" + tfstatecheck "github.com/hashicorp/terraform-provider-aws/internal/acctest/statecheck" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccKMSKey_List_basic(t *testing.T) { + ctx := acctest.Context(t) + resourceName1 := "aws_kms_key.test[0]" + resourceName2 := "aws_kms_key.test[1]" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + identity1 := tfstatecheck.Identity() + identity2 := tfstatecheck.Identity() + + acctest.ParallelTest(ctx, t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.SSMServiceID), + CheckDestroy: testAccCheckKeyDestroy(ctx), + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/Key/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + ConfigStateChecks: []statecheck.StateCheck{ + identity1.GetIdentity(resourceName1), + statecheck.ExpectKnownValue(resourceName1, tfjsonpath.New(names.AttrDescription), knownvalue.StringExact(rName+"-0")), + identity2.GetIdentity(resourceName2), + statecheck.ExpectKnownValue(resourceName2, tfjsonpath.New(names.AttrDescription), knownvalue.StringExact(rName+"-1")), + }, + }, + { + Query: true, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/Key/list_basic/"), + ConfigVariables: config.Variables{ + acctest.CtRName: config.StringVariable(rName), + }, + QueryResultChecks: []querycheck.QueryResultCheck{ + tfquerycheck.ExpectIdentityFunc("aws_kms_key.test", identity1.Checks()), + tfquerycheck.ExpectIdentityFunc("aws_kms_key.test", identity2.Checks()), + }, + }, + }, + }) +} diff --git a/internal/service/kms/service_package_gen.go b/internal/service/kms/service_package_gen.go index 9b435aad5fd1..e51552b52122 100644 --- a/internal/service/kms/service_package_gen.go +++ b/internal/service/kms/service_package_gen.go @@ -7,6 +7,8 @@ package kms import ( "context" + "iter" + "slices" "unique" "github.com/aws/aws-sdk-go-v2/aws" @@ -165,6 +167,21 @@ func (p *servicePackage) SDKResources(ctx context.Context) []*inttypes.ServicePa } } +func (p *servicePackage) SDKListResources(ctx context.Context) iter.Seq[*inttypes.ServicePackageSDKListResource] { + return slices.Values([]*inttypes.ServicePackageSDKListResource{ + { + Factory: keyResourceAsListResource, + TypeName: "aws_kms_key", + Name: "Key", + Region: unique.Make(inttypes.ResourceRegionDefault()), + Tags: unique.Make(inttypes.ServicePackageResourceTags{ + IdentifierAttribute: names.AttrID, + }), + Identity: inttypes.RegionalSingleParameterIdentity(names.AttrID), + }, + }) +} + func (p *servicePackage) ServicePackageName() string { return names.KMS } diff --git a/internal/service/kms/testdata/Key/list_basic/main.tf b/internal/service/kms/testdata/Key/list_basic/main.tf new file mode 100644 index 000000000000..8947a63a600f --- /dev/null +++ b/internal/service/kms/testdata/Key/list_basic/main.tf @@ -0,0 +1,15 @@ +# Copyright IBM Corp. 2014, 2025 +# SPDX-License-Identifier: MPL-2.0 + +resource "aws_kms_key" "test" { + count = 2 + + description = "${var.rName}-${count.index}" + deletion_window_in_days = 7 +} + +variable "rName" { + description = "Name for resource" + type = string + nullable = false +} diff --git a/internal/service/kms/testdata/Key/list_basic/main.tfquery.hcl b/internal/service/kms/testdata/Key/list_basic/main.tfquery.hcl new file mode 100644 index 000000000000..6c2d9dfb10a4 --- /dev/null +++ b/internal/service/kms/testdata/Key/list_basic/main.tfquery.hcl @@ -0,0 +1,6 @@ +# Copyright IBM Corp. 2014, 2025 +# SPDX-License-Identifier: MPL-2.0 + +list "aws_kms_key" "test" { + provider = aws +} diff --git a/website/docs/list-resources/kms_key.html.markdown b/website/docs/list-resources/kms_key.html.markdown new file mode 100644 index 000000000000..c3772b919185 --- /dev/null +++ b/website/docs/list-resources/kms_key.html.markdown @@ -0,0 +1,25 @@ +--- +subcategory: "KMS (Key Management)" +layout: "aws" +page_title: "AWS: aws_kms_key" +description: |- + Lists single-Region or multi-Region primary KMS keys. +--- + +# List Resource: aws_kms_key + +Lists single-Region or multi-Region primary KMS keys. + +## Example Usage + +```terraform +list "aws_kms_key" "example" { + provider = aws +} +``` + +## Argument Reference + +This list resource supports the following arguments: + +* `region` - (Optional) Region to query. Defaults to provider region.