Skip to content

Commit

Permalink
Merge pull request #34811 from DanielRieske/f/add-sso-admin-applicati…
Browse files Browse the repository at this point in the history
…on-access-scope

[New Resource]: Added ssoadmin `application_access_scope` resource
  • Loading branch information
jar-b authored Dec 18, 2023
2 parents 1191fde + 6926b3f commit 82c0e0b
Show file tree
Hide file tree
Showing 6 changed files with 459 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/34811.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
aws_ssoadmin_application_access_scope
```
240 changes: 240 additions & 0 deletions internal/service/ssoadmin/application_access_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package ssoadmin

import (
"context"
"errors"

"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/listplanmodifier"
"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"
intflex "github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"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"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @FrameworkResource(name="Application Access Scope")
func newResourceApplicationAccessScope(_ context.Context) (resource.ResourceWithConfigure, error) {
return &resourceApplicationAccessScope{}, nil
}

const (
ResNameApplicationAccessScope = "Application Access Scope"

applicationAccessScopeIDPartCount = 2
)

type resourceApplicationAccessScope struct {
framework.ResourceWithConfigure
}

func (r *resourceApplicationAccessScope) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = "aws_ssoadmin_application_access_scope"
}

func (r *resourceApplicationAccessScope) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"application_arn": schema.StringAttribute{
CustomType: fwtypes.ARNType,
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"authorized_targets": schema.ListAttribute{
ElementType: types.StringType,
Optional: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.RequiresReplace(),
},
},
"id": framework.IDAttribute(),
"scope": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
},
}
}

func (r *resourceApplicationAccessScope) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
conn := r.Meta().SSOAdminClient(ctx)

var plan resourceApplicationAccessScopeData
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

in := &ssoadmin.PutApplicationAccessScopeInput{
ApplicationArn: aws.String(plan.ApplicationARN.ValueString()),
Scope: aws.String(plan.Scope.ValueString()),
}

if !plan.AuthorizedTargets.IsNull() {
in.AuthorizedTargets = flex.ExpandFrameworkStringValueList(ctx, plan.AuthorizedTargets)
}

out, err := conn.PutApplicationAccessScope(ctx, in)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionCreating, ResNameApplicationAccessScope, plan.ApplicationARN.String(), err),
err.Error(),
)
return
}
if out == nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionCreating, ResNameApplicationAccessScope, plan.ApplicationARN.String(), nil),
errors.New("empty output").Error(),
)
return
}

idParts := []string{
plan.ApplicationARN.ValueString(),
plan.Scope.ValueString(),
}
id, err := intflex.FlattenResourceId(idParts, applicationAccessScopeIDPartCount, false)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionCreating, ResNameApplicationAccessScope, plan.ApplicationARN.String(), err),
err.Error(),
)
return
}

plan.ID = types.StringValue(id)

resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}

func (r *resourceApplicationAccessScope) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
conn := r.Meta().SSOAdminClient(ctx)

var state resourceApplicationAccessScopeData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

out, err := findApplicationAccessScopeByID(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, ResNameApplicationAccessScope, state.ID.String(), err),
err.Error(),
)
return
}

// ApplicationARN is not returned in the finder output. To allow import to set
// all attributes correctly, parse the ID for this value instead.
parts, err := intflex.ExpandResourceId(state.ID.ValueString(), applicationAccessScopeIDPartCount, false)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionSetting, ResNameApplicationAccessScope, state.ID.String(), err),
err.Error(),
)
return
}

state.ApplicationARN = fwtypes.ARNValue(parts[0])
state.AuthorizedTargets = flex.FlattenFrameworkStringValueList(ctx, out.AuthorizedTargets)
state.Scope = flex.StringToFramework(ctx, out.Scope)

resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (r *resourceApplicationAccessScope) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
//Update is no-op.
}

func (r *resourceApplicationAccessScope) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
conn := r.Meta().SSOAdminClient(ctx)

var state resourceApplicationAccessScopeData
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

in := &ssoadmin.DeleteApplicationAccessScopeInput{
ApplicationArn: aws.String(state.ApplicationARN.ValueString()),
Scope: aws.String(state.Scope.ValueString()),
}

_, err := conn.DeleteApplicationAccessScope(ctx, in)
if err != nil {
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return
}
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.SSOAdmin, create.ErrActionDeleting, ResNameApplicationAccessScope, state.ID.String(), err),
err.Error(),
)
return
}
}

func (r *resourceApplicationAccessScope) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}

func findApplicationAccessScopeByID(ctx context.Context, conn *ssoadmin.Client, id string) (*ssoadmin.GetApplicationAccessScopeOutput, error) {
parts, err := intflex.ExpandResourceId(id, applicationAccessScopeIDPartCount, false)
if err != nil {
return nil, err
}

in := &ssoadmin.GetApplicationAccessScopeInput{
ApplicationArn: aws.String(parts[0]),
Scope: aws.String(parts[1]),
}

out, err := conn.GetApplicationAccessScope(ctx, in)
if err != nil {
if errs.IsA[*awstypes.ResourceNotFoundException](err) {
return nil, &retry.NotFoundError{
LastError: err,
LastRequest: in,
}
}

return nil, err
}

if out == nil {
return nil, tfresource.NewEmptyResultError(in)
}

return out, nil
}

type resourceApplicationAccessScopeData struct {
ApplicationARN fwtypes.ARN `tfsdk:"application_arn"`
AuthorizedTargets types.List `tfsdk:"authorized_targets"`
ID types.String `tfsdk:"id"`
Scope types.String `tfsdk:"scope"`
}
146 changes: 146 additions & 0 deletions internal/service/ssoadmin/application_access_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// 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 TestAccSSOAdminApplicationAccessScope_basic(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_ssoadmin_application_access_scope.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: testAccCheckApplicationAccessScopeDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccApplicationAccessScopeConfig_basic(rName, "sso:account:access"),
Check: resource.ComposeTestCheckFunc(
testAccCheckApplicationAccessScopeExists(ctx, resourceName),
resource.TestCheckResourceAttrPair(resourceName, "application_arn", applicationResourceName, "application_arn"),
resource.TestCheckResourceAttr(resourceName, "scope", "sso:account:access"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSSOAdminApplicationAccessScope_disappears(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
resourceName := "aws_ssoadmin_application_access_scope.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: testAccCheckApplicationAccessScopeDestroy(ctx),
Steps: []resource.TestStep{
{
Config: testAccApplicationAccessScopeConfig_basic(rName, "sso:account:access"),
Check: resource.ComposeTestCheckFunc(
testAccCheckApplicationAccessScopeExists(ctx, resourceName),
acctest.CheckFrameworkResourceDisappears(ctx, acctest.Provider, tfssoadmin.ResourceApplicationAccessScope, resourceName),
),
ExpectNonEmptyPlan: true,
},
},
})
}

func testAccCheckApplicationAccessScopeDestroy(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_access_scope" {
continue
}

_, err := tfssoadmin.FindApplicationAccessScopeByID(ctx, conn, rs.Primary.ID)
if errs.IsA[*types.ResourceNotFoundException](err) {
return nil
}
if err != nil {
return create.Error(names.SSOAdmin, create.ErrActionCheckingDestroyed, tfssoadmin.ResNameApplicationAccessScope, rs.Primary.ID, err)
}

return create.Error(names.SSOAdmin, create.ErrActionCheckingDestroyed, tfssoadmin.ResNameApplicationAccessScope, rs.Primary.ID, errors.New("not destroyed"))
}

return nil
}
}

func testAccCheckApplicationAccessScopeExists(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.ResNameApplicationAccessScope, name, errors.New("not found"))
}

if rs.Primary.ID == "" {
return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameApplicationAccessScope, name, errors.New("not set"))
}

conn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminClient(ctx)

_, err := tfssoadmin.FindApplicationAccessScopeByID(ctx, conn, rs.Primary.ID)
if err != nil {
return create.Error(names.SSOAdmin, create.ErrActionCheckingExistence, tfssoadmin.ResNameApplicationAccessScope, rs.Primary.ID, err)
}

return nil
}
}

func testAccApplicationAccessScopeConfig_basic(rName, scope string) 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_access_scope" "test" {
application_arn = aws_ssoadmin_application.test.application_arn
authorized_targets = [aws_ssoadmin_application.test.application_arn]
scope = %[3]q
}
`, rName, testAccApplicationProviderARN, scope)
}
Loading

0 comments on commit 82c0e0b

Please sign in to comment.