diff --git a/.changelog/16504.txt b/.changelog/16504.txt new file mode 100644 index 00000000000..8ad6f68b577 --- /dev/null +++ b/.changelog/16504.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +aws_elasticache_user_group +``` diff --git a/aws/provider.go b/aws/provider.go index bc90c55b430..150ac4f20e7 100644 --- a/aws/provider.go +++ b/aws/provider.go @@ -745,6 +745,7 @@ func Provider() *schema.Provider { "aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(), "aws_elasticache_subnet_group": resourceAwsElasticacheSubnetGroup(), "aws_elasticache_user": resourceAwsElasticacheUser(), + "aws_elasticache_user_group": resourceAwsElasticacheUserGroup(), "aws_elastic_beanstalk_application": resourceAwsElasticBeanstalkApplication(), "aws_elastic_beanstalk_application_version": resourceAwsElasticBeanstalkApplicationVersion(), "aws_elastic_beanstalk_configuration_template": resourceAwsElasticBeanstalkConfigurationTemplate(), diff --git a/aws/resource_aws_elasticache_user.go b/aws/resource_aws_elasticache_user.go index e384dfb3c30..cd09f21fd29 100644 --- a/aws/resource_aws_elasticache_user.go +++ b/aws/resource_aws_elasticache_user.go @@ -111,7 +111,7 @@ func resourceAwsElasticacheUserRead(d *schema.ResourceData, meta interface{}) er ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig resp, err := finder.ElastiCacheUserById(conn, d.Id()) - if !d.IsNewResource() && tfresource.NotFound(err) { + if !d.IsNewResource() && (tfresource.NotFound(err) || isAWSErr(err, elasticache.ErrCodeUserNotFoundFault, "")) { d.SetId("") log.Printf("[DEBUG] ElastiCache User (%s) not found", d.Id()) return nil diff --git a/aws/resource_aws_elasticache_user_group.go b/aws/resource_aws_elasticache_user_group.go new file mode 100644 index 00000000000..d99d211fd68 --- /dev/null +++ b/aws/resource_aws_elasticache_user_group.go @@ -0,0 +1,268 @@ +package aws + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/keyvaluetags" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/elasticache/finder" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/tfresource" +) + +func resourceAwsElasticacheUserGroup() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsElasticacheUserGroupCreate, + Read: resourceAwsElasticacheUserGroupRead, + Update: resourceAwsElasticacheUserGroupUpdate, + Delete: resourceAwsElasticacheUserGroupDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + CustomizeDiff: SetTagsDiff, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "engine": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{"REDIS"}, false), + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + return strings.EqualFold(old, new) + }, + }, + "tags": tagsSchema(), + "tags_all": tagsSchemaComputed(), + "user_group_id": { + Type: schema.TypeString, + Required: true, + }, + "user_ids": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +var resourceAwsElasticacheUserGroupPendingStates = []string{ + "creating", + "modifying", +} + +func resourceAwsElasticacheUserGroupCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(keyvaluetags.New(d.Get("tags").(map[string]interface{}))) + + input := &elasticache.CreateUserGroupInput{ + Engine: aws.String(d.Get("engine").(string)), + UserGroupId: aws.String(d.Get("user_group_id").(string)), + } + + if v, ok := d.GetOk("user_ids"); ok { + input.UserIds = expandStringSet(v.(*schema.Set)) + } + + // Tags are currently only supported in AWS Commercial. + if len(tags) > 0 && meta.(*AWSClient).partition == endpoints.AwsPartitionID { + input.Tags = tags.IgnoreAws().ElasticacheTags() + } + + out, err := conn.CreateUserGroup(input) + if err != nil { + return fmt.Errorf("error creating ElastiCache User Group: %w", err) + } + + d.SetId(aws.StringValue(out.UserGroupId)) + + stateConf := &resource.StateChangeConf{ + Pending: resourceAwsElasticacheUserGroupPendingStates, + Target: []string{"active"}, + Refresh: resourceAwsElasticacheUserGroupStateRefreshFunc(d.Get("user_group_id").(string), conn), + Timeout: d.Timeout(schema.TimeoutCreate), + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, // Wait 30 secs before starting + } + + log.Printf("[INFO] Waiting for ElastiCache User Group (%s) to be available", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("error creating ElastiCache User Group: %w", err) + } + + return resourceAwsElasticacheUserGroupRead(d, meta) + +} + +func resourceAwsElasticacheUserGroupRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + defaultTagsConfig := meta.(*AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*AWSClient).IgnoreTagsConfig + + resp, err := finder.ElastiCacheUserGroupById(conn, d.Id()) + if !d.IsNewResource() && (tfresource.NotFound(err) || tfawserr.ErrCodeEquals(err, elasticache.ErrCodeUserGroupNotFoundFault)) { + d.SetId("") + log.Printf("[DEBUG] ElastiCache User Group (%s) not found", d.Id()) + return nil + } + + if err != nil && !tfawserr.ErrCodeEquals(err, elasticache.ErrCodeUserGroupNotFoundFault) { + return fmt.Errorf("error describing ElastiCache User Group (%s): %w", d.Id(), err) + } + + d.Set("arn", resp.ARN) + d.Set("engine", resp.Engine) + d.Set("user_ids", resp.UserIds) + d.Set("user_group_id", resp.UserGroupId) + + // Tags are currently only supported in AWS Commercial. + if meta.(*AWSClient).partition == endpoints.AwsPartitionID { + tags, err := keyvaluetags.ElasticacheListTags(conn, aws.StringValue(resp.ARN)) + + if err != nil { + return fmt.Errorf("error listing tags for ElastiCache User (%s): %w", aws.StringValue(resp.ARN), err) + } + + tags = tags.IgnoreAws().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + } else { + d.Set("tags", nil) + d.Set("tags_all", nil) + } + + return nil +} + +func resourceAwsElasticacheUserGroupUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + hasChange := false + + if d.HasChangeExcept("tags_all") { + req := &elasticache.ModifyUserGroupInput{ + UserGroupId: aws.String(d.Get("user_group_id").(string)), + } + + if d.HasChange("user_ids") { + o, n := d.GetChange("user_ids") + usersRemove := o.(*schema.Set).Difference(n.(*schema.Set)) + usersAdd := n.(*schema.Set).Difference(o.(*schema.Set)) + + if usersAdd.Len() > 0 { + req.UserIdsToAdd = expandStringSet(usersAdd) + hasChange = true + } + if usersRemove.Len() > 0 { + req.UserIdsToRemove = expandStringSet(usersRemove) + hasChange = true + } + } + + if hasChange { + _, err := conn.ModifyUserGroup(req) + if err != nil { + return fmt.Errorf("error updating ElastiCache User Group (%q): %w", d.Id(), err) + } + stateConf := &resource.StateChangeConf{ + Pending: resourceAwsElasticacheUserGroupPendingStates, + Target: []string{"active"}, + Refresh: resourceAwsElasticacheUserGroupStateRefreshFunc(d.Get("user_group_id").(string), conn), + Timeout: d.Timeout(schema.TimeoutCreate), + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, // Wait 30 secs before starting + } + + log.Printf("[INFO] Waiting for ElastiCache User Group (%s) to be available", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + return fmt.Errorf("error updating ElastiCache User Group (%q): %w", d.Id(), err) + } + } + } + + // Tags are currently only supported in AWS Commercial. + if d.HasChange("tags_all") && meta.(*AWSClient).partition == endpoints.AwsPartitionID { + o, n := d.GetChange("tags_all") + + if err := keyvaluetags.ElasticacheUpdateTags(conn, d.Get("arn").(string), o, n); err != nil { + return fmt.Errorf("error updating ElastiCache User Group (%s) tags: %w", d.Get("arn").(string), err) + } + } + + return resourceAwsElasticacheUserGroupRead(d, meta) +} + +func resourceAwsElasticacheUserGroupDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*AWSClient).elasticacheconn + + input := &elasticache.DeleteUserGroupInput{ + UserGroupId: aws.String(d.Id()), + } + + _, err := conn.DeleteUserGroup(input) + if err != nil && !tfawserr.ErrCodeEquals(err, elasticache.ErrCodeUserGroupNotFoundFault) { + return fmt.Errorf("error deleting ElastiCache User Group: %w", err) + } + stateConf := &resource.StateChangeConf{ + Pending: []string{"deleting"}, + Target: []string{}, + Refresh: resourceAwsElasticacheUserGroupStateRefreshFunc(d.Get("user_group_id").(string), conn), + Timeout: d.Timeout(schema.TimeoutCreate), + MinTimeout: 10 * time.Second, + Delay: 30 * time.Second, // Wait 30 secs before starting + } + + log.Printf("[INFO] Waiting for ElastiCache User Group (%s) to be available", d.Id()) + _, err = stateConf.WaitForState() + if err != nil { + if tfawserr.ErrCodeEquals(err, elasticache.ErrCodeUserGroupNotFoundFault) || tfawserr.ErrCodeEquals(err, elasticache.ErrCodeInvalidUserGroupStateFault) { + return nil + } + return fmt.Errorf("ElastiCache User Group cannot be deleted: %w", err) + } + + return nil +} + +func resourceAwsElasticacheUserGroupStateRefreshFunc(id string, conn *elasticache.ElastiCache) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + v, err := finder.ElastiCacheUserGroupById(conn, id) + + if err != nil { + log.Printf("Error on retrieving ElastiCache User Group when waiting: %s", err) + return nil, "", err + } + + if v == nil { + return nil, "", nil + } + + if v.Status != nil { + log.Printf("[DEBUG] ElastiCache User Group status for instance %s: %s", id, *v.Status) + } + + return v, *v.Status, nil + } +} diff --git a/aws/resource_aws_elasticache_user_group_test.go b/aws/resource_aws_elasticache_user_group_test.go new file mode 100644 index 00000000000..e5e4a066d81 --- /dev/null +++ b/aws/resource_aws_elasticache_user_group_test.go @@ -0,0 +1,267 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/service/elasticache" + "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/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/terraform-providers/terraform-provider-aws/aws/internal/service/elasticache/finder" +) + +func TestAccAWSElasticacheUserGroup_basic(t *testing.T) { + var userGroup elasticache.UserGroup + rName := acctest.RandomWithPrefix("tf-acc") + resourceName := "aws_elasticache_user_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, elasticache.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheUserGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheUserGroupConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "user_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_group_id", rName), + resource.TestCheckResourceAttr(resourceName, "engine", "redis"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSElasticacheUserGroup_update(t *testing.T) { + var userGroup elasticache.UserGroup + rName := acctest.RandomWithPrefix("tf-acc") + resourceName := "aws_elasticache_user_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, elasticache.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheUserGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheUserGroupConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "user_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_group_id", rName), + resource.TestCheckResourceAttr(resourceName, "engine", "redis"), + ), + }, + { + Config: testAccAWSElasticacheUserGroupConfigMultiple(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "user_ids.#", "2"), + resource.TestCheckResourceAttr(resourceName, "user_group_id", rName), + resource.TestCheckResourceAttr(resourceName, "engine", "redis"), + ), + }, + { + Config: testAccAWSElasticacheUserGroupConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "user_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_group_id", rName), + resource.TestCheckResourceAttr(resourceName, "engine", "redis"), + ), + }, + }, + }) +} + +func TestAccAWSElasticacheUserGroup_tags(t *testing.T) { + var userGroup elasticache.UserGroup + rName := acctest.RandomWithPrefix("tf-acc") + resourceName := "aws_elasticache_user_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, elasticache.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSElasticacheUserGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheUserGroupConfigTags(rName, "tagKey", "tagVal"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "user_ids.#", "1"), + resource.TestCheckResourceAttr(resourceName, "user_group_id", rName), + resource.TestCheckResourceAttr(resourceName, "engine", "redis"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.tagKey", "tagVal"), + ), + }, + { + Config: testAccAWSElasticacheUserGroupConfigTags(rName, "tagKey", "tagVal2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.tagKey", "tagVal2"), + ), + }, + { + Config: testAccAWSElasticacheUserGroupConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccAWSElasticacheUserGroup_disappears(t *testing.T) { + var userGroup elasticache.UserGroup + rName := acctest.RandomWithPrefix("tf-acc") + resourceName := "aws_elasticache_user_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ErrorCheck: testAccErrorCheck(t, elasticache.EndpointsID), + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSNeptuneClusterEndpointDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAWSElasticacheUserGroupConfigBasic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSElasticacheUserGroupExists(resourceName, &userGroup), + testAccCheckResourceDisappears(testAccProvider, resourceAwsElasticacheUserGroup(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckAWSElasticacheUserGroupDestroy(s *terraform.State) error { + return testAccCheckAWSElasticacheUserGroupDestroyWithProvider(s, testAccProvider) +} + +func testAccCheckAWSElasticacheUserGroupDestroyWithProvider(s *terraform.State, provider *schema.Provider) error { + conn := provider.Meta().(*AWSClient).elasticacheconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_elasticache_user_group" { + continue + } + + _, err := finder.ElastiCacheUserGroupById(conn, rs.Primary.ID) + if err != nil { + if isAWSErr(err, elasticache.ErrCodeUserGroupNotFoundFault, "") { + return nil + } + } + + return err + } + + return nil +} + +func testAccCheckAWSElasticacheUserGroupExists(n string, v *elasticache.UserGroup) resource.TestCheckFunc { + return testAccCheckAWSElasticacheUserGroupExistsWithProvider(n, v, func() *schema.Provider { return testAccProvider }) +} + +func testAccCheckAWSElasticacheUserGroupExistsWithProvider(n string, v *elasticache.UserGroup, providerF func() *schema.Provider) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No ElastiCache User Group ID is set") + } + + provider := providerF() + conn := provider.Meta().(*AWSClient).elasticacheconn + resp, err := finder.ElastiCacheUserGroupById(conn, rs.Primary.ID) + if err != nil { + return fmt.Errorf("ElastiCache User Group (%s) not found: %w", rs.Primary.ID, err) + } + + *v = *resp + + return nil + } +} + +func testAccAWSElasticacheUserGroupConfigBasic(rName string) string { + return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(` +resource "aws_elasticache_user" "test" { + user_id = %[1]q + user_name = "default" + access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember" + engine = "REDIS" + passwords = ["password123456789"] +} + +resource "aws_elasticache_user_group" "test" { + user_group_id = %[1]q + engine = "REDIS" + user_ids = [aws_elasticache_user.test.user_id] +} +`, rName)) +} + +func testAccAWSElasticacheUserGroupConfigMultiple(rName string) string { + return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(` +resource "aws_elasticache_user" "test" { + user_id = %[1]q + user_name = "default" + access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember" + engine = "REDIS" + passwords = ["password123456789"] +} + +resource "aws_elasticache_user" "test2" { + user_id = "%[1]s-2" + user_name = "username1" + access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember" + engine = "REDIS" + passwords = ["password123456789"] +} + +resource "aws_elasticache_user_group" "test" { + user_group_id = %[1]q + engine = "REDIS" + user_ids = [aws_elasticache_user.test.user_id, aws_elasticache_user.test2.user_id] +} +`, rName)) +} + +func testAccAWSElasticacheUserGroupConfigTags(rName, tagKey, tagValue string) string { + return composeConfig(testAccAvailableAZsNoOptInConfig(), fmt.Sprintf(` +resource "aws_elasticache_user" "test" { + user_id = %[1]q + user_name = "default" + access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember" + engine = "REDIS" + passwords = ["password123456789"] +} + +resource "aws_elasticache_user_group" "test" { + user_group_id = %[1]q + engine = "REDIS" + user_ids = [aws_elasticache_user.test.user_id] + + tags = { + %[2]s = %[3]q + } +} +`, rName, tagKey, tagValue)) +} diff --git a/website/docs/r/elasticache_user_group.html.markdown b/website/docs/r/elasticache_user_group.html.markdown new file mode 100644 index 00000000000..4417453dd76 --- /dev/null +++ b/website/docs/r/elasticache_user_group.html.markdown @@ -0,0 +1,54 @@ +--- +subcategory: "ElastiCache" +layout: "aws" +page_title: "AWS: aws_elasticache_user_group" +description: |- + Provides an ElastiCache user group. +--- + +# Resource: aws_elasticache_user_group + +Provides an ElastiCache user group resource. + +## Example Usage + +```terraform +resource "aws_elasticache_user" "test" { + user_id = "testUserId" + user_name = "default" + access_string = "on ~app::* -@all +@read +@hash +@bitmap +@geo -setbit -bitfield -hset -hsetnx -hmset -hincrby -hincrbyfloat -hdel -bitop -geoadd -georadius -georadiusbymember" + engine = "REDIS" + passwords = ["password123456789"] +} + +resource "aws_elasticache_user_group" "test" { + engine = "REDIS" + user_group_id = "userGroupId" + user_ids = [aws_elasticache_user.test.user_id] +} +``` + +## Argument Reference + +The following arguments are required: + +* `engine` - (Required) The current supported value is `REDIS`. +* `user_group_id` - (Required) The ID of the user group. + +The following arguments are optional: + +* `user_ids` - (Optional) The list of user IDs that belong to the user group. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `id` - The user group identifier. + +## Import + +ElastiCache user groups can be imported using the `user_group_id`, e.g. + +``` +$ terraform import aws_elasticache_user_group.my_user_group userGoupId1 +```