Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(redis): add support for cluster resource #1238

Merged
merged 22 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
69d77c8
feat(redis): cluster resource
Mia-Cross Apr 25, 2022
b72de89
Merge branch 'master' into redis_resource
Monitob Apr 25, 2022
c577339
fix(redis): wait for cluster in create and update instead of read
Codelax Apr 25, 2022
13e7e68
fix(redis): set forcenew to node_type and redis version
Codelax Apr 25, 2022
aff273f
fix(redis): remove id on read if resource does not exist
Codelax Apr 25, 2022
54fe631
fix(redis): remove unused fields
Codelax Apr 25, 2022
dbdfa30
feat(redis): add redis to github workflow
Codelax Apr 25, 2022
39737b7
update cassette
Codelax Apr 25, 2022
c4f61a5
Merge branch 'master' into redis_resource
remyleone Apr 26, 2022
ff5aa89
Update scaleway/resource_redis_cluster.go
remyleone Apr 26, 2022
dc9dd91
Update scaleway/helpers_redis.go
remyleone Apr 26, 2022
ac909ba
feat(redis): add tags to cluster
Codelax Apr 27, 2022
8119917
feat(redis): add cluster_size to cluster schema
Codelax Apr 27, 2022
e812411
feat(redis): add tls_enabled to cluster schema
Codelax Apr 27, 2022
0d0c924
feat(redis): improved tests for basic schema
Mia-Cross Apr 27, 2022
8486b90
feat(redis): added cluster migration
Mia-Cross Apr 27, 2022
4c7b522
feat(redis): update cassettes
Mia-Cross Apr 27, 2022
13958f4
Merge branch 'master' into redis_resource
Codelax Apr 27, 2022
63ab291
fix(redis): remove forcenew for fields used in migration
Codelax Apr 27, 2022
f6f0451
update cassettes
Codelax Apr 27, 2022
286d7d7
add support for created_at, updated_at and cluster_size
remyleone Apr 27, 2022
b4bacfb
fix(redis): skip tags if empty in read
Codelax Apr 27, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/acceptance-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
- Marketplace
- Object
- Rdb
- Redis
- Registry
- VPC
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
- Marketplace
- Object
- Rdb
- Redis
- Registry
- VPC
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/hashicorp/terraform-plugin-log v0.3.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.14.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220419094512-76deec7b31e7
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220425121900-20ed4de943e7
github.com/stretchr/testify v1.7.1
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -864,8 +864,8 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220419094512-76deec7b31e7 h1:zbYmcD37hndTsbmtrH9Asi672IVtYl2fTqulDZPUaIY=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220419094512-76deec7b31e7/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220425121900-20ed4de943e7 h1:b6jogEVn0MhYYjQ537a1F898df4esAJOlGhxWxyJ1xM=
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9.0.20220425121900-20ed4de943e7/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
Expand Down
55 changes: 55 additions & 0 deletions scaleway/helpers_redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package scaleway

