Skip to content

Commit 10918ad

Browse files
authored
feat(redis): add support for cluster resource (#1238)
1 parent 5000f07 commit 10918ad

10 files changed

+4963
-3
lines changed

.github/workflows/acceptance-tests.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
- Marketplace
2323
- Object
2424
- Rdb
25+
- Redis
2526
- Registry
2627
- VPC
2728
runs-on: ubuntu-latest

.github/workflows/nightly.yml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
- Marketplace
2525
- Object
2626
- Rdb
27+
- Redis
2728
- Registry
2829
- VPC
2930
runs-on: ubuntu-latest

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/hashicorp/go-retryablehttp v0.7.1
1212
github.com/hashicorp/terraform-plugin-log v0.3.0
1313
github.com/hashicorp/terraform-plugin-sdk/v2 v2.14.0
14-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220419094512-76deec7b31e7
14+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220425121900-20ed4de943e7
1515
github.com/stretchr/testify v1.7.1
1616
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f
1717

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -864,8 +864,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
864864
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
865865
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
866866
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
867-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220419094512-76deec7b31e7 h1:zbYmcD37hndTsbmtrH9Asi672IVtYl2fTqulDZPUaIY=
868-
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220419094512-76deec7b31e7/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
867+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220425121900-20ed4de943e7 h1:b6jogEVn0MhYYjQ537a1F898df4esAJOlGhxWxyJ1xM=
868+
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220425121900-20ed4de943e7/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
869869
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
870870
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
871871
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=

scaleway/helpers_redis.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package scaleway
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
redis "github.com/scaleway/scaleway-sdk-go/api/redis/v1alpha1"
9+
"github.com/scaleway/scaleway-sdk-go/scw"
10+
)
11+
12+
const (
13+
defaultRedisClusterTimeout = 15 * time.Minute
14+
defaultWaitRedisClusterRetryInterval = 5 * time.Second
15+
)
16+
17+
// newRedisApi returns a new Redis API
18+
func newRedisAPI(m interface{}) *redis.API {
19+
meta := m.(*Meta)
20+
return redis.NewAPI(meta.scwClient)
21+
}
22+
23+
// redisAPIWithZone returns a new Redis API and the zone for a Create request
24+
func redisAPIWithZone(d *schema.ResourceData, m interface{}) (*redis.API, scw.Zone, error) {
25+
meta := m.(*Meta)
26+
27+
zone, err := extractZone(d, meta)
28+
if err != nil {
29+
return nil, "", err
30+
}
31+
return newRedisAPI(m), zone, nil
32+
}
33+
34+
// redisAPIWithZoneAndID returns a Redis API with zone and ID extracted from the state
35+
func redisAPIWithZoneAndID(m interface{}, id string) (*redis.API, scw.Zone, string, error) {
36+
zone, ID, err := parseZonedID(id)
37+
if err != nil {
38+
return nil, "", "", err
39+
}
40+
return newRedisAPI(m), zone, ID, nil
41+
}
42+
43+
func waitForRedisCluster(ctx context.Context, api *redis.API, zone scw.Zone, id string, timeout time.Duration) (*redis.Cluster, error) {
44+
retryInterval := defaultWaitRedisClusterRetryInterval
45+
if DefaultWaitRetryInterval != nil {
46+
retryInterval = *DefaultWaitRetryInterval
47+
}
48+
49+
return api.WaitForCluster(&redis.WaitForClusterRequest{
50+
Zone: zone,
51+
Timeout: scw.TimeDurationPtr(timeout),
52+
ClusterID: id,
53+
RetryInterval: &retryInterval,
54+
}, scw.WithContext(ctx))
55+
}

scaleway/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
9696
"scaleway_rdb_instance": resourceScalewayRdbInstance(),
9797
"scaleway_rdb_privilege": resourceScalewayRdbPrivilege(),
9898
"scaleway_rdb_user": resourceScalewayRdbUser(),
99+
"scaleway_redis_cluster": resourceScalewayRedisCluster(),
99100
"scaleway_object_bucket": resourceScalewayObjectBucket(),
100101
"scaleway_vpc_public_gateway": resourceScalewayVPCPublicGateway(),
101102
"scaleway_vpc_gateway_network": resourceScalewayVPCGatewayNetwork(),

