Skip to content

Commit

Permalink
Database: add Redis access control options for users (#439)
Browse files Browse the repository at this point in the history
* Support Managed Redis user access control, fix some tests

* Fix redis eviction policy field state drift

* Documentation updates for database users

* More documentation updates for database users
  • Loading branch information
christhemorse committed Dec 8, 2023
1 parent 0b70047 commit 6aaee67
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 6 deletions.
40 changes: 40 additions & 0 deletions vultr/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/vultr/govultr/v3"
)

const defaultTimeout = 60 * time.Minute
Expand Down Expand Up @@ -167,3 +168,42 @@ func readReplicaSchema(isReadReplica bool) map[string]*schema.Schema {

return s
}

func redisACLSchema() map[string]*schema.Schema {
s := map[string]*schema.Schema{
"redis_acl_categories": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"redis_acl_channels": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"redis_acl_commands": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"redis_acl_keys": {
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
}

return s
}

func flattenRedisACL(dbUser *govultr.DatabaseUser) []map[string]interface{} {
f := []map[string]interface{}{
{
"redis_acl_categories": dbUser.AccessControl.RedisACLCategories,
"redis_acl_channels": dbUser.AccessControl.RedisACLChannels,
"redis_acl_commands": dbUser.AccessControl.RedisACLCommands,
"redis_acl_keys": dbUser.AccessControl.RedisACLKeys,
},
}
return f
}
1 change: 1 addition & 0 deletions vultr/resource_vultr_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ func resourceVultrDatabase() *schema.Resource {
"redis_eviction_policy": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"password": {
Type: schema.TypeString,
Expand Down
2 changes: 1 addition & 1 deletion vultr/resource_vultr_database_connection_pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func testAccCheckVultrDatabaseConnectionPoolDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).govultrClient()
_, _, err := client.Database.GetConnectionPool(context.Background(), rs.Primary.Attributes["database_id"], rs.Primary.ID)
if err != nil {
if strings.Contains(err.Error(), "Not a valid connection pool") || strings.Contains(err.Error(), "Not a valid DBaaS Subscription UUID") {
if strings.Contains(err.Error(), "Not a valid connection pool") || strings.Contains(err.Error(), "Not a valid Database Subscription UUID") {
return nil
}
return fmt.Errorf("error getting database connection pool: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion vultr/resource_vultr_database_db_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func testAccCheckVultrDatabaseDBDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).govultrClient()
_, _, err := client.Database.GetDB(context.Background(), rs.Primary.Attributes["database_id"], rs.Primary.ID)
if err != nil {
if strings.Contains(err.Error(), "Not a valid database db") || strings.Contains(err.Error(), "Not a valid DBaaS Subscription UUID") {
if strings.Contains(err.Error(), "Not a valid database db") || strings.Contains(err.Error(), "NNot a valid Database Subscription UUID") {
return nil
}
return fmt.Errorf("error getting database db: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion vultr/resource_vultr_database_replica_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func testAccCheckVultrDatabaseReplicaDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).govultrClient()
_, _, err := client.Database.Get(context.Background(), rs.Primary.ID)
if err != nil {
if strings.Contains(err.Error(), "Not a valid DBaaS Subscription UUID") {
if strings.Contains(err.Error(), "Not a valid Database Subscription UUID") {
return nil
}
return fmt.Errorf("error getting database: %s", err)
Expand Down
2 changes: 1 addition & 1 deletion vultr/resource_vultr_database_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ func testAccCheckVultrDatabaseDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).govultrClient()
_, _, err := client.Database.Get(context.Background(), rs.Primary.ID)
if err != nil {
if strings.Contains(err.Error(), "Not a valid DBaaS Subscription UUID") {
if strings.Contains(err.Error(), "Not a valid Database Subscription UUID") {
return nil
}
return fmt.Errorf("error getting database: %s", err)
Expand Down
65 changes: 65 additions & 0 deletions vultr/resource_vultr_database_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ func resourceVultrDatabaseUser() *schema.Resource {
Optional: true,
Computed: true,
},
"access_control": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: redisACLSchema(),
},
},
},
}
}
Expand All @@ -64,6 +73,13 @@ func resourceVultrDatabaseUserCreate(ctx context.Context, d *schema.ResourceData

d.SetId(databaseUser.Username)

// Redis user access control can only be updated after creation
if accessControl, accessControlOK := d.GetOk("access_control"); accessControlOK {
if err := updateRedisACL(ctx, client, databaseID, d, accessControl); err != nil {
return diag.Errorf("error updating user access control: %v", err)
}
}

return resourceVultrDatabaseUserRead(ctx, d, meta)
}

Expand Down Expand Up @@ -98,6 +114,12 @@ func resourceVultrDatabaseUserRead(ctx context.Context, d *schema.ResourceData,
}
}

if databaseUser.AccessControl != nil {
if err := d.Set("access_control", flattenRedisACL(databaseUser)); err != nil {
return diag.Errorf("unable to set resource database user `access_control` read value: %v", err)
}
}

return nil
}

Expand All @@ -118,6 +140,13 @@ func resourceVultrDatabaseUserUpdate(ctx context.Context, d *schema.ResourceData
}
}

if d.HasChange("access_control") {
_, accessControl := d.GetChange("access_control")
if err := updateRedisACL(ctx, client, databaseID, d, accessControl); err != nil {
return diag.Errorf("error updating user access control: %v", err)
}
}

return resourceVultrDatabaseUserRead(ctx, d, meta)
}

Expand All @@ -134,3 +163,39 @@ func resourceVultrDatabaseUserDelete(ctx context.Context, d *schema.ResourceData

return nil
}

func updateRedisACL(ctx context.Context, client *govultr.Client, databaseID string, d *schema.ResourceData, accessControl interface{}) error { //nolint:lll
// This should only loop once due to MaxItems: 1 in the resource definition
for _, v := range accessControl.(*schema.Set).List() {
var req2 = &govultr.DatabaseUserACLReq{}
var aclCategories, aclChannels, aclCommands, aclKeys []string
obj := v.(map[string]interface{})

for _, r := range obj["redis_acl_categories"].(*schema.Set).List() {
aclCategories = append(aclCategories, r.(string))
}
req2.RedisACLCategories = &aclCategories

for _, r := range obj["redis_acl_channels"].(*schema.Set).List() {
aclChannels = append(aclChannels, r.(string))
}
req2.RedisACLChannels = &aclChannels

for _, r := range obj["redis_acl_commands"].(*schema.Set).List() {
aclCommands = append(aclCommands, r.(string))
}
req2.RedisACLCommands = &aclCommands

for _, r := range obj["redis_acl_keys"].(*schema.Set).List() {
aclKeys = append(aclKeys, r.(string))
}
req2.RedisACLKeys = &aclKeys

log.Printf("[INFO] Updating user access control")
if _, _, err := client.Database.UpdateUserACL(ctx, databaseID, d.Id(), req2); err != nil {
return err
}
}

return nil
}
2 changes: 1 addition & 1 deletion vultr/resource_vultr_database_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func testAccCheckVultrDatabaseUserDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*Client).govultrClient()
_, _, err := client.Database.GetUser(context.Background(), rs.Primary.Attributes["database_id"], rs.Primary.ID)
if err != nil {
if strings.Contains(err.Error(), "Not a valid database user") || strings.Contains(err.Error(), "Not a valid DBaaS Subscription UUID") {
if strings.Contains(err.Error(), "Not a valid database user") || strings.Contains(err.Error(), "Not a valid Database Subscription UUID") {
return nil
}
return fmt.Errorf("error getting database user: %s", err)
Expand Down
16 changes: 15 additions & 1 deletion website/docs/r/database_user.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,25 @@ The following arguments are supported:
* `password` - (Required) The password of the new managed database user.
* `encryption` - (Optional) The encryption type of the new managed database user's password (MySQL engine types only - `caching_sha2_password`, `mysql_native_password`).

`access_control` - (Optional) The access control configuration for the new managed database user (Redis engine types only). It supports the following fields:

* `redis_acl_categories` - (Required) The list of command category rules for this managed database user.
* `redis_acl_channels` - (Required) The list of publish/subscribe channel patterns for this managed database user.
* `redis_acl_commands` - (Required) The list of individual command rules for this managed database user.
* `redis_acl_keys` - (Required) The list of access rules for this managed database user.

## Attributes Reference

The following attributes are exported:

* `database_id` - The managed database ID.
* `username` - The username of the managed database user.
* `password` - The password of the managed database user.
* `encryption` - The encryption type for the new managed database user's password (MySQL engine types only).
* `encryption` - The encryption type for the managed database user's password (MySQL engine types only).

`access_control`

* `redis_acl_categories` - List of command category rules for this managed database user (Redis engine types only).
* `redis_acl_channels` - List of publish/subscribe channel patterns for this managed database user (Redis engine types only).
* `redis_acl_commands` - List of individual command rules for this managed database user (Redis engine types only).
* `redis_acl_keys` - List of access rules for this managed database user (Redis engine types only).

0 comments on commit 6aaee67

Please sign in to comment.