import (
"context"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
redis "github.com/scaleway/scaleway-sdk-go/api/redis/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

const (
defaultRedisClusterTimeout = 15 * time.Minute
defaultWaitRedisClusterRetryInterval = 5 * time.Second
)

// newRedisApi returns a new Redis API
func newRedisAPI(m interface{}) *redis.API {
meta := m.(*Meta)
return redis.NewAPI(meta.scwClient)
}

// redisAPIWithZone returns a new Redis API and the zone for a Create request
func redisAPIWithZone(d *schema.ResourceData, m interface{}) (*redis.API, scw.Zone, error) {
meta := m.(*Meta)

zone, err := extractZone(d, meta)
if err != nil {
return nil, "", err
}
return newRedisAPI(m), zone, nil
}

// redisAPIWithZoneAndID returns a Redis API with zone and ID extracted from the state
func redisAPIWithZoneAndID(m interface{}, id string) (*redis.API, scw.Zone, string, error) {
zone, ID, err := parseZonedID(id)
if err != nil {
return nil, "", "", err
}
return newRedisAPI(m), zone, ID, nil
}

func waitForRedisCluster(ctx context.Context, api *redis.API, zone scw.Zone, id string, timeout time.Duration) (*redis.Cluster, error) {
retryInterval := defaultWaitRedisClusterRetryInterval
if DefaultWaitRetryInterval != nil {
retryInterval = *DefaultWaitRetryInterval
}

return api.WaitForCluster(&redis.WaitForClusterRequest{
Zone: zone,
Timeout: scw.TimeDurationPtr(timeout),
ClusterID: id,
RetryInterval: &retryInterval,
}, scw.WithContext(ctx))
}
1 change: 1 addition & 0 deletions scaleway/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ func Provider(config *ProviderConfig) plugin.ProviderFunc {
"scaleway_rdb_instance": resourceScalewayRdbInstance(),
"scaleway_rdb_privilege": resourceScalewayRdbPrivilege(),
"scaleway_rdb_user": resourceScalewayRdbUser(),
"scaleway_redis_cluster": resourceScalewayRedisCluster(),
"scaleway_object_bucket": resourceScalewayObjectBucket(),
"scaleway_vpc_public_gateway": resourceScalewayVPCPublicGateway(),
"scaleway_vpc_gateway_network": resourceScalewayVPCGatewayNetwork(),
Expand Down
273 changes: 273 additions & 0 deletions scaleway/resource_redis_cluster.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
package scaleway

import (
"context"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
redis "github.com/scaleway/scaleway-sdk-go/api/redis/v1alpha1"
"github.com/scaleway/scaleway-sdk-go/scw"
)

func resourceScalewayRedisCluster() *schema.Resource {
return &schema.Resource{
CreateContext: resourceScalewayRedisClusterCreate,
ReadContext: resourceScalewayRedisClusterRead,
UpdateContext: resourceScalewayRedisClusterUpdate,
DeleteContext: resourceScalewayRedisClusterDelete,
Timeouts: &schema.ResourceTimeout{
Default: schema.DefaultTimeout(defaultRedisClusterTimeout),
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
SchemaVersion: 0,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Name of the redis cluster",
},
"version": {
Type: schema.TypeString,
Required: true,
Description: "Redis version of the cluster",
},
"node_type": {
Type: schema.TypeString,
Required: true,
Description: "Type of node to use for the cluster",
},
"user_name": {
Type: schema.TypeString,
Required: true,
Description: "Name of the user created when the cluster is created",
},
"password": {
Type: schema.TypeString,
Sensitive: true,
Required: true,
Description: "Password of the user",
},
"tags": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Description: "List of tags [\"tag1\", \"tag2\", ...] attached to a redis cluster",
},
"cluster_size": {
Type: schema.TypeInt,
Optional: true,
Description: "Number of nodes for the cluster.",
},
"tls_enabled": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether or not TLS is enabled.",
ForceNew: true,
},
"created_at": {
Type: schema.TypeString,
Computed: true,
Description: "The date and time of the creation of the Redis cluster",
},
"updated_at": {
Type: schema.TypeString,
Computed: true,
Description: "The date and time of the last update of the Redis cluster",
},
// Common
"zone": zoneSchema(),
"project_id": projectIDSchema(),
},
}
}

func resourceScalewayRedisClusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
redisAPI, zone, err := redisAPIWithZone(d, meta)
if err != nil {
return diag.FromErr(err)
}

createReq := &redis.CreateClusterRequest{
Zone: zone,
ProjectID: d.Get("project_id").(string),
Name: expandOrGenerateString(d.Get("name"), "redis"),
Version: d.Get("version").(string),
NodeType: d.Get("node_type").(string),
UserName: d.Get("user_name").(string),
Password: d.Get("password").(string),
}

tags, tagsExist := d.GetOk("tags")
if tagsExist {
createReq.Tags = expandStrings(tags)
}
clusterSize, clusterSizeExist := d.GetOk("cluster_size")
if clusterSizeExist {
createReq.ClusterSize = scw.Int32Ptr(int32(clusterSize.(int)))
}
tlsEnabled, tlsEnabledExist := d.GetOk("tls_enabled")
if tlsEnabledExist {
createReq.TLSEnabled = tlsEnabled.(bool)
}

