Skip to content

Commit

Permalink
<!--- See what makes a good Pull Request at : https://github.com/hash…
Browse files Browse the repository at this point in the history
…icorp/terraform-provider-aws/blob/main/docs/contributing --->

<!--- Please keep this note for the community --->

* Please vote on this pull request by adding a 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to the original pull request comment to help the community and maintainers prioritize this request
* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for pull request followers and do not help prioritize the request

<!--- Thank you for keeping this note for the community --->

<!--- If your PR fully resolves and should automatically close the linked issue, use Closes. Otherwise, use Relates --->
Closes hashicorp#18739

Output from acceptance testing:

<!--
Replace TestAccXXX with a pattern that matches the tests affected by this PR.

Replace ec2 with the service package corresponding to your tests.

For more information on the `-run` flag, see the `go test` documentation at https://tip.golang.org/cmd/go/#hdr-Testing_flags.
-->
```
$ make testacc PKG=ssoadmin
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test ./internal/service/ssoadmin/... -v -count 1 -parallel 20   -timeout 180m
=== RUN   TestAccSSOAdminAccountAssignment_Basic_group
=== PAUSE TestAccSSOAdminAccountAssignment_Basic_group
=== RUN   TestAccSSOAdminAccountAssignment_Basic_user
=== PAUSE TestAccSSOAdminAccountAssignment_Basic_user
=== RUN   TestAccSSOAdminAccountAssignment_disappears
=== PAUSE TestAccSSOAdminAccountAssignment_disappears
=== RUN   TestAccSSOAdminAccountAssignments_Basic_group
=== PAUSE TestAccSSOAdminAccountAssignments_Basic_group
=== RUN   TestAccSSOAdminAccountAssignments_Basic_user
=== PAUSE TestAccSSOAdminAccountAssignments_Basic_user
=== RUN   TestAccSSOAdminInstancesDataSource_basic
=== PAUSE TestAccSSOAdminInstancesDataSource_basic
=== RUN   TestAccSSOAdminManagedPolicyAttachment_basic
=== PAUSE TestAccSSOAdminManagedPolicyAttachment_basic
=== RUN   TestAccSSOAdminManagedPolicyAttachment_forceNew
=== PAUSE TestAccSSOAdminManagedPolicyAttachment_forceNew
=== RUN   TestAccSSOAdminManagedPolicyAttachment_disappears
--- PASS: TestAccSSOAdminManagedPolicyAttachment_disappears (27.91s)
=== RUN   TestAccSSOAdminManagedPolicyAttachment_Disappears_permissionSet
--- PASS: TestAccSSOAdminManagedPolicyAttachment_Disappears_permissionSet (18.53s)
=== RUN   TestAccSSOAdminManagedPolicyAttachment_multipleManagedPolicies
=== PAUSE TestAccSSOAdminManagedPolicyAttachment_multipleManagedPolicies
=== RUN   TestAccSSOAdminPermissionSetDataSource_arn
=== PAUSE TestAccSSOAdminPermissionSetDataSource_arn
=== RUN   TestAccSSOAdminPermissionSetDataSource_name
=== PAUSE TestAccSSOAdminPermissionSetDataSource_name
=== RUN   TestAccSSOAdminPermissionSetDataSource_nonExistent
=== PAUSE TestAccSSOAdminPermissionSetDataSource_nonExistent
=== RUN   TestAccSSOAdminPermissionSetInlinePolicy_basic
=== PAUSE TestAccSSOAdminPermissionSetInlinePolicy_basic
=== RUN   TestAccSSOAdminPermissionSetInlinePolicy_update
=== PAUSE TestAccSSOAdminPermissionSetInlinePolicy_update
=== RUN   TestAccSSOAdminPermissionSetInlinePolicy_disappears
--- PASS: TestAccSSOAdminPermissionSetInlinePolicy_disappears (20.30s)
=== RUN   TestAccSSOAdminPermissionSetInlinePolicy_Disappears_permissionSet
--- PASS: TestAccSSOAdminPermissionSetInlinePolicy_Disappears_permissionSet (20.05s)
=== RUN   TestAccSSOAdminPermissionSet_basic
=== PAUSE TestAccSSOAdminPermissionSet_basic
=== RUN   TestAccSSOAdminPermissionSet_tags
--- PASS: TestAccSSOAdminPermissionSet_tags (55.21s)
=== RUN   TestAccSSOAdminPermissionSet_updateDescription
=== PAUSE TestAccSSOAdminPermissionSet_updateDescription
=== RUN   TestAccSSOAdminPermissionSet_updateRelayState
=== PAUSE TestAccSSOAdminPermissionSet_updateRelayState
=== RUN   TestAccSSOAdminPermissionSet_updateSessionDuration
=== PAUSE TestAccSSOAdminPermissionSet_updateSessionDuration
=== RUN   TestAccSSOAdminPermissionSet_RelayState_updateSessionDuration
=== PAUSE TestAccSSOAdminPermissionSet_RelayState_updateSessionDuration
=== RUN   TestAccSSOAdminPermissionSet_mixedPolicyAttachments
=== PAUSE TestAccSSOAdminPermissionSet_mixedPolicyAttachments
=== CONT  TestAccSSOAdminAccountAssignment_Basic_group
=== CONT  TestAccSSOAdminPermissionSetDataSource_nonExistent
=== CONT  TestAccSSOAdminPermissionSet_updateRelayState
=== CONT  TestAccSSOAdminPermissionSet_basic
=== CONT  TestAccSSOAdminPermissionSet_updateDescription
=== CONT  TestAccSSOAdminManagedPolicyAttachment_basic
=== CONT  TestAccSSOAdminPermissionSetDataSource_arn
=== CONT  TestAccSSOAdminAccountAssignment_disappears
=== CONT  TestAccSSOAdminAccountAssignments_Basic_group
=== CONT  TestAccSSOAdminPermissionSetInlinePolicy_update
=== CONT  TestAccSSOAdminManagedPolicyAttachment_multipleManagedPolicies
=== CONT  TestAccSSOAdminAccountAssignment_Basic_user
=== CONT  TestAccSSOAdminPermissionSetDataSource_name
=== CONT  TestAccSSOAdminPermissionSetInlinePolicy_basic
=== CONT  TestAccSSOAdminPermissionSet_mixedPolicyAttachments
=== CONT  TestAccSSOAdminManagedPolicyAttachment_forceNew
=== CONT  TestAccSSOAdminPermissionSet_updateSessionDuration
=== CONT  TestAccSSOAdminAccountAssignments_Basic_user
=== CONT  TestAccSSOAdminPermissionSet_RelayState_updateSessionDuration
=== CONT  TestAccSSOAdminInstancesDataSource_basic
--- PASS: TestAccSSOAdminPermissionSetDataSource_nonExistent (10.53s)
--- PASS: TestAccSSOAdminInstancesDataSource_basic (22.39s)
--- PASS: TestAccSSOAdminPermissionSetDataSource_arn (30.25s)
--- PASS: TestAccSSOAdminPermissionSet_basic (33.43s)
--- PASS: TestAccSSOAdminAccountAssignment_disappears (41.19s)
--- PASS: TestAccSSOAdminPermissionSetDataSource_name (43.32s)
--- PASS: TestAccSSOAdminPermissionSetInlinePolicy_basic (43.53s)
--- PASS: TestAccSSOAdminAccountAssignments_Basic_group (46.83s)
--- PASS: TestAccSSOAdminAccountAssignment_Basic_user (47.01s)
--- PASS: TestAccSSOAdminManagedPolicyAttachment_basic (50.46s)
--- PASS: TestAccSSOAdminPermissionSet_updateSessionDuration (54.33s)
--- PASS: TestAccSSOAdminPermissionSet_updateDescription (54.34s)
--- PASS: TestAccSSOAdminPermissionSet_updateRelayState (54.41s)
--- PASS: TestAccSSOAdminPermissionSet_RelayState_updateSessionDuration (54.94s)
--- PASS: TestAccSSOAdminPermissionSetInlinePolicy_update (60.59s)
--- PASS: TestAccSSOAdminPermissionSet_mixedPolicyAttachments (61.51s)
--- PASS: TestAccSSOAdminManagedPolicyAttachment_forceNew (70.69s)
--- PASS: TestAccSSOAdminAccountAssignment_Basic_group (76.75s)
--- PASS: TestAccSSOAdminAccountAssignments_Basic_user (76.81s)
--- PASS: TestAccSSOAdminManagedPolicyAttachment_multipleManagedPolicies (91.68s)
PASS
ok  	github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin	237.127s

```
mattrobinsonsre committed Feb 19, 2022
1 parent 1af821c commit ddbcf2a
Showing 7 changed files with 604 additions and 2 deletions.
1 change: 1 addition & 0 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
@@ -1831,6 +1831,7 @@ func Provider() *schema.Provider {
"aws_ssm_resource_data_sync": ssm.ResourceResourceDataSync(),

"aws_ssoadmin_account_assignment": ssoadmin.ResourceAccountAssignment(),
"aws_ssoadmin_account_assignments": ssoadmin.ResourceAccountAssignments(),
"aws_ssoadmin_managed_policy_attachment": ssoadmin.ResourceManagedPolicyAttachment(),
"aws_ssoadmin_permission_set": ssoadmin.ResourcePermissionSet(),
"aws_ssoadmin_permission_set_inline_policy": ssoadmin.ResourcePermissionSetInlinePolicy(),
318 changes: 318 additions & 0 deletions internal/service/ssoadmin/account_assignments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package ssoadmin

import (
"fmt"
"regexp"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssoadmin"
"github.com/hashicorp/aws-sdk-go-base/tfawserr"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
"github.com/hashicorp/terraform-provider-aws/internal/flex"
"github.com/hashicorp/terraform-provider-aws/internal/verify"
)

func ResourceAccountAssignments() *schema.Resource {
return &schema.Resource{
Create: resourceAccountAssignmentsCreate,
Read: resourceAccountAssignmentsRead,
Delete: resourceAccountAssignmentsDelete,
Update: resourceAccountAssignmentsUpdate,

Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"instance_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: verify.ValidARN,
},

"permission_set_arn": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: verify.ValidARN,
},

"principal_ids": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.All(
validation.StringLenBetween(1, 47),
validation.StringMatch(regexp.MustCompile(`^([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$`), "must match ([0-9a-f]{10}-|)[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}"),
),
},
},

