From b76c2ad1d15d893b8dbd890e41d254ffe8e8ceac Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Mon, 18 Nov 2024 08:25:04 +1100 Subject: [PATCH 01/11] initial commit --- internal/acctest/acctest.go | 22 ++ internal/service/iam/organization_features.go | 238 ++++++++++++++++++ .../service/iam/organization_features_test.go | 143 +++++++++++ internal/service/iam/service_package_gen.go | 4 + 4 files changed, 407 insertions(+) create mode 100644 internal/service/iam/organization_features.go create mode 100644 internal/service/iam/organization_features_test.go diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 36ad7252c0a..da1c45902f0 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -34,6 +34,7 @@ import ( iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/aws-sdk-go-v2/service/inspector2" inspector2types "github.com/aws/aws-sdk-go-v2/service/inspector2/types" + "github.com/aws/aws-sdk-go-v2/service/organizations" organizationstypes "github.com/aws/aws-sdk-go-v2/service/organizations/types" "github.com/aws/aws-sdk-go-v2/service/outposts" "github.com/aws/aws-sdk-go-v2/service/pinpoint" @@ -1102,6 +1103,27 @@ func PreCheckOrganizationsEnabled(ctx context.Context, t *testing.T) *organizati return PreCheckOrganizationsEnabledWithProvider(ctx, t, func() *schema.Provider { return Provider }) } +func PreCheckOrganizationsAWSServiceAccess(ctx context.Context, t *testing.T, servicePrincipal string) { + t.Helper() + + conn := Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) + + paginator := organizations.NewListAWSServiceAccessForOrganizationPaginator(conn, &organizations.ListAWSServiceAccessForOrganizationInput{}) + for paginator.HasMorePages() { + page, err := paginator.NextPage(ctx) + if err != nil { + t.Fatalf("Listing AWS Organization Service Access: %s", err) + } + + for _, service := range page.EnabledServicePrincipals { + if aws.ToString(service.ServicePrincipal) == servicePrincipal { + return + } + } + } + t.Skipf("skipping tests; The AWS Organization service %s must be enabled on AWS Organization", servicePrincipal) +} + func PreCheckOrganizationsEnabledWithProvider(ctx context.Context, t *testing.T, providerF ProviderFunc) *organizationstypes.Organization { t.Helper() diff --git a/internal/service/iam/organization_features.go b/internal/service/iam/organization_features.go new file mode 100644 index 00000000000..3ddfdf94910 --- /dev/null +++ b/internal/service/iam/organization_features.go @@ -0,0 +1,238 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam + +import ( + "context" + "slices" + + "github.com/aws/aws-sdk-go-v2/service/iam" + awstypes "github.com/aws/aws-sdk-go-v2/service/iam/types" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// Function annotations are used for resource registration to the Provider. DO NOT EDIT. +// @FrameworkResource("aws_iam_organization_features", name="Organization Features") +func newResourceOrganizationFeatures(_ context.Context) (resource.ResourceWithConfigure, error) { + r := &resourceOrganizationFeatures{} + return r, nil +} + +const ( + ResNameOrganizationFeatures = "IAM Organization Features" +) + +type resourceOrganizationFeatures struct { + framework.ResourceWithConfigure +} + +func (r *resourceOrganizationFeatures) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_iam_organization_features" +} + +func (r *resourceOrganizationFeatures) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + names.AttrID: schema.StringAttribute{ + Computed: true, + }, + "features": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + Validators: []validator.Set{ + setvalidator.ValueStringsAre( + enum.FrameworkValidate[awstypes.FeatureType](), + ), + }, + }, + }, + } +} + +func (r *resourceOrganizationFeatures) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().IAMClient(ctx) + + var plan resourceOrganizationFeaturesModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var planFeatures []string + resp.Diagnostics.Append(plan.EnabledFeatures.ElementsAs(ctx, &planFeatures, false)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := manageOrganizationFeatures(ctx, conn, planFeatures, []string{}) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.IAM, create.ErrActionCreating, ResNameOrganizationFeatures, plan.OrganizationId.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceOrganizationFeatures) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().IAMClient(ctx) + + var state resourceOrganizationFeaturesModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var input iam.ListOrganizationsFeaturesInput + out, err := conn.ListOrganizationsFeatures(ctx, &input) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.IAM, create.ErrActionReading, ResNameOrganizationFeatures, "", err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceOrganizationFeatures) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().IAMClient(ctx) + + var plan, state resourceOrganizationFeaturesModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var stateFeatures, planFeatures []string + resp.Diagnostics.Append(plan.EnabledFeatures.ElementsAs(ctx, &planFeatures, false)...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(state.EnabledFeatures.ElementsAs(ctx, &stateFeatures, false)...) + if resp.Diagnostics.HasError() { + return + } + + _, err := manageOrganizationFeatures(ctx, conn, planFeatures, stateFeatures) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.IAM, create.ErrActionCreating, ResNameOrganizationFeatures, plan.OrganizationId.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *resourceOrganizationFeatures) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().IAMClient(ctx) + + var state resourceOrganizationFeaturesModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + var stateFeatures []string + resp.Diagnostics.Append(state.EnabledFeatures.ElementsAs(ctx, &stateFeatures, false)...) + if resp.Diagnostics.HasError() { + return + } + _, err := manageOrganizationFeatures(ctx, conn, []string{}, stateFeatures) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.IAM, create.ErrActionDeleting, ResNameOrganizationFeatures, state.OrganizationId.String(), err), + err.Error(), + ) + return + } + +} + +func (r *resourceOrganizationFeatures) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +type resourceOrganizationFeaturesModel struct { + OrganizationId types.String `tfsdk:"id"` + EnabledFeatures types.Set `tfsdk:"features"` +} + +func manageOrganizationFeatures(ctx context.Context, conn *iam.Client, planFeatures, stateFeatures []string) (*iam.ListOrganizationsFeaturesOutput, error) { + + var featuresToEnable, featuresToDisable []string + for _, feature := range planFeatures { + if !slices.Contains(stateFeatures, feature) { + featuresToEnable = append(featuresToEnable, feature) + } + } + for _, feature := range stateFeatures { + if !slices.Contains(planFeatures, feature) { + featuresToDisable = append(featuresToDisable, feature) + } + } + + if slices.Contains(featuresToEnable, string(awstypes.FeatureTypeRootCredentialsManagement)) { + var input iam.EnableOrganizationsRootCredentialsManagementInput + _, err := conn.EnableOrganizationsRootCredentialsManagement(ctx, &input) + if err != nil { + return nil, err + } + } + if slices.Contains(featuresToEnable, string(awstypes.FeatureTypeRootSessions)) { + var input iam.EnableOrganizationsRootSessionsInput + _, err := conn.EnableOrganizationsRootSessions(ctx, &input) + if err != nil { + return nil, err + } + } + if slices.Contains(featuresToDisable, string(awstypes.FeatureTypeRootCredentialsManagement)) { + var input iam.DisableOrganizationsRootCredentialsManagementInput + _, err := conn.DisableOrganizationsRootCredentialsManagement(ctx, &input) + if err != nil { + return nil, err + } + } + if slices.Contains(featuresToDisable, string(awstypes.FeatureTypeRootSessions)) { + var input iam.DisableOrganizationsRootSessionsInput + _, err := conn.DisableOrganizationsRootSessions(ctx, &input) + if err != nil { + return nil, err + } + } + var input iam.ListOrganizationsFeaturesInput + out, err := conn.ListOrganizationsFeatures(ctx, &input) + if err != nil { + return nil, err + } + return out, nil +} diff --git a/internal/service/iam/organization_features_test.go b/internal/service/iam/organization_features_test.go new file mode 100644 index 00000000000..debf1da3e8f --- /dev/null +++ b/internal/service/iam/organization_features_test.go @@ -0,0 +1,143 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package iam_test + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/iam" + "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/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/names" + + // TIP: You will often need to import the package that this test file lives + // in. Since it is in the "test" context, it must import the package to use + // any normal context constants, variables, or functions. + tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" +) + +// TIP: ==== ACCEPTANCE TESTS ==== +// This is an example of a basic acceptance test. This should test as much of +// standard functionality of the resource as possible, and test importing, if +// applicable. We prefix its name with "TestAcc", the service, and the +// resource name. +// +// Acceptance test access AWS and cost money to run. +func TestAccIAMOrganizationFeatures_basic(t *testing.T) { + ctx := acctest.Context(t) + var organizationfeatures iam.ListOrganizationsFeaturesOutput + resourceName := "aws_iam_organization_features.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationsEnabled(ctx, t) + acctest.PreCheckOrganizationsAWSServiceAccess(ctx, t, "iam.amazonaws.com") + }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationFeaturesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + }, + }) +} + +func TestAccIAMOrganizationFeatures_disappears(t *testing.T) { + ctx := acctest.Context(t) + if testing.Short() { + t.Skip("skipping long-running test in short mode") + } + + var organizationfeatures iam.ListOrganizationsFeaturesOutput + resourceName := "aws_iam_organization_features.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationsEnabled(ctx, t) + acctest.PreCheckOrganizationsAWSServiceAccess(ctx, t, "iam.amazonaws.com") + }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationFeaturesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckOrganizationFeaturesDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_organization_features" { + continue + } + + out, err := conn.ListOrganizationsFeatures(ctx, &iam.ListOrganizationsFeaturesInput{}) + if err != nil { + return create.Error(names.IAM, create.ErrActionCheckingDestroyed, tfiam.ResNameOrganizationFeatures, rs.Primary.Attributes["organization_id"], err) + } + if len(out.EnabledFeatures) == 0 { + return nil + } + + return create.Error(names.IAM, create.ErrActionCheckingDestroyed, tfiam.ResNameOrganizationFeatures, rs.Primary.Attributes["organization_id"], errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckOrganizationFeaturesExists(ctx context.Context, name string, organizationfeatures *iam.ListOrganizationsFeaturesOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.IAM, create.ErrActionCheckingExistence, tfiam.ResNameOrganizationFeatures, name, errors.New("not found")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) + resp, err := conn.ListOrganizationsFeatures(ctx, &iam.ListOrganizationsFeaturesInput{}) + if err != nil { + return create.Error(names.IAM, create.ErrActionCheckingExistence, tfiam.ResNameOrganizationFeatures, rs.Primary.Attributes["organization_id"], err) + } + + *organizationfeatures = *resp + + return nil + } +} + +func testAccOrganizationFeaturesConfig_basic(features []string) string { + return fmt.Sprintf(` +resource "aws_iam_organization_features" "test" { + features = [%[1]s] +} +`, fmt.Sprintf(`"%s"`, strings.Join(features, `", "`))) +} diff --git a/internal/service/iam/service_package_gen.go b/internal/service/iam/service_package_gen.go index 8029167973b..1f2f876168c 100644 --- a/internal/service/iam/service_package_gen.go +++ b/internal/service/iam/service_package_gen.go @@ -28,6 +28,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceGroupPolicyAttachmentsExclusive, Name: "Group Policy Attachments Exclusive", }, + { + Factory: newResourceOrganizationFeatures, + Name: "Organization Features", + }, { Factory: newResourceRolePoliciesExclusive, Name: "Role Policies Exclusive", From aaea0ba6c5de7893429cb98e461ee8e09e4b29b5 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Mon, 18 Nov 2024 20:53:45 +1100 Subject: [PATCH 02/11] fixed update, documentation and changelog --- .changelog/40164.txt | 3 + internal/acctest/acctest.go | 2 +- internal/service/iam/organization_features.go | 11 +--- .../service/iam/organization_features_test.go | 41 +------------ .../r/iam_organization_features.html.markdown | 58 +++++++++++++++++++ 5 files changed, 68 insertions(+), 47 deletions(-) create mode 100644 .changelog/40164.txt create mode 100644 website/docs/r/iam_organization_features.html.markdown diff --git a/.changelog/40164.txt b/.changelog/40164.txt new file mode 100644 index 00000000000..80ec3b01777 --- /dev/null +++ b/.changelog/40164.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_iam_organization_features +``` diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index da1c45902f0..a71c7351764 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1103,7 +1103,7 @@ func PreCheckOrganizationsEnabled(ctx context.Context, t *testing.T) *organizati return PreCheckOrganizationsEnabledWithProvider(ctx, t, func() *schema.Provider { return Provider }) } -func PreCheckOrganizationsAWSServiceAccess(ctx context.Context, t *testing.T, servicePrincipal string) { +func PreCheckOrganizationsTrustedServicePrincipalAccess(ctx context.Context, t *testing.T, servicePrincipal string) { t.Helper() conn := Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) diff --git a/internal/service/iam/organization_features.go b/internal/service/iam/organization_features.go index 3ddfdf94910..e458bd71f48 100644 --- a/internal/service/iam/organization_features.go +++ b/internal/service/iam/organization_features.go @@ -13,13 +13,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-provider-aws/internal/create" "github.com/hashicorp/terraform-provider-aws/internal/enum" - "github.com/hashicorp/terraform-provider-aws/internal/framework" "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" "github.com/hashicorp/terraform-provider-aws/names" @@ -142,7 +139,7 @@ func (r *resourceOrganizationFeatures) Update(ctx context.Context, req resource. return } - _, err := manageOrganizationFeatures(ctx, conn, planFeatures, stateFeatures) + out, err := manageOrganizationFeatures(ctx, conn, planFeatures, stateFeatures) if err != nil { resp.Diagnostics.AddError( create.ProblemStandardMessage(names.IAM, create.ErrActionCreating, ResNameOrganizationFeatures, plan.OrganizationId.String(), err), @@ -150,6 +147,7 @@ func (r *resourceOrganizationFeatures) Update(ctx context.Context, req resource. ) return } + plan.OrganizationId = flex.StringToFramework(ctx, out.OrganizationId) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } @@ -175,11 +173,10 @@ func (r *resourceOrganizationFeatures) Delete(ctx context.Context, req resource. ) return } - } func (r *resourceOrganizationFeatures) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) + resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) } type resourceOrganizationFeaturesModel struct { @@ -188,7 +185,6 @@ type resourceOrganizationFeaturesModel struct { } func manageOrganizationFeatures(ctx context.Context, conn *iam.Client, planFeatures, stateFeatures []string) (*iam.ListOrganizationsFeaturesOutput, error) { - var featuresToEnable, featuresToDisable []string for _, feature := range planFeatures { if !slices.Contains(stateFeatures, feature) { @@ -200,7 +196,6 @@ func manageOrganizationFeatures(ctx context.Context, conn *iam.Client, planFeatu featuresToDisable = append(featuresToDisable, feature) } } - if slices.Contains(featuresToEnable, string(awstypes.FeatureTypeRootCredentialsManagement)) { var input iam.EnableOrganizationsRootCredentialsManagementInput _, err := conn.EnableOrganizationsRootCredentialsManagement(ctx, &input) diff --git a/internal/service/iam/organization_features_test.go b/internal/service/iam/organization_features_test.go index debf1da3e8f..04ae12ddc1a 100644 --- a/internal/service/iam/organization_features_test.go +++ b/internal/service/iam/organization_features_test.go @@ -16,21 +16,10 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/names" - - // TIP: You will often need to import the package that this test file lives - // in. Since it is in the "test" context, it must import the package to use - // any normal context constants, variables, or functions. tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/names" ) -// TIP: ==== ACCEPTANCE TESTS ==== -// This is an example of a basic acceptance test. This should test as much of -// standard functionality of the resource as possible, and test importing, if -// applicable. We prefix its name with "TestAcc", the service, and the -// resource name. -// -// Acceptance test access AWS and cost money to run. func TestAccIAMOrganizationFeatures_basic(t *testing.T) { ctx := acctest.Context(t) var organizationfeatures iam.ListOrganizationsFeaturesOutput @@ -40,7 +29,7 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsEnabled(ctx, t) - acctest.PreCheckOrganizationsAWSServiceAccess(ctx, t, "iam.amazonaws.com") + acctest.PreCheckOrganizationsTrustedServicePrincipalAccess(ctx, t, "iam.amazonaws.com") }, ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -57,35 +46,11 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { ImportState: true, ImportStateVerify: false, }, - }, - }) -} - -func TestAccIAMOrganizationFeatures_disappears(t *testing.T) { - ctx := acctest.Context(t) - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } - - var organizationfeatures iam.ListOrganizationsFeaturesOutput - resourceName := "aws_iam_organization_features.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - acctest.PreCheckOrganizationsEnabled(ctx, t) - acctest.PreCheckOrganizationsAWSServiceAccess(ctx, t, "iam.amazonaws.com") - }, - ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationFeaturesDestroy(ctx), - Steps: []resource.TestStep{ { - Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), + Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement"}), Check: resource.ComposeTestCheckFunc( testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), ), - ExpectNonEmptyPlan: true, }, }, }) diff --git a/website/docs/r/iam_organization_features.html.markdown b/website/docs/r/iam_organization_features.html.markdown new file mode 100644 index 00000000000..84b3760be2d --- /dev/null +++ b/website/docs/r/iam_organization_features.html.markdown @@ -0,0 +1,58 @@ +--- +subcategory: "IAM (Identity & Access Management)" +layout: "aws" +page_title: "AWS: aws_iam_organization_features" +description: |- + Terraform resource for managing an AWS IAM (Identity & Access Management) Organization Features. +--- + +# Resource: aws_iam_organization_features + +Manages a IAM Organization Features for centralized root access for member accounts.. More information about managing root access in IAM can be found in the [Centralize root access for member accounts](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-enable-root-access.html). + +~> **NOTE:** Before managing IAM Organization features, the AWS account utilizing this resource must be an Organizations management account. Also, you must enable trusted access for AWS Identity and Access Management in AWS Organizations. + +## Example Usage + +```terraform +resource "aws_organizations_organization" "example" { + aws_service_access_principals = ["iam.amazonaws.com"] + feature_set = "ALL" +} + +resource "aws_iam_organization_features" "example" { + features = [ + "RootCredentialsManagement", + "RootSessions" + ] +} +``` + +## Argument Reference + +The following arguments are required: + +* `features` - (Required) List of IAM features to enable. Valid values are `RootCredentialsManagement` and `RootSessions` + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - AWS Organization identifier. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import IAM (Identity & Access Management) Organization Features using the `id`. For example: + +```terraform +import { + to = aws_iam_organization_features.example + id = "o-1234567" +} +``` + +Using `terraform import`, import IAM (Identity & Access Management) Organization Features using the `id`. For example: + +```console +% terraform import aws_iam_organization_features.example o-1234567 +``` From 9458f63483049ee2dae79c8b8e9bd23140bc485b Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Mon, 18 Nov 2024 21:53:02 +1100 Subject: [PATCH 03/11] fixed website terraform fmt --- internal/acctest/acctest.go | 2 +- website/docs/r/iam_organization_features.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index a71c7351764..22a0ee27c72 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -1121,7 +1121,7 @@ func PreCheckOrganizationsTrustedServicePrincipalAccess(ctx context.Context, t * } } } - t.Skipf("skipping tests; The AWS Organization service %s must be enabled on AWS Organization", servicePrincipal) + t.Skipf("skipping tests; The AWS Organization service principal trusted access for %s must be enabled.", servicePrincipal) } func PreCheckOrganizationsEnabledWithProvider(ctx context.Context, t *testing.T, providerF ProviderFunc) *organizationstypes.Organization { diff --git a/website/docs/r/iam_organization_features.html.markdown b/website/docs/r/iam_organization_features.html.markdown index 84b3760be2d..110de505444 100644 --- a/website/docs/r/iam_organization_features.html.markdown +++ b/website/docs/r/iam_organization_features.html.markdown @@ -22,7 +22,7 @@ resource "aws_organizations_organization" "example" { resource "aws_iam_organization_features" "example" { features = [ - "RootCredentialsManagement", + "RootCredentialsManagement", "RootSessions" ] } From 73669dac20de990ae8e2f1f2c8d603e969a0ab31 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Mon, 18 Nov 2024 22:21:14 +1100 Subject: [PATCH 04/11] consmetic --- internal/service/iam/organization_features.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/service/iam/organization_features.go b/internal/service/iam/organization_features.go index e458bd71f48..bc2b6284382 100644 --- a/internal/service/iam/organization_features.go +++ b/internal/service/iam/organization_features.go @@ -22,7 +22,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// Function annotations are used for resource registration to the Provider. DO NOT EDIT. // @FrameworkResource("aws_iam_organization_features", name="Organization Features") func newResourceOrganizationFeatures(_ context.Context) (resource.ResourceWithConfigure, error) { r := &resourceOrganizationFeatures{} From d8b0e7ca5b8040fc359f3da41871cb66da017211 Mon Sep 17 00:00:00 2001 From: Alex Bacchin Date: Tue, 19 Nov 2024 19:58:54 +1100 Subject: [PATCH 05/11] added additional validation and tests --- internal/service/iam/organization_features.go | 1 + internal/service/iam/organization_features_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/internal/service/iam/organization_features.go b/internal/service/iam/organization_features.go index bc2b6284382..fc2b8a4cf36 100644 --- a/internal/service/iam/organization_features.go +++ b/internal/service/iam/organization_features.go @@ -50,6 +50,7 @@ func (r *resourceOrganizationFeatures) Schema(ctx context.Context, req resource. ElementType: types.StringType, Required: true, Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), setvalidator.ValueStringsAre( enum.FrameworkValidate[awstypes.FeatureType](), ), diff --git a/internal/service/iam/organization_features_test.go b/internal/service/iam/organization_features_test.go index 04ae12ddc1a..cf46f21570f 100644 --- a/internal/service/iam/organization_features_test.go +++ b/internal/service/iam/organization_features_test.go @@ -39,6 +39,8 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), Check: resource.ComposeTestCheckFunc( testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + resource.TestCheckResourceAttr(resourceName, "features.0", "RootCredentialsManagement"), + resource.TestCheckResourceAttr(resourceName, "features.1", "RootSessions"), ), }, { @@ -50,6 +52,18 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement"}), Check: resource.ComposeTestCheckFunc( testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + resource.TestCheckResourceAttr(resourceName, "features.0", "RootCredentialsManagement"), + ), + }, { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: false, + }, + { + Config: testAccOrganizationFeaturesConfig_basic([]string{"RootSessions"}), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + resource.TestCheckResourceAttr(resourceName, "features.0", "RootSessions"), ), }, }, From 42429096ce4dfedb2ecdc3ddf1df0df2d6c096c5 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 25 Nov 2024 15:06:40 -0500 Subject: [PATCH 06/11] 'aws_iam_organization_features' -> 'aws_iam_organizations_features'. --- .changelog/40164.txt | 2 +- internal/service/iam/exports_test.go | 1 + ..._features.go => organizations_features.go} | 22 +++---- ...test.go => organizations_features_test.go} | 22 +++---- internal/service/iam/service_package_gen.go | 8 +-- .../r/iam_organization_features.html.markdown | 58 ------------------- .../iam_organizations_features.html.markdown | 58 +++++++++++++++++++ 7 files changed, 86 insertions(+), 85 deletions(-) rename internal/service/iam/{organization_features.go => organizations_features.go} (84%) rename internal/service/iam/{organization_features_test.go => organizations_features_test.go} (77%) delete mode 100644 website/docs/r/iam_organization_features.html.markdown create mode 100644 website/docs/r/iam_organizations_features.html.markdown diff --git a/.changelog/40164.txt b/.changelog/40164.txt index 80ec3b01777..34a1c10947f 100644 --- a/.changelog/40164.txt +++ b/.changelog/40164.txt @@ -1,3 +1,3 @@ ```release-note:new-resource -aws_iam_organization_features +aws_iam_organizations_features ``` diff --git a/internal/service/iam/exports_test.go b/internal/service/iam/exports_test.go index 41cd72221c9..1bd5afbbf7c 100644 --- a/internal/service/iam/exports_test.go +++ b/internal/service/iam/exports_test.go @@ -14,6 +14,7 @@ var ( ResourceGroupPolicyAttachment = resourceGroupPolicyAttachment ResourceInstanceProfile = resourceInstanceProfile ResourceOpenIDConnectProvider = resourceOpenIDConnectProvider + ResourceOrganizationsFeatures = newOrganizationsFeaturesResource ResourcePolicy = resourcePolicy ResourcePolicyAttachment = resourcePolicyAttachment ResourceRolePolicy = resourceRolePolicy diff --git a/internal/service/iam/organization_features.go b/internal/service/iam/organizations_features.go similarity index 84% rename from internal/service/iam/organization_features.go rename to internal/service/iam/organizations_features.go index fc2b8a4cf36..6ccc06f32eb 100644 --- a/internal/service/iam/organization_features.go +++ b/internal/service/iam/organizations_features.go @@ -22,9 +22,9 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -// @FrameworkResource("aws_iam_organization_features", name="Organization Features") -func newResourceOrganizationFeatures(_ context.Context) (resource.ResourceWithConfigure, error) { - r := &resourceOrganizationFeatures{} +// @FrameworkResource("aws_iam_organizations_features", name="Organizations Features") +func newOrganizationsFeaturesResource(context.Context) (resource.ResourceWithConfigure, error) { + r := &organizationsFeaturesResource{} return r, nil } @@ -32,15 +32,15 @@ const ( ResNameOrganizationFeatures = "IAM Organization Features" ) -type resourceOrganizationFeatures struct { +type organizationsFeaturesResource struct { framework.ResourceWithConfigure } -func (r *resourceOrganizationFeatures) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (*organizationsFeaturesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = "aws_iam_organization_features" } -func (r *resourceOrganizationFeatures) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *organizationsFeaturesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ names.AttrID: schema.StringAttribute{ @@ -60,7 +60,7 @@ func (r *resourceOrganizationFeatures) Schema(ctx context.Context, req resource. } } -func (r *resourceOrganizationFeatures) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *organizationsFeaturesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { conn := r.Meta().IAMClient(ctx) var plan resourceOrganizationFeaturesModel @@ -92,7 +92,7 @@ func (r *resourceOrganizationFeatures) Create(ctx context.Context, req resource. resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) } -func (r *resourceOrganizationFeatures) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *organizationsFeaturesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { conn := r.Meta().IAMClient(ctx) var state resourceOrganizationFeaturesModel @@ -119,7 +119,7 @@ func (r *resourceOrganizationFeatures) Read(ctx context.Context, req resource.Re resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } -func (r *resourceOrganizationFeatures) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *organizationsFeaturesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { conn := r.Meta().IAMClient(ctx) var plan, state resourceOrganizationFeaturesModel @@ -152,7 +152,7 @@ func (r *resourceOrganizationFeatures) Update(ctx context.Context, req resource. resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } -func (r *resourceOrganizationFeatures) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { +func (r *organizationsFeaturesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { conn := r.Meta().IAMClient(ctx) var state resourceOrganizationFeaturesModel @@ -175,7 +175,7 @@ func (r *resourceOrganizationFeatures) Delete(ctx context.Context, req resource. } } -func (r *resourceOrganizationFeatures) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *organizationsFeaturesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) } diff --git a/internal/service/iam/organization_features_test.go b/internal/service/iam/organizations_features_test.go similarity index 77% rename from internal/service/iam/organization_features_test.go rename to internal/service/iam/organizations_features_test.go index cf46f21570f..54c18b2941c 100644 --- a/internal/service/iam/organization_features_test.go +++ b/internal/service/iam/organizations_features_test.go @@ -20,7 +20,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccIAMOrganizationFeatures_basic(t *testing.T) { +func TestAccIAMOrganizationsFeatures_basic(t *testing.T) { ctx := acctest.Context(t) var organizationfeatures iam.ListOrganizationsFeaturesOutput resourceName := "aws_iam_organization_features.test" @@ -33,12 +33,12 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { }, ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationFeaturesDestroy(ctx), + CheckDestroy: testAccCheckOrganizationsFeaturesDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), + Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + testAccCheckOrganizationsFeaturesExists(ctx, resourceName, &organizationfeatures), resource.TestCheckResourceAttr(resourceName, "features.0", "RootCredentialsManagement"), resource.TestCheckResourceAttr(resourceName, "features.1", "RootSessions"), ), @@ -49,9 +49,9 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { ImportStateVerify: false, }, { - Config: testAccOrganizationFeaturesConfig_basic([]string{"RootCredentialsManagement"}), + Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootCredentialsManagement"}), Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + testAccCheckOrganizationsFeaturesExists(ctx, resourceName, &organizationfeatures), resource.TestCheckResourceAttr(resourceName, "features.0", "RootCredentialsManagement"), ), }, { @@ -60,9 +60,9 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { ImportStateVerify: false, }, { - Config: testAccOrganizationFeaturesConfig_basic([]string{"RootSessions"}), + Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootSessions"}), Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationFeaturesExists(ctx, resourceName, &organizationfeatures), + testAccCheckOrganizationsFeaturesExists(ctx, resourceName, &organizationfeatures), resource.TestCheckResourceAttr(resourceName, "features.0", "RootSessions"), ), }, @@ -70,7 +70,7 @@ func TestAccIAMOrganizationFeatures_basic(t *testing.T) { }) } -func testAccCheckOrganizationFeaturesDestroy(ctx context.Context) resource.TestCheckFunc { +func testAccCheckOrganizationsFeaturesDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) @@ -94,7 +94,7 @@ func testAccCheckOrganizationFeaturesDestroy(ctx context.Context) resource.TestC } } -func testAccCheckOrganizationFeaturesExists(ctx context.Context, name string, organizationfeatures *iam.ListOrganizationsFeaturesOutput) resource.TestCheckFunc { +func testAccCheckOrganizationsFeaturesExists(ctx context.Context, name string, organizationfeatures *iam.ListOrganizationsFeaturesOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] if !ok { @@ -113,7 +113,7 @@ func testAccCheckOrganizationFeaturesExists(ctx context.Context, name string, or } } -func testAccOrganizationFeaturesConfig_basic(features []string) string { +func testAccOrganizationsFeaturesConfig_basic(features []string) string { return fmt.Sprintf(` resource "aws_iam_organization_features" "test" { features = [%[1]s] diff --git a/internal/service/iam/service_package_gen.go b/internal/service/iam/service_package_gen.go index 1f2f876168c..d16f91c483a 100644 --- a/internal/service/iam/service_package_gen.go +++ b/internal/service/iam/service_package_gen.go @@ -20,6 +20,10 @@ func (p *servicePackage) FrameworkDataSources(ctx context.Context) []*types.Serv func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.ServicePackageFrameworkResource { return []*types.ServicePackageFrameworkResource{ + { + Factory: newOrganizationsFeaturesResource, + Name: "Organizations Features", + }, { Factory: newResourceGroupPoliciesExclusive, Name: "Group Policies Exclusive", @@ -28,10 +32,6 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceGroupPolicyAttachmentsExclusive, Name: "Group Policy Attachments Exclusive", }, - { - Factory: newResourceOrganizationFeatures, - Name: "Organization Features", - }, { Factory: newResourceRolePoliciesExclusive, Name: "Role Policies Exclusive", diff --git a/website/docs/r/iam_organization_features.html.markdown b/website/docs/r/iam_organization_features.html.markdown deleted file mode 100644 index 110de505444..00000000000 --- a/website/docs/r/iam_organization_features.html.markdown +++ /dev/null @@ -1,58 +0,0 @@ ---- -subcategory: "IAM (Identity & Access Management)" -layout: "aws" -page_title: "AWS: aws_iam_organization_features" -description: |- - Terraform resource for managing an AWS IAM (Identity & Access Management) Organization Features. ---- - -# Resource: aws_iam_organization_features - -Manages a IAM Organization Features for centralized root access for member accounts.. More information about managing root access in IAM can be found in the [Centralize root access for member accounts](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-enable-root-access.html). - -~> **NOTE:** Before managing IAM Organization features, the AWS account utilizing this resource must be an Organizations management account. Also, you must enable trusted access for AWS Identity and Access Management in AWS Organizations. - -## Example Usage - -```terraform -resource "aws_organizations_organization" "example" { - aws_service_access_principals = ["iam.amazonaws.com"] - feature_set = "ALL" -} - -resource "aws_iam_organization_features" "example" { - features = [ - "RootCredentialsManagement", - "RootSessions" - ] -} -``` - -## Argument Reference - -The following arguments are required: - -* `features` - (Required) List of IAM features to enable. Valid values are `RootCredentialsManagement` and `RootSessions` - -## Attribute Reference - -This resource exports the following attributes in addition to the arguments above: - -* `id` - AWS Organization identifier. - -## Import - -In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import IAM (Identity & Access Management) Organization Features using the `id`. For example: - -```terraform -import { - to = aws_iam_organization_features.example - id = "o-1234567" -} -``` - -Using `terraform import`, import IAM (Identity & Access Management) Organization Features using the `id`. For example: - -```console -% terraform import aws_iam_organization_features.example o-1234567 -``` diff --git a/website/docs/r/iam_organizations_features.html.markdown b/website/docs/r/iam_organizations_features.html.markdown new file mode 100644 index 00000000000..8b738d354c3 --- /dev/null +++ b/website/docs/r/iam_organizations_features.html.markdown @@ -0,0 +1,58 @@ +--- +subcategory: "IAM (Identity & Access Management)" +layout: "aws" +page_title: "AWS: aws_iam_organizations_features" +description: |- + Manages centralized root access features. +--- + +# Resource: aws_iam_organizations_features + +Manages centralized root access features across AWS member accounts managed using AWS Organizations. More information about managing root access in IAM can be found in the [Centralize root access for member accounts](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_root-enable-root-access.html). + +~> **NOTE:** The AWS account utilizing this resource must be an Organizations management account. Also, you must enable trusted access for AWS Identity and Access Management in AWS Organizations. + +## Example Usage + +```terraform +resource "aws_organizations_organization" "example" { + aws_service_access_principals = ["iam.amazonaws.com"] + feature_set = "ALL" +} + +resource "aws_iam_organizations_features" "example" { + features = [ + "RootCredentialsManagement", + "RootSessions" + ] +} +``` + +## Argument Reference + +The following arguments are required: + +* `features` - (Required) List of IAM features to enable. Valid values are `RootCredentialsManagement` and `RootSessions`. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - AWS Organization identifier. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import root access features using the `id`. For example: + +```terraform +import { + to = aws_iam_organizations_features.example + id = "o-1234567" +} +``` + +Using `terraform import`, import root access features using the `id`. For example: + +```console +% terraform import aws_iam_organizations_features.example o-1234567 +``` From 0958acff8e288172b12307fed9f059ee75a1b46a Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 25 Nov 2024 15:22:57 -0500 Subject: [PATCH 07/11] 'PreCheckOrganizationsTrustedServicePrincipalAccess' -> 'PreCheckOrganizationsEnabledServicePrincipal'. --- internal/acctest/acctest.go | 33 +++++++------------ .../iam/organizations_features_test.go | 2 +- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 7f1835ac5e7..231d50bbeb6 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -12,8 +12,8 @@ import ( "net" "os" "os/exec" - "reflect" "regexp" + "slices" "strconv" "strings" "sync" @@ -31,10 +31,8 @@ import ( dstypes "github.com/aws/aws-sdk-go-v2/service/directoryservice/types" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/iam" - iamtypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/aws-sdk-go-v2/service/inspector2" inspector2types "github.com/aws/aws-sdk-go-v2/service/inspector2/types" - "github.com/aws/aws-sdk-go-v2/service/organizations" organizationstypes "github.com/aws/aws-sdk-go-v2/service/organizations/types" "github.com/aws/aws-sdk-go-v2/service/outposts" "github.com/aws/aws-sdk-go-v2/service/pinpoint" @@ -1123,25 +1121,18 @@ func PreCheckOrganizationsEnabled(ctx context.Context, t *testing.T) *organizati return PreCheckOrganizationsEnabledWithProvider(ctx, t, func() *schema.Provider { return Provider }) } -func PreCheckOrganizationsTrustedServicePrincipalAccess(ctx context.Context, t *testing.T, servicePrincipal string) { +func PreCheckOrganizationsEnabledServicePrincipal(ctx context.Context, t *testing.T, servicePrincipalName string) { t.Helper() - conn := Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx) + servicePrincipalNames, err := tforganizations.FindEnabledServicePrincipalNames(ctx, Provider.Meta().(*conns.AWSClient).OrganizationsClient(ctx)) - paginator := organizations.NewListAWSServiceAccessForOrganizationPaginator(conn, &organizations.ListAWSServiceAccessForOrganizationInput{}) - for paginator.HasMorePages() { - page, err := paginator.NextPage(ctx) - if err != nil { - t.Fatalf("Listing AWS Organization Service Access: %s", err) - } + if err != nil { + t.Fatalf("reading Organization service principals: %s", err) + } - for _, service := range page.EnabledServicePrincipals { - if aws.ToString(service.ServicePrincipal) == servicePrincipal { - return - } - } + if !slices.Contains(servicePrincipalNames, servicePrincipalName) { + t.Skipf("trusted access for %s must be enabled in AWS Organizations", servicePrincipalName) } - t.Skipf("skipping tests; The AWS Organization service principal trusted access for %s must be enabled.", servicePrincipal) } func PreCheckOrganizationsEnabledWithProvider(ctx context.Context, t *testing.T, providerF ProviderFunc) *organizationstypes.Organization { @@ -1292,7 +1283,7 @@ func PreCheckIAMServiceLinkedRoleWithProvider(ctx context.Context, t *testing.T, input := &iam.ListRolesInput{ PathPrefix: aws.String(pathPrefix), } - var role iamtypes.Role + var roleFound bool pages := iam.NewListRolesPaginator(conn, input) for pages.HasMorePages() { @@ -1304,13 +1295,13 @@ func PreCheckIAMServiceLinkedRoleWithProvider(ctx context.Context, t *testing.T, t.Fatalf("listing IAM roles: %s", err) } - for _, r := range page.Roles { - role = r + if len(page.Roles) > 0 { + roleFound = true break } } - if reflect.ValueOf(role).IsZero() { + if !roleFound { t.Skipf("skipping tests; missing IAM service-linked role %s. Please create the role and retry", pathPrefix) } } diff --git a/internal/service/iam/organizations_features_test.go b/internal/service/iam/organizations_features_test.go index 54c18b2941c..f27803302e9 100644 --- a/internal/service/iam/organizations_features_test.go +++ b/internal/service/iam/organizations_features_test.go @@ -29,7 +29,7 @@ func TestAccIAMOrganizationsFeatures_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckOrganizationsEnabled(ctx, t) - acctest.PreCheckOrganizationsTrustedServicePrincipalAccess(ctx, t, "iam.amazonaws.com") + acctest.PreCheckOrganizationsEnabledServicePrincipal(ctx, t, "iam.amazonaws.com") }, ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, From de609fb8fb21cc8d124cd70fb38762246433e56c Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 25 Nov 2024 17:12:21 -0500 Subject: [PATCH 08/11] r/aws_iam_organizations_features: Tidy up. --- internal/service/iam/exports_test.go | 1 + .../service/iam/organizations_features.go | 255 +++++++++++------- .../iam/organizations_features_test.go | 134 ++++++--- .../iam_organizations_features.html.markdown | 4 +- 4 files changed, 265 insertions(+), 129 deletions(-) diff --git a/internal/service/iam/exports_test.go b/internal/service/iam/exports_test.go index 1bd5afbbf7c..d75fb25008c 100644 --- a/internal/service/iam/exports_test.go +++ b/internal/service/iam/exports_test.go @@ -46,6 +46,7 @@ var ( FindGroupPolicyAttachmentsByName = findGroupPolicyAttachmentsByName FindInstanceProfileByName = findInstanceProfileByName FindOpenIDConnectProviderByARN = findOpenIDConnectProviderByARN + FindOrganizationsFeatures = findOrganizationsFeatures FindPolicyByARN = findPolicyByARN FindRolePoliciesByName = findRolePoliciesByName FindRolePolicyAttachmentsByName = findRolePolicyAttachmentsByName diff --git a/internal/service/iam/organizations_features.go b/internal/service/iam/organizations_features.go index 6ccc06f32eb..7839636612d 100644 --- a/internal/service/iam/organizations_features.go +++ b/internal/service/iam/organizations_features.go @@ -5,183 +5,201 @@ package iam import ( "context" + "fmt" "slices" "github.com/aws/aws-sdk-go-v2/service/iam" awstypes "github.com/aws/aws-sdk-go-v2/service/iam/types" - "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" - "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-provider-aws/internal/create" - "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/fwdiag" "github.com/hashicorp/terraform-provider-aws/internal/framework" - "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + 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/tfresource" + itypes "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" ) // @FrameworkResource("aws_iam_organizations_features", name="Organizations Features") func newOrganizationsFeaturesResource(context.Context) (resource.ResourceWithConfigure, error) { r := &organizationsFeaturesResource{} + return r, nil } -const ( - ResNameOrganizationFeatures = "IAM Organization Features" -) - type organizationsFeaturesResource struct { framework.ResourceWithConfigure + framework.WithImportByID } -func (*organizationsFeaturesResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "aws_iam_organization_features" +func (*organizationsFeaturesResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = "aws_iam_organizations_features" } -func (r *organizationsFeaturesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ +func (r *organizationsFeaturesResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ - names.AttrID: schema.StringAttribute{ - Computed: true, - }, - "features": schema.SetAttribute{ - ElementType: types.StringType, + "enabled_features": schema.SetAttribute{ + CustomType: fwtypes.NewSetTypeOf[fwtypes.StringEnum[awstypes.FeatureType]](ctx), Required: true, - Validators: []validator.Set{ - setvalidator.SizeAtLeast(1), - setvalidator.ValueStringsAre( - enum.FrameworkValidate[awstypes.FeatureType](), - ), - }, + ElementType: fwtypes.StringEnumType[awstypes.FeatureType](), }, + names.AttrID: framework.IDAttribute(), }, } } -func (r *organizationsFeaturesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { +func (r *organizationsFeaturesResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var data organizationsFeaturesResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + conn := r.Meta().IAMClient(ctx) - var plan resourceOrganizationFeaturesModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if resp.Diagnostics.HasError() { + var enabledFeatures []awstypes.FeatureType + response.Diagnostics.Append(fwflex.Expand(ctx, data.EnabledFeatures, &enabledFeatures)...) + if response.Diagnostics.HasError() { return } - var planFeatures []string - resp.Diagnostics.Append(plan.EnabledFeatures.ElementsAs(ctx, &planFeatures, false)...) - if resp.Diagnostics.HasError() { + if err := updateOrganizationFeatures(ctx, conn, enabledFeatures, []awstypes.FeatureType{}); err != nil { + response.Diagnostics.AddError("creating IAM Organizations Features", err.Error()) + return } - out, err := manageOrganizationFeatures(ctx, conn, planFeatures, []string{}) + output, err := findOrganizationsFeatures(ctx, conn) + if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.IAM, create.ErrActionCreating, ResNameOrganizationFeatures, plan.OrganizationId.String(), err), - err.Error(), - ) - return - } + response.Diagnostics.AddError("reading IAM Organizations Features", err.Error()) - resp.Diagnostics.Append(flex.Flatten(ctx, out, &plan)...) - if resp.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + // Set values for unknowns. + data.OrganizationID = fwflex.StringToFramework(ctx, output.OrganizationId) + + response.Diagnostics.Append(response.State.Set(ctx, data)...) } -func (r *organizationsFeaturesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { +func (r *organizationsFeaturesResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + var data organizationsFeaturesResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + conn := r.Meta().IAMClient(ctx) - var state resourceOrganizationFeaturesModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { + output, err := findOrganizationsFeatures(ctx, conn) + + if tfresource.NotFound(err) { + response.Diagnostics.Append(fwdiag.NewResourceNotFoundWarningDiagnostic(err)) + response.State.RemoveResource(ctx) + return } - var input iam.ListOrganizationsFeaturesInput - out, err := conn.ListOrganizationsFeatures(ctx, &input) if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.IAM, create.ErrActionReading, ResNameOrganizationFeatures, "", err), - err.Error(), - ) + response.Diagnostics.AddError(fmt.Sprintf("reading IAM Organizations Features (%s)", data.OrganizationID.ValueString()), err.Error()) + return } - resp.Diagnostics.Append(flex.Flatten(ctx, out, &state)...) - if resp.Diagnostics.HasError() { + response.Diagnostics.Append(fwflex.Flatten(ctx, output.EnabledFeatures, &data.EnabledFeatures)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + response.Diagnostics.Append(response.State.Set(ctx, &data)...) } -func (r *organizationsFeaturesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - conn := r.Meta().IAMClient(ctx) - - var plan, state resourceOrganizationFeaturesModel - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *organizationsFeaturesResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + var old, new organizationsFeaturesResourceModel + response.Diagnostics.Append(request.Plan.Get(ctx, &new)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(request.State.Get(ctx, &old)...) + if response.Diagnostics.HasError() { return } - var stateFeatures, planFeatures []string - resp.Diagnostics.Append(plan.EnabledFeatures.ElementsAs(ctx, &planFeatures, false)...) - if resp.Diagnostics.HasError() { + var oldFeatures, newFeatures []awstypes.FeatureType + response.Diagnostics.Append(fwflex.Expand(ctx, old.EnabledFeatures, &oldFeatures)...) + if response.Diagnostics.HasError() { return } - resp.Diagnostics.Append(state.EnabledFeatures.ElementsAs(ctx, &stateFeatures, false)...) - if resp.Diagnostics.HasError() { + response.Diagnostics.Append(fwflex.Expand(ctx, new.EnabledFeatures, &newFeatures)...) + if response.Diagnostics.HasError() { return } - out, err := manageOrganizationFeatures(ctx, conn, planFeatures, stateFeatures) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.IAM, create.ErrActionCreating, ResNameOrganizationFeatures, plan.OrganizationId.String(), err), - err.Error(), - ) + conn := r.Meta().IAMClient(ctx) + + if err := updateOrganizationFeatures(ctx, conn, newFeatures, oldFeatures); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("updating IAM Organizations Features (%s)", new.OrganizationID.ValueString()), err.Error()) + return } - plan.OrganizationId = flex.StringToFramework(ctx, out.OrganizationId) - resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) + response.Diagnostics.Append(response.State.Set(ctx, &new)...) } -func (r *organizationsFeaturesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - conn := r.Meta().IAMClient(ctx) - - var state resourceOrganizationFeaturesModel - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { +func (r *organizationsFeaturesResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + var data organizationsFeaturesResourceModel + response.Diagnostics.Append(request.State.Get(ctx, &data)...) + if response.Diagnostics.HasError() { return } - var stateFeatures []string - resp.Diagnostics.Append(state.EnabledFeatures.ElementsAs(ctx, &stateFeatures, false)...) - if resp.Diagnostics.HasError() { + + conn := r.Meta().IAMClient(ctx) + + var enabledFeatures []awstypes.FeatureType + response.Diagnostics.Append(fwflex.Expand(ctx, data.EnabledFeatures, &enabledFeatures)...) + if response.Diagnostics.HasError() { return } - _, err := manageOrganizationFeatures(ctx, conn, []string{}, stateFeatures) - if err != nil { - resp.Diagnostics.AddError( - create.ProblemStandardMessage(names.IAM, create.ErrActionDeleting, ResNameOrganizationFeatures, state.OrganizationId.String(), err), - err.Error(), - ) + + if err := updateOrganizationFeatures(ctx, conn, []awstypes.FeatureType{}, enabledFeatures); err != nil { + response.Diagnostics.AddError(fmt.Sprintf("deleting IAM Organizations Features (%s)", data.OrganizationID.ValueString()), err.Error()) + return } } -func (r *organizationsFeaturesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root(names.AttrID), req, resp) +type organizationsFeaturesResourceModel struct { + EnabledFeatures fwtypes.SetValueOf[fwtypes.StringEnum[awstypes.FeatureType]] `tfsdk:"enabled_features"` + OrganizationID types.String `tfsdk:"id"` } -type resourceOrganizationFeaturesModel struct { - OrganizationId types.String `tfsdk:"id"` - EnabledFeatures types.Set `tfsdk:"features"` +func findOrganizationsFeatures(ctx context.Context, conn *iam.Client) (*iam.ListOrganizationsFeaturesOutput, error) { + input := &iam.ListOrganizationsFeaturesInput{} + + output, err := conn.ListOrganizationsFeatures(ctx, input) + + if errs.IsA[*awstypes.OrganizationNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.EnabledFeatures) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil } func manageOrganizationFeatures(ctx context.Context, conn *iam.Client, planFeatures, stateFeatures []string) (*iam.ListOrganizationsFeaturesOutput, error) { @@ -231,3 +249,50 @@ func manageOrganizationFeatures(ctx context.Context, conn *iam.Client, planFeatu } return out, nil } + +func updateOrganizationFeatures(ctx context.Context, conn *iam.Client, new, old []awstypes.FeatureType) error { + toEnable := itypes.Set[awstypes.FeatureType](new).Difference(old) + toDisable := itypes.Set[awstypes.FeatureType](old).Difference(new) + + if slices.Contains(toEnable, awstypes.FeatureTypeRootCredentialsManagement) { + input := &iam.EnableOrganizationsRootCredentialsManagementInput{} + + _, err := conn.EnableOrganizationsRootCredentialsManagement(ctx, input) + + if err != nil { + return fmt.Errorf("enabling Organizations root credentials management: %w", err) + } + } + + if slices.Contains(toEnable, awstypes.FeatureTypeRootSessions) { + input := &iam.EnableOrganizationsRootSessionsInput{} + + _, err := conn.EnableOrganizationsRootSessions(ctx, input) + + if err != nil { + return fmt.Errorf("enabling Organizations root sessions: %w", err) + } + } + + if slices.Contains(toDisable, awstypes.FeatureTypeRootCredentialsManagement) { + input := &iam.DisableOrganizationsRootCredentialsManagementInput{} + + _, err := conn.DisableOrganizationsRootCredentialsManagement(ctx, input) + + if err != nil { + return fmt.Errorf("disabling Organizations root credentials management: %w", err) + } + } + + if slices.Contains(toDisable, awstypes.FeatureTypeRootSessions) { + input := &iam.DisableOrganizationsRootSessionsInput{} + + _, err := conn.DisableOrganizationsRootSessions(ctx, input) + + if err != nil { + return fmt.Errorf("disabling Organizations root sessions: %w", err) + } + } + + return nil +} diff --git a/internal/service/iam/organizations_features_test.go b/internal/service/iam/organizations_features_test.go index f27803302e9..8e477160777 100644 --- a/internal/service/iam/organizations_features_test.go +++ b/internal/service/iam/organizations_features_test.go @@ -5,30 +5,42 @@ package iam_test import ( "context" - "errors" "fmt" "strings" "testing" - "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccIAMOrganizationsFeatures_basic(t *testing.T) { +func TestAccIAMOrganizationsFeatures_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]func(t *testing.T){ + acctest.CtBasic: testAccOrganizationsFeatures_basic, + acctest.CtDisappears: testAccOrganizationsFeatures_disappears, + "update": testAccOrganizationsFeatures_update, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccOrganizationsFeatures_basic(t *testing.T) { ctx := acctest.Context(t) - var organizationfeatures iam.ListOrganizationsFeaturesOutput resourceName := "aws_iam_organization_features.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckOrganizationsEnabled(ctx, t) + acctest.PreCheckOrganizationManagementAccount(ctx, t) acctest.PreCheckOrganizationsEnabledServicePrincipal(ctx, t, "iam.amazonaws.com") }, ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), @@ -38,23 +50,78 @@ func TestAccIAMOrganizationsFeatures_basic(t *testing.T) { { Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationsFeaturesExists(ctx, resourceName, &organizationfeatures), - resource.TestCheckResourceAttr(resourceName, "features.0", "RootCredentialsManagement"), - resource.TestCheckResourceAttr(resourceName, "features.1", "RootSessions"), + testAccCheckOrganizationsFeaturesExists(ctx, resourceName), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetSizeExact(2)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_capabilities"), knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("RootCredentialsManagement"), + knownvalue.StringExact("RootSessions"), + })), + }, }, { ResourceName: resourceName, ImportState: true, ImportStateVerify: false, }, + }, + }) +} + +func testAccOrganizationsFeatures_disappears(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_iam_organization_features.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationManagementAccount(ctx, t) + acctest.PreCheckOrganizationsEnabledServicePrincipal(ctx, t, "iam.amazonaws.com") + }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationsFeaturesDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationsFeaturesExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfiam.ResourceOrganizationsFeatures, resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccOrganizationsFeatures_update(t *testing.T) { + ctx := acctest.Context(t) + resourceName := "aws_iam_organization_features.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckOrganizationManagementAccount(ctx, t) + acctest.PreCheckOrganizationsEnabledServicePrincipal(ctx, t, "iam.amazonaws.com") + }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationsFeaturesDestroy(ctx), + Steps: []resource.TestStep{ { Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootCredentialsManagement"}), Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationsFeaturesExists(ctx, resourceName, &organizationfeatures), - resource.TestCheckResourceAttr(resourceName, "features.0", "RootCredentialsManagement"), + testAccCheckOrganizationsFeaturesExists(ctx, resourceName), ), - }, { + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetSizeExact(1)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_capabilities"), knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("RootCredentialsManagement"), + })), + }, + }, + { ResourceName: resourceName, ImportState: true, ImportStateVerify: false, @@ -62,9 +129,14 @@ func TestAccIAMOrganizationsFeatures_basic(t *testing.T) { { Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootSessions"}), Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationsFeaturesExists(ctx, resourceName, &organizationfeatures), - resource.TestCheckResourceAttr(resourceName, "features.0", "RootSessions"), + testAccCheckOrganizationsFeaturesExists(ctx, resourceName), ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetSizeExact(1)), + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_capabilities"), knownvalue.SetExact([]knownvalue.Check{ + knownvalue.StringExact("RootSessions"), + })), + }, }, }, }) @@ -75,48 +147,46 @@ func testAccCheckOrganizationsFeaturesDestroy(ctx context.Context) resource.Test conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_iam_organization_features" { + if rs.Type != "aws_iam_organizations_features" { continue } - out, err := conn.ListOrganizationsFeatures(ctx, &iam.ListOrganizationsFeaturesInput{}) - if err != nil { - return create.Error(names.IAM, create.ErrActionCheckingDestroyed, tfiam.ResNameOrganizationFeatures, rs.Primary.Attributes["organization_id"], err) + _, err := tfiam.FindOrganizationsFeatures(ctx, conn) + + if tfresource.NotFound(err) { + continue } - if len(out.EnabledFeatures) == 0 { - return nil + + if err != nil { + return err } - return create.Error(names.IAM, create.ErrActionCheckingDestroyed, tfiam.ResNameOrganizationFeatures, rs.Primary.Attributes["organization_id"], errors.New("not destroyed")) + return fmt.Errorf("IAM Organizations Features %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckOrganizationsFeaturesExists(ctx context.Context, name string, organizationfeatures *iam.ListOrganizationsFeaturesOutput) resource.TestCheckFunc { +func testAccCheckOrganizationsFeaturesExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + _, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.IAM, create.ErrActionCheckingExistence, tfiam.ResNameOrganizationFeatures, name, errors.New("not found")) + return fmt.Errorf("Not found: %s", n) } conn := acctest.Provider.Meta().(*conns.AWSClient).IAMClient(ctx) - resp, err := conn.ListOrganizationsFeatures(ctx, &iam.ListOrganizationsFeaturesInput{}) - if err != nil { - return create.Error(names.IAM, create.ErrActionCheckingExistence, tfiam.ResNameOrganizationFeatures, rs.Primary.Attributes["organization_id"], err) - } - *organizationfeatures = *resp + _, err := tfiam.FindOrganizationsFeatures(ctx, conn) - return nil + return err } } func testAccOrganizationsFeaturesConfig_basic(features []string) string { return fmt.Sprintf(` resource "aws_iam_organization_features" "test" { - features = [%[1]s] + enabled_features = [%[1]s] } `, fmt.Sprintf(`"%s"`, strings.Join(features, `", "`))) } diff --git a/website/docs/r/iam_organizations_features.html.markdown b/website/docs/r/iam_organizations_features.html.markdown index 8b738d354c3..d3995528a76 100644 --- a/website/docs/r/iam_organizations_features.html.markdown +++ b/website/docs/r/iam_organizations_features.html.markdown @@ -21,7 +21,7 @@ resource "aws_organizations_organization" "example" { } resource "aws_iam_organizations_features" "example" { - features = [ + enabled_features = [ "RootCredentialsManagement", "RootSessions" ] @@ -32,7 +32,7 @@ resource "aws_iam_organizations_features" "example" { The following arguments are required: -* `features` - (Required) List of IAM features to enable. Valid values are `RootCredentialsManagement` and `RootSessions`. +* `enabled_features` - (Required) List of IAM features to enable. Valid values are `RootCredentialsManagement` and `RootSessions`. ## Attribute Reference From 13d37a4455e14ea2d7a3f81c934b9d228d044f59 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Mon, 25 Nov 2024 17:23:08 -0500 Subject: [PATCH 09/11] Fix typos. --- .../service/iam/organizations_features_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/service/iam/organizations_features_test.go b/internal/service/iam/organizations_features_test.go index 8e477160777..3f84d87ba93 100644 --- a/internal/service/iam/organizations_features_test.go +++ b/internal/service/iam/organizations_features_test.go @@ -35,7 +35,7 @@ func TestAccIAMOrganizationsFeatures_serial(t *testing.T) { func testAccOrganizationsFeatures_basic(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_iam_organization_features.test" + resourceName := "aws_iam_organizations_features.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -54,7 +54,7 @@ func testAccOrganizationsFeatures_basic(t *testing.T) { ), ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetSizeExact(2)), - statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_capabilities"), knownvalue.SetExact([]knownvalue.Check{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetExact([]knownvalue.Check{ knownvalue.StringExact("RootCredentialsManagement"), knownvalue.StringExact("RootSessions"), })), @@ -71,7 +71,7 @@ func testAccOrganizationsFeatures_basic(t *testing.T) { func testAccOrganizationsFeatures_disappears(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_iam_organization_features.test" + resourceName := "aws_iam_organizations_features.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -97,7 +97,7 @@ func testAccOrganizationsFeatures_disappears(t *testing.T) { func testAccOrganizationsFeatures_update(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_iam_organization_features.test" + resourceName := "aws_iam_organizations_features.test" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -116,7 +116,7 @@ func testAccOrganizationsFeatures_update(t *testing.T) { ), ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetSizeExact(1)), - statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_capabilities"), knownvalue.SetExact([]knownvalue.Check{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetExact([]knownvalue.Check{ knownvalue.StringExact("RootCredentialsManagement"), })), }, @@ -133,7 +133,7 @@ func testAccOrganizationsFeatures_update(t *testing.T) { ), ConfigStateChecks: []statecheck.StateCheck{ statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetSizeExact(1)), - statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("number_capabilities"), knownvalue.SetExact([]knownvalue.Check{ + statecheck.ExpectKnownValue(resourceName, tfjsonpath.New("enabled_features"), knownvalue.SetExact([]knownvalue.Check{ knownvalue.StringExact("RootSessions"), })), }, @@ -185,7 +185,7 @@ func testAccCheckOrganizationsFeaturesExists(ctx context.Context, n string) reso func testAccOrganizationsFeaturesConfig_basic(features []string) string { return fmt.Sprintf(` -resource "aws_iam_organization_features" "test" { +resource "aws_iam_organizations_features" "test" { enabled_features = [%[1]s] } `, fmt.Sprintf(`"%s"`, strings.Join(features, `", "`))) From 1d07a94d9cddd60570b2b25fd7a15e878009d9cd Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Nov 2024 08:24:46 -0500 Subject: [PATCH 10/11] Remove 'testAccOrganizationsFeatures_disappears'. --- .../iam/organizations_features_test.go | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/internal/service/iam/organizations_features_test.go b/internal/service/iam/organizations_features_test.go index 3f84d87ba93..38088d23dc1 100644 --- a/internal/service/iam/organizations_features_test.go +++ b/internal/service/iam/organizations_features_test.go @@ -25,9 +25,8 @@ func TestAccIAMOrganizationsFeatures_serial(t *testing.T) { t.Parallel() testCases := map[string]func(t *testing.T){ - acctest.CtBasic: testAccOrganizationsFeatures_basic, - acctest.CtDisappears: testAccOrganizationsFeatures_disappears, - "update": testAccOrganizationsFeatures_update, + acctest.CtBasic: testAccOrganizationsFeatures_basic, + "update": testAccOrganizationsFeatures_update, } acctest.RunSerialTests1Level(t, testCases, 0) @@ -69,32 +68,6 @@ func testAccOrganizationsFeatures_basic(t *testing.T) { }) } -func testAccOrganizationsFeatures_disappears(t *testing.T) { - ctx := acctest.Context(t) - resourceName := "aws_iam_organizations_features.test" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { - acctest.PreCheck(ctx, t) - acctest.PreCheckOrganizationManagementAccount(ctx, t) - acctest.PreCheckOrganizationsEnabledServicePrincipal(ctx, t, "iam.amazonaws.com") - }, - ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckOrganizationsFeaturesDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccOrganizationsFeaturesConfig_basic([]string{"RootCredentialsManagement", "RootSessions"}), - Check: resource.ComposeTestCheckFunc( - testAccCheckOrganizationsFeaturesExists(ctx, resourceName), - acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfiam.ResourceOrganizationsFeatures, resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - func testAccOrganizationsFeatures_update(t *testing.T) { ctx := acctest.Context(t) resourceName := "aws_iam_organizations_features.test" From eb6f0cb67c44e0ce9739bbf5a13e64932077ef30 Mon Sep 17 00:00:00 2001 From: Kit Ewbank Date: Tue, 26 Nov 2024 08:46:04 -0500 Subject: [PATCH 11/11] Fix golangci-lint 'func `manageOrganizationFeatures` is unused (unused)'. --- .../service/iam/organizations_features.go | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/internal/service/iam/organizations_features.go b/internal/service/iam/organizations_features.go index 7839636612d..43468a6c6e3 100644 --- a/internal/service/iam/organizations_features.go +++ b/internal/service/iam/organizations_features.go @@ -202,54 +202,6 @@ func findOrganizationsFeatures(ctx context.Context, conn *iam.Client) (*iam.List return output, nil } -func manageOrganizationFeatures(ctx context.Context, conn *iam.Client, planFeatures, stateFeatures []string) (*iam.ListOrganizationsFeaturesOutput, error) { - var featuresToEnable, featuresToDisable []string - for _, feature := range planFeatures { - if !slices.Contains(stateFeatures, feature) { - featuresToEnable = append(featuresToEnable, feature) - } - } - for _, feature := range stateFeatures { - if !slices.Contains(planFeatures, feature) { - featuresToDisable = append(featuresToDisable, feature) - } - } - if slices.Contains(featuresToEnable, string(awstypes.FeatureTypeRootCredentialsManagement)) { - var input iam.EnableOrganizationsRootCredentialsManagementInput - _, err := conn.EnableOrganizationsRootCredentialsManagement(ctx, &input) - if err != nil { - return nil, err - } - } - if slices.Contains(featuresToEnable, string(awstypes.FeatureTypeRootSessions)) { - var input iam.EnableOrganizationsRootSessionsInput - _, err := conn.EnableOrganizationsRootSessions(ctx, &input) - if err != nil { - return nil, err - } - } - if slices.Contains(featuresToDisable, string(awstypes.FeatureTypeRootCredentialsManagement)) { - var input iam.DisableOrganizationsRootCredentialsManagementInput - _, err := conn.DisableOrganizationsRootCredentialsManagement(ctx, &input) - if err != nil { - return nil, err - } - } - if slices.Contains(featuresToDisable, string(awstypes.FeatureTypeRootSessions)) { - var input iam.DisableOrganizationsRootSessionsInput - _, err := conn.DisableOrganizationsRootSessions(ctx, &input) - if err != nil { - return nil, err - } - } - var input iam.ListOrganizationsFeaturesInput - out, err := conn.ListOrganizationsFeatures(ctx, &input) - if err != nil { - return nil, err - } - return out, nil -} - func updateOrganizationFeatures(ctx context.Context, conn *iam.Client, new, old []awstypes.FeatureType) error { toEnable := itypes.Set[awstypes.FeatureType](new).Difference(old) toDisable := itypes.Set[awstypes.FeatureType](old).Difference(new)