res, err := redisAPI.CreateCluster(createReq, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

d.SetId(newZonedIDString(zone, res.ID))

_, err = waitForRedisCluster(ctx, redisAPI, zone, res.ID, d.Timeout(schema.TimeoutCreate))
if err != nil {
return diag.FromErr(err)
}

return resourceScalewayRedisClusterRead(ctx, d, meta)
}
func resourceScalewayRedisClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
if err != nil {
return diag.FromErr(err)
}

getReq := &redis.GetClusterRequest{
Zone: zone,
ClusterID: ID,
}
cluster, err := redisAPI.GetCluster(getReq, scw.WithContext(ctx))
if err != nil {
if is404Error(err) {
d.SetId("")
return nil
}
return diag.FromErr(err)
}

_ = d.Set("name", cluster.Name)
_ = d.Set("node_type", cluster.NodeType)
_ = d.Set("user_name", d.Get("user_name").(string))
_ = d.Set("password", d.Get("password").(string))
_ = d.Set("zone", cluster.Zone.String())
_ = d.Set("project_id", cluster.ProjectID)
_ = d.Set("version", cluster.Version)
_ = d.Set("tags", cluster.Tags)
_ = d.Set("cluster_size", cluster.ClusterSize)
_ = d.Set("created_at", cluster.CreatedAt.Format(time.RFC3339))
_ = d.Set("updated_at", cluster.UpdatedAt.Format(time.RFC3339))

return nil
}

func resourceScalewayRedisClusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
if err != nil {
return diag.FromErr(err)
}

req := &redis.UpdateClusterRequest{
Zone: zone,
ClusterID: ID,
}

if d.HasChange("name") {
req.Name = expandStringPtr(d.Get("name"))
}
if d.HasChange("user_name") {
req.UserName = expandStringPtr(d.Get("user_name"))
}
if d.HasChange("password") {
req.Password = expandStringPtr(d.Get("password"))
}
if d.HasChange("tags") {
req.Tags = expandStrings(d.Get("tags"))
}

_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return diag.FromErr(err)
}

_, err = redisAPI.UpdateCluster(req, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

migrateClusterRequests := []redis.MigrateClusterRequest(nil)
if d.HasChange("cluster_size") {
migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{
Zone: zone,
ClusterID: ID,
ClusterSize: scw.Uint32Ptr(uint32(d.Get("cluster_size").(int))),
})
}
if d.HasChange("version") {
migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{
Zone: zone,
ClusterID: ID,
Version: expandStringPtr(d.Get("version")),
})
}
if d.HasChange("node_type") {
migrateClusterRequests = append(migrateClusterRequests, redis.MigrateClusterRequest{
Zone: zone,
ClusterID: ID,
NodeType: expandStringPtr(d.Get("node_type")),
})
}
for _, request := range migrateClusterRequests {
_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
if err != nil && !is404Error(err) {
return diag.FromErr(err)
}
_, err = redisAPI.MigrateCluster(&request, scw.WithContext(ctx))
if err != nil {
return diag.FromErr(err)
}

_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
if err != nil && !is404Error(err) {
return diag.FromErr(err)
}
}

_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutUpdate))
if err != nil {
return diag.FromErr(err)
}

return resourceScalewayRedisClusterRead(ctx, d, meta)
}

func resourceScalewayRedisClusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
redisAPI, zone, ID, err := redisAPIWithZoneAndID(meta, d.Id())
if err != nil {
return diag.FromErr(err)
}

_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutDelete))
if err != nil {
return diag.FromErr(err)
}

_, err = redisAPI.DeleteCluster(&redis.DeleteClusterRequest{
Zone: zone,
ClusterID: ID,
}, scw.WithContext(ctx))

if err != nil {
return diag.FromErr(err)
}

_, err = waitForRedisCluster(ctx, redisAPI, zone, ID, d.Timeout(schema.TimeoutDelete))
if err != nil && !is404Error(err) {
return diag.FromErr(err)
}

return nil
}
Loading