"principal_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(ssoadmin.PrincipalType_Values(), false),
},

"target_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: verify.ValidAccountID,
},

"target_type": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice(ssoadmin.TargetType_Values(), false),
},
},
}
}

func resourceAccountAssignmentsCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).SSOAdminConn

principalIDs := []*string{}
if v, ok := d.GetOk("principal_ids"); ok {
principalIDs = flex.ExpandStringSet(v.(*schema.Set))
}

instanceArn := d.Get("instance_arn").(string)
permissionSetArn := d.Get("permission_set_arn").(string)
principalType := d.Get("principal_type").(string)
targetID := d.Get("target_id").(string)
targetType := d.Get("target_type").(string)

// We need to check if any of the assignments exists before creating them
// since the AWS SSO API doesn't prevent us from creating duplicates
assignedIDs, err := FindAccountAssignmentPrincipals(conn, principalType, targetID, permissionSetArn, instanceArn)
if err != nil {
return fmt.Errorf("error listing SSO Account Assignments for AccountId (%s) PermissionSet (%s): %w", targetID, permissionSetArn, err)
}

if len(assignedIDs) > 0 {
return fmt.Errorf("error creating SSO Account Assignments for %s: already exists", principalType)
}

err = createAccountAssignments(conn, instanceArn, permissionSetArn, targetType, targetID, principalType, principalIDs)

