Skip to content

Commit 005ed4e

Browse files
Mia-CrossMonitobCodelaxremyleone
committed
feat(redis): add support for endpoints (scaleway#1263)
Co-authored-by: jaime Bernabe <[email protected]> Co-authored-by: Jules Casteran <[email protected]> Co-authored-by: Rémy Léone <[email protected]>
1 parent e4d57cf commit 005ed4e

8 files changed

+12804
-6305
lines changed

docs/resources/redis_cluster.md

+47-4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,30 @@ resource "scaleway_redis_cluster" "main" {
4747
}
4848
}
4949
```
50+
### With a private network
51+
```hcl
52+
resource "scaleway_vpc_private_network" "pn" {
53+
name = "private-network"
54+
}
55+
56+
resource "scaleway_redis_cluster" "main" {
57+
name = "test_redis_endpoints"
58+
version = "6.2.6"
59+
node_type = "MDB-BETA-M"
60+
user_name = "my_initial_user"
61+
password = "thiZ_is_v&ry_s3cret"
62+
cluster_size = 1
63+
private_network {
64+
id = "${scaleway_vpc_private_network.pn.id}"
65+
service_ips = [
66+
"10.12.1.1/20",
67+
]
68+
}
69+
depends_on = [
70+
scaleway_vpc_private_network.pn
71+
]
72+
}
73+
```
5074

5175
## Arguments Reference
5276

@@ -72,21 +96,40 @@ The following arguments are supported:
7296

7397
- `cluster_size` - (Optional) The number of nodes in the Redis Cluster.
7498

75-
~> **Important:** You can set a bigger `cluster_size`, it will migrate the Redis Cluster, but keep in mind that you cannot downgrade a Redis Cluster so setting a smaller `cluster_size` will not have any effect.
99+
~> **Important:** You cannot set `cluster_size` to 2, you either have to choose Standalone mode (1 node) or Cluster mode which is minimum 3 (1 main node + 2 secondary nodes)
100+
101+
~> **Important:** You can set a bigger `cluster_size` than you initially did, it will migrate the Redis Cluster, but keep in mind that you cannot downgrade a Redis Cluster so setting a smaller `cluster_size` will not have any effect.
76102

77103
- `tls_enabled` - (Defaults to false) Whether TLS is enabled or not.
78104

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

81107
- `acl` - (Optional) List of acl rules, this is cluster's authorized IPs.
82108

83-
The `acl` block supports:
109+
~> The `acl` block supports:
84110

85-
- `ip` - (Required) The ip range to whitelist in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation)
86-
- `description` - (Optional) A text describing this rule. Default description: `Allow IP`
111+
- `ip` - (Required) The ip range to whitelist in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation)
112+
- `description` - (Optional) A text describing this rule. Default description: `Allow IP`
87113

88114
- `settings` - (Optional) Map of settings for redis cluster. Available settings can be found by listing redis versions with scaleway API or CLI
89115

116+
- `private_network` - (Optional) Describes the private network you want to connect to your cluster. If not set, a public network will be provided.
117+
118+
~> The `private_network` block supports :
119+
- `id` - (Required) The UUID of the private network resource.
120+
- `service_ips` - (Required) Endpoint IPv4 addresses in [CIDR notation](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation). You must provide at least one IP per node.
121+
122+
~> **Important:** The way to use private networks differs whether you are using redis in standalone or cluster mode.
123+
- Standalone mode (`cluster_size` = 1) : you can attach as many private networks as you want (each must be a separate block). If you detach your only private network, your cluster won't be reachable until you define a new private or public network. You can modify your private_network and its specs, you can have both a private and public network side by side.
124+
- Cluster mode (`cluster_size` > 1) : you can define a single private network as you create your cluster, you won't be able to edit or detach it afterwards, unless you create another cluster. Your `service_ips` must be listed as follows:
125+
```hcl
126+
service_ips = [
127+
"10.12.1.10/20",
128+
"10.12.1.11/20",
129+
"10.12.1.12/20",
130+
]
131+
```
132+
90133
## Attributes Reference
91134

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

scaleway/helpers_redis.go

+69
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,33 @@ func waitForRedisCluster(ctx context.Context, api *redis.API, zone scw.Zone, id
5555
}, scw.WithContext(ctx))
5656
}
5757

58+
func expandRedisPrivateNetwork(data []interface{}) ([]*redis.EndpointSpec, error) {
59+
if data == nil {
60+
return nil, nil
61+
}
62+
var epSpecs []*redis.EndpointSpec
63+
64+
for _, rawPN := range data {
65+
pn := rawPN.(map[string]interface{})
66+
pnID := expandID(pn["id"].(string))
67+
rawIPs := pn["service_ips"].([]interface{})
68+
ips := []scw.IPNet(nil)
69+
for _, rawIP := range rawIPs {
70+
ip, err := expandIPNet(rawIP.(string))
71+
if err != nil {
72+
return epSpecs, err
73+
}
74+
ips = append(ips, ip)
75+
}
76+
spec := &redis.EndpointSpecPrivateNetworkSpec{
77+
ID: pnID,
78+
ServiceIPs: ips,
79+
}
80+
epSpecs = append(epSpecs, &redis.EndpointSpec{PrivateNetwork: spec})
81+
}
82+
return epSpecs, nil
83+
}
84+
5885
func expandRedisACLSpecs(i interface{}) ([]*redis.ACLRuleSpec, error) {
5986
rules := []*redis.ACLRuleSpec(nil)
6087

@@ -106,3 +133,45 @@ func flattenRedisSettings(settings []*redis.ClusterSetting) interface{} {
106133
}
107134
return rawSettings
108135
}
136+
137+
func flattenRedisPrivateNetwork(endpoints []*redis.Endpoint) (interface{}, bool) {
138+
pnFlat := []map[string]interface{}(nil)
139+
for _, endpoint := range endpoints {
140+
if endpoint.PrivateNetwork == nil {
141+
continue
142+
}
143+
pn := endpoint.PrivateNetwork
144+
pnZonedID := newZonedIDString(pn.Zone, pn.ID)
145+
serviceIps := []interface{}(nil)
146+
for _, ip := range pn.ServiceIPs {
147+
serviceIps = append(serviceIps, ip.String())
148+
}
149+
pnFlat = append(pnFlat, map[string]interface{}{
150+
"endpoint_id": endpoint.ID,
151+
"zone": pn.Zone,
152+
"id": pnZonedID,
153+
"service_ips": serviceIps,
154+
})
155+
}
156+
return pnFlat, len(pnFlat) != 0
157+
}
158+
159+
func flattenRedisPublicNetwork(endpoints []*redis.Endpoint) interface{} {
160+
pnFlat := []map[string]interface{}(nil)
161+
for _, endpoint := range endpoints {
162+
if endpoint.PublicNetwork == nil {
163+
continue
164+
}
165+
ipsFlat := []interface{}(nil)
166+
for _, ip := range endpoint.IPs {
167+
ipsFlat = append(ipsFlat, ip.String())
168+
}
169+
pnFlat = append(pnFlat, map[string]interface{}{
170+
"id": endpoint.ID,
171+
"port": int(endpoint.Port),
172+
"ips": ipsFlat,
173+
})
174+
break
175+
}
176+
return pnFlat
177+
}

scaleway/resource_redis_cluster.go

+128-10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
88
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
910
redis "github.com/scaleway/scaleway-sdk-go/api/redis/v1alpha1"
1011
"github.com/scaleway/scaleway-sdk-go/scw"
1112
)
@@ -74,16 +75,6 @@ func resourceScalewayRedisCluster() *schema.Resource {
7475
Description: "Whether or not TLS is enabled.",
7576
ForceNew: true,
7677
},
77-
"created_at": {
78-
Type: schema.TypeString,
79-
Computed: true,
80-
Description: "The date and time of the creation of the Redis cluster",
81-
},
82-
"updated_at": {
83-
Type: schema.TypeString,
84-
Computed: true,
85-
Description: "The date and time of the last update of the Redis cluster",
86-
},
8778
"acl": {
8879
Type: schema.TypeList,
8980
Description: "List of acl rules.",
@@ -117,6 +108,74 @@ func resourceScalewayRedisCluster() *schema.Resource {
117108
Type: schema.TypeString,
118109
},
119110
},
111+
"private_network": {
112+
Type: schema.TypeSet,
113+
Optional: true,
114+
Description: "Private network specs details",
115+
Elem: &schema.Resource{
116+
Schema: map[string]*schema.Schema{
117+
"endpoint_id": {
118+
Type: schema.TypeString,
119+
Computed: true,
120+
Description: "UUID of the endpoint to be connected to the cluster",
121+
},
122+
"id": {
123+
Type: schema.TypeString,
124+
Required: true,
125+
ValidateFunc: validationUUIDorUUIDWithLocality(),
126+
Description: "UUID of the private network to be connected to the cluster",
127+
},
128+
"service_ips": {
129+
Type: schema.TypeList,
130+
Required: true,
131+
Elem: &schema.Schema{
132+
Type: schema.TypeString,
133+
ValidateFunc: validation.IsCIDR,
134+
},
135+
Description: "List of IPv4 addresses of the private network with a CIDR notation",
136+
},
137+
"zone": zoneSchema(),
138+
},
139+
},
140+
},
141+
// Computed
142+
"public_network": {
143+
Type: schema.TypeList,
144+
Optional: true,
145+
Computed: true,
146+
MaxItems: 1,
147+
Description: "Public network specs details",
148+
Elem: &schema.Resource{
149+
Schema: map[string]*schema.Schema{
150+
"id": {
151+
Type: schema.TypeString,
152+
Computed: true,
153+
},
154+
"port": {
155+
Type: schema.TypeInt,
156+
Computed: true,
157+
Description: "TCP port of the endpoint",
158+
},
159+
"ips": {
160+
Type: schema.TypeList,
161+
Elem: &schema.Schema{
162+
Type: schema.TypeString,
163+
},
164+
Computed: true,
165+
},
166+
},
167+
},
168+
},
169+
"created_at": {
170+
Type: schema.TypeString,
171+
Computed: true,
172+
Description: "The date and time of the creation of the Redis cluster",
173+
},
174+
"updated_at": {
175+
Type: schema.TypeString,
176+
Computed: true,
177+
Description: "The date and time of the last update of the Redis cluster",
178+
},
120179
// Common
121180
"zone": zoneSchema(),
122181
"project_id": projectIDSchema(),
@@ -165,6 +224,15 @@ func resourceScalewayRedisClusterCreate(ctx context.Context, d *schema.ResourceD
165224
createReq.ClusterSettings = expandRedisSettings(settings)
166225
}
167226

227+
privN, privNExists := d.GetOk("private_network")
228+
if privNExists {
229+
pnSpecs, err := expandRedisPrivateNetwork(privN.(*schema.Set).List())
230+
if err != nil {
231+
return diag.FromErr(err)
232+
}
233+
createReq.Endpoints = pnSpecs
234+
}
235+
168236
res, err := redisAPI.CreateCluster(createReq, scw.WithContext(ctx))
169237
if err != nil {
170238
return diag.FromErr(err)
@@ -216,6 +284,13 @@ func resourceScalewayRedisClusterRead(ctx context.Context, d *schema.ResourceDat
216284
_ = d.Set("tags", cluster.Tags)
217285
}
218286

287+
// set endpoints
288+
pnI, pnExists := flattenRedisPrivateNetwork(cluster.Endpoints)
289+
if pnExists {
290+
_ = d.Set("private_network", pnI)
291+
}
292+
_ = d.Set("public_network", flattenRedisPublicNetwork(cluster.Endpoints))
293+
219294
return nil
220295
}
221296

@@ -303,6 +378,13 @@ func resourceScalewayRedisClusterUpdate(ctx context.Context, d *schema.ResourceD
303378
}
304379
}
305380

381+
if d.HasChanges("private_network") {
382+
diagnostics := resourceScalewayRedisClusterUpdateEndpoints(ctx, d, redisAPI, zone, ID)
383+
if diagnostics != nil {
384+
return diagnostics
385+
}
386+
}
387+
306388
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
307389
if err != nil {
308390
return diag.FromErr(err)
@@ -344,6 +426,42 @@ func resourceScalewayRedisClusterUpdateSettings(ctx context.Context, d *schema.R
344426
return nil
345427
}
346428

429+
func resourceScalewayRedisClusterUpdateEndpoints(ctx context.Context, d *schema.ResourceData, redisAPI *redis.API, zone scw.Zone, clusterID string) diag.Diagnostics {
430+
// retrieve state
431+
cluster, err := waitForRedisCluster(ctx, redisAPI, zone, clusterID, d.Timeout(schema.TimeoutUpdate))
432+
if err != nil {
433+
return diag.FromErr(err)
434+
}
435+
436+
// get new desired state of endpoints
437+
rawNewEndpoints := d.Get("private_network")
438+
newEndpoints, err := expandRedisPrivateNetwork(rawNewEndpoints.(*schema.Set).List())
439+
if err != nil {
440+
return diag.FromErr(err)
441+
}
442+
if len(newEndpoints) == 0 {
443+
newEndpoints = append(newEndpoints, &redis.EndpointSpec{
444+
PublicNetwork: &redis.EndpointSpecPublicNetworkSpec{},
445+
})
446+
}
447+
// send request
448+
_, err = redisAPI.SetEndpoints(&redis.SetEndpointsRequest{
449+
Zone: cluster.Zone,
450+
ClusterID: cluster.ID,
451+
Endpoints: newEndpoints,
452+
}, scw.WithContext(ctx))
453+
if err != nil {
454+
return diag.FromErr(err)
455+
}
456+
457+
_, err = waitForRedisCluster(ctx, redisAPI, zone, clusterID, d.Timeout(schema.TimeoutUpdate))
458+
if err != nil {
459+
return diag.FromErr(err)
460+
}
461+
462+
return nil
463+
}
464+
347465
func resourceScalewayRedisClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
348466
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
349467
if err != nil {

0 commit comments

Comments
 (0)