Skip to content

Commit 58d0db9

Browse files
Codelaxremyleone
andauthored
feat(redis): add redis acl (#1256)
Co-authored-by: Rémy Léone <[email protected]>
1 parent 0b1c208 commit 58d0db9

5 files changed

+1700
-0
lines changed

docs/resources/redis_cluster.md

+12
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ resource "scaleway_redis_cluster" "main" {
2323
tags = [ "test", "redis" ]
2424
cluster_size = 1
2525
tls_enabled = "true"
26+
27+
acl {
28+
ip = "0.0.0.0/0"
29+
description = "Allow all"
30+
}
2631
}
2732
```
2833

@@ -56,6 +61,13 @@ The following arguments are supported:
5661

5762
- `project_id` - (Defaults to [provider](../index.md) `project_id`) The ID of the project the Redis Cluster is associated with.
5863

64+
- `acl` - (Optional) List of acl rules, this is cluster's authorized IPs.
65+
66+
The `acl` block supports:
67+
68+
- `ip` - (Required) The ip range to whitelist in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation)
69+
- `description` - (Optional) A text describing this rule. Default description: `Allow IP`
70+
5971
## Attributes Reference
6072

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

scaleway/helpers_redis.go

+33
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package scaleway
22

33
import (
44
"context"
5+
"fmt"
56
"time"
67

78
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -53,3 +54,35 @@ func waitForRedisCluster(ctx context.Context, api *redis.API, zone scw.Zone, id
5354
RetryInterval: &retryInterval,
5455
}, scw.WithContext(ctx))
5556
}
57+
58+
func expandRedisACLSpecs(i interface{}) ([]*redis.ACLRuleSpec, error) {
59+
rules := []*redis.ACLRuleSpec(nil)
60+
61+
for _, aclRule := range i.([]interface{}) {
62+
rawRule := aclRule.(map[string]interface{})
63+
rule := &redis.ACLRuleSpec{}
64+
if ruleDescription, hasDescription := rawRule["description"]; hasDescription {
65+
rule.Description = ruleDescription.(string)
66+
}
67+
ip, err := expandIPNet(rawRule["ip"].(string))
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to validate acl ip (%s): %w", rawRule["ip"].(string), err)
70+
}
71+
rule.IP = ip
72+
rules = append(rules, rule)
73+
}
74+
75+
return rules, nil
76+
}
77+
78+
func flattenRedisACLs(aclRules []*redis.ACLRule) interface{} {
79+
flat := []map[string]interface{}(nil)
80+
for _, acl := range aclRules {
81+
flat = append(flat, map[string]interface{}{
82+
"id": acl.ID,
83+
"ip": acl.IP.String(),
84+
"description": flattenStringPtr(acl.Description),
85+
})
86+
}
87+
return flat
88+
}

scaleway/resource_redis_cluster.go

+58
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,31 @@ func resourceScalewayRedisCluster() *schema.Resource {
8181
Computed: true,
8282
Description: "The date and time of the last update of the Redis cluster",
8383
},
84+
"acl": {
85+
Type: schema.TypeList,
86+
Description: "List of acl rules.",
87+
Optional: true,
88+
Elem: &schema.Resource{
89+
Schema: map[string]*schema.Schema{
90+
"id": {
91+
Type: schema.TypeString,
92+
Description: "ID of the rule (UUID format).",
93+
Computed: true,
94+
},
95+
"ip": {
96+
Type: schema.TypeString,
97+
Description: "IPv4 network address of the rule (IP network in a CIDR format).",
98+
Required: true,
99+
},
100+
"description": {
101+
Type: schema.TypeString,
102+
Description: "Description of the rule.",
103+
Optional: true,
104+
Computed: true,
105+
},
106+
},
107+
},
108+
},
84109
// Common
85110
"zone": zoneSchema(),
86111
"project_id": projectIDSchema(),
@@ -116,6 +141,14 @@ func resourceScalewayRedisClusterCreate(ctx context.Context, d *schema.ResourceD
116141
if tlsEnabledExist {
117142
createReq.TLSEnabled = tlsEnabled.(bool)
118143
}
144+
aclRules, aclRulesExist := d.GetOk("acl")
145+
if aclRulesExist {
146+
rules, err := expandRedisACLSpecs(aclRules)
147+
if err != nil {
148+
return diag.FromErr(err)
149+
}
150+
createReq.ACLRules = rules
151+
}
119152

120153
res, err := redisAPI.CreateCluster(createReq, scw.WithContext(ctx))
121154
if err != nil {
@@ -161,6 +194,7 @@ func resourceScalewayRedisClusterRead(ctx context.Context, d *schema.ResourceDat
161194
_ = d.Set("cluster_size", cluster.ClusterSize)
162195
_ = d.Set("created_at", cluster.CreatedAt.Format(time.RFC3339))
163196
_ = d.Set("updated_at", cluster.UpdatedAt.Format(time.RFC3339))
197+
_ = d.Set("acl", flattenRedisACLs(cluster.ACLRules))
164198

165199
if len(cluster.Tags) > 0 {
166200
_ = d.Set("tags", cluster.Tags)
@@ -192,6 +226,12 @@ func resourceScalewayRedisClusterUpdate(ctx context.Context, d *schema.ResourceD
192226
if d.HasChange("tags") {
193227
req.Tags = expandStrings(d.Get("tags"))
194228
}
229+
if d.HasChange("acl") {
230+
diagnostics := resourceScalewayRedisClusterUpdateACL(ctx, d, redisAPI, zone, ID)
231+
if diagnostics != nil {
232+
return diagnostics
233+
}
234+
}
195235

196236
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
197237
if err != nil {
@@ -249,6 +289,24 @@ func resourceScalewayRedisClusterUpdate(ctx context.Context, d *schema.ResourceD
249289
return resourceScalewayRedisClusterRead(ctx, d, meta)
250290
}
251291

292+
func resourceScalewayRedisClusterUpdateACL(ctx context.Context, d *schema.ResourceData, redisAPI *redis.API, zone scw.Zone, clusterID string) diag.Diagnostics {
293+
rules, err := expandRedisACLSpecs(d.Get("acl"))
294+
if err != nil {
295+
return diag.FromErr(err)
296+
}
297+
298+
_, err = redisAPI.SetACLRules(&redis.SetACLRulesRequest{
299+
Zone: zone,
300+
ClusterID: clusterID,
301+
ACLRules: rules,
302+
}, scw.WithContext(ctx))
303+
if err != nil {
304+
return diag.FromErr(err)
305+
}
306+
307+
return nil
308+
}
309+
252310
func resourceScalewayRedisClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
253311
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
254312
if err != nil {

scaleway/resource_redis_cluster_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,77 @@ func TestAccScalewayRedisCluster_Migrate(t *testing.T) {
167167
})
168168
}
169169

170+
func TestAccScalewayRedisCluster_ACL(t *testing.T) {
171+
tt := NewTestTools(t)
172+
defer tt.Cleanup()
173+
resource.ParallelTest(t, resource.TestCase{
174+
PreCheck: func() { testAccPreCheck(t) },
175+
ProviderFactories: tt.ProviderFactories,
176+
CheckDestroy: testAccCheckScalewayRedisClusterDestroy(tt),
177+
Steps: []resource.TestStep{
178+
{
179+
Config: `
180+
resource "scaleway_redis_cluster" "main" {
181+
name = "test_redis_acl"
182+
version = "6.2.6"
183+
node_type = "MDB-BETA-M"
184+
user_name = "my_initial_user"
185+
password = "thiZ_is_v&ry_s3cret"
186+
acl {
187+
ip = "0.0.0.0/0"
188+
description = "An acl description"
189+
}
190+
acl {
191+
ip = "192.168.10.0/24"
192+
description = "A second acl description"
193+
}
194+
}
195+
`,
196+
Check: resource.ComposeTestCheckFunc(
197+
testAccCheckScalewayRedisExists(tt, "scaleway_redis_cluster.main"),
198+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "name", "test_redis_acl"),
199+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "version", "6.2.6"),
200+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "node_type", "MDB-BETA-M"),
201+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "user_name", "my_initial_user"),
202+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "password", "thiZ_is_v&ry_s3cret"),
203+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "acl.0.ip", "0.0.0.0/0"),
204+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "acl.0.description", "An acl description"),
205+
resource.TestCheckResourceAttrSet("scaleway_redis_cluster.main", "acl.0.id"),
206+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "acl.1.ip", "192.168.10.0/24"),
207+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "acl.1.description", "A second acl description"),
208+
resource.TestCheckResourceAttrSet("scaleway_redis_cluster.main", "acl.1.id"),
209+
),
210+
},
211+
{
212+
Config: `
213+
resource "scaleway_redis_cluster" "main" {
214+
name = "test_redis_acl"
215+
version = "6.2.6"
216+
node_type = "MDB-BETA-M"
217+
user_name = "my_initial_user"
218+
password = "thiZ_is_v&ry_s3cret"
219+
acl {
220+
ip = "192.168.11.0/24"
221+
description = "Another acl description"
222+
}
223+
}
224+
`,
225+
Check: resource.ComposeTestCheckFunc(
226+
testAccCheckScalewayRedisExists(tt, "scaleway_redis_cluster.main"),
227+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "name", "test_redis_acl"),
228+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "version", "6.2.6"),
229+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "node_type", "MDB-BETA-M"),
230+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "user_name", "my_initial_user"),
231+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "password", "thiZ_is_v&ry_s3cret"),
232+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "acl.0.ip", "192.168.11.0/24"),
233+
resource.TestCheckResourceAttr("scaleway_redis_cluster.main", "acl.0.description", "Another acl description"),
234+
resource.TestCheckResourceAttrSet("scaleway_redis_cluster.main", "acl.0.id"),
235+
),
236+
},
237+
},
238+
})
239+
}
240+
170241
func testAccCheckScalewayRedisClusterDestroy(tt *TestTools) resource.TestCheckFunc {
171242
return func(state *terraform.State) error {
172243
for _, rs := range state.RootModule().Resources {

0 commit comments

Comments
 (0)