scaleway/resource_redis_cluster.go

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package scaleway
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
redis "github.com/scaleway/scaleway-sdk-go/api/redis/v1alpha1"
10+
"github.com/scaleway/scaleway-sdk-go/scw"
11+
)
12+
13+
func resourceScalewayRedisCluster() *schema.Resource {
14+
return &schema.Resource{
15+
CreateContext: resourceScalewayRedisClusterCreate,
16+
ReadContext: resourceScalewayRedisClusterRead,
17+
UpdateContext: resourceScalewayRedisClusterUpdate,
18+
DeleteContext: resourceScalewayRedisClusterDelete,
19+
Timeouts: &schema.ResourceTimeout{
20+
Default: schema.DefaultTimeout(defaultRedisClusterTimeout),
21+
},
22+
Importer: &schema.ResourceImporter{
23+
StateContext: schema.ImportStatePassthroughContext,
24+
},
25+
SchemaVersion: 0,
26+
Schema: map[string]*schema.Schema{
27+
"name": {
28+
Type: schema.TypeString,
29+
Optional: true,
30+
Computed: true,
31+
Description: "Name of the redis cluster",
32+
},
33+
"version": {
34+
Type: schema.TypeString,
35+
Required: true,
36+
Description: "Redis version of the cluster",
37+
},
38+
"node_type": {
39+
Type: schema.TypeString,
40+
Required: true,
41+
Description: "Type of node to use for the cluster",
42+
},
43+
"user_name": {
44+
Type: schema.TypeString,
45+
Required: true,
46+
Description: "Name of the user created when the cluster is created",
47+
},
48+
"password": {
49+
Type: schema.TypeString,
50+
Sensitive: true,
51+
Required: true,
52+
Description: "Password of the user",
53+
},
54+
"tags": {
55+
Type: schema.TypeList,
56+
Optional: true,
57+
Elem: &schema.Schema{
58+
Type: schema.TypeString,
59+
},
60+
Description: "List of tags [\"tag1\", \"tag2\", ...] attached to a redis cluster",
61+
},
62+
"cluster_size": {
63+
Type: schema.TypeInt,
64+
Optional: true,
65+
Description: "Number of nodes for the cluster.",
66+
},
67+
"tls_enabled": {
68+
Type: schema.TypeBool,
69+
Optional: true,
70+
Description: "Whether or not TLS is enabled.",
71+
ForceNew: true,
72+
},
73+
"created_at": {
74+
Type: schema.TypeString,
75+
Computed: true,
76+
Description: "The date and time of the creation of the Redis cluster",
77+
},
78+
"updated_at": {
79+
Type: schema.TypeString,
80+
Computed: true,
81+
Description: "The date and time of the last update of the Redis cluster",
82+
},
83+
// Common
84+
"zone": zoneSchema(),
85+
"project_id": projectIDSchema(),
86+
},
87+
}
88+
}
89+
90+
func resourceScalewayRedisClusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
91+
redisAPI, zone, err := redisAPIWithZone(d, meta)
92+
if err != nil {
93+
return diag.FromErr(err)
94+
}
95+
96+
createReq := &redis.CreateClusterRequest{
97+
Zone: zone,
98+
ProjectID: d.Get("project_id").(string),
99+
Name: expandOrGenerateString(d.Get("name"), "redis"),
100+
Version: d.Get("version").(string),
101+
NodeType: d.Get("node_type").(string),
102+
UserName: d.Get("user_name").(string),
103+
Password: d.Get("password").(string),
104+
}
105+
106+
tags, tagsExist := d.GetOk("tags")
107+
if tagsExist {
108+
createReq.Tags = expandStrings(tags)
109+
}
110+
clusterSize, clusterSizeExist := d.GetOk("cluster_size")
111+
if clusterSizeExist {
112+
createReq.ClusterSize = scw.Int32Ptr(int32(clusterSize.(int)))
113+
}
114+
tlsEnabled, tlsEnabledExist := d.GetOk("tls_enabled")
115+
if tlsEnabledExist {
116+
createReq.TLSEnabled = tlsEnabled.(bool)
117+
}
118+
119+
res, err := redisAPI.CreateCluster(createReq, scw.WithContext(ctx))
120+
if err != nil {
121+
return diag.FromErr(err)
122+
}
123+
124+
d.SetId(newZonedIDString(zone, res.ID))
125+
126+
_, err = waitForRedisCluster(ctx, redisAPI, zone, res.ID, d.Timeout(schema.TimeoutCreate))
127+
if err != nil {
128+
return diag.FromErr(err)
129+
}
130+
131+
return resourceScalewayRedisClusterRead(ctx, d, meta)
132+
}
133+
func resourceScalewayRedisClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
134+
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
135+
if err != nil {
136+
return diag.FromErr(err)
137+
}
138+
139+
getReq := &redis.GetClusterRequest{
140+
Zone: zone,
141+
ClusterID: ID,
142+
}
143+
cluster, err := redisAPI.GetCluster(getReq, scw.WithContext(ctx))
144+
if err != nil {
145+
if is404Error(err) {
146+
d.SetId("")
147+
return nil
148+
}
149+
return diag.FromErr(err)
150+
}
151+
152+
_ = d.Set("name", cluster.Name)
153+
_ = d.Set("node_type", cluster.NodeType)
154+
_ = d.Set("user_name", d.Get("user_name").(string))
155+
_ = d.Set("password", d.Get("password").(string))
156+
_ = d.Set("zone", cluster.Zone.String())
157+
_ = d.Set("project_id", cluster.ProjectID)
158+
_ = d.Set("version", cluster.Version)
159+
_ = d.Set("cluster_size", cluster.ClusterSize)
160+
_ = d.Set("created_at", cluster.CreatedAt.Format(time.RFC3339))
161+
_ = d.Set("updated_at", cluster.UpdatedAt.Format(time.RFC3339))
162+
163+
if len(cluster.Tags) > 0 {
164+
_ = d.Set("tags", cluster.Tags)
165+
}
166+
167+
return nil
168+
}
169+
170+
func resourceScalewayRedisClusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
171+
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
172+
if err != nil {
173+
return diag.FromErr(err)
174+
}
175+
176+
req := &redis.UpdateClusterRequest{
177+
Zone: zone,
178+
ClusterID: ID,
179+
}
180+
181+
if d.HasChange("name") {
182+
req.Name = expandStringPtr(d.Get("name"))
183+
}
184+
if d.HasChange("user_name") {
185+
req.UserName = expandStringPtr(d.Get("user_name"))
186+
}
187+
if d.HasChange("password") {
188+
req.Password = expandStringPtr(d.Get("password"))
189+
}
190+
if d.HasChange("tags") {
191+
req.Tags = expandStrings(d.Get("tags"))
192+
}
193+
194+
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
195+
if err != nil {
196+
return diag.FromErr(err)
197+
}
198+
199+
_, err = redisAPI.UpdateCluster(req, scw.WithContext(ctx))
200+
if err != nil {
201+
return diag.FromErr(err)
202+
}
203+
204+
migrateClusterRequests := []redis.MigrateClusterRequest(nil)
205+
if d.HasChange("cluster_size") {
206+
migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{
207+
Zone: zone,
208+
ClusterID: ID,
209+
ClusterSize: scw.Uint32Ptr(uint32(d.Get("cluster_size").(int))),
210+
})
211+
}
212+
if d.HasChange("version") {
213+
migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{
214+
Zone: zone,
215+
ClusterID: ID,
216+
Version: expandStringPtr(d.Get("version")),
217+
})
218+
}
219+
if d.HasChange("node_type") {
220+
migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{
221+
Zone: zone,
222+
ClusterID: ID,
223+
NodeType: expandStringPtr(d.Get("node_type")),
224+
})
225+
}
226+
for _, request := range migrateClusterRequests {
227+
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
228+
if err != nil && !is404Error(err) {
229+
return diag.FromErr(err)
230+
}
231+
_, err = redisAPI.MigrateCluster(&request, scw.WithContext(ctx))
232+
if err != nil {
233+
return diag.FromErr(err)
234+
}
235+
236+
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
237+
if err != nil && !is404Error(err) {
238+
return diag.FromErr(err)
239+
}
240+
}
241+
242+
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
243+
if err != nil {
244+
return diag.FromErr(err)
245+
}
246+
247+
return resourceScalewayRedisClusterRead(ctx, d, meta)
248+
}
249+
250+
func resourceScalewayRedisClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
251+
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
252+
if err != nil {
253+
return diag.FromErr(err)
254+
}
255+
256+
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutDelete))
257+
if err != nil {
258+
return diag.FromErr(err)
259+
}
260+
261+
_, err = redisAPI.DeleteCluster(&redis.DeleteClusterRequest{
262+
Zone: zone,
263+
ClusterID: ID,
264+
}, scw.WithContext(ctx))
265+
266+
if err != nil {
267+
return diag.FromErr(err)
268+
}
269+
270+
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutDelete))
271+
if err != nil && !is404Error(err) {
272+
return diag.FromErr(err)
273+
}
274+
275+
return nil
276+
}

0 commit comments

Comments
 (0)