if err != nil {
return fmt.Errorf("error creating SSO Account Assignments for %s: %w", principalType, err)
}

d.SetId(fmt.Sprintf("%s,%s,%s,%s,%s", principalType, targetID, targetType, permissionSetArn, instanceArn))

return resourceAccountAssignmentsRead(d, meta)
}

func resourceAccountAssignmentsRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).SSOAdminConn

idParts, err := ParseAccountAssignmentsID(d.Id())
if err != nil {
return fmt.Errorf("error parsing SSO Account Assignment ID: %w", err)
}

principalType := idParts[0]
targetID := idParts[1]
targetType := idParts[2]
permissionSetArn := idParts[3]
instanceArn := idParts[4]

assignedIDs, err := FindAccountAssignmentPrincipals(conn, principalType, targetID, permissionSetArn, instanceArn)

if err != nil {
return fmt.Errorf("error listing SSO Account Assignments for AccountId (%s) PermissionSet (%s): %w", targetID, permissionSetArn, err)
}

d.Set("instance_arn", instanceArn)
d.Set("permission_set_arn", permissionSetArn)
d.Set("principal_ids", assignedIDs)
d.Set("principal_type", principalType)
d.Set("target_id", targetID)
d.Set("target_type", targetType)

return nil
}

func resourceAccountAssignmentsDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).SSOAdminConn

