diff --git a/.changelog/34752.txt b/.changelog/34752.txt new file mode 100644 index 00000000000..4d671e59ccc --- /dev/null +++ b/.changelog/34752.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_ssoadmin_application_assignment_configuration +``` diff --git a/internal/service/ssoadmin/application_assignment_configuration.go b/internal/service/ssoadmin/application_assignment_configuration.go new file mode 100644 index 00000000000..32eece1d563 --- /dev/null +++ b/internal/service/ssoadmin/application_assignment_configuration.go @@ -0,0 +1,203 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssoadmin + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/ssoadmin" + awstypes "github.com/aws/aws-sdk-go-v2/service/ssoadmin/types" + "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/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/framework" + "github.com/hashicorp/terraform-provider-aws/internal/framework/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +// @FrameworkResource(name="Application Assignment Configuration") +func newResourceApplicationAssignmentConfiguration(_ context.Context) (resource.ResourceWithConfigure, error) { + return &resourceApplicationAssignmentConfiguration{}, nil +} + +const ( + ResNameApplicationAssignmentConfiguration = "Application Assignment Configuration" +) + +type resourceApplicationAssignmentConfiguration struct { + framework.ResourceWithConfigure +} + +func (r *resourceApplicationAssignmentConfiguration) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "aws_ssoadmin_application_assignment_configuration" +} + +func (r *resourceApplicationAssignmentConfiguration) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "application_arn": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "assignment_required": schema.BoolAttribute{ + Required: true, + }, + "id": framework.IDAttribute(), + }, + } +} + +func (r *resourceApplicationAssignmentConfiguration) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var plan resourceApplicationAssignmentConfigurationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + plan.ID = types.StringValue(plan.ApplicationARN.ValueString()) + + in := &ssoadmin.PutApplicationAssignmentConfigurationInput{ + ApplicationArn: aws.String(plan.ApplicationARN.ValueString()), + AssignmentRequired: aws.Bool(plan.AssignmentRequired.ValueBool()), + } + + _, err := conn.PutApplicationAssignmentConfiguration(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionCreating, ResNameApplicationAssignmentConfiguration, plan.ID.String(), err), + err.Error(), + ) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) +} + +func (r *resourceApplicationAssignmentConfiguration) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var state resourceApplicationAssignmentConfigurationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + out, err := findApplicationAssignmentConfigurationByID(ctx, conn, state.ID.ValueString()) + if tfresource.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionSetting, ResNameApplicationAssignmentConfiguration, state.ID.String(), err), + err.Error(), + ) + return + } + + state.AssignmentRequired = flex.BoolToFramework(ctx, out.AssignmentRequired) + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *resourceApplicationAssignmentConfiguration) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var plan, state resourceApplicationAssignmentConfigurationData + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + if !plan.AssignmentRequired.Equal(state.AssignmentRequired) { + in := &ssoadmin.PutApplicationAssignmentConfigurationInput{ + ApplicationArn: aws.String(plan.ApplicationARN.ValueString()), + AssignmentRequired: aws.Bool(plan.AssignmentRequired.ValueBool()), + } + + _, err := conn.PutApplicationAssignmentConfiguration(ctx, in) + if err != nil { + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionUpdating, ResNameApplicationAssignmentConfiguration, plan.ID.String(), err), + err.Error(), + ) + return + } + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +// Delete will place the application assignment configuration back into the default +// state of requiring assignment. +func (r *resourceApplicationAssignmentConfiguration) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + conn := r.Meta().SSOAdminClient(ctx) + + var state resourceApplicationAssignmentConfigurationData + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + in := &ssoadmin.PutApplicationAssignmentConfigurationInput{ + ApplicationArn: aws.String(state.ApplicationARN.ValueString()), + AssignmentRequired: aws.Bool(true), + } + + _, err := conn.PutApplicationAssignmentConfiguration(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return + } + resp.Diagnostics.AddError( + create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionDeleting, ResNameApplicationAssignmentConfiguration, state.ID.String(), err), + err.Error(), + ) + return + } +} + +func (r *resourceApplicationAssignmentConfiguration) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Set both id and application_arn on import to avoid immediate diff and planned replacement + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("application_arn"), req.ID)...) +} + +func findApplicationAssignmentConfigurationByID(ctx context.Context, conn *ssoadmin.Client, id string) (*ssoadmin.GetApplicationAssignmentConfigurationOutput, error) { + in := &ssoadmin.GetApplicationAssignmentConfigurationInput{ + ApplicationArn: aws.String(id), + } + + out, err := conn.GetApplicationAssignmentConfiguration(ctx, in) + if err != nil { + if errs.IsA[*awstypes.ResourceNotFoundException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: in, + } + } + + return nil, err + } + + return out, nil +} + +type resourceApplicationAssignmentConfigurationData struct { + ApplicationARN types.String `tfsdk:"application_arn"` + AssignmentRequired types.Bool `tfsdk:"assignment_required"` + ID types.String `tfsdk:"id"` +} diff --git a/internal/service/ssoadmin/application_assignment_configuration_test.go b/internal/service/ssoadmin/application_assignment_configuration_test.go new file mode 100644 index 00000000000..95b78d43d68 --- /dev/null +++ b/internal/service/ssoadmin/application_assignment_configuration_test.go @@ -0,0 +1,191 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package ssoadmin_test + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/ssoadmin/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tfssoadmin "github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccSSOAdminApplicationAssignmentConfiguration_basic(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_application_assignment_configuration.test" + applicationResourceName := "aws_ssoadmin_application.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSOAdminEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckApplicationAssignmentConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccApplicationAssignmentConfigurationConfig_basic(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationAssignmentConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttrPair(resourceName, "application_arn", applicationResourceName, "application_arn"), + resource.TestCheckResourceAttr(resourceName, "assignment_required", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccSSOAdminApplicationAssignmentConfiguration_disappears_Application(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_application_assignment_configuration.test" + applicationResourceName := "aws_ssoadmin_application.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSOAdminEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckApplicationAssignmentConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccApplicationAssignmentConfigurationConfig_basic(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationAssignmentConfigurationExists(ctx, resourceName), + acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfssoadmin.ResourceApplication, applicationResourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccSSOAdminApplicationAssignmentConfiguration_update(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_ssoadmin_application_assignment_configuration.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + acctest.PreCheck(ctx, t) + acctest.PreCheckPartitionHasService(t, names.SSOAdminEndpointID) + acctest.PreCheckSSOAdminInstances(ctx, t) + }, + ErrorCheck: acctest.ErrorCheck(t, names.SSOAdminEndpointID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckApplicationAssignmentConfigurationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccApplicationAssignmentConfigurationConfig_basic(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationAssignmentConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "assignment_required", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccApplicationAssignmentConfigurationConfig_basic(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationAssignmentConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "assignment_required", "false"), + ), + }, + { + Config: testAccApplicationAssignmentConfigurationConfig_basic(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckApplicationAssignmentConfigurationExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "assignment_required", "true"), + ), + }, + }, + }) +} + +func testAccCheckApplicationAssignmentConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_ssoadmin_application_assignment_configuration" { + continue + } + + _, err := tfssoadmin.FindApplicationAssignmentConfigurationByID(ctx, conn, rs.Primary.ID) + if errs.IsA[*types.ResourceNotFoundException](err) { + return nil + } + if err != nil { + return create.Error(names.SSOAdmin, create.ErrActionCheckingDestroyed, tfssoadmin.ResNameApplicationAssignmentConfiguration, rs.Primary.ID, err) + } + + return create.Error(names.SSOAdmin, create.ErrActionCheckingDestroyed, tfssoadmin.ResNameApplicationAssignmentConfiguration, rs.Primary.ID, errors.New("not destroyed")) + } + + return nil + } +} + +func testAccCheckApplicationAssignmentConfigurationExists(ctx context.Context, name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[name] + if !ok { + return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameApplicationAssignmentConfiguration, name, errors.New("not found")) + } + + if rs.Primary.ID == "" { + return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameApplicationAssignmentConfiguration, name, errors.New("not set")) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminClient(ctx) + + _, err := tfssoadmin.FindApplicationAssignmentConfigurationByID(ctx, conn, rs.Primary.ID) + if err != nil { + return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameApplicationAssignmentConfiguration, rs.Primary.ID, err) + } + + return nil + } +} + +func testAccApplicationAssignmentConfigurationConfig_basic(rName string, assignmentRequired bool) string { + return fmt.Sprintf(` +data "aws_ssoadmin_instances" "test" {} + +resource "aws_ssoadmin_application" "test" { + name = %[1]q + application_provider_arn = %[2]q + instance_arn = tolist(data.aws_ssoadmin_instances.test.arns)[0] +} + +resource "aws_ssoadmin_application_assignment_configuration" "test" { + application_arn = aws_ssoadmin_application.test.application_arn + assignment_required = %[3]t +} +`, rName, testAccApplicationProviderARN, assignmentRequired) +} diff --git a/internal/service/ssoadmin/exports_test.go b/internal/service/ssoadmin/exports_test.go index 12c317e8215..4ab0999ae72 100644 --- a/internal/service/ssoadmin/exports_test.go +++ b/internal/service/ssoadmin/exports_test.go @@ -5,9 +5,11 @@ package ssoadmin // Exports for use in tests only. var ( - ResourceApplication = newResourceApplication - ResourceApplicationAssignment = newResourceApplicationAssignment + ResourceApplication = newResourceApplication + ResourceApplicationAssignment = newResourceApplicationAssignment + ResourceApplicationAssignmentConfiguration = newResourceApplicationAssignmentConfiguration - FindApplicationByID = findApplicationByID - FindApplicationAssignmentByID = findApplicationAssignmentByID + FindApplicationByID = findApplicationByID + FindApplicationAssignmentByID = findApplicationAssignmentByID + FindApplicationAssignmentConfigurationByID = findApplicationAssignmentConfigurationByID ) diff --git a/internal/service/ssoadmin/service_package_gen.go b/internal/service/ssoadmin/service_package_gen.go index 3c416a36425..3a292af70eb 100644 --- a/internal/service/ssoadmin/service_package_gen.go +++ b/internal/service/ssoadmin/service_package_gen.go @@ -32,6 +32,10 @@ func (p *servicePackage) FrameworkResources(ctx context.Context) []*types.Servic Factory: newResourceApplicationAssignment, Name: "Application Assignment", }, + { + Factory: newResourceApplicationAssignmentConfiguration, + Name: "Application Assignment Configuration", + }, } } diff --git a/website/docs/r/ssoadmin_application_assignment_configuration.html.markdown b/website/docs/r/ssoadmin_application_assignment_configuration.html.markdown new file mode 100644 index 00000000000..a73d66f43e2 --- /dev/null +++ b/website/docs/r/ssoadmin_application_assignment_configuration.html.markdown @@ -0,0 +1,56 @@ +--- +subcategory: "SSO Admin" +layout: "aws" +page_title: "AWS: aws_ssoadmin_application_assignment_configuration" +description: |- + Terraform resource for managing an AWS SSO Admin Application Assignment Configuration. +--- +# Resource: aws_ssoadmin_application_assignment_configuration + +Terraform resource for managing an AWS SSO Admin Application Assignment Configuration. + +By default, applications will require users to have an explicit assignment in order to access an application. +This resource can be used to adjust this default behavior if necessary. + +~> Deleting this resource will return the assignment configuration for the application to the default AWS behavior (ie. `assignment_required = true`). + +## Example Usage + +### Basic Usage + +```terraform +resource "aws_ssoadmin_application_assignment_configuration" "example" { + application_arn = aws_ssoadmin_application.example.application_arn + assignment_required = true +} +``` + +## Argument Reference + +The following arguments are required: + +* `application_arn` - (Required) ARN of the application. +* `assignment_required` - (Required) Indicates whether users must have an explicit assignment to access the application. If `false`, all users have access to the application. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `id` - ARN of the application. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import SSO Admin Application Assignment Configuration using the `id`. For example: + +```terraform +import { + to = aws_ssoadmin_application_assignment_configuration.example + id = "arn:aws:sso::012345678901:application/id-12345678" +} +``` + +Using `terraform import`, import SSO Admin Application Assignment Configuration using the `id`. For example: + +```console +% terraform import aws_ssoadmin_application_assignment_configuration.example arn:aws:sso::012345678901:application/id-12345678 +```