Skip to content

Commit

Permalink
r/aws_ssoadmin_application_assignment_configuration: new resource
Browse files Browse the repository at this point in the history
This resource will allow practitioners to manage AWS Identity Center
application assignment configurations with Terraform.
  • Loading branch information
jar-b committed Dec 5, 2023
1 parent 3318226 commit 0c35de7
Show file tree
Hide file tree
Showing 5 changed files with 460 additions and 4 deletions.
203 changes: 203 additions & 0 deletions internal/service/ssoadmin/application_assignment_configuration.go
Original file line number Diff line number Diff line change
@@ -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"`
}
191 changes: 191 additions & 0 deletions internal/service/ssoadmin/application_assignment_configuration_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 0c35de7

Please sign in to comment.