idParts, err := ParseAccountAssignmentsID(d.Id())
if err != nil {
return fmt.Errorf("error parsing SSO Account Assignment ID: %w", err)
}

principalType := idParts[0]
targetID := idParts[1]
targetType := idParts[2]
permissionSetArn := idParts[3]
instanceArn := idParts[4]

principalIDs := []*string{}
if v, ok := d.GetOk("principal_ids"); ok {
principalIDs = flex.ExpandStringSet(v.(*schema.Set))
}

err = deleteAccountAssignments(conn, instanceArn, permissionSetArn, targetType, targetID, principalType, principalIDs)

if err != nil {
return fmt.Errorf("error deleting SSO Account Assignments for %s: %w", principalType, err)
}

return nil
}

func resourceAccountAssignmentsUpdate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*conns.AWSClient).SSOAdminConn

idParts, err := ParseAccountAssignmentsID(d.Id())
if err != nil {
return fmt.Errorf("error parsing SSO Account Assignment ID: %w", err)
}

principalType := idParts[0]
targetID := idParts[1]
targetType := idParts[2]
permissionSetArn := idParts[3]
instanceArn := idParts[4]

principalIDs := []*string{}
if v, ok := d.GetOk("principal_ids"); ok {
principalIDs = flex.ExpandStringSet(v.(*schema.Set))
}

assignedIDs, err := FindAccountAssignmentPrincipals(conn, principalType, targetID, permissionSetArn, instanceArn)

if err != nil {
return fmt.Errorf("error listing SSO Account Assignments for AccountId (%s) PermissionSet (%s): %w", targetID, permissionSetArn, err)
}

var createPrincipalIDs []*string
for _, principalID := range principalIDs {
found := false
for _, assignedID := range assignedIDs {
if aws.StringValue(principalID) == aws.StringValue(assignedID) {
found = true
break
}
}
if !found {
createPrincipalIDs = append(createPrincipalIDs, principalID)
}
}

err = createAccountAssignments(conn, instanceArn, permissionSetArn, targetType, targetID, principalType, createPrincipalIDs)

if err != nil {
return fmt.Errorf("error creating SSO Account Assignments for %s: %w", principalType, err)
}

var deletePrincipalIDs []*string
for _, assignedID := range assignedIDs {
found := false
for _, principalID := range principalIDs {
if aws.StringValue(principalID) == aws.StringValue(assignedID) {
found = true
break
}
}
if !found {
deletePrincipalIDs = append(deletePrincipalIDs, assignedID)
}
}

err = deleteAccountAssignments(conn, instanceArn, permissionSetArn, targetType, targetID, principalType, deletePrincipalIDs)

if err != nil {
return fmt.Errorf("error deleting SSO Account Assignments for %s: %w", principalType, err)
}

return resourceAccountAssignmentsRead(d, meta)
}

func createAccountAssignments(conn *ssoadmin.SSOAdmin, instanceArn string, permissionSetArn string, targetType string, targetID string, principalType string, principalIDs []*string) error {

for _, principalID := range principalIDs {
input := &ssoadmin.CreateAccountAssignmentInput{
InstanceArn: aws.String(instanceArn),
PermissionSetArn: aws.String(permissionSetArn),
PrincipalId: aws.String(*principalID),
PrincipalType: aws.String(principalType),
TargetId: aws.String(targetID),
TargetType: aws.String(targetType),
}

output, err := conn.CreateAccountAssignment(input)
if err != nil {
return fmt.Errorf("error creating SSO Account Assignment for %s (%s): %w", principalType, *principalID, err)
}

if output == nil || output.AccountAssignmentCreationStatus == nil {
return fmt.Errorf("error creating SSO Account Assignment for %s (%s): empty output", principalType, *principalID)

}

status := output.AccountAssignmentCreationStatus

_, err = waitAccountAssignmentCreated(conn, instanceArn, aws.StringValue(status.RequestId))
if err != nil {
return fmt.Errorf("error waiting for SSO Account Assignment for %s (%s) to be created: %w", principalType, *principalID, err)
}

}
return nil
}

