diff --git a/aws/provider.go b/aws/provider.go index 0655be2d4c65..5a376501315c 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -635,6 +635,7 @@ func Provider() terraform.ResourceProvider { "aws_placement_group": resourceAwsPlacementGroup(), "aws_proxy_protocol_policy": resourceAwsProxyProtocolPolicy(), "aws_quicksight_group": resourceAwsQuickSightGroup(), + "aws_quicksight_user": resourceAwsQuickSightUser(), "aws_ram_principal_association": resourceAwsRamPrincipalAssociation(), "aws_ram_resource_association": resourceAwsRamResourceAssociation(), "aws_ram_resource_share": resourceAwsRamResourceShare(), diff --git a/aws/resource_aws_quicksight_user.go b/aws/resource_aws_quicksight_user.go new file mode 100644 index 000000000000..63f2145b8c8c --- /dev/null +++ b/aws/resource_aws_quicksight_user.go @@ -0,0 +1,227 @@ +package aws + +import ( + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/quicksight" +) + +func resourceAwsQuickSightUser() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsQuickSightUserCreate, + Read: resourceAwsQuickSightUserRead, + Update: resourceAwsQuickSightUserUpdate, + Delete: resourceAwsQuickSightUserDelete, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "aws_account_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "email": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "iam_arn": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "identity_type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + quicksight.IdentityTypeIam, + quicksight.IdentityTypeQuicksight, + }, false), + }, + + "namespace": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "default", + ValidateFunc: validation.StringInSlice([]string{ + "default", + }, false), + }, + + "session_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "user_name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.NoZeroValues, + }, + + "user_role": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + quicksight.UserRoleReader, + quicksight.UserRoleAuthor, + quicksight.UserRoleAdmin, + }, false), + }, + }, + } +} + +func resourceAwsQuickSightUserCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountID := meta.(*AWSClient).accountid + + namespace := d.Get("namespace").(string) + + if v, ok := d.GetOk("aws_account_id"); ok { + awsAccountID = v.(string) + } + + createOpts := &quicksight.RegisterUserInput{ + AwsAccountId: aws.String(awsAccountID), + Email: aws.String(d.Get("email").(string)), + IdentityType: aws.String(d.Get("identity_type").(string)), + Namespace: aws.String(namespace), + UserRole: aws.String(d.Get("user_role").(string)), + } + + if v, ok := d.GetOk("iam_arn"); ok { + createOpts.IamArn = aws.String(v.(string)) + } + + if v, ok := d.GetOk("session_name"); ok { + createOpts.SessionName = aws.String(v.(string)) + } + + if v, ok := d.GetOk("user_name"); ok { + createOpts.UserName = aws.String(v.(string)) + } + + resp, err := conn.RegisterUser(createOpts) + if err != nil { + return fmt.Errorf("Error registering QuickSight user: %s", err) + } + + d.SetId(fmt.Sprintf("%s/%s/%s", awsAccountID, namespace, aws.StringValue(resp.User.UserName))) + + return resourceAwsQuickSightUserRead(d, meta) +} + +func resourceAwsQuickSightUserRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountID, namespace, userName, err := resourceAwsQuickSightUserParseID(d.Id()) + if err != nil { + return err + } + + descOpts := &quicksight.DescribeUserInput{ + AwsAccountId: aws.String(awsAccountID), + Namespace: aws.String(namespace), + UserName: aws.String(userName), + } + + resp, err := conn.DescribeUser(descOpts) + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] QuickSight User %s is not found", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error describing QuickSight User (%s): %s", d.Id(), err) + } + + d.Set("arn", resp.User.Arn) + d.Set("aws_account_id", awsAccountID) + d.Set("email", resp.User.Email) + d.Set("namespace", namespace) + d.Set("user_role", resp.User.Role) + d.Set("user_name", resp.User.UserName) + + return nil +} + +func resourceAwsQuickSightUserUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountID, namespace, userName, err := resourceAwsQuickSightUserParseID(d.Id()) + if err != nil { + return err + } + + updateOpts := &quicksight.UpdateUserInput{ + AwsAccountId: aws.String(awsAccountID), + Email: aws.String(d.Get("email").(string)), + Namespace: aws.String(namespace), + Role: aws.String(d.Get("user_role").(string)), + UserName: aws.String(userName), + } + + _, err = conn.UpdateUser(updateOpts) + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + log.Printf("[WARN] QuickSight User %s is not found", d.Id()) + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error updating QuickSight User %s: %s", d.Id(), err) + } + + return resourceAwsQuickSightUserRead(d, meta) +} + +func resourceAwsQuickSightUserDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).quicksightconn + + awsAccountID, namespace, userName, err := resourceAwsQuickSightUserParseID(d.Id()) + if err != nil { + return err + } + + deleteOpts := &quicksight.DeleteUserInput{ + AwsAccountId: aws.String(awsAccountID), + Namespace: aws.String(namespace), + UserName: aws.String(userName), + } + + if _, err := conn.DeleteUser(deleteOpts); err != nil { + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + return nil + } + return fmt.Errorf("Error deleting QuickSight User %s: %s", d.Id(), err) + } + + return nil +} + +func resourceAwsQuickSightUserParseID(id string) (string, string, string, error) { + parts := strings.SplitN(id, "/", 3) + if len(parts) < 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected AWS_ACCOUNT_ID/NAMESPACE/USER_NAME", id) + } + return parts[0], parts[1], parts[2], nil +} diff --git a/aws/resource_aws_quicksight_user_test.go b/aws/resource_aws_quicksight_user_test.go new file mode 100644 index 000000000000..d83438c8dc0b --- /dev/null +++ b/aws/resource_aws_quicksight_user_test.go @@ -0,0 +1,207 @@ +package aws + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/service/quicksight" +) + +func TestAccAWSQuickSightUser_basic(t *testing.T) { + var user quicksight.User + rName1 := "tfacctest" + acctest.RandString(10) + resourceName1 := "aws_quicksight_user." + rName1 + rName2 := "tfacctest" + acctest.RandString(10) + resourceName2 := "aws_quicksight_user." + rName2 + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckQuickSightUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightUserConfig(rName1), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightUserExists(resourceName1, &user), + resource.TestCheckResourceAttr(resourceName1, "user_name", rName1), + testAccCheckResourceAttrRegionalARN(resourceName1, "arn", "quicksight", fmt.Sprintf("user/default/%s", rName1)), + ), + }, + { + Config: testAccAWSQuickSightUserConfig(rName2), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightUserExists(resourceName2, &user), + resource.TestCheckResourceAttr(resourceName2, "user_name", rName2), + testAccCheckResourceAttrRegionalARN(resourceName2, "arn", "quicksight", fmt.Sprintf("user/default/%s", rName2)), + ), + }, + }, + }) +} + +func TestAccAWSQuickSightUser_withInvalidFormattedEmailStillWorks(t *testing.T) { + var user quicksight.User + rName := "tfacctest" + acctest.RandString(10) + resourceName := "aws_quicksight_user." + rName + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckQuickSightUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightUserConfigWithEmail(rName, "nottarealemailbutworks"), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "email", "nottarealemailbutworks"), + ), + }, + { + Config: testAccAWSQuickSightUserConfigWithEmail(rName, "nottarealemailbutworks2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightUserExists(resourceName, &user), + resource.TestCheckResourceAttr(resourceName, "email", "nottarealemailbutworks2"), + ), + }, + }, + }) +} + +func TestAccAWSQuickSightUser_disappears(t *testing.T) { + var user quicksight.User + rName := "tfacctest" + acctest.RandString(10) + resourceName := "aws_quicksight_user." + rName + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckQuickSightUserDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSQuickSightUserConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckQuickSightUserExists(resourceName, &user), + testAccCheckQuickSightUserDisappears(&user), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckQuickSightUserExists(resourceName string, user *quicksight.User) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + awsAccountID, namespace, userName, err := resourceAwsQuickSightUserParseID(rs.Primary.ID) + if err != nil { + return err + } + + conn := testAccProvider.Meta().(*AWSClient).quicksightconn + + input := &quicksight.DescribeUserInput{ + AwsAccountId: aws.String(awsAccountID), + Namespace: aws.String(namespace), + UserName: aws.String(userName), + } + + output, err := conn.DescribeUser(input) + + if err != nil { + return err + } + + if output == nil || output.User == nil { + return fmt.Errorf("QuickSight User (%s) not found", rs.Primary.ID) + } + + *user = *output.User + + return nil + } +} + +func testAccCheckQuickSightUserDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).quicksightconn + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_quicksight_user" { + continue + } + + awsAccountID, namespace, userName, err := resourceAwsQuickSightUserParseID(rs.Primary.ID) + if err != nil { + return err + } + + _, err = conn.DescribeUser(&quicksight.DescribeUserInput{ + AwsAccountId: aws.String(awsAccountID), + Namespace: aws.String(namespace), + UserName: aws.String(userName), + }) + if isAWSErr(err, quicksight.ErrCodeResourceNotFoundException, "") { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("QuickSight User '%s' was not deleted properly", rs.Primary.ID) + } + + return nil +} + +func testAccCheckQuickSightUserDisappears(v *quicksight.User) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*AWSClient).quicksightconn + + arn, err := arn.Parse(aws.StringValue(v.Arn)) + if err != nil { + return err + } + + parts := strings.SplitN(arn.Resource, "/", 3) + + input := &quicksight.DeleteUserInput{ + AwsAccountId: aws.String(arn.AccountID), + Namespace: aws.String(parts[1]), + UserName: v.UserName, + } + + if _, err := conn.DeleteUser(input); err != nil { + return err + } + + return nil + } +} + +func testAccAWSQuickSightUserConfigWithEmail(rName, email string) string { + return fmt.Sprintf(` +data "aws_caller_identity" "current" {} + +resource "aws_quicksight_user" %[1]q { + aws_account_id = "${data.aws_caller_identity.current.account_id}" + user_name = %[1]q + email = %[2]q + identity_type = "QUICKSIGHT" + user_role = "READER" +} +`, rName, email) +} + +func testAccAWSQuickSightUserConfig(rName string) string { + return testAccAWSQuickSightUserConfigWithEmail(rName, "fakeemail@example.com") +} diff --git a/website/aws.erb b/website/aws.erb index 8b880c6a1b07..bf22d8e7bd17 100644 --- a/website/aws.erb +++ b/website/aws.erb @@ -2244,6 +2244,9 @@
  • aws_quicksight_group
  • +
  • + aws_quicksight_user +
  • diff --git a/website/docs/r/quicksight_user.html.markdown b/website/docs/r/quicksight_user.html.markdown new file mode 100644 index 000000000000..47be99adf559 --- /dev/null +++ b/website/docs/r/quicksight_user.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "aws" +page_title: "AWS: aws_quicksight_user" +description: |- + Manages a Resource QuickSight User. +--- + +# Resource: aws_quicksight_user + +Resource for managing QuickSight User + +## Example Usage + +```hcl +resource "aws_quicksight_user" "example" { + user_name = "an-author" + email = "author@example.com" + identity_type = "IAM" + user_role = "AUTHOR" +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `email` - (Required) The email address of the user that you want to register. +* `identity_type` - (Required) Amazon QuickSight supports several ways of managing the identity of users. This parameter accepts two values: `IAM` and `QUICKSIGHT`. +* `user_role` - (Required) The Amazon QuickSight role of the user. The user role can be one of the following: `READER`, `AUTHOR`, or `ADMIN` +* `user_name` - (Optional) The Amazon QuickSight user name that you want to create for the user you are registering. +* `aws_account_id` - (Optional) The ID for the AWS account that the group is in. Currently, you use the ID for the AWS account that contains your Amazon QuickSight account. +* `iam_arn` - (Optional) The ARN of the IAM user or role that you are registering with Amazon QuickSight. +* `namespace` - (Optional) The namespace. Currently, you should set this to `default`. +* `session_name` - (Optional) The name of the IAM session to use when assuming roles that can embed QuickSight dashboards. + +## Attributes Reference + +All above attributes except for `session_name` and `identity_type` are exported as well as: + +* `arn` - Amazon Resource Name (ARN) of the user + +## Import + +Importing is currently not supported on this resource.