func deleteAccountAssignments(conn *ssoadmin.SSOAdmin, instanceArn string, permissionSetArn string, targetType string, targetID string, principalType string, principalIDs []*string) error {

for _, principalID := range principalIDs {

input := &ssoadmin.DeleteAccountAssignmentInput{
PrincipalId: aws.String(*principalID),
InstanceArn: aws.String(instanceArn),
PermissionSetArn: aws.String(permissionSetArn),
TargetType: aws.String(targetType),
TargetId: aws.String(targetID),
PrincipalType: aws.String(principalType),
}

output, err := conn.DeleteAccountAssignment(input)
if err != nil {
if tfawserr.ErrCodeEquals(err, ssoadmin.ErrCodeResourceNotFoundException) {
return nil
}
return fmt.Errorf("error deleting SSO Account Assignment for Principal (%s): %w", *principalID, err)
}

if output == nil || output.AccountAssignmentDeletionStatus == nil {
return fmt.Errorf("error deleting SSO Account Assignment for Principal (%s): empty output", *principalID)
}

status := output.AccountAssignmentDeletionStatus

_, err = waitAccountAssignmentDeleted(conn, instanceArn, aws.StringValue(status.RequestId))
if err != nil {
return fmt.Errorf("error waiting for SSO Account Assignment for Principal (%s) to be deleted: %w", *principalID, err)
}
}

return nil
}

func ParseAccountAssignmentsID(id string) ([]string, error) {
idParts := strings.Split(id, ",")
if len(idParts) != 5 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" ||
idParts[3] == "" || idParts[4] == "" {
return nil, fmt.Errorf("unexpected format for ID (%q), expected PRINCIPAL_TYPE,TARGET_ID,TARGET_TYPE,PERMISSION_SET_ARN,INSTANCE_ARN", id)
}
return idParts, nil
}
163 changes: 163 additions & 0 deletions internal/service/ssoadmin/account_assignments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
package ssoadmin_test

import (
"fmt"
"os"
"testing"

"github.com/aws/aws-sdk-go/service/ssoadmin"
sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/internal/conns"
tfssoadmin "github.com/hashicorp/terraform-provider-aws/internal/service/ssoadmin"
)

func TestAccSSOAdminAccountAssignments_Basic_group(t *testing.T) {
resourceName := "aws_ssoadmin_account_assignments.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
groupName := os.Getenv("AWS_IDENTITY_STORE_GROUP_NAME")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(t)
testAccPreCheckInstances(t)
},
ErrorCheck: acctest.ErrorCheck(t, ssoadmin.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckAccountAssignmentsDestroy,
Steps: []resource.TestStep{
{
Config: testAccAccountAssignmentsBasicGroupConfig(groupName, rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "target_type", "AWS_ACCOUNT"),
resource.TestCheckResourceAttr(resourceName, "principal_type", "GROUP"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccSSOAdminAccountAssignments_Basic_user(t *testing.T) {
resourceName := "aws_ssoadmin_account_assignments.test"
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
userName := os.Getenv("AWS_IDENTITY_STORE_USER_NAME")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(t)
testAccPreCheckInstances(t)
},
ErrorCheck: acctest.ErrorCheck(t, ssoadmin.EndpointsID),
Providers: acctest.Providers,
CheckDestroy: testAccCheckAccountAssignmentsDestroy,
Steps: []resource.TestStep{
{
Config: testAccAccountAssignmentsBasicUserConfig(userName, rName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "target_type", "AWS_ACCOUNT"),
resource.TestCheckResourceAttr(resourceName, "principal_type", "USER"),
),
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccAccountAssignmentsBaseConfig(rName string) string {
return fmt.Sprintf(`
data "aws_ssoadmin_instances" "test" {}
data "aws_caller_identity" "current" {}
resource "aws_ssoadmin_permission_set" "test" {
name = %q
instance_arn = tolist(data.aws_ssoadmin_instances.test.arns)[0]
}
`, rName)
}

func testAccAccountAssignmentsBasicGroupConfig(groupName, rName string) string {
return acctest.ConfigCompose(
testAccAccountAssignmentsBaseConfig(rName),
fmt.Sprintf(`
data "aws_identitystore_group" "test" {
identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0]
filter {
attribute_path = "DisplayName"
attribute_value = %q
}
}
resource "aws_ssoadmin_account_assignments" "test" {
instance_arn = aws_ssoadmin_permission_set.test.instance_arn
permission_set_arn = aws_ssoadmin_permission_set.test.arn
target_type = "AWS_ACCOUNT"
target_id = data.aws_caller_identity.current.account_id
principal_type = "GROUP"
principal_ids = [data.aws_identitystore_group.test.group_id]
}
`, groupName))
}

func testAccAccountAssignmentsBasicUserConfig(userName, rName string) string {
return acctest.ConfigCompose(
testAccAccountAssignmentsBaseConfig(rName),
fmt.Sprintf(`
data "aws_identitystore_user" "test" {
identity_store_id = tolist(data.aws_ssoadmin_instances.test.identity_store_ids)[0]
filter {
attribute_path = "UserName"
attribute_value = %q
}
}
resource "aws_ssoadmin_account_assignments" "test" {
instance_arn = aws_ssoadmin_permission_set.test.instance_arn
permission_set_arn = aws_ssoadmin_permission_set.test.arn
target_type = "AWS_ACCOUNT"
target_id = data.aws_caller_identity.current.account_id
principal_type = "USER"
principal_ids = [data.aws_identitystore_user.test.user_id]
}
`, userName))
}

func testAccCheckAccountAssignmentsDestroy(s *terraform.State) error {
conn := acctest.Provider.Meta().(*conns.AWSClient).SSOAdminConn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_ssoadmin_account_assignments" {
continue
}

idParts, err := tfssoadmin.ParseAccountAssignmentsID(rs.Primary.ID)

if err != nil {
return fmt.Errorf("error parsing SSO Account Assignments ID (%s): %w", rs.Primary.ID, err)
}

principalType := idParts[0]
targetID := idParts[1]
permissionSetArn := idParts[3]
instanceArn := idParts[4]

assignedIDs, _ := tfssoadmin.FindAccountAssignmentPrincipals(conn, principalType, targetID, permissionSetArn, instanceArn)

if len(assignedIDs) > 0 {
return fmt.Errorf("SSO Account Assignments still exist")
}
}

return nil
}
33 changes: 33 additions & 0 deletions internal/service/ssoadmin/find.go
Original file line number Diff line number Diff line change
@@ -69,3 +69,36 @@ func FindManagedPolicy(conn *ssoadmin.SSOAdmin, managedPolicyArn, permissionSetA

return attachedPolicy, err
}

// FindAccountAssignmentPrincipals returns the principal ids assigned to a permission set within a specified SSO instance.
func FindAccountAssignmentPrincipals(conn *ssoadmin.SSOAdmin, principalType, accountId, permissionSetArn, instanceArn string) ([]*string, error) {
input := &ssoadmin.ListAccountAssignmentsInput{
AccountId: aws.String(accountId),
InstanceArn: aws.String(instanceArn),
PermissionSetArn: aws.String(permissionSetArn),
}

var principalIds []*string

err := conn.ListAccountAssignmentsPages(input, func(page *ssoadmin.ListAccountAssignmentsOutput, lastPage bool) bool {
if page == nil {
return !lastPage
}

for _, a := range page.AccountAssignments {
if a == nil {
continue
}

if aws.StringValue(a.PrincipalType) != principalType {
continue
}

principalIds = append(principalIds, a.PrincipalId)
}

return !lastPage
})

return principalIds, err
}
4 changes: 2 additions & 2 deletions internal/service/ssoadmin/wait.go
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ const (
awsSSOAdminPermissionSetProvisionTimeout = 10 * time.Minute
)

func waitAccountAssignmentCreated(conn *ssoadmin.SSOAdmin, instanceArn, requestID string) (*ssoadmin.AccountAssignmentOperationStatus, error) {
func waitAccountAssignmentCreated(conn *ssoadmin.SSOAdmin, instanceArn, requestID string) (*ssoadmin.AccountAssignmentOperationStatus, error) { //nolint:unparam
stateConf := &resource.StateChangeConf{
Pending: []string{ssoadmin.StatusValuesInProgress},
Target: []string{ssoadmin.StatusValuesSucceeded},
@@ -34,7 +34,7 @@ func waitAccountAssignmentCreated(conn *ssoadmin.SSOAdmin, instanceArn, requestI
return nil, err
}

func waitAccountAssignmentDeleted(conn *ssoadmin.SSOAdmin, instanceArn, requestID string) (*ssoadmin.AccountAssignmentOperationStatus, error) {
func waitAccountAssignmentDeleted(conn *ssoadmin.SSOAdmin, instanceArn, requestID string) (*ssoadmin.AccountAssignmentOperationStatus, error) { //nolint:unparam
stateConf := &resource.StateChangeConf{
Pending: []string{ssoadmin.StatusValuesInProgress},
Target: []string{ssoadmin.StatusValuesSucceeded},
2 changes: 2 additions & 0 deletions website/docs/r/ssoadmin_account_assignment.html.markdown
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ description: |-
Manages a Single Sign-On (SSO) Account Assignment
---

~> **Note:** `aws_ssoadmin_account_assignment` **cannot** be used in conjunction with `aws_ssoadmin_account_assignments` or they will fight over what principal ids should be associated with what permission sets.

# Resource: aws_ssoadmin_account_assignment

Provides a Single Sign-On (SSO) Account Assignment resource
85 changes: 85 additions & 0 deletions website/docs/r/ssoadmin_account_assignments.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
---
subcategory: "SSO Admin"
layout: "aws"
page_title: "AWS: aws_ssoadmin_account_assignments"
description: |-
Manages Multiple Single Sign-On (SSO) Account Assignments Authoritatively
---

# Resource: aws_ssoadmin_account_assignments

Manages multiple Single Sign-On (SSO) Account Assignments Authoritatively.

This resource is authoritative over a given combination of `instance_arn`, `permission_set_arn` and `principal type`.

~> **Note:** `aws_ssoadmin_account_assignments` **cannot** be used in conjunction with `aws_ssoadmin_account_assignment` or they will fight over what principal ids should be associated with what permission sets.


## Example Usage

```terraform
data "aws_ssoadmin_instances" "example" {}
data "aws_ssoadmin_permission_set" "example" {
instance_arn = tolist(data.aws_ssoadmin_instances.example.arns)[0]
name = "AWSReadOnlyAccess"
}
data "aws_identitystore_group" "example1" {
identity_store_id = tolist(data.aws_ssoadmin_instances.example.identity_store_ids)[0]
filter {
attribute_path = "DisplayName"
attribute_value = "ExampleGroup1"
}
}
data "aws_identitystore_group" "example2" {
identity_store_id = tolist(data.aws_ssoadmin_instances.example.identity_store_ids)[0]
filter {
attribute_path = "DisplayName"
attribute_value = "ExampleGroup2"
}
}
resource "aws_ssoadmin_account_assignments" "example" {
instance_arn = data.aws_ssoadmin_permission_set.example.instance_arn
permission_set_arn = data.aws_ssoadmin_permission_set.example.arn
principal_ids = [
data.aws_identitystore_group.example1.group_id,
data.aws_identitystore_group.example2.group_id
]
principal_type = "GROUP"
target_id = "012347678910"
target_type = "AWS_ACCOUNT"
}
```

## Argument Reference

The following arguments are supported:

* `instance_arn` - (Required, Forces new resource) The Amazon Resource Name (ARN) of the SSO Instance.
* `permission_set_arn` - (Required, Forces new resource) The Amazon Resource Name (ARN) of the Permission Set that the admin wants to grant the principal access to.
* `principal_ids` - (Required) A list of identifiers for objects in SSO, such as a user or group. PrincipalIds are GUIDs (For example, `f81d4fae-7dec-11d0-a765-00a0c91e6bf6`).
* `principal_type` - (Required, Forces new resource) The entity type for which the assignment will be created. Valid values: `USER`, `GROUP`.
* `target_id` - (Required, Forces new resource) An AWS account identifier, typically a 10-12 digit string.
* `target_type` - (Optional, Forces new resource) The entity type for which the assignment will be created. Valid values: `AWS_ACCOUNT`.

## Attributes Reference

In addition to all arguments above, the following attributes are exported:

* `id` - The identifier of the Account Assignment i.e., `principal_type`, `target_id`, `target_type`, `permission_set_arn`, `instance_arn` separated by commas (`,`).

## Import

SSO Account Assignments can be imported using the `principal_type`, `target_id`, `target_type`, `permission_set_arn`, `instance_arn` separated by commas (`,`) e.g.,

```
$ terraform import aws_ssoadmin_account_assignments.example GROUP,1234567890,AWS_ACCOUNT,arn:aws:sso:::permissionSet/ssoins-0123456789abcdef/ps-0123456789abcdef,arn:aws:sso:::instance/ssoins-0123456789abcdef
```

0 comments on commit ddbcf2a

Please sign in to comment.