diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ca71021..929c1690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See updating [Changelog example here](https://keepachangelog.com/en/1.0.0/) +# 2.3.2 (22nd August 2025) + +### Changed + +- Refactoring and cleaning up of code. + +# 2.3.1 (21st August 2025) + +### Changed + +- Updating and fixing documentation. # 2.3.0 (19th August 2025) diff --git a/go.mod b/go.mod index 38e69efd..9eac0a08 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 github.com/stretchr/testify v1.10.0 + golang.org/x/net v0.43.0 ) require ( @@ -57,7 +58,6 @@ require ( github.com/zclconf/go-cty v1.16.2 // indirect golang.org/x/crypto v0.41.0 // indirect golang.org/x/mod v0.27.0 // indirect - golang.org/x/net v0.43.0 // indirect golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect diff --git a/provider/datasource_rediscloud_cloud_account.go b/provider/account/datasource_rediscloud_cloud_account.go similarity index 93% rename from provider/datasource_rediscloud_cloud_account.go rename to provider/account/datasource_rediscloud_cloud_account.go index b283e7c4..d46856a9 100644 --- a/provider/datasource_rediscloud_cloud_account.go +++ b/provider/account/datasource_rediscloud_cloud_account.go @@ -1,16 +1,18 @@ -package provider +package account import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "strconv" ) -func dataSourceRedisCloudCloudAccount() *schema.Resource { +func DataSourceRedisCloudCloudAccount() *schema.Resource { return &schema.Resource{ Description: "The Cloud Account data source allows access to the ID of a Cloud Account configuration. This ID can be used when creating Subscription resources.", ReadContext: dataSourceRedisCloudCloudAccountRead, @@ -46,9 +48,9 @@ func dataSourceRedisCloudCloudAccount() *schema.Resource { func dataSourceRedisCloudCloudAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - client := meta.(*apiClient) + client := meta.(*utils.ApiClient) - accounts, err := client.client.CloudAccount.List(ctx) + accounts, err := client.Client.CloudAccount.List(ctx) if err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_payment_method.go b/provider/account/datasource_rediscloud_payment_method.go similarity index 94% rename from provider/datasource_rediscloud_payment_method.go rename to provider/account/datasource_rediscloud_payment_method.go index f36a5de9..afa7b60d 100644 --- a/provider/datasource_rediscloud_payment_method.go +++ b/provider/account/datasource_rediscloud_payment_method.go @@ -1,19 +1,21 @@ -package provider +package account import ( "context" "fmt" + "regexp" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/account" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "regexp" - "strconv" - "time" ) -func dataSourceRedisCloudPaymentMethod() *schema.Resource { +func DataSourceRedisCloudPaymentMethod() *schema.Resource { return &schema.Resource{ Description: "The Payment Method data source allows access to the ID of a Payment Method configured against your Redis Enterprise Cloud account. This ID can be used when creating Subscription resources.", ReadContext: dataSourceRedisCloudPaymentMethodRead, @@ -45,9 +47,9 @@ func dataSourceRedisCloudPaymentMethod() *schema.Resource { func dataSourceRedisCloudPaymentMethodRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - client := meta.(*apiClient) + client := meta.(*utils.ApiClient) - methods, err := client.client.Account.ListPaymentMethods(ctx) + methods, err := client.Client.Account.ListPaymentMethods(ctx) if err != nil { return diag.FromErr(err) diff --git a/provider/resource_rediscloud_cloud_account.go b/provider/account/resource_rediscloud_cloud_account.go similarity index 91% rename from provider/resource_rediscloud_cloud_account.go rename to provider/account/resource_rediscloud_cloud_account.go index 5d31369f..df77e334 100644 --- a/provider/resource_rediscloud_cloud_account.go +++ b/provider/account/resource_rediscloud_cloud_account.go @@ -1,19 +1,21 @@ -package provider +package account import ( "context" + "log" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "log" - "strconv" - "time" ) -func resourceRedisCloudCloudAccount() *schema.Resource { +func ResourceRedisCloudCloudAccount() *schema.Resource { return &schema.Resource{ Description: "Creates a Cloud Account resource representing the access credentials to a cloud provider account, (`AWS` or `GCP`). Your Redis Enterprise Cloud account uses these credentials to provision databases within your infrastructure. ", CreateContext: resourceRedisCloudCloudAccountCreate, @@ -88,7 +90,7 @@ func resourceRedisCloudCloudAccount() *schema.Resource { } func resourceRedisCloudCloudAccountCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*apiClient) + client := meta.(*utils.ApiClient) accessKey := d.Get("access_key_id").(string) secretKey := d.Get("access_secret_key").(string) @@ -98,7 +100,7 @@ func resourceRedisCloudCloudAccountCreate(ctx context.Context, d *schema.Resourc provider := d.Get("provider_type").(string) signInLoginUrl := d.Get("sign_in_login_url").(string) - id, err := client.client.CloudAccount.Create(ctx, cloud_accounts.CreateCloudAccount{ + id, err := client.Client.CloudAccount.Create(ctx, cloud_accounts.CreateCloudAccount{ AccessKeyID: redis.String(accessKey), AccessSecretKey: redis.String(secretKey), ConsoleUsername: redis.String(consoleUsername), @@ -121,7 +123,7 @@ func resourceRedisCloudCloudAccountCreate(ctx context.Context, d *schema.Resourc return resourceRedisCloudCloudAccountRead(ctx, d, meta) } -func waitForCloudAccountToBeActive(ctx context.Context, id int, client *apiClient) error { +func waitForCloudAccountToBeActive(ctx context.Context, id int, client *utils.ApiClient) error { wait := &retry.StateChangeConf{ Delay: 10 * time.Second, Pending: []string{cloud_accounts.StatusDraft, cloud_accounts.StatusChangeDraft}, @@ -131,7 +133,7 @@ func waitForCloudAccountToBeActive(ctx context.Context, id int, client *apiClien Refresh: func() (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for cloud account %d to be active", id) - account, err := client.client.CloudAccount.Get(ctx, id) + account, err := client.Client.CloudAccount.Get(ctx, id) if err != nil { return nil, "", err } @@ -148,7 +150,7 @@ func waitForCloudAccountToBeActive(ctx context.Context, id int, client *apiClien } func resourceRedisCloudCloudAccountRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*apiClient) + client := meta.(*utils.ApiClient) var diags diag.Diagnostics @@ -157,7 +159,7 @@ func resourceRedisCloudCloudAccountRead(ctx context.Context, d *schema.ResourceD return diag.FromErr(err) } - account, err := client.client.CloudAccount.Get(ctx, id) + account, err := client.Client.CloudAccount.Get(ctx, id) if err != nil { if _, ok := err.(*cloud_accounts.NotFound); ok { d.SetId("") @@ -183,7 +185,7 @@ func resourceRedisCloudCloudAccountRead(ctx context.Context, d *schema.ResourceD } func resourceRedisCloudCloudAccountUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*apiClient) + client := meta.(*utils.ApiClient) id, err := strconv.Atoi(d.Id()) if err != nil { @@ -197,7 +199,7 @@ func resourceRedisCloudCloudAccountUpdate(ctx context.Context, d *schema.Resourc name := d.Get("name").(string) signInLoginUrl := d.Get("sign_in_login_url").(string) - err = client.client.CloudAccount.Update(ctx, id, cloud_accounts.UpdateCloudAccount{ + err = client.Client.CloudAccount.Update(ctx, id, cloud_accounts.UpdateCloudAccount{ AccessKeyID: redis.String(accessKey), AccessSecretKey: redis.String(secretKey), ConsoleUsername: redis.String(consoleUsername), @@ -214,14 +216,14 @@ func resourceRedisCloudCloudAccountUpdate(ctx context.Context, d *schema.Resourc } func resourceRedisCloudCloudAccountDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client := meta.(*apiClient) + client := meta.(*utils.ApiClient) id, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - err = client.client.CloudAccount.Delete(ctx, id) + err = client.Client.CloudAccount.Delete(ctx, id) if err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_acl_role.go b/provider/acl/datasource_rediscloud_acl_role.go similarity index 94% rename from provider/datasource_rediscloud_acl_role.go rename to provider/acl/datasource_rediscloud_acl_role.go index e8f337d3..e7c4eee0 100644 --- a/provider/datasource_rediscloud_acl_role.go +++ b/provider/acl/datasource_rediscloud_acl_role.go @@ -1,15 +1,17 @@ -package provider +package acl import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/roles" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceRedisCloudAclRole() *schema.Resource { +func DataSourceRedisCloudAclRole() *schema.Resource { return &schema.Resource{ Description: "The ACL Role grants a number of permissions to databases", ReadContext: dataSourceRedisCloudAclRoleRead, @@ -67,7 +69,7 @@ func dataSourceRedisCloudAclRole() *schema.Resource { func dataSourceRedisCloudAclRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var filters []func(role *roles.GetRoleResponse) bool if v, ok := d.GetOk("name"); ok { @@ -76,7 +78,7 @@ func dataSourceRedisCloudAclRoleRead(ctx context.Context, d *schema.ResourceData }) } - list, err := api.client.Roles.List(ctx) + list, err := api.Client.Roles.List(ctx) if err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_acl_rule.go b/provider/acl/datasource_rediscloud_acl_rule.go similarity index 91% rename from provider/datasource_rediscloud_acl_rule.go rename to provider/acl/datasource_rediscloud_acl_rule.go index 2334ffea..15cafe9d 100644 --- a/provider/datasource_rediscloud_acl_rule.go +++ b/provider/acl/datasource_rediscloud_acl_rule.go @@ -1,15 +1,17 @@ -package provider +package acl import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/redis_rules" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceRedisCloudAclRule() *schema.Resource { +func DataSourceRedisCloudAclRule() *schema.Resource { return &schema.Resource{ Description: "The ACL Rule (known also as RedisRule) allows fine-grained permissions to be assigned to a subset of ACL Users", ReadContext: dataSourceRedisCloudAclRuleRead, @@ -31,7 +33,7 @@ func dataSourceRedisCloudAclRule() *schema.Resource { func dataSourceRedisCloudAclRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var filters []func(rule *redis_rules.GetRedisRuleResponse) bool if v, ok := d.GetOk("name"); ok { @@ -40,7 +42,7 @@ func dataSourceRedisCloudAclRuleRead(ctx context.Context, d *schema.ResourceData }) } - list, err := api.client.RedisRules.List(ctx) + list, err := api.Client.RedisRules.List(ctx) if err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_acl_user.go b/provider/acl/datasource_rediscloud_acl_user.go similarity index 91% rename from provider/datasource_rediscloud_acl_user.go rename to provider/acl/datasource_rediscloud_acl_user.go index df70ba8b..7757d9c5 100644 --- a/provider/datasource_rediscloud_acl_user.go +++ b/provider/acl/datasource_rediscloud_acl_user.go @@ -1,15 +1,17 @@ -package provider +package acl import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/users" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceRedisCloudAclUser() *schema.Resource { +func DataSourceRedisCloudAclUser() *schema.Resource { return &schema.Resource{ Description: "The ACL User is an authenticated entity whose permissions are described by an associated Role", ReadContext: dataSourceRedisCloudAclUserRead, @@ -31,7 +33,7 @@ func dataSourceRedisCloudAclUser() *schema.Resource { func dataSourceRedisCloudAclUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var filters []func(user *users.GetUserResponse) bool if v, ok := d.GetOk("name"); ok { @@ -40,7 +42,7 @@ func dataSourceRedisCloudAclUserRead(ctx context.Context, d *schema.ResourceData }) } - list, err := api.client.Users.List(ctx) + list, err := api.Client.Users.List(ctx) if err != nil { return diag.FromErr(err) } diff --git a/provider/resource_rediscloud_acl_role.go b/provider/acl/resource_rediscloud_acl_role.go similarity index 93% rename from provider/resource_rediscloud_acl_role.go rename to provider/acl/resource_rediscloud_acl_role.go index 27c0f579..e0ee3d46 100644 --- a/provider/resource_rediscloud_acl_role.go +++ b/provider/acl/resource_rediscloud_acl_role.go @@ -1,19 +1,21 @@ -package provider +package acl import ( "context" "fmt" + "log" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/roles" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "log" - "strconv" - "time" ) -func resourceRedisCloudAclRole() *schema.Resource { +func ResourceRedisCloudAclRole() *schema.Resource { return &schema.Resource{ Description: "Create an ACL Role within your Redis Enterprise Cloud Account", CreateContext: resourceRedisCloudAclRoleCreate, @@ -86,7 +88,7 @@ func resourceRedisCloudAclRole() *schema.Resource { } func resourceRedisCloudAclRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) name := d.Get("name").(string) associateWithRules := extractRules(d) @@ -96,7 +98,7 @@ func resourceRedisCloudAclRoleCreate(ctx context.Context, d *schema.ResourceData RedisRules: associateWithRules, } - id, err := api.client.Roles.Create(ctx, createRoleRequest) + id, err := api.Client.Roles.Create(ctx, createRoleRequest) if err != nil { return diag.FromErr(err) @@ -123,7 +125,7 @@ func resourceRedisCloudAclRoleCreate(ctx context.Context, d *schema.ResourceData } func resourceRedisCloudAclRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics id, err := strconv.Atoi(d.Id()) @@ -131,7 +133,7 @@ func resourceRedisCloudAclRoleRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } - role, err := api.client.Roles.Get(ctx, id) + role, err := api.Client.Roles.Get(ctx, id) if err != nil { if _, ok := err.(*roles.NotFound); ok { d.SetId("") @@ -150,7 +152,7 @@ func resourceRedisCloudAclRoleRead(ctx context.Context, d *schema.ResourceData, } func resourceRedisCloudAclRoleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) id, err := strconv.Atoi(d.Id()) if err != nil { @@ -165,7 +167,7 @@ func resourceRedisCloudAclRoleUpdate(ctx context.Context, d *schema.ResourceData rules := extractRules(d) updateRoleRequest.RedisRules = rules - err = api.client.Roles.Update(ctx, id, updateRoleRequest) + err = api.Client.Roles.Update(ctx, id, updateRoleRequest) if err != nil { return diag.FromErr(err) } @@ -190,7 +192,7 @@ func resourceRedisCloudAclRoleUpdate(ctx context.Context, d *schema.ResourceData } func resourceRedisCloudAclRoleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics id, err := strconv.Atoi(d.Id()) @@ -206,7 +208,7 @@ func resourceRedisCloudAclRoleDelete(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } - err = api.client.Roles.Delete(ctx, id) + err = api.Client.Roles.Delete(ctx, id) if err != nil { return diag.FromErr(err) @@ -216,7 +218,7 @@ func resourceRedisCloudAclRoleDelete(ctx context.Context, d *schema.ResourceData // Wait until it's really disappeared err = retry.RetryContext(ctx, 5*time.Minute, func() *retry.RetryError { - role, err := api.client.Roles.Get(ctx, id) + role, err := api.Client.Roles.Get(ctx, id) if err != nil { if _, ok := err.(*roles.NotFound); ok { @@ -258,7 +260,7 @@ func extractRules(d *schema.ResourceData) []*roles.CreateRuleInRoleRequest { var regions []*string = nil if databaseMap["regions"] != nil { - regions = setToStringSlice(databaseMap["regions"].(*schema.Set)) + regions = utils.SetToStringSlice(databaseMap["regions"].(*schema.Set)) } createDatabaseAssociation := roles.CreateDatabaseInRuleInRoleRequest{ @@ -310,7 +312,7 @@ func flattenDatabases(databases []*roles.GetDatabaseInRuleInRoleResponse) []map[ return tfs } -func waitForAclRoleToBeActive(ctx context.Context, id int, api *apiClient) error { +func waitForAclRoleToBeActive(ctx context.Context, id int, api *utils.ApiClient) error { wait := &retry.StateChangeConf{ Delay: 5 * time.Second, Pending: []string{roles.StatusPending}, @@ -320,7 +322,7 @@ func waitForAclRoleToBeActive(ctx context.Context, id int, api *apiClient) error Refresh: func() (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for role %d to be active", id) - role, err := api.client.Roles.Get(ctx, id) + role, err := api.Client.Roles.Get(ctx, id) if err != nil { return nil, "", err } diff --git a/provider/resource_rediscloud_acl_rule.go b/provider/acl/resource_rediscloud_acl_rule.go similarity index 77% rename from provider/resource_rediscloud_acl_rule.go rename to provider/acl/resource_rediscloud_acl_rule.go index 2b7e0bd8..70a72e1d 100644 --- a/provider/resource_rediscloud_acl_rule.go +++ b/provider/acl/resource_rediscloud_acl_rule.go @@ -1,19 +1,20 @@ -package provider +package acl import ( "context" "fmt" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/redis_rules" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "log" - "strconv" - "time" ) -func resourceRedisCloudAclRule() *schema.Resource { +func ResourceRedisCloudAclRule() *schema.Resource { return &schema.Resource{ Description: "Create an ACL Rule within your Redis Enterprise Cloud Account", CreateContext: resourceRedisCloudAclRuleCreate, @@ -48,7 +49,7 @@ func resourceRedisCloudAclRule() *schema.Resource { } func resourceRedisCloudAclRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) name := d.Get("name").(string) rule := d.Get("rule").(string) @@ -58,14 +59,14 @@ func resourceRedisCloudAclRuleCreate(ctx context.Context, d *schema.ResourceData RedisRule: redis.String(rule), } - id, err := api.client.RedisRules.Create(ctx, createRule) + id, err := api.Client.RedisRules.Create(ctx, createRule) if err != nil { return diag.FromErr(err) } d.SetId(strconv.Itoa(id)) - err = waitForAclRuleToBeActive(ctx, id, api) + err = utils.WaitForAclRuleToBeActive(ctx, id, api) if err != nil { return diag.FromErr(err) } @@ -74,7 +75,7 @@ func resourceRedisCloudAclRuleCreate(ctx context.Context, d *schema.ResourceData } func resourceRedisCloudAclRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics id, err := strconv.Atoi(d.Id()) @@ -82,7 +83,7 @@ func resourceRedisCloudAclRuleRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } - rule, err := api.client.RedisRules.Get(ctx, id) + rule, err := api.Client.RedisRules.Get(ctx, id) if err != nil { if _, ok := err.(*redis_rules.NotFound); ok { d.SetId("") @@ -102,7 +103,7 @@ func resourceRedisCloudAclRuleRead(ctx context.Context, d *schema.ResourceData, } func resourceRedisCloudAclRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) id, err := strconv.Atoi(d.Id()) if err != nil { @@ -120,12 +121,12 @@ func resourceRedisCloudAclRuleUpdate(ctx context.Context, d *schema.ResourceData rule := d.Get("rule").(string) updateRedisRuleRequest.RedisRule = &rule - err = api.client.RedisRules.Update(ctx, id, updateRedisRuleRequest) + err = api.Client.RedisRules.Update(ctx, id, updateRedisRuleRequest) if err != nil { return diag.FromErr(err) } - err = waitForAclRuleToBeActive(ctx, id, api) + err = utils.WaitForAclRuleToBeActive(ctx, id, api) if err != nil { return diag.FromErr(err) } @@ -135,7 +136,7 @@ func resourceRedisCloudAclRuleUpdate(ctx context.Context, d *schema.ResourceData } func resourceRedisCloudAclRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics id, err := strconv.Atoi(d.Id()) @@ -143,7 +144,7 @@ func resourceRedisCloudAclRuleDelete(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } - err = api.client.RedisRules.Delete(ctx, id) + err = api.Client.RedisRules.Delete(ctx, id) if err != nil { return diag.FromErr(err) @@ -153,7 +154,7 @@ func resourceRedisCloudAclRuleDelete(ctx context.Context, d *schema.ResourceData // Wait until it's really disappeared err = retry.RetryContext(ctx, 5*time.Minute, func() *retry.RetryError { - rule, err := api.client.RedisRules.Get(ctx, id) + rule, err := api.Client.RedisRules.Get(ctx, id) if err != nil { if _, ok := err.(*redis_rules.NotFound); ok { @@ -176,28 +177,3 @@ func resourceRedisCloudAclRuleDelete(ctx context.Context, d *schema.ResourceData return diags } - -func waitForAclRuleToBeActive(ctx context.Context, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Delay: 5 * time.Second, - Pending: []string{redis_rules.StatusPending}, - Target: []string{redis_rules.StatusActive}, - Timeout: 5 * time.Minute, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for rule %d to be active", id) - - rule, err := api.client.RedisRules.Get(ctx, id) - if err != nil { - return nil, "", err - } - - return redis.StringValue(rule.Status), redis.StringValue(rule.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} diff --git a/provider/resource_rediscloud_acl_user.go b/provider/acl/resource_rediscloud_acl_user.go similarity index 91% rename from provider/resource_rediscloud_acl_user.go rename to provider/acl/resource_rediscloud_acl_user.go index 94809bed..317720ef 100644 --- a/provider/resource_rediscloud_acl_user.go +++ b/provider/acl/resource_rediscloud_acl_user.go @@ -1,19 +1,21 @@ -package provider +package acl import ( "context" "fmt" + "log" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/users" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "log" - "strconv" - "time" ) -func resourceRedisCloudAclUser() *schema.Resource { +func ResourceRedisCloudAclUser() *schema.Resource { return &schema.Resource{ Description: "Create an ACL User within your Redis Enterprise Cloud Account", CreateContext: resourceRedisCloudAclUserCreate, @@ -56,7 +58,7 @@ func resourceRedisCloudAclUser() *schema.Resource { } func resourceRedisCloudAclUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) name := d.Get("name").(string) role := d.Get("role").(string) @@ -68,7 +70,7 @@ func resourceRedisCloudAclUserCreate(ctx context.Context, d *schema.ResourceData Password: redis.String(password), } - id, err := api.client.Users.Create(ctx, createUser) + id, err := api.Client.Users.Create(ctx, createUser) if err != nil { return diag.FromErr(err) } @@ -94,7 +96,7 @@ func resourceRedisCloudAclUserCreate(ctx context.Context, d *schema.ResourceData } func resourceRedisCloudAclUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics id, err := strconv.Atoi(d.Id()) @@ -102,7 +104,7 @@ func resourceRedisCloudAclUserRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } - user, err := api.client.Users.Get(ctx, id) + user, err := api.Client.Users.Get(ctx, id) if err != nil { if _, ok := err.(*users.NotFound); ok { d.SetId("") @@ -122,7 +124,7 @@ func resourceRedisCloudAclUserRead(ctx context.Context, d *schema.ResourceData, } func resourceRedisCloudAclUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) id, err := strconv.Atoi(d.Id()) if err != nil { @@ -137,7 +139,7 @@ func resourceRedisCloudAclUserUpdate(ctx context.Context, d *schema.ResourceData password := d.Get("password").(string) updateUserRequest.Password = &password - err = api.client.Users.Update(ctx, id, updateUserRequest) + err = api.Client.Users.Update(ctx, id, updateUserRequest) if err != nil { return diag.FromErr(err) } @@ -162,7 +164,7 @@ func resourceRedisCloudAclUserUpdate(ctx context.Context, d *schema.ResourceData } func resourceRedisCloudAclUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics id, err := strconv.Atoi(d.Id()) @@ -178,7 +180,7 @@ func resourceRedisCloudAclUserDelete(ctx context.Context, d *schema.ResourceData return diag.FromErr(err) } - err = api.client.Users.Delete(ctx, id) + err = api.Client.Users.Delete(ctx, id) if err != nil { return diag.FromErr(err) @@ -188,7 +190,7 @@ func resourceRedisCloudAclUserDelete(ctx context.Context, d *schema.ResourceData // Wait until it's really disappeared err = retry.RetryContext(ctx, 5*time.Minute, func() *retry.RetryError { - user, err := api.client.Users.Get(ctx, id) + user, err := api.Client.Users.Get(ctx, id) if err != nil { if _, ok := err.(*users.NotFound); ok { @@ -212,7 +214,7 @@ func resourceRedisCloudAclUserDelete(ctx context.Context, d *schema.ResourceData return diags } -func waitForAclUserToBeActive(ctx context.Context, id int, api *apiClient) error { +func waitForAclUserToBeActive(ctx context.Context, id int, api *utils.ApiClient) error { wait := &retry.StateChangeConf{ Delay: 5 * time.Second, Pending: []string{users.StatusPending}, @@ -222,7 +224,7 @@ func waitForAclUserToBeActive(ctx context.Context, id int, api *apiClient) error Refresh: func() (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for user %d to be active", id) - user, err := api.client.Users.Get(ctx, id) + user, err := api.Client.Users.Get(ctx, id) if err != nil { return nil, "", err } diff --git a/provider/datasource_rediscloud_active_active_database.go b/provider/active_active/datasource_rediscloud_active_active_database.go similarity index 93% rename from provider/datasource_rediscloud_active_active_database.go rename to provider/active_active/datasource_rediscloud_active_active_database.go index 695338ef..204ce864 100644 --- a/provider/datasource_rediscloud_active_active_database.go +++ b/provider/active_active/datasource_rediscloud_active_active_database.go @@ -1,4 +1,4 @@ -package provider +package active_active import ( "context" @@ -6,11 +6,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceRedisCloudActiveActiveDatabase() *schema.Resource { +func DataSourceRedisCloudActiveActiveDatabase() *schema.Resource { return &schema.Resource{ Description: "The Active Active Database data source allows access to the details of an existing AA database within your Redis Enterprise Cloud account.", ReadContext: dataSourceRedisCloudActiveActiveDatabaseRead, @@ -244,7 +245,7 @@ func dataSourceRedisCloudActiveActiveDatabase() *schema.Resource { func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId := d.Get("subscription_id").(int) @@ -267,7 +268,7 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema }) } - list := api.client.Database.ListActiveActive(ctx, subId) + list := api.Client.Database.ListActiveActive(ctx, subId) dbs, err := filterAADatabases(list, filters) if err != nil { @@ -283,7 +284,7 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema } // Some attributes are only returned when retrieving a single database - db, err := api.client.Database.GetActiveActive(ctx, subId, redis.IntValue(dbs[0].ID)) + db, err := api.Client.Database.GetActiveActive(ctx, subId, redis.IntValue(dbs[0].ID)) if err != nil { return diag.FromErr(list.Err()) } @@ -342,11 +343,11 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema var parsedLatestBackupStatuses []map[string]interface{} for _, regionDb := range db.CrdbDatabases { region := redis.StringValue(regionDb.Region) - latestBackupStatus, err := api.client.LatestBackup.GetActiveActive(ctx, subId, dbId, region) + latestBackupStatus, err := api.Client.LatestBackup.GetActiveActive(ctx, subId, dbId, region) if err != nil { // Forgive errors here, sometimes we just can't get a latest status } else { - parsedLatestBackupStatus, err := parseLatestBackupStatus(latestBackupStatus) + parsedLatestBackupStatus, err := utils.ParseLatestBackupStatus(latestBackupStatus) if err != nil { return diag.FromErr(err) } @@ -360,11 +361,11 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema } var parsedLatestImportStatus []map[string]interface{} - latestImportStatus, err := api.client.LatestImport.Get(ctx, subId, dbId) + latestImportStatus, err := api.Client.LatestImport.Get(ctx, subId, dbId) if err != nil { // Forgive errors here, sometimes we just can't get a latest status } else { - parsedLatestImportStatus, err = parseLatestImportStatus(latestImportStatus) + parsedLatestImportStatus, err = utils.ParseLatestImportStatus(latestImportStatus) if err != nil { return diag.FromErr(err) } @@ -373,7 +374,7 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema return diag.FromErr(err) } - if err := readTags(ctx, api, subId, dbId, d); err != nil { + if err := utils.ReadTags(ctx, api, subId, dbId, d); err != nil { return diag.FromErr(err) } @@ -388,8 +389,8 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema return diags } -func getCertificateData(ctx context.Context, api *apiClient, subId int, dbId int) (*databases.DatabaseCertificate, error) { - dbTlsCertificate, err := api.client.Database.GetCertificate(ctx, subId, dbId) +func getCertificateData(ctx context.Context, api *utils.ApiClient, subId int, dbId int) (*databases.DatabaseCertificate, error) { + dbTlsCertificate, err := api.Client.Database.GetCertificate(ctx, subId, dbId) if err != nil { return nil, err diff --git a/provider/datasource_rediscloud_active_active_private_service_connect.go b/provider/active_active/datasource_rediscloud_active_active_private_service_connect.go similarity index 86% rename from provider/datasource_rediscloud_active_active_private_service_connect.go rename to provider/active_active/datasource_rediscloud_active_active_private_service_connect.go index 371231b0..95d415c0 100644 --- a/provider/datasource_rediscloud_active_active_private_service_connect.go +++ b/provider/active_active/datasource_rediscloud_active_active_private_service_connect.go @@ -1,14 +1,15 @@ -package provider +package active_active import ( "context" "strconv" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceActiveActivePrivateServiceConnect() *schema.Resource { +func DataSourceActiveActivePrivateServiceConnect() *schema.Resource { return &schema.Resource{ Description: "The Active-Active Private Service Connect data source allows access to an available Private Service Connect Service within your Redis Enterprise Cloud Account.", ReadContext: dataSourceActiveActivePrivateServiceConnectRead, @@ -50,7 +51,7 @@ func dataSourceActiveActivePrivateServiceConnect() *schema.Resource { func dataSourceActiveActivePrivateServiceConnectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -58,12 +59,12 @@ func dataSourceActiveActivePrivateServiceConnectRead(ctx context.Context, d *sch } regionId := d.Get("region_id").(int) - pscService, err := api.client.PrivateServiceConnect.GetActiveActiveService(ctx, subId, regionId) + pscService, err := api.Client.PrivateServiceConnect.GetActiveActiveService(ctx, subId, regionId) if err != nil { return diag.FromErr(err) } - d.SetId(buildPrivateServiceConnectActiveActiveId(subId, regionId, *pscService.ID)) + d.SetId(utils.BuildPrivateServiceConnectActiveActiveId(subId, regionId, *pscService.ID)) if err := d.Set("private_service_connect_service_id", pscService.ID); err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_active_active_subscription.go b/provider/active_active/datasource_rediscloud_active_active_subscription.go similarity index 92% rename from provider/datasource_rediscloud_active_active_subscription.go rename to provider/active_active/datasource_rediscloud_active_active_subscription.go index 6fafbe91..e41336f3 100644 --- a/provider/datasource_rediscloud_active_active_subscription.go +++ b/provider/active_active/datasource_rediscloud_active_active_subscription.go @@ -1,15 +1,17 @@ -package provider +package active_active import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceRedisCloudActiveActiveSubscription() *schema.Resource { +func DataSourceRedisCloudActiveActiveSubscription() *schema.Resource { return &schema.Resource{ Description: "The Active Active Subscription data source allows access to the details of an existing AA subscription within your Redis Enterprise Cloud account.", ReadContext: dataSourceRedisCloudActiveActiveSubscriptionRead, @@ -146,9 +148,9 @@ func dataSourceRedisCloudActiveActiveSubscription() *schema.Resource { func dataSourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - subs, err := api.client.Subscription.List(ctx) + subs, err := api.Client.Subscription.List(ctx) if err != nil { return diag.FromErr(err) } @@ -166,7 +168,7 @@ func dataSourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sc }) } - subs = filterSubscriptions(subs, filters) + subs = utils.FilterSubscriptions(subs, filters) if len(subs) == 0 { return diag.Errorf("Your query returned no results. Please change your search criteria and try again.") @@ -214,19 +216,19 @@ func dataSourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sc subId := redis.IntValue(sub.ID) - m, err := api.client.Maintenance.Get(ctx, subId) + m, err := api.Client.Maintenance.Get(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { + if err := d.Set("maintenance_windows", utils.FlattenMaintenance(m)); err != nil { return diag.FromErr(err) } - pricingList, err := api.client.Pricing.List(ctx, subId) + pricingList, err := api.Client.Pricing.List(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { + if err := d.Set("pricing", utils.FlattenPricing(pricingList)); err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_active_active_subscription_regions.go b/provider/active_active/datasource_rediscloud_active_active_subscription_regions.go similarity index 93% rename from provider/datasource_rediscloud_active_active_subscription_regions.go rename to provider/active_active/datasource_rediscloud_active_active_subscription_regions.go index 820bb40c..9a470218 100644 --- a/provider/datasource_rediscloud_active_active_subscription_regions.go +++ b/provider/active_active/datasource_rediscloud_active_active_subscription_regions.go @@ -1,15 +1,17 @@ -package provider +package active_active import ( "context" "fmt" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceRedisCloudActiveActiveSubscriptionRegions() *schema.Resource { +func DataSourceRedisCloudActiveActiveSubscriptionRegions() *schema.Resource { return &schema.Resource{ Description: "Gets a list of regions in the specified Active-Active subscription.", ReadContext: dataSourceRedisCloudActiveActiveRegionsRead, @@ -79,9 +81,9 @@ func dataSourceRedisCloudActiveActiveSubscriptionRegions() *schema.Resource { func dataSourceRedisCloudActiveActiveRegionsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - subs, err := api.client.Subscription.List(ctx) + subs, err := api.Client.Subscription.List(ctx) if err != nil { return diag.FromErr(err) @@ -101,7 +103,7 @@ func dataSourceRedisCloudActiveActiveRegionsRead(ctx context.Context, d *schema. }) } - subs = filterSubscriptions(subs, filters) + subs = utils.FilterSubscriptions(subs, filters) if len(subs) == 0 { return diag.Errorf("Your query returned no results. Please change your search criteria and try again.") @@ -113,7 +115,7 @@ func dataSourceRedisCloudActiveActiveRegionsRead(ctx context.Context, d *schema. sub := subs[0] - regions, err := api.client.Subscription.ListActiveActiveRegions(ctx, *sub.ID) + regions, err := api.Client.Subscription.ListActiveActiveRegions(ctx, *sub.ID) if err != nil { return diag.FromErr(err) diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/active_active/resource_rediscloud_active_active_database.go similarity index 90% rename from provider/resource_rediscloud_active_active_database.go rename to provider/active_active/resource_rediscloud_active_active_database.go index bdac1505..b395a3ed 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/active_active/resource_rediscloud_active_active_database.go @@ -1,8 +1,7 @@ -package provider +package active_active import ( "context" - "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "log" "regexp" "strings" @@ -10,13 +9,14 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudActiveActiveDatabase() *schema.Resource { +func ResourceRedisCloudActiveActiveDatabase() *schema.Resource { return &schema.Resource{ Description: "Creates database resource within an active-active subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudActiveActiveDatabaseCreate, @@ -26,7 +26,7 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - subId, dbId, err := toDatabaseId(d.Id()) + subId, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { if err := d.Set("db_id", dbId); err != nil { return nil, err } - d.SetId(buildResourceId(subId, dbId)) + d.SetId(utils.BuildResourceId(subId, dbId)) return []*schema.ResourceData{d}, nil }, }, @@ -57,7 +57,7 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { } for _, key := range keys { - if err := remoteBackupIntervalSetCorrectly(key)(ctx, diff, i); err != nil { + if err := utils.RemoteBackupIntervalSetCorrectly(key)(ctx, diff, i); err != nil { return err } } @@ -268,7 +268,7 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { Description: "Defines the hour automatic backups are made - only applicable when interval is `every-12-hours` or `every-24-hours`", Type: schema.TypeString, Optional: true, - ValidateDiagFunc: isTime(), + ValidateDiagFunc: utils.IsTime(), }, "storage_type": { Description: "Defines the provider of the storage location", @@ -332,22 +332,22 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { Type: schema.TypeString, }, Optional: true, - ValidateDiagFunc: validateTagsfunc, + ValidateDiagFunc: utils.ValidateTagsfunc, }, }, } } func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId := d.Get("subscription_id").(int) - subscriptionMutex.Lock(subId) + utils.SubscriptionMutex.Lock(subId) name := d.Get("name").(string) supportOSSClusterAPI := d.Get("support_oss_cluster_api").(bool) useExternalEndpointForOSSClusterAPI := d.Get("external_endpoint_for_oss_cluster_api").(bool) - globalSourceIp := setToStringSlice(d.Get("global_source_ips").(*schema.Set)) + globalSourceIp := utils.SetToStringSlice(d.Get("global_source_ips").(*schema.Set)) createAlerts := make([]*databases.Alert, 0) alerts := d.Get("global_alert").(*schema.Set) @@ -366,7 +366,7 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema } createModules := make([]*databases.Module, 0) - planModules := interfaceToStringSlice(d.Get("global_modules").([]interface{})) + planModules := utils.InterfaceToStringSlice(d.Get("global_modules").([]interface{})) for _, module := range planModules { createModule := &databases.Module{ Name: module, @@ -375,9 +375,9 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema } // Get regions from /subscriptions/{subscriptionId}/regions, this will use the Regions API - regions, err := api.client.Regions.List(ctx, subId) + regions, err := api.Client.Regions.List(ctx, subId) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } @@ -436,44 +436,44 @@ func resourceRedisCloudActiveActiveDatabaseCreate(ctx context.Context, d *schema }) // Confirm Subscription Active status before creating database - err = waitForSubscriptionToBeActive(ctx, subId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subId, api) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - dbId, err := api.client.Database.ActiveActiveCreate(ctx, subId, createDatabase) + dbId, err := api.Client.Database.ActiveActiveCreate(ctx, subId, createDatabase) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - d.SetId(buildResourceId(subId, dbId)) + d.SetId(utils.BuildResourceId(subId, dbId)) // Confirm Database Active status - err = waitForDatabaseToBeActive(ctx, subId, dbId, api) + err = utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } // Some attributes on a database are not accessible by the subscription creation API. // Run the subscription update function to apply any additional changes to the databases, such as password and so on. - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return resourceRedisCloudActiveActiveDatabaseUpdate(ctx, d, meta) } func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics - subId, dbId, err := toDatabaseId(d.Id()) + subId, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } @@ -483,7 +483,7 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R subId = d.Get("subscription_id").(int) } - db, err := api.client.Database.GetActiveActive(ctx, subId, dbId) + db, err := api.Client.Database.GetActiveActive(ctx, subId, dbId) if err != nil { if _, ok := err.(*databases.NotFound); ok { d.SetId("") @@ -574,10 +574,10 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R stateOverrideAlerts := getStateAlertsFromDbRegion(getStateOverrideRegion(d, region)) if len(stateOverrideAlerts) > 0 { - regionDbConfig["override_global_alert"] = flattenAlerts(regionDb.Alerts) + regionDbConfig["override_global_alert"] = utils.FlattenAlerts(regionDb.Alerts) } - regionDbConfig["remote_backup"] = flattenBackupPlan(regionDb.Backup, getStateRemoteBackup(d, region), "") + regionDbConfig["remote_backup"] = utils.FlattenBackupPlan(regionDb.Backup, getStateRemoteBackup(d, region), "") regionDbConfigs = append(regionDbConfigs, regionDbConfig) } @@ -604,11 +604,11 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R } tlsAuthEnabled := *db.CrdbDatabases[0].Security.TLSClientAuthentication - if err := applyCertificateHints(tlsAuthEnabled, d); err != nil { + if err := utils.ApplyCertificateHints(tlsAuthEnabled, d); err != nil { return diag.FromErr(err) } - if err := readTags(ctx, api, subId, dbId, d); err != nil { + if err := utils.ReadTags(ctx, api, subId, dbId, d); err != nil { return diag.FromErr(err) } @@ -617,24 +617,24 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R func resourceRedisCloudActiveActiveDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId := d.Get("subscription_id").(int) - _, dbId, err := toDatabaseId(d.Id()) + _, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { return diag.FromErr(err) } - dbErr := api.client.Database.Delete(ctx, subId, dbId) + dbErr := api.Client.Database.Delete(ctx, subId, dbId) if dbErr != nil { diag.FromErr(dbErr) } @@ -647,16 +647,16 @@ func resourceRedisCloudActiveActiveDatabaseDelete(ctx context.Context, d *schema } func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - _, dbId, err := toDatabaseId(d.Id()) + _, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } subId := d.Get("subscription_id").(int) - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) // Forcibly initialise, so we have a non-nil, zero-length slice // A pointer to a nil-slice is interpreted as empty and omitted from the json payload @@ -671,7 +671,7 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema }) } - globalSourceIps := setToStringSlice(d.Get("global_source_ips").(*schema.Set)) + globalSourceIps := utils.SetToStringSlice(d.Get("global_source_ips").(*schema.Set)) // Make a list of region-specific configurations var regions []*databases.LocalRegionProperties @@ -716,7 +716,7 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema } } - regionProps.RemoteBackup = buildBackupPlan(dbRegion["remote_backup"], nil) + regionProps.RemoteBackup = utils.BuildBackupPlan(dbRegion["remote_backup"], nil) regions = append(regions, regionProps) } @@ -755,7 +755,7 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema //The cert validation is done by the API (HTTP 400 is returned if it's invalid). clientSSLCertificate := d.Get("client_ssl_certificate").(string) - clientTLSCertificates := interfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) + clientTLSCertificates := utils.InterfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) enableTLS := d.Get("enable_tls").(bool) if enableTLS { update.EnableTls = redis.Bool(enableTLS) @@ -780,21 +780,21 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema update.UseExternalEndpointForOSSClusterAPI = redis.Bool(d.Get("external_endpoint_for_oss_cluster_api").(bool)) - err = api.client.Database.ActiveActiveUpdate(ctx, subId, dbId, update) + err = api.Client.Database.ActiveActiveUpdate(ctx, subId, dbId, update) if err != nil { return diag.FromErr(err) } - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } // The Tags API is synchronous so we shouldn't have to wait for anything - if err := writeTags(ctx, api, subId, dbId, d); err != nil { + if err := utils.WriteTags(ctx, api, subId, dbId, d); err != nil { return diag.FromErr(err) } @@ -841,7 +841,7 @@ func getStateAlertsFromDbRegion(dbRegion map[string]interface{}) []*databases.Al return overrideAlerts } -func waitForDatabaseToBeDeleted(ctx context.Context, subId int, dbId int, api *apiClient) error { +func waitForDatabaseToBeDeleted(ctx context.Context, subId int, dbId int, api *utils.ApiClient) error { wait := &retry.StateChangeConf{ Delay: 30 * time.Second, Pending: []string{"pending"}, @@ -852,7 +852,7 @@ func waitForDatabaseToBeDeleted(ctx context.Context, subId int, dbId int, api *a Refresh: func() (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for database %d to be deleted", dbId) - _, err = api.client.Database.Get(ctx, subId, dbId) + _, err = api.Client.Database.Get(ctx, subId, dbId) if err != nil { if _, ok := err.(*databases.NotFound); ok { return "deleted", "deleted", nil diff --git a/provider/resource_rediscloud_active_active_subscription.go b/provider/active_active/resource_rediscloud_active_active_subscription.go similarity index 90% rename from provider/resource_rediscloud_active_active_subscription.go rename to provider/active_active/resource_rediscloud_active_active_subscription.go index f8cef29d..9224d6e5 100644 --- a/provider/resource_rediscloud_active_active_subscription.go +++ b/provider/active_active/resource_rediscloud_active_active_subscription.go @@ -1,4 +1,4 @@ -package provider +package active_active import ( "bytes" @@ -11,12 +11,14 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/maintenance" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/pro" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudActiveActiveSubscription() *schema.Resource { +func ResourceRedisCloudActiveActiveSubscription() *schema.Resource { return &schema.Resource{ Description: "Creates an Active-Active Subscription within your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudActiveActiveSubscriptionCreate, @@ -328,7 +330,7 @@ func resourceRedisCloudActiveActiveSubscription() *schema.Resource { } func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) plan := d.Get("creation_plan").([]interface{}) @@ -345,7 +347,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc name := d.Get("name").(string) paymentMethod := d.Get("payment_method").(string) - paymentMethodID, err := readPaymentMethodID(d) + paymentMethodID, err := utils.ReadPaymentMethodID(d) if err != nil { return diag.FromErr(err) } @@ -368,7 +370,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc createSubscriptionRequest.RedisVersion = redis.String(redisVersion) } - subId, err := api.client.Subscription.Create(ctx, createSubscriptionRequest) + subId, err := api.Client.Subscription.Create(ctx, createSubscriptionRequest) if err != nil { return diag.FromErr(err) } @@ -377,7 +379,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc // If in a CMK flow, verify the pending state if cmkEnabled { - err = waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) + err = utils.WaitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) if err != nil { return diag.FromErr(err) } @@ -385,29 +387,29 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc } // Confirm Subscription Active status - err = waitForSubscriptionToBeActive(ctx, subId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subId, api) if err != nil { return diag.FromErr(err) } // There is a timing issue where the subscription is marked as active before the creation-plan databases are listed. - // This additional wait ensures that the databases will be listed before calling api.client.Database.List() + // This additional wait ensures that the databases will be listed before calling api.Client.Database.List() time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } // Locate Databases to confirm Active status - dbList := api.client.Database.List(ctx, subId) + dbList := api.Client.Database.List(ctx, subId) for dbList.Next() { dbId := *dbList.Value().ID - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { return diag.FromErr(err) } // Delete each creation-plan database - dbErr := api.client.Database.Delete(ctx, subId, dbId) + dbErr := api.Client.Database.Delete(ctx, subId, dbId) if dbErr != nil { diag.FromErr(dbErr) } @@ -417,7 +419,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc } // Check that the subscription is in an active state before calling the read function - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } @@ -430,7 +432,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc windows = append(windows, &maintenance.Window{ StartHour: redis.Int(wMap["start_hour"].(int)), DurationInHours: redis.Int(wMap["duration_in_hours"].(int)), - Days: interfaceToStringSlice(wMap["days"].([]interface{})), + Days: utils.InterfaceToStringSlice(wMap["days"].([]interface{})), }) } @@ -438,7 +440,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc Mode: redis.String(mMap["mode"].(string)), Windows: windows, } - err = api.client.Maintenance.Update(ctx, subId, updateMaintenanceRequest) + err = api.Client.Maintenance.Update(ctx, subId, updateMaintenanceRequest) if err != nil { return diag.FromErr(err) } @@ -448,7 +450,7 @@ func resourceRedisCloudActiveActiveSubscriptionCreate(ctx context.Context, d *sc } func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics @@ -457,7 +459,7 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche return diag.FromErr(err) } - subscription, err := api.client.Subscription.Get(ctx, subId) + subscription, err := api.Client.Subscription.Get(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -492,19 +494,19 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche cmkEnabled := d.Get("customer_managed_key_enabled").(bool) if !cmkEnabled { - m, err := api.client.Maintenance.Get(ctx, subId) + m, err := api.Client.Maintenance.Get(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { + if err := d.Set("maintenance_windows", utils.FlattenMaintenance(m)); err != nil { return diag.FromErr(err) } - pricingList, err := api.client.Pricing.List(ctx, subId) + pricingList, err := api.Client.Pricing.List(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { + if err := d.Set("pricing", utils.FlattenPricing(pricingList)); err != nil { return diag.FromErr(err) } } @@ -519,17 +521,17 @@ func resourceRedisCloudActiveActiveSubscriptionRead(ctx context.Context, d *sche } func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - subscription, err := api.client.Subscription.Get(ctx, subId) + subscription, err := api.Client.Subscription.Get(ctx, subId) if err != nil { return diag.FromErr(err) } @@ -554,7 +556,7 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc } if d.HasChange("payment_method_id") { - paymentMethodID, err := readPaymentMethodID(d) + paymentMethodID, err := utils.ReadPaymentMethodID(d) if err != nil { return diag.FromErr(err) } @@ -562,13 +564,13 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc updateSubscriptionRequest.PaymentMethodID = paymentMethodID } - err = api.client.Subscription.Update(ctx, subId, updateSubscriptionRequest) + err = api.Client.Subscription.Update(ctx, subId, updateSubscriptionRequest) if err != nil { return diag.FromErr(err) } } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } @@ -583,7 +585,7 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc windows = append(windows, &maintenance.Window{ StartHour: redis.Int(wMap["start_hour"].(int)), DurationInHours: redis.Int(wMap["duration_in_hours"].(int)), - Days: interfaceToStringSlice(wMap["days"].([]interface{})), + Days: utils.InterfaceToStringSlice(wMap["days"].([]interface{})), }) } @@ -596,7 +598,7 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc Mode: redis.String("automatic"), } } - err = api.client.Maintenance.Update(ctx, subId, updateMaintenanceRequest) + err = api.Client.Maintenance.Update(ctx, subId, updateMaintenanceRequest) if err != nil { return diag.FromErr(err) } @@ -605,7 +607,7 @@ func resourceRedisCloudActiveActiveSubscriptionUpdate(ctx context.Context, d *sc return resourceRedisCloudActiveActiveSubscriptionRead(ctx, d, meta) } -func resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *apiClient, subId int) diag.Diagnostics { +func resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *utils.ApiClient, subId int) diag.Diagnostics { cmkResourcesRaw, exists := d.GetOk("customer_managed_key") if !exists { @@ -625,11 +627,11 @@ func resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx context.Context, d CustomerManagedKeys: &customerManagedKeys, } - if err := api.client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { + if err := api.Client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } @@ -638,7 +640,7 @@ func resourceRedisCloudActiveActiveSubscriptionUpdateCmk(ctx context.Context, d func resourceRedisCloudActiveActiveSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics @@ -647,36 +649,36 @@ func resourceRedisCloudActiveActiveSubscriptionDelete(ctx context.Context, d *sc return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - subscription, err := api.client.Subscription.Get(ctx, subId) + subscription, err := api.Client.Subscription.Get(ctx, subId) if err != nil { return diag.FromErr(err) } if *subscription.Status != subscriptions.SubscriptionStatusEncryptionKeyPending { // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. // This additional wait ensures that the databases are deleted before the subscription is deleted. time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } // Delete subscription once all databases are deleted } - err = api.client.Subscription.Delete(ctx, subId) + err = api.Client.Subscription.Delete(ctx, subId) if err != nil { return diag.FromErr(err) } d.SetId("") - err = waitForSubscriptionToBeDeleted(ctx, subId, api) + err = utils.WaitForSubscriptionToBeDeleted(ctx, subId, api) if err != nil { return diag.FromErr(err) } @@ -696,7 +698,7 @@ func newCreateSubscription(name string, paymentMethodID *int, paymentMethod stri } if cmkEnabled { - req.PersistentStorageEncryptionType = redis.String(CMK_ENABLED_STRING) + req.PersistentStorageEncryptionType = redis.String(pro.CMK_ENABLED_STRING) } return req @@ -764,7 +766,7 @@ func buildSubscriptionCreatePlanAADatabases(planMap map[string]interface{}) []*s } createModules := make([]*subscriptions.CreateModules, 0) - planModules := interfaceToStringSlice(planMap["modules"].([]interface{})) + planModules := utils.InterfaceToStringSlice(planMap["modules"].([]interface{})) for _, module := range planModules { createModule := &subscriptions.CreateModules{ Name: module, diff --git a/provider/resource_rediscloud_active_active_subscription_regions.go b/provider/active_active/resource_rediscloud_active_active_subscription_regions.go similarity index 91% rename from provider/resource_rediscloud_active_active_subscription_regions.go rename to provider/active_active/resource_rediscloud_active_active_subscription_regions.go index b66a543f..028549f0 100644 --- a/provider/resource_rediscloud_active_active_subscription_regions.go +++ b/provider/active_active/resource_rediscloud_active_active_subscription_regions.go @@ -1,4 +1,4 @@ -package provider +package active_active import ( "context" @@ -11,12 +11,13 @@ import ( "github.com/RedisLabs/rediscloud-go-api/service/databases" "github.com/RedisLabs/rediscloud-go-api/service/regions" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudActiveActiveSubscriptionRegions() *schema.Resource { +func ResourceRedisCloudActiveActiveSubscriptionRegions() *schema.Resource { return &schema.Resource{ Description: "Creates an Active Active Region and within your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudActiveActiveRegionCreate, @@ -137,7 +138,7 @@ func resourceRedisCloudActiveActiveRegionCreate(ctx context.Context, d *schema.R } func resourceRedisCloudActiveActiveRegionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) if err != nil { @@ -147,7 +148,7 @@ func resourceRedisCloudActiveActiveRegionUpdate(ctx context.Context, d *schema.R // Get existing regions, so we can do a manual diff // Query API for existing Regions for a given Subscription - existingRegions, err := api.client.Regions.List(ctx, subId) + existingRegions, err := api.Client.Regions.List(ctx, subId) if err != nil { return diag.FromErr(err) } @@ -241,10 +242,10 @@ func resourceRedisCloudActiveActiveRegionUpdate(ctx context.Context, d *schema.R } func resourceRedisCloudActiveActiveRegionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) - existingRegions, err := api.client.Regions.List(ctx, subId) + existingRegions, err := api.Client.Regions.List(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -310,14 +311,14 @@ func resourceRedisCloudActiveActiveRegionDelete(ctx context.Context, d *schema.R return resourceRedisCloudActiveActiveRegionRead(ctx, d, meta) } -func regionsCreate(ctx context.Context, subId int, regionsToCreate []*RequestedRegion, api *apiClient) error { +func regionsCreate(ctx context.Context, subId int, regionsToCreate []*RequestedRegion, api *utils.ApiClient) error { // If no new regions were defined return if len(regionsToCreate) == 0 { return nil } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) // Call GO API createRegion for all non-existing regions for _, currentRegion := range regionsToCreate { @@ -342,21 +343,21 @@ func regionsCreate(ctx context.Context, subId int, regionsToCreate []*RequestedR Databases: createDatabases, } - _, err := api.client.Regions.Create(ctx, subId, createRegion) + _, err := api.Client.Regions.Create(ctx, subId, createRegion) if err != nil { return err } // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return err } // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. // This additional wait ensures that the databases are deleted before the subscription is deleted. time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return err } } @@ -364,7 +365,7 @@ func regionsCreate(ctx context.Context, subId int, regionsToCreate []*RequestedR return nil } -func regionsUpdateDatabases(ctx context.Context, subId int, api *apiClient, regionsToUpdateDatabases []*RequestedRegion, existingRegionMap map[string]*regions.Region) error { +func regionsUpdateDatabases(ctx context.Context, subId int, api *utils.ApiClient, regionsToUpdateDatabases []*RequestedRegion, existingRegionMap map[string]*regions.Region) error { databaseUpdates := make(map[int][]*databases.LocalRegionProperties) for _, desiredRegion := range regionsToUpdateDatabases { // Collect existing databases to a map @@ -390,27 +391,27 @@ func regionsUpdateDatabases(ctx context.Context, subId int, api *apiClient, regi } if len(databaseUpdates) > 0 { - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) for dbId, localRegionProperties := range databaseUpdates { dbUpdate := databases.UpdateActiveActiveDatabase{ Regions: localRegionProperties, } - err := api.client.Database.ActiveActiveUpdate(ctx, subId, dbId, dbUpdate) + err := api.Client.Database.ActiveActiveUpdate(ctx, subId, dbId, dbUpdate) if err != nil { return err } // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return err } // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. // This additional wait ensures that the databases are deleted before the subscription is deleted. time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return err } } @@ -419,9 +420,9 @@ func regionsUpdateDatabases(ctx context.Context, subId int, api *apiClient, regi return nil } -func regionsDelete(ctx context.Context, subId int, regionsToDelete []*string, api *apiClient) error { - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) +func regionsDelete(ctx context.Context, subId int, regionsToDelete []*string, api *utils.ApiClient) error { + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) deleteRegions := regions.DeleteRegions{} for _, region := range regionsToDelete { @@ -431,20 +432,20 @@ func regionsDelete(ctx context.Context, subId int, regionsToDelete []*string, ap deleteRegions.Regions = append(deleteRegions.Regions, &deleteRegion) } - err := api.client.Regions.DeleteWithQuery(ctx, subId, deleteRegions) + err := api.Client.Regions.DeleteWithQuery(ctx, subId, deleteRegions) if err != nil { return err } // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return err } // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. // This additional wait ensures that the databases are deleted before the subscription is deleted. time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return err } diff --git a/provider/datasource_rediscloud_acl_rule_test.go b/provider/datasource_rediscloud_acl_rule_test.go index 418e6bdc..f0c8459c 100644 --- a/provider/datasource_rediscloud_acl_rule_test.go +++ b/provider/datasource_rediscloud_acl_rule_test.go @@ -2,9 +2,10 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccDataSourceRedisCloudAclRule_ForDefaultRule(t *testing.T) { diff --git a/provider/datasource_rediscloud_essentials_plan_test.go b/provider/datasource_rediscloud_essentials_plan_test.go index ee1dae1a..c9ac2d07 100644 --- a/provider/datasource_rediscloud_essentials_plan_test.go +++ b/provider/datasource_rediscloud_essentials_plan_test.go @@ -1,9 +1,10 @@ package provider import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccDataSourceRedisCloudEssentialsPlan_basic(t *testing.T) { diff --git a/provider/datasource_rediscloud_payment_method_test.go b/provider/datasource_rediscloud_payment_method_test.go index bc0f572e..cf94fb36 100644 --- a/provider/datasource_rediscloud_payment_method_test.go +++ b/provider/datasource_rediscloud_payment_method_test.go @@ -1,9 +1,10 @@ package provider import ( - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccDataSourceRedisCloudPaymentMethod_basic(t *testing.T) { diff --git a/provider/datasource_rediscloud_subscription_peerings_test.go b/provider/datasource_rediscloud_subscription_peerings_test.go index 30ef98d2..4976e1b7 100644 --- a/provider/datasource_rediscloud_subscription_peerings_test.go +++ b/provider/datasource_rediscloud_subscription_peerings_test.go @@ -37,7 +37,11 @@ func TestAccDataSourceRedisCloudSubscriptionPeerings_basic(t *testing.T) { const dataSourceName = "data.rediscloud_subscription_peerings.example" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccAwsPeeringPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccAwsPeeringPreCheck(t) + testAccAwsPreExistingCloudAccountPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckProSubscriptionDestroy, Steps: []resource.TestStep{ diff --git a/provider/datasource_rediscloud_essentials_database.go b/provider/essentials/datasource_rediscloud_essentials_database.go similarity index 92% rename from provider/datasource_rediscloud_essentials_database.go rename to provider/essentials/datasource_rediscloud_essentials_database.go index 97769de3..a3e9f2fe 100644 --- a/provider/datasource_rediscloud_essentials_database.go +++ b/provider/essentials/datasource_rediscloud_essentials_database.go @@ -1,15 +1,17 @@ -package provider +package essentials import ( "context" + "github.com/RedisLabs/rediscloud-go-api/redis" fixedDatabases "github.com/RedisLabs/rediscloud-go-api/service/fixed/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func dataSourceRedisCloudEssentialsDatabase() *schema.Resource { +func DataSourceRedisCloudEssentialsDatabase() *schema.Resource { return &schema.Resource{ Description: "The Essentials Database data source allows access to the details of an existing database within your Redis Enterprise Cloud account.", ReadContext: dataSourceRedisCloudEssentialsDatabaseRead, @@ -367,7 +369,7 @@ func dataSourceRedisCloudEssentialsDatabase() *schema.Resource { func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId := d.Get("subscription_id").(int) @@ -385,7 +387,7 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R }) } - list := api.client.FixedDatabases.List(ctx, subId) + list := api.Client.FixedDatabases.List(ctx, subId) dbs, err := filterFixedDatabases(list, filters) if err != nil { return diag.FromErr(list.Err()) @@ -400,14 +402,14 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R } // Some attributes are only returned when retrieving a single database - db, err := api.client.FixedDatabases.Get(ctx, subId, redis.IntValue(dbs[0].DatabaseId)) + db, err := api.Client.FixedDatabases.Get(ctx, subId, redis.IntValue(dbs[0].DatabaseId)) if err != nil { return diag.FromErr(list.Err()) } databaseId := redis.IntValue(db.DatabaseId) - d.SetId(buildResourceId(subId, databaseId)) + d.SetId(utils.BuildResourceId(subId, databaseId)) if err := d.Set("db_id", redis.IntValue(db.DatabaseId)); err != nil { return diag.FromErr(err) @@ -482,19 +484,19 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R if err := d.Set("enable_default_user", redis.Bool(*db.Security.EnableDefaultUser)); err != nil { return diag.FromErr(err) } - if err := d.Set("alert", flattenAlerts(*db.Alerts)); err != nil { + if err := d.Set("alert", utils.FlattenAlerts(*db.Alerts)); err != nil { return diag.FromErr(err) } - if err := d.Set("modules", flattenModules(*db.Modules)); err != nil { + if err := d.Set("modules", utils.FlattenModules(*db.Modules)); err != nil { return diag.FromErr(err) } var parsedLatestBackupStatus []map[string]interface{} - latestBackupStatus, err := api.client.LatestBackup.GetFixed(ctx, subId, databaseId) + latestBackupStatus, err := api.Client.LatestBackup.GetFixed(ctx, subId, databaseId) if err != nil { // Forgive errors here, sometimes we just can't get a latest status } else { - parsedLatestBackupStatus, err = parseLatestBackupStatus(latestBackupStatus) + parsedLatestBackupStatus, err = utils.ParseLatestBackupStatus(latestBackupStatus) if err != nil { return diag.FromErr(err) } @@ -504,11 +506,11 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R } var parsedLatestImportStatus []map[string]interface{} - latestImportStatus, err := api.client.LatestImport.GetFixed(ctx, subId, databaseId) + latestImportStatus, err := api.Client.LatestImport.GetFixed(ctx, subId, databaseId) if err != nil { // Forgive errors here, sometimes we just can't get a latest status } else { - parsedLatestImportStatus, err = parseLatestImportStatus(latestImportStatus) + parsedLatestImportStatus, err = utils.ParseLatestImportStatus(latestImportStatus) if err != nil { return diag.FromErr(err) } @@ -539,7 +541,7 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R if err := d.Set("enable_database_clustering", redis.BoolValue(db.Clustering.Enabled)); err != nil { return diag.FromErr(err) } - if err := d.Set("regex_rules", flattenRegexRules(db.Clustering.RegexRules)); err != nil { + if err := d.Set("regex_rules", utils.FlattenRegexRules(db.Clustering.RegexRules)); err != nil { return diag.FromErr(err) } } @@ -560,26 +562,3 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R return diags } - -func filterFixedDatabases(list *fixedDatabases.ListFixedDatabase, filters []func(db *fixedDatabases.FixedDatabase) bool) ([]*fixedDatabases.FixedDatabase, error) { - var filtered []*fixedDatabases.FixedDatabase - for list.Next() { - if filterFixedDatabase(list.Value(), filters) { - filtered = append(filtered, list.Value()) - } - } - if list.Err() != nil { - return nil, list.Err() - } - - return filtered, nil -} - -func filterFixedDatabase(db *fixedDatabases.FixedDatabase, filters []func(db *fixedDatabases.FixedDatabase) bool) bool { - for _, filter := range filters { - if !filter(db) { - return false - } - } - return true -} diff --git a/provider/datasource_rediscloud_essentials_plan.go b/provider/essentials/datasource_rediscloud_essentials_plan.go similarity index 95% rename from provider/datasource_rediscloud_essentials_plan.go rename to provider/essentials/datasource_rediscloud_essentials_plan.go index 8c868315..796b27fb 100644 --- a/provider/datasource_rediscloud_essentials_plan.go +++ b/provider/essentials/datasource_rediscloud_essentials_plan.go @@ -1,16 +1,18 @@ -package provider +package essentials import ( "context" + "strconv" + "strings" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/fixed/plans" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" - "strings" ) -func dataSourceRedisCloudEssentialsPlan() *schema.Resource { +func DataSourceRedisCloudEssentialsPlan() *schema.Resource { return &schema.Resource{ Description: "An Essentials subscription plan", ReadContext: dataSourceRedisCloudEssentialsPlanRead, @@ -150,7 +152,7 @@ func dataSourceRedisCloudEssentialsPlan() *schema.Resource { func dataSourceRedisCloudEssentialsPlanRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) list, err := getResourceList(ctx, d, api) @@ -297,16 +299,16 @@ func dataSourceRedisCloudEssentialsPlanRead(ctx context.Context, d *schema.Resou return diags } -func getResourceList(ctx context.Context, d *schema.ResourceData, api *apiClient) ([]*plans.GetPlanResponse, error) { +func getResourceList(ctx context.Context, d *schema.ResourceData, api *utils.ApiClient) ([]*plans.GetPlanResponse, error) { var list []*plans.GetPlanResponse var err error if id, ok := d.GetOk("subscription_id"); ok { - list, err = api.client.FixedPlanSubscriptions.List(ctx, id.(int)) + list, err = api.Client.FixedPlanSubscriptions.List(ctx, id.(int)) } else if provider, ok := d.GetOk("cloud_provider"); ok { - list, err = api.client.FixedPlans.ListWithProvider(ctx, strings.ToUpper(provider.(string))) + list, err = api.Client.FixedPlans.ListWithProvider(ctx, strings.ToUpper(provider.(string))) } else { - list, err = api.client.FixedPlans.List(ctx) + list, err = api.Client.FixedPlans.List(ctx) } return list, err diff --git a/provider/datasource_rediscloud_essentials_subscription.go b/provider/essentials/datasource_rediscloud_essentials_subscription.go similarity index 93% rename from provider/datasource_rediscloud_essentials_subscription.go rename to provider/essentials/datasource_rediscloud_essentials_subscription.go index e6accacd..4ce18121 100644 --- a/provider/datasource_rediscloud_essentials_subscription.go +++ b/provider/essentials/datasource_rediscloud_essentials_subscription.go @@ -1,4 +1,4 @@ -package provider +package essentials import ( "context" @@ -6,11 +6,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" fs "github.com/RedisLabs/rediscloud-go-api/service/fixed/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceRedisCloudEssentialsSubscription() *schema.Resource { +func DataSourceRedisCloudEssentialsSubscription() *schema.Resource { return &schema.Resource{ Description: "Watches an Essentials Subscription within your Redis Enterprise Cloud Account.", ReadContext: dataSourceRedisCloudEssentialsSubscriptionRead, @@ -53,9 +54,9 @@ func dataSourceRedisCloudEssentialsSubscription() *schema.Resource { func dataSourceRedisCloudEssentialsSubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - subs, err := api.client.FixedSubscriptions.List(ctx) + subs, err := api.Client.FixedSubscriptions.List(ctx) if err != nil { return diag.FromErr(err) } diff --git a/provider/resource_rediscloud_essentials_database.go b/provider/essentials/resource_rediscloud_essentials_database.go similarity index 90% rename from provider/resource_rediscloud_essentials_database.go rename to provider/essentials/resource_rediscloud_essentials_database.go index 483aee16..d21ab596 100644 --- a/provider/resource_rediscloud_essentials_database.go +++ b/provider/essentials/resource_rediscloud_essentials_database.go @@ -1,4 +1,4 @@ -package provider +package essentials import ( "context" @@ -6,17 +6,17 @@ import ( "time" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/databases" fixedDatabases "github.com/RedisLabs/rediscloud-go-api/service/fixed/databases" "github.com/RedisLabs/rediscloud-go-api/service/tags" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - - "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudEssentialsDatabase() *schema.Resource { +func ResourceRedisCloudEssentialsDatabase() *schema.Resource { return &schema.Resource{ Description: "Creates database resource within an essentials subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudEssentialsDatabaseCreate, @@ -26,7 +26,7 @@ func resourceRedisCloudEssentialsDatabase() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - subId, dbId, err := toDatabaseId(d.Id()) + subId, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func resourceRedisCloudEssentialsDatabase() *schema.Resource { if err := d.Set("db_id", dbId); err != nil { return nil, err } - d.SetId(buildResourceId(subId, dbId)) + d.SetId(utils.BuildResourceId(subId, dbId)) return []*schema.ResourceData{d}, nil }, }, @@ -290,18 +290,18 @@ func resourceRedisCloudEssentialsDatabase() *schema.Resource { Type: schema.TypeString, }, Optional: true, - ValidateDiagFunc: validateTagsfunc, + ValidateDiagFunc: utils.ValidateTagsfunc, }, }, } } func resourceRedisCloudEssentialsDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId := d.Get("subscription_id").(int) - subscriptionMutex.Lock(subId) + utils.SubscriptionMutex.Lock(subId) createDatabaseRequest := fixedDatabases.CreateFixedDatabase{ Name: redis.String(d.Get("name").(string)), @@ -321,7 +321,7 @@ func resourceRedisCloudEssentialsDatabaseCreate(ctx context.Context, d *schema.R createDatabaseRequest.RespVersion = redis.String(respVersion) } - sourceIps := interfaceToStringSlice(d.Get("source_ips").([]interface{})) + sourceIps := utils.InterfaceToStringSlice(d.Get("source_ips").([]interface{})) if len(sourceIps) == 0 { createDatabaseRequest.SourceIPs = []*string{redis.String("0.0.0.0/0")} } else { @@ -348,7 +348,7 @@ func resourceRedisCloudEssentialsDatabaseCreate(ctx context.Context, d *schema.R createDatabaseRequest.Replica = createReplica } - tlsCertificates := interfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) + tlsCertificates := utils.InterfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) if len(tlsCertificates) > 0 { createCertificates := make([]*fixedDatabases.DatabaseCertificate, 0) for _, cert := range tlsCertificates { @@ -401,38 +401,38 @@ func resourceRedisCloudEssentialsDatabaseCreate(ctx context.Context, d *schema.R createDatabaseRequest.SupportOSSClusterAPI = redis.Bool(d.Get("support_oss_cluster_api").(bool)) createDatabaseRequest.UseExternalEndpointForOSSClusterAPI = redis.Bool(d.Get("external_endpoint_for_oss_cluster_api").(bool)) createDatabaseRequest.EnableDatabaseClustering = redis.Bool(d.Get("enable_database_clustering").(bool)) - createDatabaseRequest.RegexRules = interfaceToStringSlice(d.Get("regex_rules").([]interface{})) + createDatabaseRequest.RegexRules = utils.InterfaceToStringSlice(d.Get("regex_rules").([]interface{})) createDatabaseRequest.EnableTls = redis.Bool(d.Get("enable_tls").(bool)) } - databaseId, err := api.client.FixedDatabases.Create(ctx, subId, createDatabaseRequest) + databaseId, err := api.Client.FixedDatabases.Create(ctx, subId, createDatabaseRequest) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - d.SetId(buildResourceId(subId, databaseId)) + d.SetId(utils.BuildResourceId(subId, databaseId)) // Confirm Subscription Active status err = waitForEssentialsDatabaseToBeActive(ctx, subId, databaseId, api) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } // Some attributes on a database are not accessible by the subscription creation API. // Run the subscription update function to apply any additional changes to the databases (enableDefaultUser) // Others are omitted here _because_ the update will take care of them, such as tags - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return resourceRedisCloudEssentialsDatabaseUpdate(ctx, d, meta) } func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics - subId, databaseId, err := toDatabaseId(d.Id()) + subId, databaseId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } @@ -442,7 +442,7 @@ func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.Res subId = d.Get("subscription_id").(int) } - db, err := api.client.FixedDatabases.Get(ctx, subId, databaseId) + db, err := api.Client.FixedDatabases.Get(ctx, subId, databaseId) if err != nil { if _, ok := err.(*fixedDatabases.NotFound); ok { d.SetId("") @@ -451,7 +451,7 @@ func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.Res return diag.FromErr(err) } - d.SetId(buildResourceId(subId, databaseId)) + d.SetId(utils.BuildResourceId(subId, databaseId)) if err := d.Set("db_id", redis.IntValue(db.DatabaseId)); err != nil { return diag.FromErr(err) @@ -532,10 +532,10 @@ func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.Res if err := d.Set("enable_default_user", redis.Bool(*db.Security.EnableDefaultUser)); err != nil { return diag.FromErr(err) } - if err := d.Set("alert", flattenAlerts(*db.Alerts)); err != nil { + if err := d.Set("alert", utils.FlattenAlerts(*db.Alerts)); err != nil { return diag.FromErr(err) } - if err := d.Set("modules", flattenModules(*db.Modules)); err != nil { + if err := d.Set("modules", utils.FlattenModules(*db.Modules)); err != nil { return diag.FromErr(err) } @@ -561,7 +561,7 @@ func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.Res if err := d.Set("enable_database_clustering", redis.BoolValue(db.Clustering.Enabled)); err != nil { return diag.FromErr(err) } - if err := d.Set("regex_rules", flattenRegexRules(db.Clustering.RegexRules)); err != nil { + if err := d.Set("regex_rules", utils.FlattenRegexRules(db.Clustering.RegexRules)); err != nil { return diag.FromErr(err) } } @@ -584,16 +584,16 @@ func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.Res } func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - _, databaseId, err := toDatabaseId(d.Id()) + _, databaseId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } subId := d.Get("subscription_id").(int) - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) updateDatabaseRequest := fixedDatabases.UpdateFixedDatabase{ Name: redis.String(d.Get("name").(string)), @@ -609,7 +609,7 @@ func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.R updateDatabaseRequest.RespVersion = redis.String(respVersion) } - sourceIps := interfaceToStringSlice(d.Get("source_ips").([]interface{})) + sourceIps := utils.InterfaceToStringSlice(d.Get("source_ips").([]interface{})) if len(sourceIps) == 0 { updateDatabaseRequest.SourceIPs = []*string{redis.String("0.0.0.0/0")} } else { @@ -636,7 +636,7 @@ func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.R updateDatabaseRequest.Replica = createReplica } - tlsCertificates := interfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) + tlsCertificates := utils.InterfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) if len(tlsCertificates) > 0 { createCertificates := make([]*fixedDatabases.DatabaseCertificate, 0) for _, cert := range tlsCertificates { @@ -679,11 +679,11 @@ func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.R updateDatabaseRequest.SupportOSSClusterAPI = redis.Bool(d.Get("support_oss_cluster_api").(bool)) updateDatabaseRequest.UseExternalEndpointForOSSClusterAPI = redis.Bool(d.Get("external_endpoint_for_oss_cluster_api").(bool)) updateDatabaseRequest.EnableDatabaseClustering = redis.Bool(d.Get("enable_database_clustering").(bool)) - updateDatabaseRequest.RegexRules = interfaceToStringSlice(d.Get("regex_rules").([]interface{})) + updateDatabaseRequest.RegexRules = utils.InterfaceToStringSlice(d.Get("regex_rules").([]interface{})) updateDatabaseRequest.EnableTls = redis.Bool(d.Get("enable_tls").(bool)) } - err = api.client.FixedDatabases.Update(ctx, subId, databaseId, updateDatabaseRequest) + err = api.Client.FixedDatabases.Update(ctx, subId, databaseId, updateDatabaseRequest) if err != nil { return diag.FromErr(err) } @@ -706,31 +706,31 @@ func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.R func resourceRedisCloudEssentialsDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId := d.Get("subscription_id").(int) - _, databaseId, err := toDatabaseId(d.Id()) + _, databaseId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) if err := waitForEssentialsDatabaseToBeActive(ctx, subId, databaseId, api); err != nil { return diag.FromErr(err) } - dbErr := api.client.FixedDatabases.Delete(ctx, subId, databaseId) + dbErr := api.Client.FixedDatabases.Delete(ctx, subId, databaseId) if dbErr != nil { diag.FromErr(dbErr) } return diags } -func waitForEssentialsDatabaseToBeActive(ctx context.Context, subId, id int, api *apiClient) error { +func waitForEssentialsDatabaseToBeActive(ctx context.Context, subId, id int, api *utils.ApiClient) error { wait := &retry.StateChangeConf{ Delay: 30 * time.Second, Pending: []string{ @@ -746,13 +746,13 @@ func waitForEssentialsDatabaseToBeActive(ctx context.Context, subId, id int, api databases.StatusDynamicEndpointsCreationPending, }, Target: []string{databases.StatusActive}, - Timeout: safetyTimeout, + Timeout: utils.SafetyTimeout, PollInterval: 30 * time.Second, Refresh: func() (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for fixed database %d to be active", id) - database, err := api.client.FixedDatabases.Get(ctx, subId, id) + database, err := api.Client.FixedDatabases.Get(ctx, subId, id) if err != nil { return nil, "", err } @@ -788,9 +788,9 @@ func suppressIfPaygDisabled(_, _, _ string, d *schema.ResourceData) bool { return !d.Get("enable_payg_features").(bool) } -func readFixedTags(ctx context.Context, api *apiClient, subId int, databaseId int, d *schema.ResourceData) error { +func readFixedTags(ctx context.Context, api *utils.ApiClient, subId int, databaseId int, d *schema.ResourceData) error { t := make(map[string]string) - tagResponse, err := api.client.Tags.GetFixed(ctx, subId, databaseId) + tagResponse, err := api.Client.Tags.GetFixed(ctx, subId, databaseId) if err != nil { return err } @@ -802,7 +802,7 @@ func readFixedTags(ctx context.Context, api *apiClient, subId int, databaseId in return d.Set("tags", t) } -func writeFixedTags(ctx context.Context, api *apiClient, subId int, databaseId int, d *schema.ResourceData) error { +func writeFixedTags(ctx context.Context, api *utils.ApiClient, subId int, databaseId int, d *schema.ResourceData) error { t := make([]*tags.Tag, 0) tState := d.Get("tags").(map[string]interface{}) for k, v := range tState { @@ -811,5 +811,5 @@ func writeFixedTags(ctx context.Context, api *apiClient, subId int, databaseId i Value: redis.String(v.(string)), }) } - return api.client.Tags.PutFixed(ctx, subId, databaseId, tags.AllTags{Tags: &t}) + return api.Client.Tags.PutFixed(ctx, subId, databaseId, tags.AllTags{Tags: &t}) } diff --git a/provider/resource_rediscloud_essentials_subscription.go b/provider/essentials/resource_rediscloud_essentials_subscription.go similarity index 74% rename from provider/resource_rediscloud_essentials_subscription.go rename to provider/essentials/resource_rediscloud_essentials_subscription.go index d293669b..26df495b 100644 --- a/provider/resource_rediscloud_essentials_subscription.go +++ b/provider/essentials/resource_rediscloud_essentials_subscription.go @@ -1,23 +1,21 @@ -package provider +package essentials import ( "context" "errors" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "log" "regexp" "strconv" "time" "github.com/RedisLabs/rediscloud-go-api/redis" fixedSubscriptions "github.com/RedisLabs/rediscloud-go-api/service/fixed/subscriptions" - "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudEssentialsSubscription() *schema.Resource { +func ResourceRedisCloudEssentialsSubscription() *schema.Resource { return &schema.Resource{ Description: "Manages an Essentials Subscription within your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudEssentialsSubscriptionCreate, @@ -83,7 +81,7 @@ func resourceRedisCloudEssentialsSubscription() *schema.Resource { func resourceRedisCloudEssentialsSubscriptionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) createSubscriptionRequest := fixedSubscriptions.FixedSubscriptionRequest{ Name: redis.String(d.Get("name").(string)), @@ -104,7 +102,7 @@ func resourceRedisCloudEssentialsSubscriptionCreate(ctx context.Context, d *sche } // Create Subscription - subId, err := api.client.FixedSubscriptions.Create(ctx, createSubscriptionRequest) + subId, err := api.Client.FixedSubscriptions.Create(ctx, createSubscriptionRequest) if err != nil { return append(diags, diag.FromErr(err)...) } @@ -122,14 +120,14 @@ func resourceRedisCloudEssentialsSubscriptionCreate(ctx context.Context, d *sche func resourceRedisCloudEssentialsSubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - subscription, err := api.client.FixedSubscriptions.Get(ctx, subId) + subscription, err := api.Client.FixedSubscriptions.Get(ctx, subId) if err != nil { if _, ok := err.(*fixedSubscriptions.NotFound); ok { d.SetId("") @@ -162,15 +160,15 @@ func resourceRedisCloudEssentialsSubscriptionRead(ctx context.Context, d *schema func resourceRedisCloudEssentialsSubscriptionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) change := d.HasChanges("name", "plan_id", "payment_method_id") @@ -191,7 +189,7 @@ func resourceRedisCloudEssentialsSubscriptionUpdate(ctx context.Context, d *sche updateSubscriptionRequest.PaymentMethodID = redis.Int(v.(int)) } - err = api.client.FixedSubscriptions.Update(ctx, subId, updateSubscriptionRequest) + err = api.Client.FixedSubscriptions.Update(ctx, subId, updateSubscriptionRequest) if err != nil { return append(diags, diag.FromErr(err)...) } @@ -201,15 +199,15 @@ func resourceRedisCloudEssentialsSubscriptionUpdate(ctx context.Context, d *sche func resourceRedisCloudEssentialsSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) // Wait for the subscription to be active before deleting it. if err := waitForEssentialsSubscriptionToBeActive(ctx, subId, api); err != nil { @@ -217,7 +215,7 @@ func resourceRedisCloudEssentialsSubscriptionDelete(ctx context.Context, d *sche } // Delete subscription once all databases are deleted - err = api.client.FixedSubscriptions.Delete(ctx, subId) + err = api.Client.FixedSubscriptions.Delete(ctx, subId) if err != nil { return diag.FromErr(err) } @@ -231,56 +229,3 @@ func resourceRedisCloudEssentialsSubscriptionDelete(ctx context.Context, d *sche return diags } - -func waitForEssentialsSubscriptionToBeActive(ctx context.Context, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Delay: 10 * time.Second, - Pending: []string{subscriptions.SubscriptionStatusPending}, - Target: []string{subscriptions.SubscriptionStatusActive}, - Timeout: safetyTimeout, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for fixed subscription %d to be active", id) - - subscription, err := api.client.FixedSubscriptions.Get(ctx, id) - if err != nil { - return nil, "", err - } - - return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} - -func waitForEssentialsSubscriptionToBeDeleted(ctx context.Context, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Delay: 10 * time.Second, - Pending: []string{subscriptions.SubscriptionStatusDeleting}, - Target: []string{"deleted"}, - Timeout: safetyTimeout, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for fixed subscription %d to be deleted", id) - - subscription, err := api.client.FixedSubscriptions.Get(ctx, id) - if err != nil { - if _, ok := err.(*fixedSubscriptions.NotFound); ok { - return "deleted", "deleted", nil - } - return nil, "", err - } - - return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} diff --git a/provider/essentials/utils.go b/provider/essentials/utils.go new file mode 100644 index 00000000..52a630f9 --- /dev/null +++ b/provider/essentials/utils.go @@ -0,0 +1,90 @@ +package essentials + +import ( + "context" + "log" + "time" + + "github.com/RedisLabs/rediscloud-go-api/redis" + fixedDatabases "github.com/RedisLabs/rediscloud-go-api/service/fixed/databases" + fixedSubscriptions "github.com/RedisLabs/rediscloud-go-api/service/fixed/subscriptions" + "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +func filterFixedDatabases(list *fixedDatabases.ListFixedDatabase, filters []func(db *fixedDatabases.FixedDatabase) bool) ([]*fixedDatabases.FixedDatabase, error) { + var filtered []*fixedDatabases.FixedDatabase + for list.Next() { + if filterFixedDatabase(list.Value(), filters) { + filtered = append(filtered, list.Value()) + } + } + if list.Err() != nil { + return nil, list.Err() + } + + return filtered, nil +} + +func filterFixedDatabase(db *fixedDatabases.FixedDatabase, filters []func(db *fixedDatabases.FixedDatabase) bool) bool { + for _, filter := range filters { + if !filter(db) { + return false + } + } + return true +} + +func waitForEssentialsSubscriptionToBeActive(ctx context.Context, id int, api *utils.ApiClient) error { + wait := &retry.StateChangeConf{ + Delay: 10 * time.Second, + Pending: []string{subscriptions.SubscriptionStatusPending}, + Target: []string{subscriptions.SubscriptionStatusActive}, + Timeout: utils.SafetyTimeout, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for fixed subscription %d to be active", id) + + subscription, err := api.Client.FixedSubscriptions.Get(ctx, id) + if err != nil { + return nil, "", err + } + + return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func waitForEssentialsSubscriptionToBeDeleted(ctx context.Context, id int, api *utils.ApiClient) error { + wait := &retry.StateChangeConf{ + Delay: 10 * time.Second, + Pending: []string{subscriptions.SubscriptionStatusDeleting}, + Target: []string{"deleted"}, + Timeout: utils.SafetyTimeout, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for fixed subscription %d to be deleted", id) + + subscription, err := api.Client.FixedSubscriptions.Get(ctx, id) + if err != nil { + if _, ok := err.(*fixedSubscriptions.NotFound); ok { + return "deleted", "deleted", nil + } + return nil, "", err + } + + return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} diff --git a/provider/datasource_rediscloud_data_persistence.go b/provider/misc/datasource_rediscloud_data_persistence.go similarity index 88% rename from provider/datasource_rediscloud_data_persistence.go rename to provider/misc/datasource_rediscloud_data_persistence.go index 87c67da4..47b60846 100644 --- a/provider/datasource_rediscloud_data_persistence.go +++ b/provider/misc/datasource_rediscloud_data_persistence.go @@ -1,13 +1,15 @@ -package provider +package misc import ( "context" + "github.com/RedisLabs/rediscloud-go-api/service/account" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceRedisCloudDataPersistence() *schema.Resource { +func DataSourceRedisCloudDataPersistence() *schema.Resource { return &schema.Resource{ Description: "The data persistence data source allows access to a list of supported data persistence options. Each option represents the rate at which a database will persist its data to storage.", ReadContext: dataSourceRedisCloudDataPersistenceRead, @@ -38,9 +40,9 @@ func dataSourceRedisCloudDataPersistence() *schema.Resource { func dataSourceRedisCloudDataPersistenceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - dataPersistence, err := api.client.Account.ListDataPersistence(ctx) + dataPersistence, err := api.Client.Account.ListDataPersistence(ctx) if err != nil { return diag.FromErr(err) diff --git a/provider/datasource_rediscloud_database_modules.go b/provider/misc/datasource_rediscloud_database_modules.go similarity index 87% rename from provider/datasource_rediscloud_database_modules.go rename to provider/misc/datasource_rediscloud_database_modules.go index 88f3d6f8..46aedb7a 100644 --- a/provider/datasource_rediscloud_database_modules.go +++ b/provider/misc/datasource_rediscloud_database_modules.go @@ -1,13 +1,15 @@ -package provider +package misc import ( "context" + "github.com/RedisLabs/rediscloud-go-api/service/account" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceRedisCloudDatabaseModules() *schema.Resource { +func DataSourceRedisCloudDatabaseModules() *schema.Resource { return &schema.Resource{ Description: "The Database data source allows access to the details of an existing database within your Redis Enterprise Cloud account.", ReadContext: dataSourceRedisCloudDatabaseModulesRead, @@ -38,9 +40,9 @@ func dataSourceRedisCloudDatabaseModules() *schema.Resource { func dataSourceRedisCloudDatabaseModulesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - modules, err := api.client.Account.ListDatabaseModules(ctx) + modules, err := api.Client.Account.ListDatabaseModules(ctx) if err != nil { return diag.FromErr(err) diff --git a/provider/datasource_rediscloud_regions.go b/provider/misc/datasource_rediscloud_regions.go similarity index 93% rename from provider/datasource_rediscloud_regions.go rename to provider/misc/datasource_rediscloud_regions.go index 77340019..eaa2a36d 100644 --- a/provider/datasource_rediscloud_regions.go +++ b/provider/misc/datasource_rediscloud_regions.go @@ -1,17 +1,19 @@ -package provider +package misc import ( "context" + "strings" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/account" "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "strings" ) -func dataSourceRedisCloudRegions() *schema.Resource { +func DataSourceRedisCloudRegions() *schema.Resource { return &schema.Resource{ Description: "The Regions data source allows access to a list of supported cloud provider regions. These regions can be used with the subscription resource.", ReadContext: dataSourceRedisCloudRegionsRead, @@ -48,9 +50,9 @@ func dataSourceRedisCloudRegions() *schema.Resource { func dataSourceRedisCloudRegionsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - regions, err := api.client.Account.ListRegions(ctx) + regions, err := api.Client.Account.ListRegions(ctx) if err != nil { return diag.FromErr(err) diff --git a/provider/datasource_rediscloud_active_active_private_service_connect_endpoints.go b/provider/private_service_connect/datasource_rediscloud_active_active_private_service_connect_endpoints.go similarity index 88% rename from provider/datasource_rediscloud_active_active_private_service_connect_endpoints.go rename to provider/private_service_connect/datasource_rediscloud_active_active_private_service_connect_endpoints.go index 8eaafb17..4b088d33 100644 --- a/provider/datasource_rediscloud_active_active_private_service_connect_endpoints.go +++ b/provider/private_service_connect/datasource_rediscloud_active_active_private_service_connect_endpoints.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -6,11 +6,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourceActiveActivePrivateServiceConnectEndpoints() *schema.Resource { +func DataSourceActiveActivePrivateServiceConnectEndpoints() *schema.Resource { return &schema.Resource{ Description: "The Active-Active Private Service Connect Endpoints data source allows access to an available endpoints on a Private Service Connect Service within your Redis Enterprise Cloud Account.", ReadContext: dataSourceActiveActivePrivateServiceConnectEndpointsRead, @@ -104,7 +105,7 @@ func dataSourceActiveActivePrivateServiceConnectEndpoints() *schema.Resource { func dataSourceActiveActivePrivateServiceConnectEndpointsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -114,7 +115,7 @@ func dataSourceActiveActivePrivateServiceConnectEndpointsRead(ctx context.Contex regionId := d.Get("region_id").(int) pscServiceId := d.Get("private_service_connect_service_id").(int) - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subId, regionId, pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subId, regionId, pscServiceId) if err != nil { return diag.FromErr(err) } @@ -123,7 +124,7 @@ func dataSourceActiveActivePrivateServiceConnectEndpointsRead(ctx context.Contex for _, endpoint := range endpoints.Endpoints { serviceAttachments[*endpoint.ID] = []psc.TerraformGCPServiceAttachment{} if redis.StringValue(endpoint.Status) != psc.EndpointStatusRejected && redis.StringValue(endpoint.Status) != psc.EndpointStatusDeleted { - script, err := api.client.PrivateServiceConnect.GetActiveActiveEndpointCreationScripts(ctx, subId, regionId, pscServiceId, redis.IntValue(endpoint.ID), true) + script, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpointCreationScripts(ctx, subId, regionId, pscServiceId, redis.IntValue(endpoint.ID), true) if err != nil { return diag.FromErr(err) } @@ -131,11 +132,11 @@ func dataSourceActiveActivePrivateServiceConnectEndpointsRead(ctx context.Contex } } - if err := d.Set("endpoints", flattenPrivateServiceConnectEndpoints(endpoints.Endpoints, serviceAttachments)); err != nil { + if err := d.Set("endpoints", utils.FlattenPrivateServiceConnectEndpoints(endpoints.Endpoints, serviceAttachments)); err != nil { return diag.FromErr(err) } - d.SetId(buildPrivateServiceConnectActiveActiveId(subId, regionId, pscServiceId)) + d.SetId(utils.BuildPrivateServiceConnectActiveActiveId(subId, regionId, pscServiceId)) return diags } diff --git a/provider/datasource_rediscloud_private_service_connect.go b/provider/private_service_connect/datasource_rediscloud_private_service_connect.go similarity index 88% rename from provider/datasource_rediscloud_private_service_connect.go rename to provider/private_service_connect/datasource_rediscloud_private_service_connect.go index 4ff8027b..e9ab0c97 100644 --- a/provider/datasource_rediscloud_private_service_connect.go +++ b/provider/private_service_connect/datasource_rediscloud_private_service_connect.go @@ -1,14 +1,15 @@ -package provider +package private_service_connect import ( "context" "strconv" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourcePrivateServiceConnect() *schema.Resource { +func DataSourcePrivateServiceConnect() *schema.Resource { return &schema.Resource{ Description: "The Private Service Connect data source allows access to an available Private Service Connect Service within your Redis Enterprise Cloud Account.", ReadContext: dataSourcePrivateServiceConnectRead, @@ -45,14 +46,14 @@ func dataSourcePrivateServiceConnect() *schema.Resource { func dataSourcePrivateServiceConnectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - pscService, err := api.client.PrivateServiceConnect.GetService(ctx, subId) + pscService, err := api.Client.PrivateServiceConnect.GetService(ctx, subId) if err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_private_service_connect_endpoints.go b/provider/private_service_connect/datasource_rediscloud_private_service_connect_endpoints.go similarity index 75% rename from provider/datasource_rediscloud_private_service_connect_endpoints.go rename to provider/private_service_connect/datasource_rediscloud_private_service_connect_endpoints.go index a35317e2..42168e5a 100644 --- a/provider/datasource_rediscloud_private_service_connect_endpoints.go +++ b/provider/private_service_connect/datasource_rediscloud_private_service_connect_endpoints.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -6,11 +6,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func dataSourcePrivateServiceConnectEndpoints() *schema.Resource { +func DataSourcePrivateServiceConnectEndpoints() *schema.Resource { return &schema.Resource{ Description: "The Private Service Connect Endpoints data source allows access to an available endpoints on a Private Service Connect Service within your Redis Enterprise Cloud Account.", ReadContext: dataSourcePrivateServiceConnectEndpointsRead, @@ -99,7 +100,7 @@ func dataSourcePrivateServiceConnectEndpoints() *schema.Resource { func dataSourcePrivateServiceConnectEndpointsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -108,7 +109,7 @@ func dataSourcePrivateServiceConnectEndpointsRead(ctx context.Context, d *schema pscServiceId := d.Get("private_service_connect_service_id").(int) - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, subId, pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, subId, pscServiceId) if err != nil { return diag.FromErr(err) } @@ -117,7 +118,7 @@ func dataSourcePrivateServiceConnectEndpointsRead(ctx context.Context, d *schema for _, endpoint := range endpoints.Endpoints { serviceAttachments[*endpoint.ID] = []psc.TerraformGCPServiceAttachment{} if redis.StringValue(endpoint.Status) != psc.EndpointStatusRejected && redis.StringValue(endpoint.Status) != psc.EndpointStatusDeleted { - script, err := api.client.PrivateServiceConnect.GetEndpointCreationScripts(ctx, subId, pscServiceId, redis.IntValue(endpoint.ID), true) + script, err := api.Client.PrivateServiceConnect.GetEndpointCreationScripts(ctx, subId, pscServiceId, redis.IntValue(endpoint.ID), true) if err != nil { return diag.FromErr(err) } @@ -125,7 +126,7 @@ func dataSourcePrivateServiceConnectEndpointsRead(ctx context.Context, d *schema } } - if err := d.Set("endpoints", flattenPrivateServiceConnectEndpoints(endpoints.Endpoints, serviceAttachments)); err != nil { + if err := d.Set("endpoints", utils.FlattenPrivateServiceConnectEndpoints(endpoints.Endpoints, serviceAttachments)); err != nil { return diag.FromErr(err) } @@ -133,25 +134,3 @@ func dataSourcePrivateServiceConnectEndpointsRead(ctx context.Context, d *schema return diags } - -func flattenPrivateServiceConnectEndpoints(endpoints []*psc.PrivateServiceConnectEndpoint, - serviceAttachments map[int][]psc.TerraformGCPServiceAttachment) []map[string]interface{} { - - var rl []map[string]interface{} - for _, endpoint := range endpoints { - - endpointMapString := map[string]interface{}{ - "private_service_connect_endpoint_id": redis.IntValue(endpoint.ID), - "gcp_project_id": redis.StringValue(endpoint.GCPProjectID), - "gcp_vpc_name": redis.StringValue(endpoint.GCPVPCName), - "gcp_vpc_subnet_name": redis.StringValue(endpoint.GCPVPCSubnetName), - "endpoint_connection_name": redis.StringValue(endpoint.EndpointConnectionName), - "status": redis.StringValue(endpoint.Status), - "service_attachments": flattenPrivateServiceConnectEndpointServiceAttachments(serviceAttachments[redis.IntValue(endpoint.ID)]), - } - - rl = append(rl, endpointMapString) - } - - return rl -} diff --git a/provider/datasource_rediscloud_subscription_peerings.go b/provider/private_service_connect/datasource_rediscloud_subscription_peerings.go similarity index 96% rename from provider/datasource_rediscloud_subscription_peerings.go rename to provider/private_service_connect/datasource_rediscloud_subscription_peerings.go index 56b38dcf..9ccde606 100644 --- a/provider/datasource_rediscloud_subscription_peerings.go +++ b/provider/private_service_connect/datasource_rediscloud_subscription_peerings.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -7,12 +7,13 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func dataSourceRedisCloudSubscriptionPeerings() *schema.Resource { +func DataSourceRedisCloudSubscriptionPeerings() *schema.Resource { return &schema.Resource{ Description: "The Subscription Peerings data source allows access to a list of VPC peerings configured on the subscription.", ReadContext: dataSourceRedisCloudSubscriptionPeeringsRead, @@ -116,14 +117,14 @@ func dataSourceRedisCloudSubscriptionPeerings() *schema.Resource { func dataSourceRedisCloudSubscriptionPeeringsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - vpcPeering, err := api.client.Subscription.ListVPCPeering(ctx, subId) + vpcPeering, err := api.Client.Subscription.ListVPCPeering(ctx, subId) if err != nil { return diag.FromErr(err) diff --git a/provider/resource_rediscloud_active_active_private_service_connect.go b/provider/private_service_connect/resource_rediscloud_active_active_private_service_connect.go similarity index 76% rename from provider/resource_rediscloud_active_active_private_service_connect.go rename to provider/private_service_connect/resource_rediscloud_active_active_private_service_connect.go index 1f647836..91611fe1 100644 --- a/provider/resource_rediscloud_active_active_private_service_connect.go +++ b/provider/private_service_connect/resource_rediscloud_active_active_private_service_connect.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -11,11 +11,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceRedisCloudActiveActivePrivateServiceConnect() *schema.Resource { +func ResourceRedisCloudActiveActivePrivateServiceConnect() *schema.Resource { return &schema.Resource{ Description: "Manages a Private Service Connect to an Active-Active Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudActiveActivePrivateServiceConnectCreate, @@ -55,52 +56,52 @@ func resourceRedisCloudActiveActivePrivateServiceConnect() *schema.Resource { } func resourceRedisCloudActiveActivePrivateServiceConnectCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) regionId := d.Get("region_id").(int) - pscServiceId, err := api.client.PrivateServiceConnect.CreateActiveActiveService(ctx, subscriptionId, regionId) + pscServiceId, err := api.Client.PrivateServiceConnect.CreateActiveActiveService(ctx, subscriptionId, regionId) if err != nil { - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return diag.FromErr(err) } - d.SetId(buildPrivateServiceConnectActiveActiveId(subscriptionId, regionId, pscServiceId)) + d.SetId(utils.BuildPrivateServiceConnectActiveActiveId(subscriptionId, regionId, pscServiceId)) - err = waitForPrivateServiceConnectServiceToBeActive(ctx, func() (result interface{}, state string, err error) { + err = utils.WaitForPrivateServiceConnectServiceToBeActive(ctx, func() (result interface{}, state string, err error) { return refreshPrivateServiceConnectServiceActiveActiveStatus(ctx, subscriptionId, regionId, api) }) if err != nil { - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return diag.FromErr(err) } - err = waitForSubscriptionToBeActive(ctx, subscriptionId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subscriptionId, api) if err != nil { - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return diag.FromErr(err) } - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return resourceRedisCloudActiveActivePrivateServiceConnectRead(ctx, d, meta) } func resourceRedisCloudActiveActivePrivateServiceConnectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) resId, err := toPscServiceActiveActiveId(d.Id()) if err != nil { return diag.FromErr(err) } - pscObj, err := api.client.PrivateServiceConnect.GetActiveActiveService(ctx, resId.subscriptionId, resId.regionId) + pscObj, err := api.Client.PrivateServiceConnect.GetActiveActiveService(ctx, resId.subscriptionId, resId.regionId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -110,7 +111,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectRead(ctx context.Context return diag.FromErr(err) } - d.SetId(buildPrivateServiceConnectActiveActiveId(resId.subscriptionId, resId.regionId, resId.pscServiceId)) + d.SetId(utils.BuildPrivateServiceConnectActiveActiveId(resId.subscriptionId, resId.regionId, resId.pscServiceId)) err = d.Set("subscription_id", strconv.Itoa(resId.subscriptionId)) if err != nil { @@ -132,18 +133,18 @@ func resourceRedisCloudActiveActivePrivateServiceConnectRead(ctx context.Context func resourceRedisCloudActiveActivePrivateServiceConnectDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) - defer subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) + defer utils.SubscriptionMutex.Unlock(subscriptionId) regionId := d.Get("region_id").(int) - err = api.client.PrivateServiceConnect.DeleteActiveActiveService(ctx, subscriptionId, regionId) + err = api.Client.PrivateServiceConnect.DeleteActiveActiveService(ctx, subscriptionId, regionId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -155,7 +156,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectDelete(ctx context.Conte d.SetId("") - err = waitForSubscriptionToBeActive(ctx, subscriptionId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subscriptionId, api) if err != nil { return diag.FromErr(err) } @@ -163,10 +164,6 @@ func resourceRedisCloudActiveActivePrivateServiceConnectDelete(ctx context.Conte return diags } -func buildPrivateServiceConnectActiveActiveId(subId int, regionId int, pscServiceId int) string { - return fmt.Sprintf("%d/%d/%d", subId, regionId, pscServiceId) -} - type privateServiceConnectServiceActiveActiveId struct { subscriptionId int regionId int @@ -201,10 +198,10 @@ func toPscServiceActiveActiveId(id string) (*privateServiceConnectServiceActiveA }, nil } -func refreshPrivateServiceConnectServiceActiveActiveStatus(ctx context.Context, subscriptionId int, regionId int, api *apiClient) (result interface{}, state string, err error) { +func refreshPrivateServiceConnectServiceActiveActiveStatus(ctx context.Context, subscriptionId int, regionId int, api *utils.ApiClient) (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for private service connect service status %d/%d to be active", subscriptionId, regionId) - pscService, err := api.client.PrivateServiceConnect.GetActiveActiveService(ctx, subscriptionId, regionId) + pscService, err := api.Client.PrivateServiceConnect.GetActiveActiveService(ctx, subscriptionId, regionId) if err != nil { return nil, "", err } diff --git a/provider/resource_rediscloud_active_active_private_service_connect_endpoint.go b/provider/private_service_connect/resource_rediscloud_active_active_private_service_connect_endpoint.go similarity index 86% rename from provider/resource_rediscloud_active_active_private_service_connect_endpoint.go rename to provider/private_service_connect/resource_rediscloud_active_active_private_service_connect_endpoint.go index decd4f5f..86f43cdd 100644 --- a/provider/resource_rediscloud_active_active_private_service_connect_endpoint.go +++ b/provider/private_service_connect/resource_rediscloud_active_active_private_service_connect_endpoint.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -11,13 +11,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -const placeholderStatusDisappear = "disappeared" - -func resourceRedisCloudActiveActivePrivateServiceConnectEndpoint() *schema.Resource { +func ResourceRedisCloudActiveActivePrivateServiceConnectEndpoint() *schema.Resource { return &schema.Resource{ Description: "Manages a Private Service Connect Endpoint to an Active-Active Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudActiveActivePrivateServiceConnectEndpointCreate, @@ -116,14 +115,14 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpoint() *schema.Resou } func resourceRedisCloudActiveActivePrivateServiceConnectEndpointCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) - defer subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) + defer utils.SubscriptionMutex.Unlock(subscriptionId) regionId := d.Get("region_id").(int) pscServiceId := d.Get("private_service_connect_service_id").(int) @@ -132,7 +131,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointCreate(ctx conte gcpVpcSubnetName := d.Get("gcp_vpc_subnet_name").(string) endpointConnectionNamePrefix := d.Get("endpoint_connection_name").(string) - endpointId, err := api.client.PrivateServiceConnect.CreateActiveActiveEndpoint(ctx, subscriptionId, regionId, pscServiceId, psc.CreatePrivateServiceConnectEndpoint{ + endpointId, err := api.Client.PrivateServiceConnect.CreateActiveActiveEndpoint(ctx, subscriptionId, regionId, pscServiceId, psc.CreatePrivateServiceConnectEndpoint{ GCPProjectID: &gcpProjectId, GCPVPCName: &gcpVpcName, GCPVPCSubnetName: &gcpVpcSubnetName, @@ -145,7 +144,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointCreate(ctx conte d.SetId(buildPrivateServiceConnectActiveActiveEndpointId(subscriptionId, regionId, pscServiceId, endpointId)) - err = waitForSubscriptionToBeActive(ctx, subscriptionId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subscriptionId, api) if err != nil { return diag.FromErr(err) } @@ -155,14 +154,14 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointCreate(ctx conte func resourceRedisCloudActiveActivePrivateServiceConnectEndpointRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) resId, err := toPscEndpointActiveActiveId(d.Id()) if err != nil { return diag.FromErr(err) } - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -172,7 +171,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointRead(ctx context return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) if endpoint == nil { d.SetId("") return diags @@ -181,7 +180,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointRead(ctx context d.SetId(buildPrivateServiceConnectActiveActiveEndpointId(resId.subscriptionId, resId.regionId, resId.pscServiceId, redis.IntValue(endpoint.ID))) if redis.StringValue(endpoint.Status) != psc.EndpointStatusRejected && redis.StringValue(endpoint.Status) != psc.EndpointStatusDeleted { - creationScript, err := api.client.PrivateServiceConnect.GetActiveActiveEndpointCreationScripts(ctx, + creationScript, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpointCreationScripts(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId, redis.IntValue(endpoint.ID), true) if err != nil { var notFound *psc.NotFoundActiveActive @@ -192,7 +191,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointRead(ctx context return diag.FromErr(err) } - if err := d.Set("service_attachments", flattenPrivateServiceConnectEndpointServiceAttachments(creationScript.Script.TerraformGcp.ServiceAttachments)); err != nil { + if err := d.Set("service_attachments", utils.FlattenPrivateServiceConnectEndpointServiceAttachments(creationScript.Script.TerraformGcp.ServiceAttachments)); err != nil { return diag.FromErr(err) } } else { @@ -246,16 +245,16 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointRead(ctx context func resourceRedisCloudActiveActivePrivateServiceConnectEndpointDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) resId, err := toPscEndpointActiveActiveId(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(resId.subscriptionId) - defer subscriptionMutex.Unlock(resId.subscriptionId) + utils.SubscriptionMutex.Lock(resId.subscriptionId) + defer utils.SubscriptionMutex.Unlock(resId.subscriptionId) - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -265,7 +264,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointDelete(ctx conte return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) if endpoint == nil { d.SetId("") return diags @@ -273,7 +272,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointDelete(ctx conte if redis.StringValue(endpoint.Status) == psc.EndpointStatusInitialized { // It's only possible to delete an endpoint in initialized status - err = api.client.PrivateServiceConnect.DeleteActiveActiveEndpoint(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId, resId.endpointId) + err = api.Client.PrivateServiceConnect.DeleteActiveActiveEndpoint(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId, resId.endpointId) if err != nil { return diag.FromErr(err) } @@ -282,7 +281,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointDelete(ctx conte // Endpoints will be automatically removed once related GCP resources are removed. So we will wait for this // to happen, but we can't check the GCP resources from this provider - err = waitForPrivateServiceConnectServiceEndpointDisappear(ctx, func() (result interface{}, state string, err error) { + err = utils.WaitForPrivateServiceConnectServiceEndpointDisappear(ctx, func() (result interface{}, state string, err error) { return refreshPrivateServiceConnectServiceActiveActiveEndpointDisappear(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId, resId.endpointId, api) }) if err != nil { @@ -348,18 +347,18 @@ func toPscEndpointActiveActiveId(id string) (*privateServiceConnectActiveActiveE } func refreshPrivateServiceConnectServiceActiveActiveEndpointDisappear(ctx context.Context, subscriptionId int, - regionId int, pscServiceId int, endpointId int, api *apiClient) (result interface{}, state string, err error) { + regionId int, pscServiceId int, endpointId int, api *utils.ApiClient) (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for private service connect service endpoint %d/%d/%d to be deleted", subscriptionId, pscServiceId, endpointId) - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subscriptionId, regionId, pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subscriptionId, regionId, pscServiceId) if err != nil { return nil, "", err } - endpoint := findPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) if endpoint == nil { - return placeholderStatusDisappear, placeholderStatusDisappear, nil + return utils.PlaceholderStatusDisappear, utils.PlaceholderStatusDisappear, nil } return redis.StringValue(endpoint.Status), redis.StringValue(endpoint.Status), nil diff --git a/provider/resource_rediscloud_active_active_private_service_connect_endpoint_accepter.go b/provider/private_service_connect/resource_rediscloud_active_active_private_service_connect_endpoint_accepter.go similarity index 75% rename from provider/resource_rediscloud_active_active_private_service_connect_endpoint_accepter.go rename to provider/private_service_connect/resource_rediscloud_active_active_private_service_connect_endpoint_accepter.go index b02a9c08..c0d59b18 100644 --- a/provider/resource_rediscloud_active_active_private_service_connect_endpoint_accepter.go +++ b/provider/private_service_connect/resource_rediscloud_active_active_private_service_connect_endpoint_accepter.go @@ -1,22 +1,22 @@ -package provider +package private_service_connect import ( "context" "errors" "fmt" - "log" "strconv" "strings" "time" "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter() *schema.Resource { +func ResourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter() *schema.Resource { return &schema.Resource{ Description: "Manages the state of Private Service Connect Endpoint to an Active-Active Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate, @@ -68,21 +68,21 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter() *sche func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) - defer subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) + defer utils.SubscriptionMutex.Unlock(subscriptionId) regionId := d.Get("region_id").(int) pscServiceId := d.Get("private_service_connect_service_id").(int) endpointId := d.Get("private_service_connect_endpoint_id").(int) action := d.Get("action").(string) - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subscriptionId, regionId, pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subscriptionId, regionId, pscServiceId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -92,7 +92,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate(c return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) if endpoint == nil { return diag.FromErr(fmt.Errorf("endpoint with id %d not found", endpointId)) } @@ -116,7 +116,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate(c } if redis.StringValue(endpoint.Status) == psc.EndpointStatusInitialized || redis.StringValue(endpoint.Status) == psc.EndpointStatusProcessing { - err = waitForPrivateServiceConnectServiceEndpointToBePending(ctx, refreshFunc) + err = utils.WaitForPrivateServiceConnectServiceEndpointToBePending(ctx, refreshFunc) if err != nil { return diag.FromErr(err) } @@ -124,7 +124,7 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate(c d.SetId(buildPrivateServiceConnectActiveActiveEndpointAccepterId(subscriptionId, regionId, pscServiceId, endpointId)) - err = api.client.PrivateServiceConnect.UpdateActiveActiveEndpoint(ctx, subscriptionId, regionId, pscServiceId, endpointId, &psc.UpdatePrivateServiceConnectEndpoint{ + err = api.Client.PrivateServiceConnect.UpdateActiveActiveEndpoint(ctx, subscriptionId, regionId, pscServiceId, endpointId, &psc.UpdatePrivateServiceConnectEndpoint{ Action: redis.String(action), }) if err != nil { @@ -132,12 +132,12 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate(c } if action == psc.EndpointActionAccept { - err = waitForPrivateServiceConnectServiceEndpointToBeActive(ctx, refreshFunc) + err = utils.WaitForPrivateServiceConnectServiceEndpointToBeActive(ctx, refreshFunc) if err != nil { return diag.FromErr(err) } } else { - err = waitForPrivateServiceConnectServiceEndpointToBeRejected(ctx, refreshFunc) + err = utils.WaitForPrivateServiceConnectServiceEndpointToBeRejected(ctx, refreshFunc) if err != nil { return diag.FromErr(err) } @@ -148,14 +148,14 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterCreate(c func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - resId, err := toPscEndpointActiveActiveAccepterId(d.Id()) + resId, err := ToPscEndpointActiveActiveAccepterId(d.Id()) if err != nil { return diag.FromErr(err) } - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, resId.subscriptionId, resId.regionId, resId.pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, resId.SubscriptionId, resId.RegionId, resId.PscServiceId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -165,25 +165,25 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterRead(ctx return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(resId.EndpointId, endpoints.Endpoints) if endpoint == nil { d.SetId("") return diags } - d.SetId(buildPrivateServiceConnectActiveActiveEndpointAccepterId(resId.subscriptionId, resId.regionId, resId.pscServiceId, redis.IntValue(endpoint.ID))) + d.SetId(buildPrivateServiceConnectActiveActiveEndpointAccepterId(resId.SubscriptionId, resId.RegionId, resId.PscServiceId, redis.IntValue(endpoint.ID))) - err = d.Set("subscription_id", strconv.Itoa(resId.subscriptionId)) + err = d.Set("subscription_id", strconv.Itoa(resId.SubscriptionId)) if err != nil { return diag.FromErr(err) } - err = d.Set("region_id", resId.regionId) + err = d.Set("region_id", resId.RegionId) if err != nil { return diag.FromErr(err) } - err = d.Set("private_service_connect_service_id", resId.pscServiceId) + err = d.Set("private_service_connect_service_id", resId.PscServiceId) if err != nil { return diag.FromErr(err) } @@ -205,17 +205,17 @@ func buildPrivateServiceConnectActiveActiveEndpointAccepterId(subId int, regionI } type privateServiceConnectActiveActiveEndpointAccepterId struct { - subscriptionId int - regionId int - pscServiceId int - endpointId int + SubscriptionId int + RegionId int + PscServiceId int + EndpointId int } func (p privateServiceConnectActiveActiveEndpointAccepterId) String() string { - return fmt.Sprintf("%d/%d/%d/%d", p.subscriptionId, p.regionId, p.pscServiceId, p.endpointId) + return fmt.Sprintf("%d/%d/%d/%d", p.SubscriptionId, p.RegionId, p.PscServiceId, p.EndpointId) } -func toPscEndpointActiveActiveAccepterId(id string) (*privateServiceConnectActiveActiveEndpointAccepterId, error) { +func ToPscEndpointActiveActiveAccepterId(id string) (*privateServiceConnectActiveActiveEndpointAccepterId, error) { parts := strings.Split(id, "/") if len(parts) != 4 { return nil, fmt.Errorf("invalid id: %s", id) @@ -242,10 +242,10 @@ func toPscEndpointActiveActiveAccepterId(id string) (*privateServiceConnectActiv } return &privateServiceConnectActiveActiveEndpointAccepterId{ - subscriptionId: subId, - regionId: regionId, - pscServiceId: pscId, - endpointId: endpointId, + SubscriptionId: subId, + RegionId: regionId, + PscServiceId: pscId, + EndpointId: endpointId, }, nil } @@ -258,21 +258,3 @@ func resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepterDelete(_ d.SetId("") return diags } - -func refreshPrivateServiceConnectServiceEndpointActiveActiveStatus(ctx context.Context, subscriptionId int, regionId int, - pscServiceId int, endpointId int, targetStatus string, api *apiClient) (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for private service connect service endpoint status %d/%d/%d/%d to be %s", - subscriptionId, regionId, pscServiceId, endpointId, targetStatus) - - endpoints, err := api.client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subscriptionId, regionId, pscServiceId) - if err != nil { - return nil, "", err - } - - endpoint := findPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) - if endpoint == nil { - return nil, "", fmt.Errorf("endpoint with id %d not found", endpointId) - } - - return redis.StringValue(endpoint.Status), redis.StringValue(endpoint.Status), nil -} diff --git a/provider/resource_rediscloud_active_active_subscription_peering.go b/provider/private_service_connect/resource_rediscloud_active_active_subscription_peering.go similarity index 93% rename from provider/resource_rediscloud_active_active_subscription_peering.go rename to provider/private_service_connect/resource_rediscloud_active_active_subscription_peering.go index 78b23f48..e9b96043 100644 --- a/provider/resource_rediscloud_active_active_subscription_peering.go +++ b/provider/private_service_connect/resource_rediscloud_active_active_subscription_peering.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -11,13 +11,14 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudActiveActiveSubscriptionPeering() *schema.Resource { +func ResourceRedisCloudActiveActiveSubscriptionPeering() *schema.Resource { return &schema.Resource{ Description: "Creates a VPC peering for an existing Redis Enterprise Cloud Active-Active Subscription, allowing access to your subscription databases as if they were on the same network.", CreateContext: resourceRedisCloudSubscriptionActiveActivePeeringCreate, @@ -148,15 +149,15 @@ func resourceRedisCloudActiveActiveSubscriptionPeering() *schema.Resource { } func resourceRedisCloudSubscriptionActiveActivePeeringCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) providerName := d.Get("provider_name").(string) @@ -187,7 +188,7 @@ func resourceRedisCloudSubscriptionActiveActivePeeringCreate(ctx context.Context if vpcCIDR, ok := d.GetOk("vpc_cidr"); ok { peeringRequest.VPCCidr = redis.String(vpcCIDR.(string)) } else if vpcCIDRs, ok := d.GetOk("vpc_cidrs"); ok { - peeringRequest.VPCCidrs = setToStringSlice(vpcCIDRs.(*schema.Set)) + peeringRequest.VPCCidrs = utils.SetToStringSlice(vpcCIDRs.(*schema.Set)) } else { return diag.Errorf("`vpc_cidr` or `vpc_cidrs` must be set when `provider_name` is `AWS`") } @@ -221,12 +222,12 @@ func resourceRedisCloudSubscriptionActiveActivePeeringCreate(ctx context.Context peeringRequest.VPCNetworkName = redis.String(gcpNetworkName.(string)) } - peering, err := api.client.Subscription.CreateActiveActiveVPCPeering(ctx, subId, peeringRequest) + peering, err := api.Client.Subscription.CreateActiveActiveVPCPeering(ctx, subId, peeringRequest) if err != nil { return diag.FromErr(err) } - d.SetId(buildResourceId(subId, peering)) + d.SetId(utils.BuildResourceId(subId, peering)) err = waitForActiveActivePeeringToBeInitiated(ctx, subId, peering, api) if err != nil { @@ -237,7 +238,7 @@ func resourceRedisCloudSubscriptionActiveActivePeeringCreate(ctx context.Context } func resourceRedisCloudSubscriptionActiveActivePeeringRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId, id, err := toVpcPeeringId(d.Id()) @@ -249,7 +250,7 @@ func resourceRedisCloudSubscriptionActiveActivePeeringRead(ctx context.Context, return diag.FromErr(err) } - peerings, err := api.client.Subscription.ListActiveActiveVPCPeering(ctx, subId) + peerings, err := api.Client.Subscription.ListActiveActiveVPCPeering(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -347,7 +348,7 @@ func resourceRedisCloudSubscriptionActiveActivePeeringRead(ctx context.Context, } func resourceRedisCloudSubscriptionActiveActivePeeringDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId, id, err := toVpcPeeringId(d.Id()) @@ -355,10 +356,10 @@ func resourceRedisCloudSubscriptionActiveActivePeeringDelete(ctx context.Context return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - err = api.client.Subscription.DeleteActiveActiveVPCPeering(ctx, subId, id) + err = api.Client.Subscription.DeleteActiveActiveVPCPeering(ctx, subId, id) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -384,7 +385,7 @@ func findActiveActiveVpcPeering(id int, regions []*subscriptions.ActiveActiveVpc return nil, nil } -func waitForActiveActivePeeringToBeInitiated(ctx context.Context, subId, id int, api *apiClient) error { +func waitForActiveActivePeeringToBeInitiated(ctx context.Context, subId, id int, api *utils.ApiClient) error { wait := &retry.StateChangeConf{ Delay: 30 * time.Second, Pending: []string{ @@ -401,7 +402,7 @@ func waitForActiveActivePeeringToBeInitiated(ctx context.Context, subId, id int, Refresh: func() (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for vpc peering %d to be initiated. Status: %s", id, state) - list, err := api.client.Subscription.ListActiveActiveVPCPeering(ctx, subId) + list, err := api.Client.Subscription.ListActiveActiveVPCPeering(ctx, subId) if err != nil { return nil, "", err } diff --git a/provider/resource_rediscloud_private_service_connect.go b/provider/private_service_connect/resource_rediscloud_private_service_connect.go similarity index 71% rename from provider/resource_rediscloud_private_service_connect.go rename to provider/private_service_connect/resource_rediscloud_private_service_connect.go index 13686e6f..6a4f70dc 100644 --- a/provider/resource_rediscloud_private_service_connect.go +++ b/provider/private_service_connect/resource_rediscloud_private_service_connect.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -11,12 +11,12 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceRedisCloudPrivateServiceConnect() *schema.Resource { +func ResourceRedisCloudPrivateServiceConnect() *schema.Resource { return &schema.Resource{ Description: "Manages a Private Service Connect to an Active-Active Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudPrivateServiceConnectCreate, @@ -50,50 +50,50 @@ func resourceRedisCloudPrivateServiceConnect() *schema.Resource { } func resourceRedisCloudPrivateServiceConnectCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) - pscServiceId, err := api.client.PrivateServiceConnect.CreateService(ctx, subscriptionId) + pscServiceId, err := api.Client.PrivateServiceConnect.CreateService(ctx, subscriptionId) if err != nil { - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return diag.FromErr(err) } d.SetId(buildPrivateServiceConnectId(subscriptionId, pscServiceId)) - err = waitForPrivateServiceConnectServiceToBeActive(ctx, func() (result interface{}, state string, err error) { + err = utils.WaitForPrivateServiceConnectServiceToBeActive(ctx, func() (result interface{}, state string, err error) { return refreshPrivateServiceConnectServiceStatus(ctx, subscriptionId, api) }) if err != nil { - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return diag.FromErr(err) } - err = waitForSubscriptionToBeActive(ctx, subscriptionId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subscriptionId, api) if err != nil { - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return diag.FromErr(err) } - subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Unlock(subscriptionId) return resourceRedisCloudPrivateServiceConnectRead(ctx, d, meta) } func resourceRedisCloudPrivateServiceConnectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) resId, err := toPscServiceId(d.Id()) if err != nil { return diag.FromErr(err) } - pscObj, err := api.client.PrivateServiceConnect.GetService(ctx, resId.subscriptionId) + pscObj, err := api.Client.PrivateServiceConnect.GetService(ctx, resId.subscriptionId) if err != nil { var notFound *psc.NotFound if errors.As(err, ¬Found) { @@ -120,16 +120,16 @@ func resourceRedisCloudPrivateServiceConnectRead(ctx context.Context, d *schema. func resourceRedisCloudPrivateServiceConnectDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) - defer subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) + defer utils.SubscriptionMutex.Unlock(subscriptionId) - err = api.client.PrivateServiceConnect.DeleteService(ctx, subscriptionId) + err = api.Client.PrivateServiceConnect.DeleteService(ctx, subscriptionId) if err != nil { var notFound *psc.NotFound if errors.As(err, ¬Found) { @@ -141,7 +141,7 @@ func resourceRedisCloudPrivateServiceConnectDelete(ctx context.Context, d *schem d.SetId("") - err = waitForSubscriptionToBeActive(ctx, subscriptionId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subscriptionId, api) if err != nil { return diag.FromErr(err) } @@ -180,33 +180,13 @@ func toPscServiceId(id string) (*privateServiceConnectServiceId, error) { }, nil } -func refreshPrivateServiceConnectServiceStatus(ctx context.Context, subscriptionId int, api *apiClient) (result interface{}, state string, err error) { +func refreshPrivateServiceConnectServiceStatus(ctx context.Context, subscriptionId int, api *utils.ApiClient) (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for private service connect service status %d to be active", subscriptionId) - pscService, err := api.client.PrivateServiceConnect.GetService(ctx, subscriptionId) + pscService, err := api.Client.PrivateServiceConnect.GetService(ctx, subscriptionId) if err != nil { return nil, "", err } return redis.StringValue(pscService.Status), redis.StringValue(pscService.Status), nil } - -func waitForPrivateServiceConnectServiceToBeActive(ctx context.Context, refreshFunc func() (result interface{}, state string, err error)) error { - wait := &retry.StateChangeConf{ - Pending: []string{ - psc.ServiceStatusCreateQueued, - psc.ServiceStatusInitialized, - psc.ServiceStatusCreatePending}, - Target: []string{psc.ServiceStatusActive}, - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: refreshFunc, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} diff --git a/provider/resource_rediscloud_private_service_connect_endpoint.go b/provider/private_service_connect/resource_rediscloud_private_service_connect_endpoint.go similarity index 61% rename from provider/resource_rediscloud_private_service_connect_endpoint.go rename to provider/private_service_connect/resource_rediscloud_private_service_connect_endpoint.go index 2cdc5b28..dede2631 100644 --- a/provider/resource_rediscloud_private_service_connect_endpoint.go +++ b/provider/private_service_connect/resource_rediscloud_private_service_connect_endpoint.go @@ -1,22 +1,19 @@ -package provider +package private_service_connect import ( "context" "errors" - "fmt" - "log" "strconv" - "strings" "time" "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func resourceRedisCloudPrivateServiceConnectEndpoint() *schema.Resource { +func ResourceRedisCloudPrivateServiceConnectEndpoint() *schema.Resource { return &schema.Resource{ Description: "Manages a Private Service Connect Endpoint to a Pro Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudPrivateServiceConnectEndpointCreate, @@ -109,14 +106,14 @@ func resourceRedisCloudPrivateServiceConnectEndpoint() *schema.Resource { } func resourceRedisCloudPrivateServiceConnectEndpointCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) - defer subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) + defer utils.SubscriptionMutex.Unlock(subscriptionId) pscServiceId := d.Get("private_service_connect_service_id").(int) gcpProjectId := d.Get("gcp_project_id").(string) @@ -124,7 +121,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointCreate(ctx context.Context, gcpVpcSubnetName := d.Get("gcp_vpc_subnet_name").(string) endpointConnectionNamePrefix := d.Get("endpoint_connection_name").(string) - endpointId, err := api.client.PrivateServiceConnect.CreateEndpoint(ctx, subscriptionId, pscServiceId, psc.CreatePrivateServiceConnectEndpoint{ + endpointId, err := api.Client.PrivateServiceConnect.CreateEndpoint(ctx, subscriptionId, pscServiceId, psc.CreatePrivateServiceConnectEndpoint{ GCPProjectID: &gcpProjectId, GCPVPCName: &gcpVpcName, GCPVPCSubnetName: &gcpVpcSubnetName, @@ -137,7 +134,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointCreate(ctx context.Context, d.SetId(buildPrivateServiceConnectEndpointId(subscriptionId, pscServiceId, endpointId)) - err = waitForSubscriptionToBeActive(ctx, subscriptionId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subscriptionId, api) if err != nil { return diag.FromErr(err) } @@ -147,14 +144,14 @@ func resourceRedisCloudPrivateServiceConnectEndpointCreate(ctx context.Context, func resourceRedisCloudPrivateServiceConnectEndpointRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) resId, err := toPscEndpointId(d.Id()) if err != nil { return diag.FromErr(err) } - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, resId.subscriptionId, resId.pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, resId.subscriptionId, resId.pscServiceId) if err != nil { var notFound *psc.NotFound if errors.As(err, ¬Found) { @@ -164,7 +161,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointRead(ctx context.Context, d return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) if endpoint == nil { d.SetId("") return diags @@ -173,7 +170,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointRead(ctx context.Context, d d.SetId(buildPrivateServiceConnectEndpointId(resId.subscriptionId, resId.pscServiceId, *endpoint.ID)) if redis.StringValue(endpoint.Status) != psc.EndpointStatusRejected && redis.StringValue(endpoint.Status) != psc.EndpointStatusDeleted { - creationScript, err := api.client.PrivateServiceConnect.GetEndpointCreationScripts(ctx, + creationScript, err := api.Client.PrivateServiceConnect.GetEndpointCreationScripts(ctx, resId.subscriptionId, resId.pscServiceId, *endpoint.ID, true) if err != nil { var notFound *psc.NotFound @@ -184,7 +181,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointRead(ctx context.Context, d return diag.FromErr(err) } - if err := d.Set("service_attachments", flattenPrivateServiceConnectEndpointServiceAttachments(creationScript.Script.TerraformGcp.ServiceAttachments)); err != nil { + if err := d.Set("service_attachments", utils.FlattenPrivateServiceConnectEndpointServiceAttachments(creationScript.Script.TerraformGcp.ServiceAttachments)); err != nil { return diag.FromErr(err) } } else { @@ -233,16 +230,16 @@ func resourceRedisCloudPrivateServiceConnectEndpointRead(ctx context.Context, d func resourceRedisCloudPrivateServiceConnectEndpointDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) resId, err := toPscEndpointId(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(resId.subscriptionId) - defer subscriptionMutex.Unlock(resId.subscriptionId) + utils.SubscriptionMutex.Lock(resId.subscriptionId) + defer utils.SubscriptionMutex.Unlock(resId.subscriptionId) - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, resId.subscriptionId, resId.pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, resId.subscriptionId, resId.pscServiceId) if err != nil { var notFound *psc.NotFound if errors.As(err, ¬Found) { @@ -252,7 +249,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointDelete(ctx context.Context, return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) if endpoint == nil { d.SetId("") return diags @@ -260,7 +257,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointDelete(ctx context.Context, if redis.StringValue(endpoint.Status) == psc.EndpointStatusInitialized { // It's only possible to delete an endpoint in initialized status - err = api.client.PrivateServiceConnect.DeleteEndpoint(ctx, resId.subscriptionId, resId.pscServiceId, resId.endpointId) + err = api.Client.PrivateServiceConnect.DeleteEndpoint(ctx, resId.subscriptionId, resId.pscServiceId, resId.endpointId) if err != nil { return diag.FromErr(err) } @@ -269,7 +266,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointDelete(ctx context.Context, // Endpoints will be automatically removed once related GCP resources are removed. So we will wait for this // to happen, but we can't check the GCP resources from this provider - err = waitForPrivateServiceConnectServiceEndpointDisappear(ctx, func() (result interface{}, state string, err error) { + err = utils.WaitForPrivateServiceConnectServiceEndpointDisappear(ctx, func() (result interface{}, state string, err error) { return refreshPrivateServiceConnectServiceEndpointDisappear(ctx, resId.subscriptionId, resId.pscServiceId, resId.endpointId, api) }) if err != nil { @@ -280,119 +277,3 @@ func resourceRedisCloudPrivateServiceConnectEndpointDelete(ctx context.Context, return diags } - -func buildPrivateServiceConnectEndpointId(subId int, pscId int, endpointId int) string { - return privateServiceConnectEndpointId{ - subscriptionId: subId, - pscServiceId: pscId, - endpointId: endpointId}.String() -} - -type privateServiceConnectEndpointId struct { - subscriptionId int - pscServiceId int - endpointId int -} - -func (p privateServiceConnectEndpointId) String() string { - return fmt.Sprintf("%d/%d/%d", p.subscriptionId, p.pscServiceId, p.endpointId) -} - -func toPscEndpointId(id string) (*privateServiceConnectEndpointId, error) { - parts := strings.Split(id, "/") - if len(parts) != 3 { - return nil, fmt.Errorf("invalid id: %s", id) - } - - subId, err := strconv.Atoi(parts[0]) - if err != nil { - return nil, err - } - - pscId, err := strconv.Atoi(parts[1]) - if err != nil { - return nil, err - } - - endpointId, err := strconv.Atoi(parts[2]) - if err != nil { - return nil, err - } - - return &privateServiceConnectEndpointId{ - subscriptionId: subId, - pscServiceId: pscId, - endpointId: endpointId, - }, nil -} - -func refreshPrivateServiceConnectServiceEndpointDisappear(ctx context.Context, subscriptionId int, - pscServiceId int, endpointId int, api *apiClient) (result interface{}, state string, err error) { - - log.Printf("[DEBUG] Waiting for private service connect service endpoint %d/%d/%d to be deleted", - subscriptionId, pscServiceId, endpointId) - - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, subscriptionId, pscServiceId) - if err != nil { - return nil, "", err - } - - endpoint := findPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) - if endpoint == nil { - return placeholderStatusDisappear, placeholderStatusDisappear, nil - } - - return redis.StringValue(endpoint.Status), redis.StringValue(endpoint.Status), nil -} - -func findPrivateServiceConnectEndpoints(id int, endpoints []*psc.PrivateServiceConnectEndpoint) *psc.PrivateServiceConnectEndpoint { - for _, endpoint := range endpoints { - if redis.IntValue(endpoint.ID) == id { - return endpoint - } - } - return nil -} - -func flattenPrivateServiceConnectEndpointServiceAttachments(serviceAttachments []psc.TerraformGCPServiceAttachment) []map[string]interface{} { - var rl []map[string]interface{} - for _, serviceAttachment := range serviceAttachments { - - serviceAttachmentMapString := map[string]interface{}{ - "name": serviceAttachment.Name, - "dns_record": serviceAttachment.DNSRecord, - "ip_address_name": serviceAttachment.IPAddressName, - "forwarding_rule_name": serviceAttachment.ForwardingRuleName, - } - - rl = append(rl, serviceAttachmentMapString) - } - - return rl -} - -func waitForPrivateServiceConnectServiceEndpointDisappear(ctx context.Context, refreshFunc func() (result interface{}, state string, err error)) error { - wait := &retry.StateChangeConf{ - Pending: []string{ - psc.EndpointStatusProcessing, - psc.EndpointStatusPending, - psc.EndpointStatusAcceptPending, - psc.EndpointStatusActive, - psc.EndpointStatusDeleted, - psc.EndpointStatusRejected, - psc.EndpointStatusRejectPending, - psc.EndpointStatusFailed, - }, - Target: []string{placeholderStatusDisappear}, - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: refreshFunc, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} diff --git a/provider/resource_rediscloud_private_service_connect_endpoint_accepter.go b/provider/private_service_connect/resource_rediscloud_private_service_connect_endpoint_accepter.go similarity index 64% rename from provider/resource_rediscloud_private_service_connect_endpoint_accepter.go rename to provider/private_service_connect/resource_rediscloud_private_service_connect_endpoint_accepter.go index d37945fb..c44fca42 100644 --- a/provider/resource_rediscloud_private_service_connect_endpoint_accepter.go +++ b/provider/private_service_connect/resource_rediscloud_private_service_connect_endpoint_accepter.go @@ -1,4 +1,4 @@ -package provider +package private_service_connect import ( "context" @@ -11,13 +11,13 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudPrivateServiceConnectEndpointAccepter() *schema.Resource { +func ResourceRedisCloudPrivateServiceConnectEndpointAccepter() *schema.Resource { return &schema.Resource{ Description: "Manages the state of Private Service Connect Endpoint to a Pro Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate, @@ -63,20 +63,20 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepter() *schema.Resource func resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subscriptionId) - defer subscriptionMutex.Unlock(subscriptionId) + utils.SubscriptionMutex.Lock(subscriptionId) + defer utils.SubscriptionMutex.Unlock(subscriptionId) pscServiceId := d.Get("private_service_connect_service_id").(int) endpointId := d.Get("private_service_connect_endpoint_id").(int) action := d.Get("action").(string) - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, subscriptionId, pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, subscriptionId, pscServiceId) if err != nil { var notFound *psc.NotFoundActiveActive if errors.As(err, ¬Found) { @@ -86,7 +86,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate(ctx context.C return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) if endpoint == nil { return diag.FromErr(fmt.Errorf("endpoint with id %d not found", endpointId)) } @@ -110,7 +110,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate(ctx context.C } if redis.StringValue(endpoint.Status) == psc.EndpointStatusInitialized || redis.StringValue(endpoint.Status) == psc.EndpointStatusProcessing { - err = waitForPrivateServiceConnectServiceEndpointToBePending(ctx, refreshFunc) + err = utils.WaitForPrivateServiceConnectServiceEndpointToBePending(ctx, refreshFunc) if err != nil { return diag.FromErr(err) } @@ -118,7 +118,7 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate(ctx context.C d.SetId(buildPrivateServiceConnectEndpointAccepterId(subscriptionId, pscServiceId, endpointId)) - err = api.client.PrivateServiceConnect.UpdateEndpoint(ctx, subscriptionId, pscServiceId, endpointId, &psc.UpdatePrivateServiceConnectEndpoint{ + err = api.Client.PrivateServiceConnect.UpdateEndpoint(ctx, subscriptionId, pscServiceId, endpointId, &psc.UpdatePrivateServiceConnectEndpoint{ Action: redis.String(action), }) if err != nil { @@ -126,12 +126,12 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate(ctx context.C } if action == psc.EndpointActionAccept { - err = waitForPrivateServiceConnectServiceEndpointToBeActive(ctx, refreshFunc) + err = utils.WaitForPrivateServiceConnectServiceEndpointToBeActive(ctx, refreshFunc) if err != nil { return diag.FromErr(err) } } else { - err = waitForPrivateServiceConnectServiceEndpointToBeRejected(ctx, refreshFunc) + err = utils.WaitForPrivateServiceConnectServiceEndpointToBeRejected(ctx, refreshFunc) if err != nil { return diag.FromErr(err) } @@ -142,14 +142,14 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterCreate(ctx context.C func resourceRedisCloudPrivateServiceConnectEndpointAccepterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - resId, err := toPscEndpointAccepterId(d.Id()) + resId, err := ToPscEndpointAccepterId(d.Id()) if err != nil { return diag.FromErr(err) } - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, resId.subscriptionId, resId.pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, resId.SubscriptionId, resId.PscServiceId) if err != nil { var notFound *psc.NotFound if errors.As(err, ¬Found) { @@ -159,20 +159,20 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterRead(ctx context.Con return diag.FromErr(err) } - endpoint := findPrivateServiceConnectEndpoints(resId.endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(resId.EndpointId, endpoints.Endpoints) if endpoint == nil { d.SetId("") return diags } - d.SetId(buildPrivateServiceConnectEndpointAccepterId(resId.subscriptionId, resId.pscServiceId, redis.IntValue(endpoint.ID))) + d.SetId(buildPrivateServiceConnectEndpointAccepterId(resId.SubscriptionId, resId.PscServiceId, redis.IntValue(endpoint.ID))) - err = d.Set("subscription_id", strconv.Itoa(resId.subscriptionId)) + err = d.Set("subscription_id", strconv.Itoa(resId.SubscriptionId)) if err != nil { return diag.FromErr(err) } - err = d.Set("private_service_connect_service_id", resId.pscServiceId) + err = d.Set("private_service_connect_service_id", resId.PscServiceId) if err != nil { return diag.FromErr(err) } @@ -193,16 +193,16 @@ func buildPrivateServiceConnectEndpointAccepterId(subId int, pscId int, endpoint } type privateServiceConnectEndpointAccepterId struct { - subscriptionId int - pscServiceId int - endpointId int + SubscriptionId int + PscServiceId int + EndpointId int } func (p privateServiceConnectEndpointAccepterId) String() string { - return fmt.Sprintf("%d/%d/%d", p.subscriptionId, p.pscServiceId, p.endpointId) + return fmt.Sprintf("%d/%d/%d", p.SubscriptionId, p.PscServiceId, p.EndpointId) } -func toPscEndpointAccepterId(id string) (*privateServiceConnectEndpointAccepterId, error) { +func ToPscEndpointAccepterId(id string) (*privateServiceConnectEndpointAccepterId, error) { parts := strings.Split(id, "/") if len(parts) != 3 { return nil, fmt.Errorf("invalid id: %s", id) @@ -224,9 +224,9 @@ func toPscEndpointAccepterId(id string) (*privateServiceConnectEndpointAccepterI } return &privateServiceConnectEndpointAccepterId{ - subscriptionId: subId, - pscServiceId: pscId, - endpointId: endpointId, + SubscriptionId: subId, + PscServiceId: pscId, + EndpointId: endpointId, }, nil } @@ -241,64 +241,19 @@ func resourceRedisCloudPrivateServiceConnectEndpointAccepterDelete(_ context.Con } func refreshPrivateServiceConnectServiceEndpointStatus(ctx context.Context, subscriptionId int, - pscServiceId int, endpointId int, targetStatus string, api *apiClient) (result interface{}, state string, err error) { + pscServiceId int, endpointId int, targetStatus string, api *utils.ApiClient) (result interface{}, state string, err error) { log.Printf("[DEBUG] Waiting for private service connect service endpoint status %d/%d/%d to be %s", subscriptionId, pscServiceId, endpointId, targetStatus) - endpoints, err := api.client.PrivateServiceConnect.GetEndpoints(ctx, subscriptionId, pscServiceId) + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, subscriptionId, pscServiceId) if err != nil { return nil, "", err } - endpoint := findPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) + endpoint := FindPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) if endpoint == nil { return nil, "", fmt.Errorf("endpoint with id %d not found", endpointId) } return redis.StringValue(endpoint.Status), redis.StringValue(endpoint.Status), nil } - -func waitForPrivateServiceConnectServiceEndpointToBePending(ctx context.Context, refreshFunc func(targetStatus string) (result interface{}, state string, err error)) error { - targetStatus := psc.EndpointStatusPending - return waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx, func() (result interface{}, state string, err error) { - return refreshFunc(targetStatus) - }, targetStatus, []string{ - psc.EndpointStatusInitialized, - psc.EndpointStatusProcessing}) -} - -func waitForPrivateServiceConnectServiceEndpointToBeActive(ctx context.Context, refreshFunc func(targetStatus string) (result interface{}, state string, err error)) error { - targetStatus := psc.EndpointStatusActive - return waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx, func() (result interface{}, state string, err error) { - return refreshFunc(targetStatus) - }, targetStatus, []string{ - psc.EndpointStatusPending, - psc.EndpointStatusAcceptPending}) -} - -func waitForPrivateServiceConnectServiceEndpointToBeRejected(ctx context.Context, refreshFunc func(targetStatus string) (result interface{}, state string, err error)) error { - targetStatus := psc.EndpointStatusRejected - return waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx, func() (result interface{}, state string, err error) { - return refreshFunc(targetStatus) - }, targetStatus, []string{ - psc.EndpointStatusPending, - psc.EndpointStatusRejectPending}) -} - -func waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx context.Context, - refreshFunc func() (result interface{}, state string, err error), status string, pendingStatus []string) error { - wait := &retry.StateChangeConf{ - Pending: pendingStatus, - Target: []string{status}, - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: refreshFunc, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} diff --git a/provider/resource_rediscloud_subscription_peering.go b/provider/private_service_connect/resource_rediscloud_subscription_peering.go similarity index 81% rename from provider/resource_rediscloud_subscription_peering.go rename to provider/private_service_connect/resource_rediscloud_subscription_peering.go index 11da255e..60645841 100644 --- a/provider/resource_rediscloud_subscription_peering.go +++ b/provider/private_service_connect/resource_rediscloud_subscription_peering.go @@ -1,9 +1,7 @@ -package provider +package private_service_connect import ( "context" - "fmt" - "log" "regexp" "strconv" "strings" @@ -12,13 +10,13 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudSubscriptionPeering() *schema.Resource { +func ResourceRedisCloudSubscriptionPeering() *schema.Resource { return &schema.Resource{ Description: "Creates a VPC peering for an existing Redis Enterprise Cloud Subscription, allowing access to your subscription databases as if they were on the same network.", CreateContext: resourceRedisCloudSubscriptionPeeringCreate, @@ -143,15 +141,15 @@ func resourceRedisCloudSubscriptionPeering() *schema.Resource { } func resourceRedisCloudSubscriptionPeeringCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) providerName := d.Get("provider_name").(string) @@ -177,7 +175,7 @@ func resourceRedisCloudSubscriptionPeeringCreate(ctx context.Context, d *schema. if vpcCIDR, ok := d.GetOk("vpc_cidr"); ok { peeringRequest.VPCCidr = redis.String(vpcCIDR.(string)) } else if vpcCIDRs, ok := d.GetOk("vpc_cidrs"); ok { - peeringRequest.VPCCidrs = setToStringSlice(vpcCIDRs.(*schema.Set)) + peeringRequest.VPCCidrs = utils.SetToStringSlice(vpcCIDRs.(*schema.Set)) } else { return diag.Errorf("`vpc_cidr` or `vpc_cidrs` must be set when `provider_name` is `AWS`") } @@ -204,12 +202,12 @@ func resourceRedisCloudSubscriptionPeeringCreate(ctx context.Context, d *schema. peeringRequest.VPCNetworkName = redis.String(gcpNetworkName.(string)) } - peering, err := api.client.Subscription.CreateVPCPeering(ctx, subId, peeringRequest) + peering, err := api.Client.Subscription.CreateVPCPeering(ctx, subId, peeringRequest) if err != nil { return diag.FromErr(err) } - d.SetId(buildResourceId(subId, peering)) + d.SetId(utils.BuildResourceId(subId, peering)) err = waitForPeeringToBeInitiated(ctx, subId, peering, api) if err != nil { @@ -220,7 +218,7 @@ func resourceRedisCloudSubscriptionPeeringCreate(ctx context.Context, d *schema. } func resourceRedisCloudSubscriptionPeeringRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId, id, err := toVpcPeeringId(d.Id()) @@ -232,7 +230,7 @@ func resourceRedisCloudSubscriptionPeeringRead(ctx context.Context, d *schema.Re return diag.FromErr(err) } - peerings, err := api.client.Subscription.ListVPCPeering(ctx, subId) + peerings, err := api.Client.Subscription.ListVPCPeering(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -323,7 +321,7 @@ func resourceRedisCloudSubscriptionPeeringRead(ctx context.Context, d *schema.Re } func resourceRedisCloudSubscriptionPeeringDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId, id, err := toVpcPeeringId(d.Id()) @@ -331,10 +329,10 @@ func resourceRedisCloudSubscriptionPeeringDelete(ctx context.Context, d *schema. return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - err = api.client.Subscription.DeleteVPCPeering(ctx, subId, id) + err = api.Client.Subscription.DeleteVPCPeering(ctx, subId, id) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -347,68 +345,3 @@ func resourceRedisCloudSubscriptionPeeringDelete(ctx context.Context, d *schema. return diags } - -func toVpcPeeringId(id string) (int, int, error) { - parts := strings.Split(id, "/") - if len(parts) != 2 { - return 0, 0, fmt.Errorf("invalid id: %s", id) - } - - sub, err := strconv.Atoi(parts[0]) - if err != nil { - return 0, 0, err - } - - peering, err := strconv.Atoi(parts[1]) - if err != nil { - return 0, 0, err - } - - return sub, peering, nil -} - -func findVpcPeering(id int, peerings []*subscriptions.VPCPeering) *subscriptions.VPCPeering { - for _, peering := range peerings { - if redis.IntValue(peering.ID) == id { - return peering - } - } - return nil -} - -func waitForPeeringToBeInitiated(ctx context.Context, subId, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Delay: 10 * time.Second, - Pending: []string{ - subscriptions.VPCPeeringStatusInitiatingRequest, - }, - Target: []string{ - subscriptions.VPCPeeringStatusActive, - subscriptions.VPCPeeringStatusInactive, - subscriptions.VPCPeeringStatusPendingAcceptance, - }, - Timeout: 10 * time.Minute, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for vpc peering %d to be initiated", id) - - list, err := api.client.Subscription.ListVPCPeering(ctx, subId) - if err != nil { - return nil, "", err - } - - peering := findVpcPeering(id, list) - if peering == nil { - log.Printf("Peering %d/%d not present yet", subId, id) - return nil, "", nil - } - - return redis.StringValue(peering.Status), redis.StringValue(peering.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} diff --git a/provider/private_service_connect/utils.go b/provider/private_service_connect/utils.go new file mode 100644 index 00000000..087ac881 --- /dev/null +++ b/provider/private_service_connect/utils.go @@ -0,0 +1,172 @@ +package private_service_connect + +import ( + "context" + "fmt" + "log" + "strconv" + "strings" + "time" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +func buildPrivateServiceConnectEndpointId(subId int, pscId int, endpointId int) string { + return privateServiceConnectEndpointId{ + subscriptionId: subId, + pscServiceId: pscId, + endpointId: endpointId}.String() +} + +type privateServiceConnectEndpointId struct { + subscriptionId int + pscServiceId int + endpointId int +} + +func (p privateServiceConnectEndpointId) String() string { + return fmt.Sprintf("%d/%d/%d", p.subscriptionId, p.pscServiceId, p.endpointId) +} + +func toPscEndpointId(id string) (*privateServiceConnectEndpointId, error) { + parts := strings.Split(id, "/") + if len(parts) != 3 { + return nil, fmt.Errorf("invalid id: %s", id) + } + + subId, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, err + } + + pscId, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, err + } + + endpointId, err := strconv.Atoi(parts[2]) + if err != nil { + return nil, err + } + + return &privateServiceConnectEndpointId{ + subscriptionId: subId, + pscServiceId: pscId, + endpointId: endpointId, + }, nil +} + +func refreshPrivateServiceConnectServiceEndpointDisappear(ctx context.Context, subscriptionId int, + pscServiceId int, endpointId int, api *utils.ApiClient) (result interface{}, state string, err error) { + + log.Printf("[DEBUG] Waiting for private service connect service endpoint %d/%d/%d to be deleted", + subscriptionId, pscServiceId, endpointId) + + endpoints, err := api.Client.PrivateServiceConnect.GetEndpoints(ctx, subscriptionId, pscServiceId) + if err != nil { + return nil, "", err + } + + endpoint := FindPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) + if endpoint == nil { + return utils.PlaceholderStatusDisappear, utils.PlaceholderStatusDisappear, nil + } + + return redis.StringValue(endpoint.Status), redis.StringValue(endpoint.Status), nil +} + +func FindPrivateServiceConnectEndpoints(id int, endpoints []*psc.PrivateServiceConnectEndpoint) *psc.PrivateServiceConnectEndpoint { + for _, endpoint := range endpoints { + if redis.IntValue(endpoint.ID) == id { + return endpoint + } + } + return nil +} + +func refreshPrivateServiceConnectServiceEndpointActiveActiveStatus(ctx context.Context, subscriptionId int, regionId int, + pscServiceId int, endpointId int, targetStatus string, api *utils.ApiClient) (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for private service connect service endpoint status %d/%d/%d/%d to be %s", + subscriptionId, regionId, pscServiceId, endpointId, targetStatus) + + endpoints, err := api.Client.PrivateServiceConnect.GetActiveActiveEndpoints(ctx, subscriptionId, regionId, pscServiceId) + if err != nil { + return nil, "", err + } + + endpoint := FindPrivateServiceConnectEndpoints(endpointId, endpoints.Endpoints) + if endpoint == nil { + return nil, "", fmt.Errorf("endpoint with id %d not found", endpointId) + } + + return redis.StringValue(endpoint.Status), redis.StringValue(endpoint.Status), nil +} + +func toVpcPeeringId(id string) (int, int, error) { + parts := strings.Split(id, "/") + if len(parts) != 2 { + return 0, 0, fmt.Errorf("invalid id: %s", id) + } + + sub, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, err + } + + peering, err := strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, err + } + + return sub, peering, nil +} + +func findVpcPeering(id int, peerings []*subscriptions.VPCPeering) *subscriptions.VPCPeering { + for _, peering := range peerings { + if redis.IntValue(peering.ID) == id { + return peering + } + } + return nil +} + +func waitForPeeringToBeInitiated(ctx context.Context, subId, id int, api *utils.ApiClient) error { + wait := &retry.StateChangeConf{ + Delay: 10 * time.Second, + Pending: []string{ + subscriptions.VPCPeeringStatusInitiatingRequest, + }, + Target: []string{ + subscriptions.VPCPeeringStatusActive, + subscriptions.VPCPeeringStatusInactive, + subscriptions.VPCPeeringStatusPendingAcceptance, + }, + Timeout: 10 * time.Minute, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for vpc peering %d to be initiated", id) + + list, err := api.Client.Subscription.ListVPCPeering(ctx, subId) + if err != nil { + return nil, "", err + } + + peering := findVpcPeering(id, list) + if peering == nil { + log.Printf("Peering %d/%d not present yet", subId, id) + return nil, "", nil + } + + return redis.StringValue(peering.Status), redis.StringValue(peering.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} diff --git a/provider/datasource_rediscloud_pro_database.go b/provider/pro/datasource_rediscloud_pro_database.go similarity index 94% rename from provider/datasource_rediscloud_pro_database.go rename to provider/pro/datasource_rediscloud_pro_database.go index 1bb336be..5b280c47 100644 --- a/provider/datasource_rediscloud_pro_database.go +++ b/provider/pro/datasource_rediscloud_pro_database.go @@ -1,4 +1,4 @@ -package provider +package pro import ( "context" @@ -8,12 +8,13 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func dataSourceRedisCloudProDatabase() *schema.Resource { +func DataSourceRedisCloudProDatabase() *schema.Resource { return &schema.Resource{ Description: "The Pro Database data source allows access to the details of an existing database within your Redis Enterprise Cloud account.", ReadContext: dataSourceRedisCloudProDatabaseRead, @@ -317,7 +318,7 @@ func dataSourceRedisCloudProDatabase() *schema.Resource { func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -352,7 +353,7 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource }) } - list := api.client.Database.List(ctx, subId) + list := api.Client.Database.List(ctx, subId) dbs, err := filterProDatabases(list, filters) if err != nil { return diag.FromErr(list.Err()) @@ -368,7 +369,7 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource // Some attributes are only returned when retrieving a single database dbId := redis.IntValue(dbs[0].ID) - db, err := api.client.Database.Get(ctx, subId, dbId) + db, err := api.Client.Database.Get(ctx, subId, dbId) if err != nil { return diag.FromErr(list.Err()) } @@ -436,15 +437,15 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource return diag.FromErr(err) } } - if err := d.Set("alert", flattenAlerts(db.Alerts)); err != nil { + if err := d.Set("alert", utils.FlattenAlerts(db.Alerts)); err != nil { return diag.FromErr(err) } - if err := d.Set("module", flattenModules(db.Modules)); err != nil { + if err := d.Set("module", utils.FlattenModules(db.Modules)); err != nil { return diag.FromErr(err) } if db.Clustering != nil { - if err := d.Set("hashing_policy", flattenRegexRules(db.Clustering.RegexRules)); err != nil { + if err := d.Set("hashing_policy", utils.FlattenRegexRules(db.Clustering.RegexRules)); err != nil { return diag.FromErr(err) } } @@ -455,11 +456,11 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource } var parsedLatestBackupStatus []map[string]interface{} - latestBackupStatus, err := api.client.LatestBackup.Get(ctx, subId, dbId) + latestBackupStatus, err := api.Client.LatestBackup.Get(ctx, subId, dbId) if err != nil { // Forgive errors here, sometimes we just can't get a latest status } else { - parsedLatestBackupStatus, err = parseLatestBackupStatus(latestBackupStatus) + parsedLatestBackupStatus, err = utils.ParseLatestBackupStatus(latestBackupStatus) if err != nil { return diag.FromErr(err) } @@ -469,11 +470,11 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource } var parsedLatestImportStatus []map[string]interface{} - latestImportStatus, err := api.client.LatestImport.Get(ctx, subId, dbId) + latestImportStatus, err := api.Client.LatestImport.Get(ctx, subId, dbId) if err != nil { // Forgive errors here, sometimes we just can't get a latest status } else { - parsedLatestImportStatus, err = parseLatestImportStatus(latestImportStatus) + parsedLatestImportStatus, err = utils.ParseLatestImportStatus(latestImportStatus) if err != nil { return diag.FromErr(err) } @@ -482,7 +483,7 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource return diag.FromErr(err) } - if err := readTags(ctx, api, subId, dbId, d); err != nil { + if err := utils.ReadTags(ctx, api, subId, dbId, d); err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_pro_subscription.go b/provider/pro/datasource_rediscloud_pro_subscription.go similarity index 90% rename from provider/datasource_rediscloud_pro_subscription.go rename to provider/pro/datasource_rediscloud_pro_subscription.go index 8fe6554b..95efdd9c 100644 --- a/provider/datasource_rediscloud_pro_subscription.go +++ b/provider/pro/datasource_rediscloud_pro_subscription.go @@ -1,15 +1,17 @@ -package provider +package pro import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceRedisCloudProSubscription() *schema.Resource { +func DataSourceRedisCloudProSubscription() *schema.Resource { return &schema.Resource{ Description: "The Pro Subscription data source allows access to the details of an existing pro subscription within your Redis Enterprise Cloud account.", ReadContext: dataSourceRedisCloudProSubscriptionRead, @@ -222,9 +224,9 @@ func dataSourceRedisCloudProSubscription() *schema.Resource { func dataSourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - subs, err := api.client.Subscription.List(ctx) + subs, err := api.Client.Subscription.List(ctx) if err != nil { return diag.FromErr(err) } @@ -242,7 +244,7 @@ func dataSourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Reso }) } - subs = filterSubscriptions(subs, filters) + subs = utils.FilterSubscriptions(subs, filters) if len(subs) == 0 { return diag.Errorf("Your query returned no results. Please change your search criteria and try again.") @@ -282,19 +284,19 @@ func dataSourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Reso subId := redis.IntValue(sub.ID) - m, err := api.client.Maintenance.Get(ctx, subId) + m, err := api.Client.Maintenance.Get(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { + if err := d.Set("maintenance_windows", utils.FlattenMaintenance(m)); err != nil { return diag.FromErr(err) } - pricingList, err := api.client.Pricing.List(ctx, subId) + pricingList, err := api.Client.Pricing.List(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { + if err := d.Set("pricing", utils.FlattenPricing(pricingList)); err != nil { return diag.FromErr(err) } @@ -302,23 +304,3 @@ func dataSourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Reso return diags } - -func filterSubscriptions(subs []*subscriptions.Subscription, filters []func(sub *subscriptions.Subscription) bool) []*subscriptions.Subscription { - var filteredSubs []*subscriptions.Subscription - for _, sub := range subs { - if filterSub(sub, filters) { - filteredSubs = append(filteredSubs, sub) - } - } - - return filteredSubs -} - -func filterSub(method *subscriptions.Subscription, filters []func(method *subscriptions.Subscription) bool) bool { - for _, f := range filters { - if !f(method) { - return false - } - } - return true -} diff --git a/provider/resource_rediscloud_pro_database.go b/provider/pro/resource_rediscloud_pro_database.go similarity index 71% rename from provider/resource_rediscloud_pro_database.go rename to provider/pro/resource_rediscloud_pro_database.go index c397a0f5..2c46c006 100644 --- a/provider/resource_rediscloud_pro_database.go +++ b/provider/pro/resource_rediscloud_pro_database.go @@ -1,26 +1,21 @@ -package provider +package pro import ( "context" "fmt" - "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "log" "regexp" - "strconv" - "strings" "time" - redisTags "github.com/RedisLabs/rediscloud-go-api/service/tags" - "github.com/hashicorp/go-cty/cty" - "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -func resourceRedisCloudProDatabase() *schema.Resource { +func ResourceRedisCloudProDatabase() *schema.Resource { return &schema.Resource{ Description: "Creates database resource within a pro subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudProDatabaseCreate, @@ -30,7 +25,7 @@ func resourceRedisCloudProDatabase() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - subId, dbId, err := toDatabaseId(d.Id()) + subId, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return nil, err } @@ -40,7 +35,7 @@ func resourceRedisCloudProDatabase() *schema.Resource { if err := d.Set("db_id", dbId); err != nil { return nil, err } - d.SetId(buildResourceId(subId, dbId)) + d.SetId(utils.BuildResourceId(subId, dbId)) return []*schema.ResourceData{d}, nil }, }, @@ -321,7 +316,7 @@ func resourceRedisCloudProDatabase() *schema.Resource { Description: "Defines the hour automatic backups are made - only applicable when interval is `every-12-hours` or `every-24-hours`", Type: schema.TypeString, Optional: true, - ValidateDiagFunc: isTime(), + ValidateDiagFunc: utils.IsTime(), DiffSuppressFunc: skipDiffIfIntervalIs12And12HourTimeDiff, }, "storage_type": { @@ -345,17 +340,17 @@ func resourceRedisCloudProDatabase() *schema.Resource { Type: schema.TypeString, }, Optional: true, - ValidateDiagFunc: validateTagsfunc, + ValidateDiagFunc: utils.ValidateTagsfunc, }, }, } } func resourceRedisCloudProDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId := *utils.GetInt(d, "subscription_id") - subscriptionMutex.Lock(subId) + utils.SubscriptionMutex.Lock(subId) createModules := make([]*databases.Module, 0) modules := d.Get("modules").(*schema.Set) @@ -400,7 +395,7 @@ func resourceRedisCloudProDatabaseCreate(ctx context.Context, d *schema.Resource }, Modules: createModules, Alerts: createAlerts, - RemoteBackup: buildBackupPlan(d.Get("remote_backup").([]interface{}), d.Get("periodic_backup_path")), + RemoteBackup: utils.BuildBackupPlan(d.Get("remote_backup").([]interface{}), d.Get("periodic_backup_path")), } utils.SetStringIfNotEmpty(d, "query_performance_factor", func(s *string) { @@ -436,40 +431,40 @@ func resourceRedisCloudProDatabaseCreate(ctx context.Context, d *schema.Resource }) // Confirm sub is ready to accept a db request - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } - dbId, err := api.client.Database.Create(ctx, subId, createDatabase) + dbId, err := api.Client.Database.Create(ctx, subId, createDatabase) if err != nil { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - d.SetId(buildResourceId(subId, dbId)) + d.SetId(utils.BuildResourceId(subId, dbId)) // Confirm db + sub active status - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } // Some attributes on a database are not accessible by the subscription creation API. // Run the subscription update function to apply any additional changes to the databases, such as password, enableDefaultUser and so on. - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return resourceRedisCloudProDatabaseUpdate(ctx, d, meta) } func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics - subId, dbId, err := toDatabaseId(d.Id()) + subId, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } @@ -479,7 +474,7 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa subId = d.Get("subscription_id").(int) } - db, err := api.client.Database.Get(ctx, subId, dbId) + db, err := api.Client.Database.Get(ctx, subId, dbId) if err != nil { if _, ok := err.(*databases.NotFound); ok { d.SetId("") @@ -544,11 +539,11 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa return diag.FromErr(err) } - if err := d.Set("modules", flattenModules(db.Modules)); err != nil { + if err := d.Set("modules", utils.FlattenModules(db.Modules)); err != nil { return diag.FromErr(err) } - if err := d.Set("alert", flattenAlerts(db.Alerts)); err != nil { + if err := d.Set("alert", utils.FlattenAlerts(db.Alerts)); err != nil { return diag.FromErr(err) } @@ -595,7 +590,7 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa return diag.FromErr(err) } - if err := d.Set("hashing_policy", flattenRegexRules(db.Clustering.RegexRules)); err != nil { + if err := d.Set("hashing_policy", utils.FlattenRegexRules(db.Clustering.RegexRules)); err != nil { return diag.FromErr(err) } @@ -608,11 +603,11 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa } tlsAuthEnabled := *db.Security.TLSClientAuthentication - if err := applyCertificateHints(tlsAuthEnabled, d); err != nil { + if err := utils.ApplyCertificateHints(tlsAuthEnabled, d); err != nil { return diag.FromErr(err) } - if err := d.Set("remote_backup", flattenBackupPlan(db.Backup, d.Get("remote_backup").([]interface{}), d.Get("periodic_backup_path").(string))); err != nil { + if err := d.Set("remote_backup", utils.FlattenBackupPlan(db.Backup, d.Get("remote_backup").([]interface{}), d.Get("periodic_backup_path").(string))); err != nil { return diag.FromErr(err) } @@ -622,7 +617,7 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa } } - if err := readTags(ctx, api, subId, dbId, d); err != nil { + if err := utils.ReadTags(ctx, api, subId, dbId, d); err != nil { return diag.FromErr(err) } @@ -631,32 +626,32 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa func resourceRedisCloudProDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics subId := d.Get("subscription_id").(int) - _, dbId, err := toDatabaseId(d.Id()) + _, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) // Confirm sub + db are ready to accept a db request - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { return diag.FromErr(err) } - if err := api.client.Database.Delete(ctx, subId, dbId); err != nil { + if err := api.Client.Database.Delete(ctx, subId, dbId); err != nil { return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } @@ -664,15 +659,15 @@ func resourceRedisCloudProDatabaseDelete(ctx context.Context, d *schema.Resource } func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) - _, dbId, err := toDatabaseId(d.Id()) + _, dbId, err := utils.ToDatabaseId(d.Id()) if err != nil { return diag.FromErr(err) } subId := d.Get("subscription_id").(int) - subscriptionMutex.Lock(subId) + utils.SubscriptionMutex.Lock(subId) // If the recommended approach is taken and there are 0 alerts, a nil-slice value is sent to the UpdateDatabase // constructor. We instead want a non-nil (but zero length) slice to be passed forward. @@ -699,9 +694,9 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource DataPersistence: utils.GetString(d, "data_persistence"), DataEvictionPolicy: utils.GetString(d, "data_eviction"), - SourceIP: setToStringSlice(d.Get("source_ips").(*schema.Set)), + SourceIP: utils.SetToStringSlice(d.Get("source_ips").(*schema.Set)), Alerts: &alerts, - RemoteBackup: buildBackupPlan(d.Get("remote_backup").([]interface{}), d.Get("periodic_backup_path")), + RemoteBackup: utils.BuildBackupPlan(d.Get("remote_backup").([]interface{}), d.Get("periodic_backup_path")), EnableDefaultUser: utils.GetBool(d, "enable_default_user"), } @@ -715,7 +710,7 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource } // The below fields are optional and will only be sent in the request if they are present in the Terraform configuration - if len(setToStringSlice(d.Get("source_ips").(*schema.Set))) == 0 { + if len(utils.SetToStringSlice(d.Get("source_ips").(*schema.Set))) == 0 { update.SourceIP = []*string{redis.String("0.0.0.0/0")} } @@ -728,14 +723,14 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource update.Password = redis.String(d.Get("password").(string)) } - update.ReplicaOf = setToStringSlice(d.Get("replica_of").(*schema.Set)) + update.ReplicaOf = utils.SetToStringSlice(d.Get("replica_of").(*schema.Set)) if update.ReplicaOf == nil { update.ReplicaOf = make([]*string, 0) } // The cert validation is done by the API (HTTP 400 is returned if it's invalid). clientSSLCertificate := d.Get("client_ssl_certificate").(string) - clientTLSCertificates := interfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) + clientTLSCertificates := utils.InterfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) enableTLS := d.Get("enable_tls").(bool) if enableTLS { update.EnableTls = redis.Bool(enableTLS) @@ -751,7 +746,7 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource if clientSSLCertificate != "" { update.ClientSSLCertificate = redis.String(clientSSLCertificate) } else if len(clientTLSCertificates) > 0 { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return diag.Errorf("TLS certificates may not be provided while enable_tls is false") } else { // Default: enable_tls=false, client_ssl_certificate="" @@ -761,7 +756,7 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource regex := d.Get("hashing_policy").([]interface{}) if len(regex) != 0 { - update.RegexRules = interfaceToStringSlice(regex) + update.RegexRules = utils.InterfaceToStringSlice(regex) } backupPath := d.Get("periodic_backup_path").(string) @@ -777,12 +772,12 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource } // Confirm sub + db are ready to accept a db request - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } @@ -796,7 +791,7 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource if originalVersion.(string) != "" && newVersion.(string) != "" { if diags, unlocked := upgradeRedisVersion(ctx, api, subId, dbId, newVersion.(string)); diags != nil { if !unlocked { - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) } return diags } @@ -805,277 +800,49 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource // Confirm db + sub active status - if err := api.client.Database.Update(ctx, subId, dbId, update); err != nil { - subscriptionMutex.Unlock(subId) + if err := api.Client.Database.Update(ctx, subId, dbId, update); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } // Confirm db + sub active status - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err) } // The Tags API is synchronous so we shouldn't have to wait for anything - if err := writeTags(ctx, api, subId, dbId, d); err != nil { + if err := utils.WriteTags(ctx, api, subId, dbId, d); err != nil { return diag.FromErr(err) } - subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Unlock(subId) return resourceRedisCloudProDatabaseRead(ctx, d, meta) } -func upgradeRedisVersion(ctx context.Context, api *apiClient, subId int, dbId int, newVersion string) (diag.Diagnostics, bool) { +func upgradeRedisVersion(ctx context.Context, api *utils.ApiClient, subId int, dbId int, newVersion string) (diag.Diagnostics, bool) { log.Printf("[INFO] Requesting Redis version change to %s...", newVersion) upgrade := databases.UpgradeRedisVersion{ TargetRedisVersion: redis.String(newVersion), } - if err := api.client.Database.UpgradeRedisVersion(ctx, subId, dbId, upgrade); err != nil { - subscriptionMutex.Unlock(subId) + if err := api.Client.Database.UpgradeRedisVersion(ctx, subId, dbId, upgrade); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.Errorf("failed to change Redis version to %s: %v", newVersion, err), true } log.Printf("[INFO] Redis version change request to %s accepted by API", newVersion) // wait for upgrade - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { - subscriptionMutex.Unlock(subId) + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + utils.SubscriptionMutex.Unlock(subId) return diag.FromErr(err), true } return nil, false } - -func buildBackupPlan(data interface{}, periodicBackupPath interface{}) *databases.DatabaseBackupConfig { - var d map[string]interface{} - - switch v := data.(type) { - case []interface{}: - if len(v) != 1 { - if periodicBackupPath == nil { - return &databases.DatabaseBackupConfig{Active: redis.Bool(false)} - } else { - return nil - } - } - d = v[0].(map[string]interface{}) - default: - d = v.(map[string]interface{}) - } - - config := databases.DatabaseBackupConfig{ - Active: redis.Bool(true), - Interval: redis.String(d["interval"].(string)), - StorageType: redis.String(d["storage_type"].(string)), - StoragePath: redis.String(d["storage_path"].(string)), - } - - if v := d["time_utc"].(string); v != "" { - config.TimeUTC = redis.String(v) - } - - return &config -} - -func flattenBackupPlan(backup *databases.Backup, existing []interface{}, periodicBackupPath string) []map[string]interface{} { - if backup == nil || !redis.BoolValue(backup.Enabled) || periodicBackupPath != "" { - return nil - } - - storageType := "" - if len(existing) == 1 { - d := existing[0].(map[string]interface{}) - storageType = d["storage_type"].(string) - } - - return []map[string]interface{}{ - { - "interval": redis.StringValue(backup.Interval), - "time_utc": redis.StringValue(backup.TimeUTC), - "storage_type": storageType, - "storage_path": redis.StringValue(backup.Destination), - }, - } -} - -func toDatabaseId(id string) (int, int, error) { - parts := strings.Split(id, "/") - - if len(parts) > 2 { - return 0, 0, fmt.Errorf("invalid id: %s", id) - } - - if len(parts) == 1 { - dbId, err := strconv.Atoi(parts[0]) - if err != nil { - return 0, 0, err - } - return 0, dbId, nil - } - - subId, err := strconv.Atoi(parts[0]) - if err != nil { - return 0, 0, err - } - - dbId, err := strconv.Atoi(parts[1]) - if err != nil { - return 0, 0, err - } - - return subId, dbId, nil -} - -func skipDiffIfIntervalIs12And12HourTimeDiff(k, oldValue, newValue string, d *schema.ResourceData) bool { - // If interval is set to every 12 hours and the `time_utc` is in the afternoon, - // then the API will return the _morning_ time when queried. - // `interval` is assumed to be an attribute within the same block as the attribute being diffed. - - parts := strings.Split(k, ".") - parts[len(parts)-1] = "interval" - - var interval = d.Get(strings.Join(parts, ".")) - - if interval != databases.BackupIntervalEvery12Hours { - return false - } - - oldTime, err := time.Parse("15:04", oldValue) - if err != nil { - return false - } - newTime, err := time.Parse("15:04", newValue) - if err != nil { - return false - } - - return oldTime.Minute() == newTime.Minute() && oldTime.Add(12*time.Hour).Hour() == newTime.Hour() -} - -func customizeDiff() schema.CustomizeDiffFunc { - return func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { - if err := validateQueryPerformanceFactor()(ctx, diff, meta); err != nil { - return err - } - if err := remoteBackupIntervalSetCorrectly("remote_backup")(ctx, diff, meta); err != nil { - return err - } - return nil - } -} - -func validateQueryPerformanceFactor() schema.CustomizeDiffFunc { - return func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { - // Check if "query_performance_factor" is set - qpf, qpfExists := diff.GetOk("query_performance_factor") - - // Ensure "modules" is explicitly defined in the HCL - _, modulesExists := diff.GetOkExists("modules") - - if qpfExists && qpf.(string) != "" { - if !modulesExists { - return fmt.Errorf(`"query_performance_factor" requires the "modules" key to be explicitly defined in HCL`) - } - - // Retrieve modules as a slice of interfaces - rawModules := diff.Get("modules").(*schema.Set).List() - - // Convert modules to []map[string]interface{} - var modules []map[string]interface{} - for _, rawModule := range rawModules { - if moduleMap, ok := rawModule.(map[string]interface{}); ok { - modules = append(modules, moduleMap) - } - } - - // Check if "RediSearch" exists - if !containsDBModule(modules, "RediSearch") { - return fmt.Errorf(`"query_performance_factor" requires the "modules" list to contain "RediSearch"`) - } - } - return nil - } -} - -// Helper function to check if a module exists -func containsDBModule(modules []map[string]interface{}, moduleName string) bool { - for _, module := range modules { - if name, ok := module["name"].(string); ok && name == moduleName { - return true - } - } - return false -} - -func remoteBackupIntervalSetCorrectly(key string) schema.CustomizeDiffFunc { - // Validate multiple attributes - https://github.com/hashicorp/terraform-plugin-sdk/issues/233 - - return func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error { - if v, ok := diff.GetOk(key); ok { - backups := v.([]interface{}) - if len(backups) == 1 { - v := backups[0].(map[string]interface{}) - - interval := v["interval"].(string) - timeUtc := v["time_utc"].(string) - - if interval != databases.BackupIntervalEvery12Hours && interval != databases.BackupIntervalEvery24Hours && timeUtc != "" { - return fmt.Errorf("unexpected value at %s.0.time_utc - time_utc can only be set when interval is either %s or %s", key, databases.BackupIntervalEvery24Hours, databases.BackupIntervalEvery12Hours) - } - } - } - return nil - } - -} - -func readTags(ctx context.Context, api *apiClient, subId int, databaseId int, d *schema.ResourceData) error { - tags := make(map[string]string) - tagResponse, err := api.client.Tags.Get(ctx, subId, databaseId) - if err != nil { - return err - } - if tagResponse.Tags != nil { - for _, t := range *tagResponse.Tags { - tags[redis.StringValue(t.Key)] = redis.StringValue(t.Value) - } - } - return d.Set("tags", tags) -} - -func writeTags(ctx context.Context, api *apiClient, subId int, databaseId int, d *schema.ResourceData) error { - tags := make([]*redisTags.Tag, 0) - tState := d.Get("tags").(map[string]interface{}) - for k, v := range tState { - tags = append(tags, &redisTags.Tag{ - Key: redis.String(k), - Value: redis.String(v.(string)), - }) - } - return api.client.Tags.Put(ctx, subId, databaseId, redisTags.AllTags{Tags: &tags}) -} - -func validateTagsfunc(tagsRaw interface{}, _ cty.Path) diag.Diagnostics { - tags := tagsRaw.(map[string]interface{}) - invalid := make([]string, 0) - for k, v := range tags { - if k != strings.ToLower(k) { - invalid = append(invalid, k) - } - vStr := v.(string) - if vStr != strings.ToLower(vStr) { - invalid = append(invalid, vStr) - } - } - - if len(invalid) > 0 { - return diag.Errorf("tag keys and values must be lower case, invalid entries: %s", strings.Join(invalid, ", ")) - } - return nil -} diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/pro/resource_rediscloud_pro_subscription.go similarity index 76% rename from provider/resource_rediscloud_pro_subscription.go rename to provider/pro/resource_rediscloud_pro_subscription.go index 0d3e1cb0..ac8a9f7c 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/pro/resource_rediscloud_pro_subscription.go @@ -1,10 +1,9 @@ -package provider +package pro import ( "bytes" "context" "fmt" - "log" "reflect" "regexp" "strconv" @@ -14,10 +13,9 @@ import ( "github.com/RedisLabs/rediscloud-go-api/service/cloud_accounts" "github.com/RedisLabs/rediscloud-go-api/service/databases" "github.com/RedisLabs/rediscloud-go-api/service/maintenance" - "github.com/RedisLabs/rediscloud-go-api/service/pricing" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) @@ -33,7 +31,7 @@ func containsModule(modules []interface{}, requiredModule string) bool { return false } -func resourceRedisCloudProSubscription() *schema.Resource { +func ResourceRedisCloudProSubscription() *schema.Resource { return &schema.Resource{ CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { @@ -558,16 +556,16 @@ func shouldForceNewRegion(oldRegion, newRegion interface{}) bool { } func getSubscription(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) (*subscriptions.Subscription, error) { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionID, err := strconv.Atoi(diff.Id()) if err != nil { return nil, fmt.Errorf("invalid subscription ID: %w", err) } - return api.client.Subscription.Get(ctx, subscriptionID) + return api.Client.Subscription.Get(ctx, subscriptionID) } func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) // Create CloudProviders providers, err := buildCreateCloudProviders(d.Get("cloud_provider")) @@ -579,7 +577,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso name := d.Get("name").(string) paymentMethod := d.Get("payment_method").(string) - paymentMethodID, err := readPaymentMethodID(d) + paymentMethodID, err := utils.ReadPaymentMethodID(d) if err != nil { return diag.FromErr(err) } @@ -591,7 +589,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso // Create creation-plan databases planMap := plan[0].(map[string]interface{}) - dbs, diags := buildSubscriptionCreatePlanDatabases(memoryStorage, planMap) + dbs, diags := BuildSubscriptionCreatePlanDatabases(memoryStorage, planMap) if diags.HasError() { return diags } @@ -617,7 +615,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso createSubscriptionRequest.PersistentStorageEncryptionType = redis.String(CMK_ENABLED_STRING) } - subId, err := api.client.Subscription.Create(ctx, createSubscriptionRequest) + subId, err := api.Client.Subscription.Create(ctx, createSubscriptionRequest) if err != nil { return append(diags, diag.FromErr(err)...) } @@ -626,7 +624,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso // If in a CMK flow, verify the pending state if cmkEnabled { - err = waitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) + err = utils.WaitForSubscriptionToBeEncryptionKeyPending(ctx, subId, api) if err != nil { return append(diags, diag.FromErr(err)...) } @@ -634,30 +632,30 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso } // Confirm Subscription Active status - err = waitForSubscriptionToBeActive(ctx, subId, api) + err = utils.WaitForSubscriptionToBeActive(ctx, subId, api) if err != nil { return append(diags, diag.FromErr(err)...) } // There is a timing issue where the subscription is marked as active before the creation-plan databases are listed. - // This additional wait ensures that the databases will be listed before calling api.client.Database.List() + // This additional wait ensures that the databases will be listed before calling api.Client.Database.List() time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return append(diags, diag.FromErr(err)...) } // Locate Databases to confirm Active status - dbList := api.client.Database.List(ctx, subId) + dbList := api.Client.Database.List(ctx, subId) for dbList.Next() { dbId := *dbList.Value().ID - if err := waitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { + if err := utils.WaitForDatabaseToBeActive(ctx, subId, dbId, api); err != nil { return append(diags, diag.FromErr(err)...) } // Delete each creation-plan database - dbErr := api.client.Database.Delete(ctx, subId, dbId) + dbErr := api.Client.Database.Delete(ctx, subId, dbId) if dbErr != nil { diag.FromErr(dbErr) } @@ -672,7 +670,7 @@ func resourceRedisCloudProSubscriptionCreate(ctx context.Context, d *schema.Reso } func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics @@ -681,7 +679,7 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour return diag.FromErr(err) } - subscription, err := api.client.Subscription.Get(ctx, subId) + subscription, err := api.Client.Subscription.Get(ctx, subId) if err != nil { if _, ok := err.(*subscriptions.NotFound); ok { d.SetId("") @@ -729,19 +727,19 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour cmkEnabled := d.Get("customer_managed_key_enabled").(bool) if !cmkEnabled { - m, err := api.client.Maintenance.Get(ctx, subId) + m, err := api.Client.Maintenance.Get(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("maintenance_windows", flattenMaintenance(m)); err != nil { + if err := d.Set("maintenance_windows", utils.FlattenMaintenance(m)); err != nil { return diag.FromErr(err) } - pricingList, err := api.client.Pricing.List(ctx, subId) + pricingList, err := api.Client.Pricing.List(ctx, subId) if err != nil { return diag.FromErr(err) } - if err := d.Set("pricing", flattenPricing(pricingList)); err != nil { + if err := d.Set("pricing", utils.FlattenPricing(pricingList)); err != nil { return diag.FromErr(err) } } @@ -755,17 +753,17 @@ func resourceRedisCloudProSubscriptionRead(ctx context.Context, d *schema.Resour } func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Id()) if err != nil { return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - subscription, err := api.client.Subscription.Get(ctx, subId) + subscription, err := api.Client.Subscription.Get(ctx, subId) if err != nil { return diag.FromErr(err) } @@ -782,10 +780,10 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso } if d.HasChange("allowlist") { - cidrs := setToStringSlice(d.Get("allowlist.0.cidrs").(*schema.Set)) - sgs := setToStringSlice(d.Get("allowlist.0.security_group_ids").(*schema.Set)) + cidrs := utils.SetToStringSlice(d.Get("allowlist.0.cidrs").(*schema.Set)) + sgs := utils.SetToStringSlice(d.Get("allowlist.0.security_group_ids").(*schema.Set)) - err := api.client.Subscription.UpdateCIDRAllowlist(ctx, subId, subscriptions.UpdateCIDRAllowlist{ + err := api.Client.Subscription.UpdateCIDRAllowlist(ctx, subId, subscriptions.UpdateCIDRAllowlist{ CIDRIPs: cidrs, SecurityGroupIDs: sgs, }) @@ -803,7 +801,7 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso } if d.HasChange("payment_method_id") { - paymentMethodID, err := readPaymentMethodID(d) + paymentMethodID, err := utils.ReadPaymentMethodID(d) if err != nil { return diag.FromErr(err) } @@ -811,13 +809,13 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso updateSubscriptionRequest.PaymentMethodID = paymentMethodID } - err = api.client.Subscription.Update(ctx, subId, updateSubscriptionRequest) + err = api.Client.Subscription.Update(ctx, subId, updateSubscriptionRequest) if err != nil { return diag.FromErr(err) } } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } @@ -832,7 +830,7 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso windows = append(windows, &maintenance.Window{ StartHour: redis.Int(wMap["start_hour"].(int)), DurationInHours: redis.Int(wMap["duration_in_hours"].(int)), - Days: interfaceToStringSlice(wMap["days"].([]interface{})), + Days: utils.InterfaceToStringSlice(wMap["days"].([]interface{})), }) } @@ -845,7 +843,7 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso Mode: redis.String("automatic"), } } - err = api.client.Maintenance.Update(ctx, subId, updateMaintenanceRequest) + err = api.Client.Maintenance.Update(ctx, subId, updateMaintenanceRequest) if err != nil { return diag.FromErr(err) } @@ -854,7 +852,7 @@ func resourceRedisCloudProSubscriptionUpdate(ctx context.Context, d *schema.Reso return resourceRedisCloudProSubscriptionRead(ctx, d, meta) } -func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *apiClient, subId int) diag.Diagnostics { +func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.ResourceData, api *utils.ApiClient, subId int) diag.Diagnostics { cmkResourcesRaw, exists := d.GetOk("customer_managed_key") if !exists { @@ -874,11 +872,11 @@ func resourceRedisCloudProSubscriptionUpdateCmk(ctx context.Context, d *schema.R CustomerManagedKeys: &customerManagedKeys, } - if err := api.client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { + if err := api.Client.Subscription.UpdateCMKs(ctx, subId, updateCmkRequest); err != nil { return diag.FromErr(err) } - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } @@ -902,7 +900,7 @@ func buildProCmks(cmkResources []interface{}) []subscriptions.CustomerManagedKey func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { // use the meta value to retrieve your client from the provider configure method - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) var diags diag.Diagnostics @@ -911,37 +909,37 @@ func resourceRedisCloudProSubscriptionDelete(ctx context.Context, d *schema.Reso return diag.FromErr(err) } - subscriptionMutex.Lock(subId) - defer subscriptionMutex.Unlock(subId) + utils.SubscriptionMutex.Lock(subId) + defer utils.SubscriptionMutex.Unlock(subId) - subscription, err := api.client.Subscription.Get(ctx, subId) + subscription, err := api.Client.Subscription.Get(ctx, subId) if err != nil { return diag.FromErr(err) } if *subscription.Status != subscriptions.SubscriptionStatusEncryptionKeyPending { // Wait for the subscription to be active before deleting it. - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } // There is a timing issue where the subscription is marked as active before the creation-plan databases are deleted. // This additional wait ensures that the databases are deleted before the subscription is deleted. time.Sleep(30 * time.Second) //lintignore:R018 - if err := waitForSubscriptionToBeActive(ctx, subId, api); err != nil { + if err := utils.WaitForSubscriptionToBeActive(ctx, subId, api); err != nil { return diag.FromErr(err) } } // Delete subscription once all databases are deleted - err = api.client.Subscription.Delete(ctx, subId) + err = api.Client.Subscription.Delete(ctx, subId) if err != nil { return diag.FromErr(err) } d.SetId("") - err = waitForSubscriptionToBeDeleted(ctx, subId, api) + err = utils.WaitForSubscriptionToBeDeleted(ctx, subId, api) if err != nil { return diag.FromErr(err) } @@ -974,7 +972,7 @@ func buildCreateCloudProviders(providers interface{}) ([]*subscriptions.CreateCl createRegion := subscriptions.CreateRegion{ Region: redis.String(regionStr), MultipleAvailabilityZones: redis.Bool(multipleAvailabilityZones), - PreferredAvailabilityZones: interfaceToStringSlice(preferredAZs), + PreferredAvailabilityZones: utils.InterfaceToStringSlice(preferredAZs), } if v, ok := regionMap["networking_deployment_cidr"]; ok && v != "" { @@ -1006,7 +1004,7 @@ func buildCreateCloudProviders(providers interface{}) ([]*subscriptions.CreateCl return createCloudProviders, nil } -func buildSubscriptionCreatePlanDatabases(memoryStorage string, planMap map[string]interface{}) ([]*subscriptions.CreateDatabase, diag.Diagnostics) { +func BuildSubscriptionCreatePlanDatabases(memoryStorage string, planMap map[string]interface{}) ([]*subscriptions.CreateDatabase, diag.Diagnostics) { createDatabases := make([]*subscriptions.CreateDatabase, 0) @@ -1018,7 +1016,7 @@ func buildSubscriptionCreatePlanDatabases(memoryStorage string, planMap map[stri numDatabases := planMap["quantity"].(int) supportOSSClusterAPI := planMap["support_oss_cluster_api"].(bool) replication := planMap["replication"].(bool) - planModules := interfaceToStringSlice(planMap["modules"].([]interface{})) + planModules := utils.InterfaceToStringSlice(planMap["modules"].([]interface{})) memoryLimitInGB := 0.0 if v, ok := planMap["memory_limit_in_gb"]; ok && v != nil { @@ -1134,324 +1132,3 @@ func createDatabase(dbName string, idx *int, modules []*subscriptions.CreateModu } return dbs } - -func waitForSubscriptionToBeActive(ctx context.Context, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Pending: []string{subscriptions.SubscriptionStatusPending}, - Target: []string{subscriptions.SubscriptionStatusActive}, - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for subscription %d to be %s", id, subscriptions.SubscriptionStatusActive) - - subscription, err := api.client.Subscription.Get(ctx, id) - if err != nil { - return nil, "", err - } - - return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} - -func waitForSubscriptionToBeEncryptionKeyPending(ctx context.Context, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Pending: []string{subscriptions.SubscriptionStatusPending}, - Target: []string{subscriptions.SubscriptionStatusEncryptionKeyPending, subscriptions.SubscriptionStatusActive}, - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for subscription %d to be %s", id, subscriptions.SubscriptionStatusEncryptionKeyPending) - - subscription, err := api.client.Subscription.Get(ctx, id) - if err != nil { - return nil, "", err - } - - return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} - -func waitForSubscriptionToBeDeleted(ctx context.Context, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Pending: []string{subscriptions.SubscriptionStatusDeleting}, - Target: []string{"deleted"}, // TODO: update this with deleted field in SDK - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for subscription %d to be deleted", id) - - subscription, err := api.client.Subscription.Get(ctx, id) - if err != nil { - if _, ok := err.(*subscriptions.NotFound); ok { - return "deleted", "deleted", nil - } // TODO: update this with deleted field in SDK - return nil, "", err - } - - return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} - -func waitForDatabaseToBeActive(ctx context.Context, subId, id int, api *apiClient) error { - wait := &retry.StateChangeConf{ - Pending: []string{ - databases.StatusDraft, - databases.StatusPending, - databases.StatusActiveChangePending, - databases.StatusRCPActiveChangeDraft, - databases.StatusActiveChangeDraft, - databases.StatusRCPDraft, - databases.StatusRCPChangePending, - databases.StatusProxyPolicyChangePending, - databases.StatusProxyPolicyChangeDraft, - databases.StatusDynamicEndpointsCreationPending, - databases.StatusActiveUpgradePending, - }, - Target: []string{databases.StatusActive}, - Timeout: safetyTimeout, - Delay: 10 * time.Second, - PollInterval: 30 * time.Second, - - Refresh: func() (result interface{}, state string, err error) { - log.Printf("[DEBUG] Waiting for database %d to be active", id) - - database, err := api.client.Database.Get(ctx, subId, id) - if err != nil { - return nil, "", err - } - - return redis.StringValue(database.Status), redis.StringValue(database.Status), nil - }, - } - if _, err := wait.WaitForStateContext(ctx); err != nil { - return err - } - - return nil -} - -func flattenSubscriptionAllowlist(ctx context.Context, subId int, api *apiClient) ([]map[string]interface{}, error) { - allowlist, err := api.client.Subscription.GetCIDRAllowlist(ctx, subId) - if err != nil { - return nil, err - } - - if !isNil(allowlist.Errors) { - return nil, fmt.Errorf("unable to read allowlist for subscription %d: %v", subId, allowlist.Errors) - } - - var cidrs []string - for _, cidr := range allowlist.CIDRIPs { - cidrs = append(cidrs, redis.StringValue(cidr)) - } - var sgs []string - for _, sg := range allowlist.SecurityGroupIDs { - sgs = append(sgs, redis.StringValue(sg)) - } - - tfs := map[string]interface{}{} - - if len(cidrs) != 0 { - tfs["cidrs"] = cidrs - } - if len(sgs) != 0 { - tfs["security_group_ids"] = sgs - } - if len(tfs) == 0 { - return nil, nil - } - - return []map[string]interface{}{tfs}, nil -} - -func isNil(i interface{}) bool { - if i == nil { - return true - } - - if l, ok := i.([]interface{}); ok { - if len(l) == 0 { - return true - } - } - - if m, ok := i.(map[string]interface{}); ok { - if len(m) == 0 { - return true - } - } - - return false -} - -func flattenCloudDetails(cloudDetails []*subscriptions.CloudDetail, isResource bool) []map[string]interface{} { - var cdl []map[string]interface{} - - for _, currentCloudDetail := range cloudDetails { - - var regions []interface{} - for _, currentRegion := range currentCloudDetail.Regions { - - regionMapString := map[string]interface{}{ - "region": currentRegion.Region, - "multiple_availability_zones": currentRegion.MultipleAvailabilityZones, - "preferred_availability_zones": currentRegion.PreferredAvailabilityZones, - "networks": flattenNetworks(currentRegion.Networking), - } - - if isResource { - if len(currentRegion.Networking) > 0 && !redis.BoolValue(currentRegion.MultipleAvailabilityZones) { - regionMapString["networking_deployment_cidr"] = currentRegion.Networking[0].DeploymentCIDR - } else { - regionMapString["networking_deployment_cidr"] = "" - } - } - - regions = append(regions, regionMapString) - } - - cdlMapString := map[string]interface{}{ - "provider": currentCloudDetail.Provider, - "cloud_account_id": strconv.Itoa(redis.IntValue(currentCloudDetail.CloudAccountID)), - "region": regions, - } - cdl = append(cdl, cdlMapString) - } - - return cdl -} - -func flattenNetworks(networks []*subscriptions.Networking) []map[string]interface{} { - var cdl []map[string]interface{} - - for _, currentNetwork := range networks { - - networkMapString := map[string]interface{}{ - "networking_deployment_cidr": currentNetwork.DeploymentCIDR, - "networking_vpc_id": currentNetwork.VPCId, - "networking_subnet_id": currentNetwork.SubnetID, - } - - cdl = append(cdl, networkMapString) - } - - return cdl -} - -func flattenAlerts(alerts []*databases.Alert) []map[string]interface{} { - var tfs = make([]map[string]interface{}, 0) - - for _, alert := range alerts { - tf := map[string]interface{}{ - "name": redis.StringValue(alert.Name), - "value": redis.IntValue(alert.Value), - } - tfs = append(tfs, tf) - } - - return tfs -} - -func flattenModules(modules []*databases.Module) []map[string]interface{} { - - var tfs = make([]map[string]interface{}, 0) - for _, module := range modules { - - tf := map[string]interface{}{ - "name": redis.StringValue(module.Name), - } - tfs = append(tfs, tf) - } - - return tfs -} - -func flattenRegexRules(rules []*databases.RegexRule) []string { - ret := make([]string, len(rules)) - for _, rule := range rules { - ret[rule.Ordinal] = rule.Pattern - } - - if len(ret) == 2 && ret[0] == ".*\\{(?.*)\\}.*" && ret[1] == "(?.*)" { - // This is the default regex rules - https://docs.redislabs.com/latest/rc/concepts/clustering/#custom-hashing-policy - return []string{} - } - - return ret -} - -func readPaymentMethodID(d *schema.ResourceData) (*int, error) { - pmID := d.Get("payment_method_id").(string) - if pmID != "" { - pmID, err := strconv.Atoi(pmID) - if err != nil { - return nil, err - } - return redis.Int(pmID), nil - } - return nil, nil -} - -func flattenPricing(pricing []*pricing.Pricing) []map[string]interface{} { - var tfs = make([]map[string]interface{}, 0) - for _, p := range pricing { - - tf := map[string]interface{}{ - "database_name": p.DatabaseName, - "type": p.Type, - "type_details": p.TypeDetails, - "quantity": p.Quantity, - "quantity_measurement": p.QuantityMeasurement, - "price_per_unit": p.PricePerUnit, - "price_currency": p.PriceCurrency, - "price_period": p.PricePeriod, - "region": p.Region, - } - tfs = append(tfs, tf) - } - - return tfs -} - -func flattenMaintenance(m *maintenance.Maintenance) []map[string]interface{} { - var windows []map[string]interface{} - for _, w := range m.Windows { - tfw := map[string]interface{}{ - "start_hour": w.StartHour, - "duration_in_hours": w.DurationInHours, - "days": w.Days, - } - windows = append(windows, tfw) - } - - tf := map[string]interface{}{ - "mode": m.Mode, - "window": windows, - } - - return []map[string]interface{}{tf} -} diff --git a/provider/pro/utils.go b/provider/pro/utils.go new file mode 100644 index 00000000..06f63b22 --- /dev/null +++ b/provider/pro/utils.go @@ -0,0 +1,204 @@ +package pro + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func flattenSubscriptionAllowlist(ctx context.Context, subId int, api *utils.ApiClient) ([]map[string]interface{}, error) { + allowlist, err := api.Client.Subscription.GetCIDRAllowlist(ctx, subId) + if err != nil { + return nil, err + } + + if !isNil(allowlist.Errors) { + return nil, fmt.Errorf("unable to read allowlist for subscription %d: %v", subId, allowlist.Errors) + } + + var cidrs []string + for _, cidr := range allowlist.CIDRIPs { + cidrs = append(cidrs, redis.StringValue(cidr)) + } + var sgs []string + for _, sg := range allowlist.SecurityGroupIDs { + sgs = append(sgs, redis.StringValue(sg)) + } + + tfs := map[string]interface{}{} + + if len(cidrs) != 0 { + tfs["cidrs"] = cidrs + } + if len(sgs) != 0 { + tfs["security_group_ids"] = sgs + } + if len(tfs) == 0 { + return nil, nil + } + + return []map[string]interface{}{tfs}, nil +} + +func isNil(i interface{}) bool { + if i == nil { + return true + } + + if l, ok := i.([]interface{}); ok { + if len(l) == 0 { + return true + } + } + + if m, ok := i.(map[string]interface{}); ok { + if len(m) == 0 { + return true + } + } + + return false +} + +func flattenCloudDetails(cloudDetails []*subscriptions.CloudDetail, isResource bool) []map[string]interface{} { + var cdl []map[string]interface{} + + for _, currentCloudDetail := range cloudDetails { + + var regions []interface{} + for _, currentRegion := range currentCloudDetail.Regions { + + regionMapString := map[string]interface{}{ + "region": currentRegion.Region, + "multiple_availability_zones": currentRegion.MultipleAvailabilityZones, + "preferred_availability_zones": currentRegion.PreferredAvailabilityZones, + "networks": flattenNetworks(currentRegion.Networking), + } + + if isResource { + if len(currentRegion.Networking) > 0 && !redis.BoolValue(currentRegion.MultipleAvailabilityZones) { + regionMapString["networking_deployment_cidr"] = currentRegion.Networking[0].DeploymentCIDR + } else { + regionMapString["networking_deployment_cidr"] = "" + } + } + + regions = append(regions, regionMapString) + } + + cdlMapString := map[string]interface{}{ + "provider": currentCloudDetail.Provider, + "cloud_account_id": strconv.Itoa(redis.IntValue(currentCloudDetail.CloudAccountID)), + "region": regions, + } + cdl = append(cdl, cdlMapString) + } + + return cdl +} + +func flattenNetworks(networks []*subscriptions.Networking) []map[string]interface{} { + var cdl []map[string]interface{} + + for _, currentNetwork := range networks { + + networkMapString := map[string]interface{}{ + "networking_deployment_cidr": currentNetwork.DeploymentCIDR, + "networking_vpc_id": currentNetwork.VPCId, + "networking_subnet_id": currentNetwork.SubnetID, + } + + cdl = append(cdl, networkMapString) + } + + return cdl +} + +func skipDiffIfIntervalIs12And12HourTimeDiff(k, oldValue, newValue string, d *schema.ResourceData) bool { + // If interval is set to every 12 hours and the `time_utc` is in the afternoon, + // then the API will return the _morning_ time when queried. + // `interval` is assumed to be an attribute within the same block as the attribute being diffed. + + parts := strings.Split(k, ".") + parts[len(parts)-1] = "interval" + + var interval = d.Get(strings.Join(parts, ".")) + + if interval != databases.BackupIntervalEvery12Hours { + return false + } + + oldTime, err := time.Parse("15:04", oldValue) + if err != nil { + return false + } + newTime, err := time.Parse("15:04", newValue) + if err != nil { + return false + } + + return oldTime.Minute() == newTime.Minute() && oldTime.Add(12*time.Hour).Hour() == newTime.Hour() +} + +func customizeDiff() schema.CustomizeDiffFunc { + return func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { + if err := validateQueryPerformanceFactor()(ctx, diff, meta); err != nil { + return err + } + if err := utils.RemoteBackupIntervalSetCorrectly("remote_backup")(ctx, diff, meta); err != nil { + return err + } + return nil + } +} + +func validateQueryPerformanceFactor() schema.CustomizeDiffFunc { + return func(ctx context.Context, diff *schema.ResourceDiff, meta interface{}) error { + // Check if "query_performance_factor" is set + qpf, qpfExists := diff.GetOk("query_performance_factor") + + // Ensure "modules" is explicitly defined in the HCL + _, modulesExists := diff.GetOkExists("modules") + + if qpfExists && qpf.(string) != "" { + if !modulesExists { + return fmt.Errorf(`"query_performance_factor" requires the "modules" key to be explicitly defined in HCL`) + } + + // Retrieve modules as a slice of interfaces + rawModules := diff.Get("modules").(*schema.Set).List() + + // Convert modules to []map[string]interface{} + var modules []map[string]interface{} + for _, rawModule := range rawModules { + if moduleMap, ok := rawModule.(map[string]interface{}); ok { + modules = append(modules, moduleMap) + } + } + + // Check if "RediSearch" exists + if !containsDBModule(modules, "RediSearch") { + return fmt.Errorf(`"query_performance_factor" requires the "modules" list to contain "RediSearch"`) + } + } + return nil + } +} + +// Helper function to check if a module exists +func containsDBModule(modules []map[string]interface{}, moduleName string) bool { + for _, module := range modules { + if name, ok := module["name"].(string); ok && name == moduleName { + return true + } + } + return false +} diff --git a/provider/provider.go b/provider/provider.go index 8275ab6e..c8e21e93 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -6,6 +6,15 @@ import ( "log" "strings" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/account" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/acl" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/active_active" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/essentials" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/misc" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/private_service_connect" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/pro" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/transitgateway" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -13,8 +22,6 @@ import ( rediscloudApi "github.com/RedisLabs/rediscloud-go-api" ) -const RedisCloudUrlEnvVar = "REDISCLOUD_URL" - func init() { schema.DescriptionKind = schema.StringMarkdown } @@ -25,9 +32,9 @@ func New(version string) func() *schema.Provider { Schema: map[string]*schema.Schema{ "url": { Type: schema.TypeString, - Description: fmt.Sprintf("This is the URL of Redis Cloud and will default to `https://api.redislabs.com/v1`. This can also be set by the `%s` environment variable.", RedisCloudUrlEnvVar), + Description: fmt.Sprintf("This is the URL of Redis Cloud and will default to `https://api.redislabs.com/v1`. This can also be set by the `%s` environment variable.", utils.RedisCloudUrlEnvVar), Optional: true, - DefaultFunc: schema.EnvDefaultFunc(RedisCloudUrlEnvVar, ""), + DefaultFunc: schema.EnvDefaultFunc(utils.RedisCloudUrlEnvVar, ""), }, "api_key": { Type: schema.TypeString, @@ -41,62 +48,61 @@ func New(version string) func() *schema.Provider { }, }, DataSourcesMap: map[string]*schema.Resource{ - "rediscloud_cloud_account": dataSourceRedisCloudCloudAccount(), - "rediscloud_data_persistence": dataSourceRedisCloudDataPersistence(), + "rediscloud_cloud_account": account.DataSourceRedisCloudCloudAccount(), + "rediscloud_data_persistence": misc.DataSourceRedisCloudDataPersistence(), // Note the difference in public data-source name and the file/method name. // This is to help the developer relate their changes to what they would see happening in the Redis Console. // == flexible == pro - "rediscloud_subscription": dataSourceRedisCloudProSubscription(), - "rediscloud_database": dataSourceRedisCloudProDatabase(), - "rediscloud_database_modules": dataSourceRedisCloudDatabaseModules(), - "rediscloud_payment_method": dataSourceRedisCloudPaymentMethod(), - "rediscloud_regions": dataSourceRedisCloudRegions(), - "rediscloud_essentials_plan": dataSourceRedisCloudEssentialsPlan(), - "rediscloud_essentials_subscription": dataSourceRedisCloudEssentialsSubscription(), - "rediscloud_essentials_database": dataSourceRedisCloudEssentialsDatabase(), - "rediscloud_subscription_peerings": dataSourceRedisCloudSubscriptionPeerings(), - "rediscloud_private_service_connect": dataSourcePrivateServiceConnect(), - "rediscloud_private_service_connect_endpoints": dataSourcePrivateServiceConnectEndpoints(), - "rediscloud_active_active_subscription": dataSourceRedisCloudActiveActiveSubscription(), - "rediscloud_active_active_subscription_regions": dataSourceRedisCloudActiveActiveSubscriptionRegions(), + "rediscloud_subscription": pro.DataSourceRedisCloudProSubscription(), + "rediscloud_database": pro.DataSourceRedisCloudProDatabase(), + "rediscloud_database_modules": misc.DataSourceRedisCloudDatabaseModules(), + "rediscloud_payment_method": account.DataSourceRedisCloudPaymentMethod(), + "rediscloud_regions": misc.DataSourceRedisCloudRegions(), + "rediscloud_essentials_plan": essentials.DataSourceRedisCloudEssentialsPlan(), + "rediscloud_essentials_subscription": essentials.DataSourceRedisCloudEssentialsSubscription(), + "rediscloud_essentials_database": essentials.DataSourceRedisCloudEssentialsDatabase(), + "rediscloud_subscription_peerings": private_service_connect.DataSourceRedisCloudSubscriptionPeerings(), + "rediscloud_private_service_connect": private_service_connect.DataSourcePrivateServiceConnect(), + "rediscloud_private_service_connect_endpoints": private_service_connect.DataSourcePrivateServiceConnectEndpoints(), + "rediscloud_active_active_subscription": active_active.DataSourceRedisCloudActiveActiveSubscription(), + "rediscloud_active_active_subscription_regions": active_active.DataSourceRedisCloudActiveActiveSubscriptionRegions(), // Note the difference in public data-source name and the file/method name. // active_active_subscription_database == active_active_database - "rediscloud_active_active_subscription_database": dataSourceRedisCloudActiveActiveDatabase(), - "rediscloud_active_active_private_service_connect": dataSourceActiveActivePrivateServiceConnect(), - "rediscloud_active_active_private_service_connect_endpoints": dataSourceActiveActivePrivateServiceConnectEndpoints(), - "rediscloud_transit_gateway": dataSourceTransitGateway(), - "rediscloud_active_active_transit_gateway": dataSourceActiveActiveTransitGateway(), - "rediscloud_acl_rule": dataSourceRedisCloudAclRule(), - "rediscloud_acl_role": dataSourceRedisCloudAclRole(), - "rediscloud_acl_user": dataSourceRedisCloudAclUser(), + "rediscloud_active_active_subscription_database": active_active.DataSourceRedisCloudActiveActiveDatabase(), + "rediscloud_active_active_private_service_connect": active_active.DataSourceActiveActivePrivateServiceConnect(), + "rediscloud_active_active_private_service_connect_endpoints": private_service_connect.DataSourceActiveActivePrivateServiceConnectEndpoints(), + "rediscloud_transit_gateway": transitgateway.DataSourceTransitGateway(), + "rediscloud_active_active_transit_gateway": transitgateway.DataSourceActiveActiveTransitGateway(), + "rediscloud_acl_rule": acl.DataSourceRedisCloudAclRule(), + "rediscloud_acl_role": acl.DataSourceRedisCloudAclRole(), + "rediscloud_acl_user": acl.DataSourceRedisCloudAclUser(), }, ResourcesMap: map[string]*schema.Resource{ - "rediscloud_cloud_account": resourceRedisCloudCloudAccount(), - "rediscloud_essentials_subscription": resourceRedisCloudEssentialsSubscription(), - "rediscloud_essentials_database": resourceRedisCloudEssentialsDatabase(), + "rediscloud_cloud_account": account.ResourceRedisCloudCloudAccount(), + "rediscloud_essentials_subscription": essentials.ResourceRedisCloudEssentialsSubscription(), + "rediscloud_essentials_database": essentials.ResourceRedisCloudEssentialsDatabase(), // Note the difference in public resource name and the file/method name. // == flexible == pro - "rediscloud_subscription": resourceRedisCloudProSubscription(), - "rediscloud_subscription_database": resourceRedisCloudProDatabase(), - "rediscloud_subscription_peering": resourceRedisCloudSubscriptionPeering(), - "rediscloud_private_service_connect": resourceRedisCloudPrivateServiceConnect(), - "rediscloud_private_service_connect_endpoint": resourceRedisCloudPrivateServiceConnectEndpoint(), - "rediscloud_private_service_connect_endpoint_accepter": resourceRedisCloudPrivateServiceConnectEndpointAccepter(), - "rediscloud_active_active_subscription": resourceRedisCloudActiveActiveSubscription(), + "rediscloud_subscription": pro.ResourceRedisCloudProSubscription(), + "rediscloud_subscription_database": pro.ResourceRedisCloudProDatabase(), + "rediscloud_subscription_peering": private_service_connect.ResourceRedisCloudSubscriptionPeering(), + "rediscloud_private_service_connect": private_service_connect.ResourceRedisCloudPrivateServiceConnect(), + "rediscloud_private_service_connect_endpoint": private_service_connect.ResourceRedisCloudPrivateServiceConnectEndpoint(), + "rediscloud_private_service_connect_endpoint_accepter": private_service_connect.ResourceRedisCloudPrivateServiceConnectEndpointAccepter(), + "rediscloud_active_active_subscription": active_active.ResourceRedisCloudActiveActiveSubscription(), // Note the difference in public resource name and the file/method name. // active_active_subscription_database == active_active_database - "rediscloud_active_active_subscription_database": resourceRedisCloudActiveActiveDatabase(), - "rediscloud_active_active_subscription_regions": resourceRedisCloudActiveActiveSubscriptionRegions(), - "rediscloud_active_active_subscription_peering": resourceRedisCloudActiveActiveSubscriptionPeering(), - "rediscloud_active_active_private_service_connect": resourceRedisCloudActiveActivePrivateServiceConnect(), - "rediscloud_active_active_private_service_connect_endpoint": resourceRedisCloudActiveActivePrivateServiceConnectEndpoint(), - "rediscloud_active_active_private_service_connect_endpoint_accepter": resourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter(), - "rediscloud_transit_gateway_attachment": resourceRedisCloudTransitGatewayAttachment(), - "rediscloud_active_active_transit_gateway_attachment": resourceRedisCloudActiveActiveTransitGatewayAttachment(), - "rediscloud_acl_rule": resourceRedisCloudAclRule(), - "rediscloud_acl_role": resourceRedisCloudAclRole(), - "rediscloud_acl_user": resourceRedisCloudAclUser(), + "rediscloud_active_active_subscription_database": active_active.ResourceRedisCloudActiveActiveDatabase(), + "rediscloud_active_active_subscription_regions": active_active.ResourceRedisCloudActiveActiveSubscriptionRegions(), + "rediscloud_active_active_subscription_peering": private_service_connect.ResourceRedisCloudActiveActiveSubscriptionPeering(), + "rediscloud_active_active_private_service_connect": private_service_connect.ResourceRedisCloudActiveActivePrivateServiceConnect(), + "rediscloud_active_active_private_service_connect_endpoint": private_service_connect.ResourceRedisCloudActiveActivePrivateServiceConnectEndpoint(), + "rediscloud_active_active_private_service_connect_endpoint_accepter": private_service_connect.ResourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter(), + "rediscloud_transit_gateway_attachment": transitgateway.ResourceRedisCloudTransitGatewayAttachment(), + "rediscloud_acl_rule": acl.ResourceRedisCloudAclRule(), + "rediscloud_acl_role": acl.ResourceRedisCloudAclRole(), + "rediscloud_acl_user": acl.ResourceRedisCloudAclUser(), }, } @@ -106,13 +112,6 @@ func New(version string) func() *schema.Provider { } } -// Lock that must be acquired when modifying something related to a subscription as only one _thing_ can modify a subscription and all sub-resources at any time -var subscriptionMutex = newPerIdLock() - -type apiClient struct { - client *rediscloudApi.Client -} - func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) { return func(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { var config []rediscloudApi.Option @@ -141,8 +140,8 @@ func configure(version string, p *schema.Provider) func(context.Context, *schema return nil, diag.FromErr(err) } - return &apiClient{ - client: client, + return &utils.ApiClient{ + Client: client, }, nil } } diff --git a/provider/provider_test.go b/provider/provider_test.go index f76ee953..4b0cf572 100644 --- a/provider/provider_test.go +++ b/provider/provider_test.go @@ -5,10 +5,12 @@ import ( "testing" rediscloudApi "github.com/RedisLabs/rediscloud-go-api" - + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +const testResourcePrefix = "tf-test" + var testProvider *schema.Provider var providerFactories map[string]func() (*schema.Provider, error) @@ -28,7 +30,7 @@ func TestProvider(t *testing.T) { } func testAccPreCheck(t *testing.T) { - requireEnvironmentVariables(t, RedisCloudUrlEnvVar, rediscloudApi.AccessKeyEnvVar, rediscloudApi.SecretKeyEnvVar) + requireEnvironmentVariables(t, utils.RedisCloudUrlEnvVar, rediscloudApi.AccessKeyEnvVar, rediscloudApi.SecretKeyEnvVar) } func testAccAwsPreExistingCloudAccountPreCheck(t *testing.T) { @@ -62,3 +64,11 @@ func requireEnvironmentVariables(t *testing.T, names ...string) { } } } + +func testAccRequiresEnvVar(t *testing.T, envVarName string) string { + envVarValue := os.Getenv(envVarName) + if envVarValue == "" || envVarValue == "false" { + t.Skipf("Skipping test because %s is not set.", envVarName) + } + return envVarValue +} diff --git a/provider/rediscloud_acl_role_test.go b/provider/rediscloud_acl_role_test.go index 4753bf30..a4f82a83 100644 --- a/provider/rediscloud_acl_role_test.go +++ b/provider/rediscloud_acl_role_test.go @@ -3,14 +3,16 @@ package provider import ( "context" "fmt" - "github.com/RedisLabs/rediscloud-go-api/redis" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "os" "regexp" "strconv" "testing" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourceRedisCloudAclRole_CRUDI(t *testing.T) { @@ -65,8 +67,8 @@ func TestAccResourceRedisCloudAclRole_CRUDI(t *testing.T) { return fmt.Errorf("couldn't parse the role ID: %s", redis.StringValue(&r.Primary.ID)) } - client := testProvider.Meta().(*apiClient) - role, err := client.client.Roles.Get(context.TODO(), id) + client := testProvider.Meta().(*utils.ApiClient) + role, err := client.Client.Roles.Get(context.TODO(), id) if err != nil { return err } diff --git a/provider/rediscloud_acl_user_test.go b/provider/rediscloud_acl_user_test.go index 33c6f584..c265a1f9 100644 --- a/provider/rediscloud_acl_user_test.go +++ b/provider/rediscloud_acl_user_test.go @@ -3,14 +3,16 @@ package provider import ( "context" "fmt" - "github.com/RedisLabs/rediscloud-go-api/redis" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "os" "regexp" "strconv" "testing" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourceRedisCloudAclUser_CRUDI(t *testing.T) { @@ -81,8 +83,8 @@ func TestAccResourceRedisCloudAclUser_CRUDI(t *testing.T) { return fmt.Errorf("couldn't parse the role ID: %s", redis.StringValue(&r.Primary.ID)) } - client := testProvider.Meta().(*apiClient) - user, err := client.client.Users.Get(context.TODO(), id) + client := testProvider.Meta().(*utils.ApiClient) + user, err := client.Client.Users.Get(context.TODO(), id) if err != nil { return err } @@ -193,7 +195,7 @@ data "rediscloud_acl_user" "test" { ` func testAccCheckAclUserDestroy(s *terraform.State) error { - client := testProvider.Meta().(*apiClient) + client := testProvider.Meta().(*utils.ApiClient) for _, r := range s.RootModule().Resources { if r.Type != "rediscloud_acl_user" { @@ -205,7 +207,7 @@ func testAccCheckAclUserDestroy(s *terraform.State) error { return err } - roles, err := client.client.Users.List(context.TODO()) + roles, err := client.Client.Users.List(context.TODO()) if err != nil { return err } @@ -219,3 +221,84 @@ func testAccCheckAclUserDestroy(s *terraform.State) error { return nil } + +const testAccResourceRedisCloudProDatabase = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" + last_four_numbers = "5556" +} + +data "rediscloud_cloud_account" "account" { + exclude_internal_account = true + provider_type = "AWS" + name = "%s" +} + +resource "rediscloud_subscription" "example" { + + name = "%s" + payment_method_id = data.rediscloud_payment_method.card.id + memory_storage = "ram" + + allowlist { + cidrs = ["192.168.0.0/16"] + security_group_ids = [] + } + + cloud_provider { + provider = data.rediscloud_cloud_account.account.provider_type + cloud_account_id = data.rediscloud_cloud_account.account.id + region { + region = "eu-west-1" + networking_deployment_cidr = "10.0.0.0/24" + preferred_availability_zones = ["eu-west-1a"] + } + } + + creation_plan { + dataset_size_in_gb = 1 + throughput_measurement_by = "operations-per-second" + throughput_measurement_value = 1000 + quantity = 1 + replication=false + support_oss_cluster_api=false + modules = [] + } +} + +resource "rediscloud_subscription_database" "example" { + subscription_id = rediscloud_subscription.example.id + name = "example" + protocol = "redis" + dataset_size_in_gb = 3 + data_persistence = "none" + data_eviction = "allkeys-random" + throughput_measurement_by = "operations-per-second" + throughput_measurement_value = 1000 + password = "%s" + support_oss_cluster_api = false + external_endpoint_for_oss_cluster_api = false + replication = false + average_item_size_in_bytes = 0 + client_ssl_certificate = "" + periodic_backup_path = "" + enable_default_user = true + redis_version = 7.2 + + alert { + name = "dataset-size" + value = 1 + } + + modules = [ + { + name = "RedisBloom" + } + ] + + tags = { + "market" = "emea" + "material" = "cardboard" + } +} +` diff --git a/provider/rediscloud_active_active_database_test.go b/provider/rediscloud_active_active_database_test.go index 6a0abb62..9716db16 100644 --- a/provider/rediscloud_active_active_database_test.go +++ b/provider/rediscloud_active_active_database_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -83,8 +84,8 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) { return fmt.Errorf("couldn't parse the subscription ID: %s", redis.StringValue(&r.Primary.ID)) } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -93,7 +94,7 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) { return fmt.Errorf("unexpected name value: %s", redis.StringValue(sub.Name)) } - listDb := client.client.Database.List(context.TODO(), subId) + listDb := client.Client.Database.List(context.TODO(), subId) if listDb.Next() != true { return fmt.Errorf("no database found: %s", listDb.Err()) } diff --git a/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go b/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go index e9cb0145..0bb08fd0 100644 --- a/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go +++ b/provider/rediscloud_active_active_private_service_connect_endpoint_accepter_test.go @@ -8,6 +8,8 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + psc2 "github.com/RedisLabs/terraform-provider-rediscloud/provider/private_service_connect" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -23,7 +25,11 @@ func TestAccResourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter_ gcpProjectId := os.Getenv("GCP_PROJECT_ID") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccGcpProjectPreCheck(t); testAccGcpCredentialsPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccGcpProjectPreCheck(t) + testAccGcpCredentialsPreCheck(t) + }, ProviderFactories: providerFactories, ExternalProviders: map[string]resource.ExternalProvider{ "google": { @@ -40,21 +46,21 @@ func TestAccResourceRedisCloudActiveActivePrivateServiceConnectEndpointAccepter_ func(s *terraform.State) error { r := s.RootModule().Resources[resourceName] - accepterId, err := toPscEndpointActiveActiveAccepterId(r.Primary.ID) + accepterId, err := psc2.ToPscEndpointActiveActiveAccepterId(r.Primary.ID) if err != nil { return fmt.Errorf("couldn't parse the accepter ID: %s", r.Primary.ID) } - client := testProvider.Meta().(*apiClient) - endpoints, err := client.client.PrivateServiceConnect.GetActiveActiveEndpoints(context.TODO(), - accepterId.subscriptionId, accepterId.regionId, accepterId.pscServiceId) + client := testProvider.Meta().(*utils.ApiClient) + endpoints, err := client.Client.PrivateServiceConnect.GetActiveActiveEndpoints(context.TODO(), + accepterId.SubscriptionId, accepterId.RegionId, accepterId.PscServiceId) if err != nil { return err } - endpoint := findPrivateServiceConnectEndpoints(accepterId.endpointId, endpoints.Endpoints) + endpoint := psc2.FindPrivateServiceConnectEndpoints(accepterId.EndpointId, endpoints.Endpoints) if endpoint == nil { - return fmt.Errorf("couldn't find endpoint with ID: %d", accepterId.endpointId) + return fmt.Errorf("couldn't find endpoint with ID: %d", accepterId.EndpointId) } if redis.StringValue(endpoint.Status) != psc.EndpointStatusActive { diff --git a/provider/rediscloud_active_active_subscription_test.go b/provider/rediscloud_active_active_subscription_test.go index fe825f68..fd2e0c3d 100644 --- a/provider/rediscloud_active_active_subscription_test.go +++ b/provider/rediscloud_active_active_subscription_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -111,8 +112,8 @@ func TestAccResourceRedisCloudActiveActiveSubscription_CRUDI(t *testing.T) { return err } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -315,7 +316,7 @@ func TestAccResourceRedisCloudActiveActiveSubscription_createUpdateMarketplacePa } func testAccCheckActiveActiveSubscriptionDestroy(s *terraform.State) error { - client := testProvider.Meta().(*apiClient) + client := testProvider.Meta().(*utils.ApiClient) for _, r := range s.RootModule().Resources { if r.Type != "rediscloud_active_active_subscription" { @@ -327,7 +328,7 @@ func testAccCheckActiveActiveSubscriptionDestroy(s *terraform.State) error { return err } - subs, err := client.client.Subscription.List(context.TODO()) + subs, err := client.Client.Subscription.List(context.TODO()) if err != nil { return err } diff --git a/provider/rediscloud_essentials_subscription_test.go b/provider/rediscloud_essentials_subscription_test.go index 87dbdaba..89911235 100644 --- a/provider/rediscloud_essentials_subscription_test.go +++ b/provider/rediscloud_essentials_subscription_test.go @@ -4,13 +4,15 @@ import ( "context" "flag" "fmt" + "regexp" + "strconv" + "testing" + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "regexp" - "strconv" - "testing" ) var essentialsMarketplaceFlag = flag.Bool("essentialsMarketplace", false, @@ -413,7 +415,7 @@ data "rediscloud_essentials_subscription" "example" { ` func testAccCheckEssentialsSubscriptionDestroy(s *terraform.State) error { - client := testProvider.Meta().(*apiClient) + client := testProvider.Meta().(*utils.ApiClient) for _, r := range s.RootModule().Resources { if r.Type != "rediscloud_essentials_subscription" { @@ -425,7 +427,7 @@ func testAccCheckEssentialsSubscriptionDestroy(s *terraform.State) error { return err } - subs, err := client.client.FixedSubscriptions.List(context.TODO()) + subs, err := client.Client.FixedSubscriptions.List(context.TODO()) if err != nil { return err } diff --git a/provider/rediscloud_private_service_connect_endpoint_accepter_test.go b/provider/rediscloud_private_service_connect_endpoint_accepter_test.go index dfddf854..30fa42fd 100644 --- a/provider/rediscloud_private_service_connect_endpoint_accepter_test.go +++ b/provider/rediscloud_private_service_connect_endpoint_accepter_test.go @@ -8,6 +8,8 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/psc" + psc2 "github.com/RedisLabs/terraform-provider-rediscloud/provider/private_service_connect" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -23,7 +25,11 @@ func TestAccResourceRedisCloudPrivateServiceConnectEndpointAccepter_Create(t *te gcpProjectId := os.Getenv("GCP_PROJECT_ID") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccGcpProjectPreCheck(t); testAccGcpCredentialsPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccGcpProjectPreCheck(t) + testAccGcpCredentialsPreCheck(t) + }, ProviderFactories: providerFactories, ExternalProviders: map[string]resource.ExternalProvider{ "google": { @@ -40,20 +46,20 @@ func TestAccResourceRedisCloudPrivateServiceConnectEndpointAccepter_Create(t *te func(s *terraform.State) error { r := s.RootModule().Resources[resourceName] - accepterId, err := toPscEndpointAccepterId(r.Primary.ID) + accepterId, err := psc2.ToPscEndpointAccepterId(r.Primary.ID) if err != nil { return fmt.Errorf("couldn't parse the accepter ID: %s", r.Primary.ID) } - client := testProvider.Meta().(*apiClient) - endpoints, err := client.client.PrivateServiceConnect.GetEndpoints(context.TODO(), accepterId.subscriptionId, accepterId.pscServiceId) + client := testProvider.Meta().(*utils.ApiClient) + endpoints, err := client.Client.PrivateServiceConnect.GetEndpoints(context.TODO(), accepterId.SubscriptionId, accepterId.PscServiceId) if err != nil { return err } - endpoint := findPrivateServiceConnectEndpoints(accepterId.endpointId, endpoints.Endpoints) + endpoint := psc2.FindPrivateServiceConnectEndpoints(accepterId.EndpointId, endpoints.Endpoints) if endpoint == nil { - return fmt.Errorf("couldn't find endpoint with ID: %d", accepterId.endpointId) + return fmt.Errorf("couldn't find endpoint with ID: %d", accepterId.EndpointId) } if redis.StringValue(endpoint.Status) != psc.EndpointStatusActive { diff --git a/provider/resource_rediscloud_acl_rule_test.go b/provider/resource_rediscloud_acl_rule_test.go index 4e654aa3..cf098aba 100644 --- a/provider/resource_rediscloud_acl_rule_test.go +++ b/provider/resource_rediscloud_acl_rule_test.go @@ -3,13 +3,15 @@ package provider import ( "context" "fmt" + "regexp" + "strconv" + "testing" + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - "regexp" - "strconv" - "testing" ) func TestAccResourceRedisCloudAclRule_CRUDI(t *testing.T) { @@ -53,8 +55,8 @@ func TestAccResourceRedisCloudAclRule_CRUDI(t *testing.T) { return fmt.Errorf("couldn't parse the rule ID: %s", redis.StringValue(&r.Primary.ID)) } - client := testProvider.Meta().(*apiClient) - rule, err := client.client.RedisRules.Get(context.TODO(), id) + client := testProvider.Meta().(*utils.ApiClient) + rule, err := client.Client.RedisRules.Get(context.TODO(), id) if err != nil { return err } @@ -145,7 +147,7 @@ data "rediscloud_acl_rule" "test" { ` func testAccCheckAclRuleDestroy(s *terraform.State) error { - client := testProvider.Meta().(*apiClient) + client := testProvider.Meta().(*utils.ApiClient) for _, r := range s.RootModule().Resources { if r.Type != "rediscloud_acl_rule" { @@ -157,7 +159,7 @@ func testAccCheckAclRuleDestroy(s *terraform.State) error { return err } - rules, err := client.client.RedisRules.List(context.TODO()) + rules, err := client.Client.RedisRules.List(context.TODO()) if err != nil { return err } diff --git a/provider/resource_rediscloud_active_active_subscription_cmk_test.go b/provider/resource_rediscloud_active_active_subscription_cmk_test.go index 5f2e5984..d8990d1c 100644 --- a/provider/resource_rediscloud_active_active_subscription_cmk_test.go +++ b/provider/resource_rediscloud_active_active_subscription_cmk_test.go @@ -2,10 +2,11 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "os" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) // TestAccResourceRedisCloudActiveActiveSubscription_CMK is a semi-automated test that requires the user to pause midway through diff --git a/provider/resource_rediscloud_active_active_subscription_peering_test.go b/provider/resource_rediscloud_active_active_subscription_peering_test.go index 8afda00a..d046bf6f 100644 --- a/provider/resource_rediscloud_active_active_subscription_peering_test.go +++ b/provider/resource_rediscloud_active_active_subscription_peering_test.go @@ -2,11 +2,12 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "os" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccResourceRedisCloudActiveActiveSubscriptionPeering_aws(t *testing.T) { @@ -47,7 +48,11 @@ func TestAccResourceRedisCloudActiveActiveSubscriptionPeering_aws(t *testing.T) const resourceName = "rediscloud_active_active_subscription_peering.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccAwsPeeringPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccAwsPeeringPreCheck(t) + testAccAwsPreExistingCloudAccountPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckActiveActiveSubscriptionDestroy, Steps: []resource.TestStep{ diff --git a/provider/resource_rediscloud_active_active_subscription_regions_test.go b/provider/resource_rediscloud_active_active_subscription_regions_test.go index 10958efa..7c32300a 100644 --- a/provider/resource_rediscloud_active_active_subscription_regions_test.go +++ b/provider/resource_rediscloud_active_active_subscription_regions_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -59,8 +60,8 @@ func TestAccResourceRedisCloudActiveActiveSubscriptionRegions_CRUDI(t *testing.T return err } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } diff --git a/provider/resource_rediscloud_cloud_account_test.go b/provider/resource_rediscloud_cloud_account_test.go index 2f7e2499..937b1fd7 100644 --- a/provider/resource_rediscloud_cloud_account_test.go +++ b/provider/resource_rediscloud_cloud_account_test.go @@ -3,14 +3,16 @@ package provider import ( "context" "fmt" - "github.com/RedisLabs/rediscloud-go-api/redis" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "os" "regexp" "strconv" "testing" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccResourceRedisCloudCloudAccount_basic(t *testing.T) { @@ -58,7 +60,7 @@ func TestAccResourceRedisCloudCloudAccount_basic(t *testing.T) { } func testAccCheckCloudAccountDestroy(s *terraform.State) error { - client := testProvider.Meta().(*apiClient) + client := testProvider.Meta().(*utils.ApiClient) for _, r := range s.RootModule().Resources { if r.Type != "rediscloud_cloud_account" { @@ -70,7 +72,7 @@ func testAccCheckCloudAccountDestroy(s *terraform.State) error { return err } - accounts, err := client.client.CloudAccount.List(context.TODO()) + accounts, err := client.Client.CloudAccount.List(context.TODO()) if err != nil { return err } diff --git a/provider/resource_rediscloud_essentials_database_test.go b/provider/resource_rediscloud_essentials_database_test.go index d28e6d36..971225b7 100644 --- a/provider/resource_rediscloud_essentials_database_test.go +++ b/provider/resource_rediscloud_essentials_database_test.go @@ -2,10 +2,11 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "regexp" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) func TestAccResourceRedisCloudEssentialsDatabase_CRUDI(t *testing.T) { diff --git a/provider/resource_rediscloud_pro_database_test.go b/provider/resource_rediscloud_pro_database_test.go index c0caf218..bb065012 100644 --- a/provider/resource_rediscloud_pro_database_test.go +++ b/provider/resource_rediscloud_pro_database_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -35,7 +36,7 @@ func TestAccResourceRedisCloudProDatabase_CRUDI(t *testing.T) { Steps: []resource.TestStep{ // Test database and replica database creation { - Config: fmt.Sprintf(testAccResourceRedisCloudProDatabase, testCloudAccountName, name, password) + testAccResourceRedisCloudProDatabaseReplica, + Config: fmt.Sprintf(TestAccResourceRedisCloudProDatabase, testCloudAccountName, name, password) + testAccResourceRedisCloudProDatabaseReplica, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", "example"), resource.TestCheckResourceAttr(resourceName, "protocol", "redis"), @@ -79,8 +80,8 @@ func TestAccResourceRedisCloudProDatabase_CRUDI(t *testing.T) { return fmt.Errorf("couldn't parse the subscription ID: %s", redis.StringValue(&r.Primary.ID)) } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -89,7 +90,7 @@ func TestAccResourceRedisCloudProDatabase_CRUDI(t *testing.T) { return fmt.Errorf("unexpected name value: %s", redis.StringValue(sub.Name)) } - listDb := client.client.Database.List(context.TODO(), subId) + listDb := client.Client.Database.List(context.TODO(), subId) if listDb.Next() != true { return fmt.Errorf("no database found: %s", listDb.Err()) } @@ -360,7 +361,7 @@ resource "rediscloud_subscription" "example" { // Create and Read tests // TF config for provisioning a new database -const testAccResourceRedisCloudProDatabase = proSubscriptionBoilerplate + ` +const TestAccResourceRedisCloudProDatabase = proSubscriptionBoilerplate + ` resource "rediscloud_subscription_database" "example" { subscription_id = rediscloud_subscription.example.id name = "example" diff --git a/provider/resource_rediscloud_pro_subscription_cmk_test.go b/provider/resource_rediscloud_pro_subscription_cmk_test.go index d20395a8..381cdbb9 100644 --- a/provider/resource_rediscloud_pro_subscription_cmk_test.go +++ b/provider/resource_rediscloud_pro_subscription_cmk_test.go @@ -2,10 +2,11 @@ package provider import ( "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "os" "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) // TestAccResourceRedisCloudProSubscription_CMK is a semi-automated test that requires the user to pause midway through diff --git a/provider/resource_rediscloud_pro_subscription_test.go b/provider/resource_rediscloud_pro_subscription_test.go index 0ea71a11..4a4651e5 100644 --- a/provider/resource_rediscloud_pro_subscription_test.go +++ b/provider/resource_rediscloud_pro_subscription_test.go @@ -11,6 +11,8 @@ import ( "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/pro" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -74,8 +76,8 @@ func TestAccResourceRedisCloudProSubscription_CRUDI(t *testing.T) { return err } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -451,7 +453,7 @@ func TestFlexSubModulesAllocationWhenGraphAndQuantityIsOne(t *testing.T) { "throughput_measurement_by": "operations-per-second", "throughput_measurement_value": 10000, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRamAndFlash, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRamAndFlash, planMap) assert.Empty(t, diags) otherDatabases := 0 graphDatabases := 0 @@ -489,7 +491,7 @@ func TestFlexSubModulesAllocationWhenGraphAndQuantityMoreThanOne(t *testing.T) { "throughput_measurement_by": "operations-per-second", "throughput_measurement_value": 10000, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Empty(t, diags) graphDatabases := 0 otherDatabases := 0 @@ -526,7 +528,7 @@ func TestFlexSubModulesAllocationWhenOnlyGraphModule(t *testing.T) { "throughput_measurement_by": "operations-per-second", "throughput_measurement_value": 10000, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Len(t, createDbs, numDatabases) assert.Empty(t, diags) for _, createDb := range createDbs { @@ -551,7 +553,7 @@ func TestFlexSubModulesAllocationWhenNoGraph(t *testing.T) { "throughput_measurement_by": "number-of-shards", "throughput_measurement_value": 2, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Len(t, createDbs, numDatabases) assert.Empty(t, diags) for _, createDb := range createDbs { @@ -578,7 +580,7 @@ func TestFlexSubNoModulesInCreatePlanDatabases(t *testing.T) { "throughput_measurement_by": "operations-per-second", "throughput_measurement_value": 10000, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Len(t, createDbs, 2) assert.Empty(t, diags) for _, createDb := range createDbs { @@ -601,7 +603,7 @@ func TestFlexSubNoAverageItemSizeInBytes(t *testing.T) { "throughput_measurement_by": "operations-per-second", "throughput_measurement_value": 10000, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Len(t, createDbs, 2) assert.Empty(t, diags) for _, createDb := range createDbs { @@ -623,7 +625,7 @@ func TestFlexSubRediSearchThroughputMeasurementWhenReplicationIsFalse(t *testing "throughput_measurement_by": "number-of-shards", "throughput_measurement_value": 2, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Empty(t, diags) createDb := createDbs[0] assert.Equal(t, "number-of-shards", *createDb.ThroughputMeasurement.By) @@ -644,7 +646,7 @@ func TestFlexSubRediSearchThroughputMeasurementWhenReplicationIsTrue(t *testing. "throughput_measurement_by": "number-of-shards", "throughput_measurement_value": 2, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Empty(t, diags) createDb := createDbs[0] assert.Equal(t, "number-of-shards", *createDb.ThroughputMeasurement.By) @@ -665,7 +667,7 @@ func TestFlexSubRedisGraphThroughputMeasurementWhenReplicationIsFalse(t *testing "throughput_measurement_by": "number-of-shards", "throughput_measurement_value": 2, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Empty(t, diags) createDb := createDbs[0] assert.Equal(t, "operations-per-second", *createDb.ThroughputMeasurement.By) @@ -686,7 +688,7 @@ func TestFlexSubRedisGraphThroughputMeasurementWhenReplicationIsTrue(t *testing. "throughput_measurement_by": "number-of-shards", "throughput_measurement_value": 2, } - createDbs, diags := buildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) + createDbs, diags := pro.BuildSubscriptionCreatePlanDatabases(databases.MemoryStorageRam, planMap) assert.Len(t, diags, 1, "Warning should be reported when storage was ram and using `average_item_size_in_bytes`") assert.Equal(t, diag.Warning, diags[0].Severity) createDb := createDbs[0] @@ -695,7 +697,7 @@ func TestFlexSubRedisGraphThroughputMeasurementWhenReplicationIsTrue(t *testing. } func testAccCheckProSubscriptionDestroy(s *terraform.State) error { - client := testProvider.Meta().(*apiClient) + client := testProvider.Meta().(*utils.ApiClient) for _, r := range s.RootModule().Resources { if r.Type != "rediscloud_subscription" { @@ -707,7 +709,7 @@ func testAccCheckProSubscriptionDestroy(s *terraform.State) error { return err } - subs, err := client.client.Subscription.List(context.TODO()) + subs, err := client.Client.Subscription.List(context.TODO()) if err != nil { return err } diff --git a/provider/resource_rediscloud_pro_tls_test.go b/provider/resource_rediscloud_pro_tls_test.go index b68033b4..990807b9 100644 --- a/provider/resource_rediscloud_pro_tls_test.go +++ b/provider/resource_rediscloud_pro_tls_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" @@ -58,8 +59,8 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn return err } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -68,7 +69,7 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn return fmt.Errorf("unexpected name value: %s", redis.StringValue(sub.Name)) } - listDb := client.client.Database.List(context.TODO(), subId) + listDb := client.Client.Database.List(context.TODO(), subId) if listDb.Next() != true { return fmt.Errorf("no database found: %s", listDb.Err()) } @@ -162,8 +163,8 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn return err } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -172,7 +173,7 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn return fmt.Errorf("unexpected name value: %s", redis.StringValue(sub.Name)) } - listDb := client.client.Database.List(context.TODO(), subId) + listDb := client.Client.Database.List(context.TODO(), subId) if listDb.Next() != true { return fmt.Errorf("no database found: %s", listDb.Err()) } @@ -343,8 +344,8 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn return err } - client := testProvider.Meta().(*apiClient) - sub, err := client.client.Subscription.Get(context.TODO(), subId) + client := testProvider.Meta().(*utils.ApiClient) + sub, err := client.Client.Subscription.Get(context.TODO(), subId) if err != nil { return err } @@ -353,7 +354,7 @@ func TestAccResourceRedisCloudSubscriptionTls_createWithDatabaseWithEnabledTlsAn return fmt.Errorf("unexpected name value: %s", redis.StringValue(sub.Name)) } - listDb := client.client.Database.List(context.TODO(), subId) + listDb := client.Client.Database.List(context.TODO(), subId) if listDb.Next() != true { return fmt.Errorf("no database found: %s", listDb.Err()) } diff --git a/provider/resource_rediscloud_subscription_peering_test.go b/provider/resource_rediscloud_subscription_peering_test.go index 4ec71d8c..ce979d7e 100644 --- a/provider/resource_rediscloud_subscription_peering_test.go +++ b/provider/resource_rediscloud_subscription_peering_test.go @@ -52,7 +52,11 @@ func TestAccResourceRedisCloudSubscriptionPeering_aws(t *testing.T) { const resourceName = "rediscloud_subscription_peering.test" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t); testAccAwsPeeringPreCheck(t); testAccAwsPreExistingCloudAccountPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccAwsPeeringPreCheck(t) + testAccAwsPreExistingCloudAccountPreCheck(t) + }, ProviderFactories: providerFactories, CheckDestroy: testAccCheckProSubscriptionDestroy, Steps: []resource.TestStep{ diff --git a/provider/sweeper_test.go b/provider/sweeper_test.go index 01c5f450..971372d3 100644 --- a/provider/sweeper_test.go +++ b/provider/sweeper_test.go @@ -15,11 +15,10 @@ import ( "github.com/RedisLabs/rediscloud-go-api/service/databases" fixedSubscriptions "github.com/RedisLabs/rediscloud-go-api/service/fixed/subscriptions" "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -const testResourcePrefix = "tf-test" - var sweeperClients map[string]*rediscloudApi.Client func TestMain(m *testing.M) { @@ -32,11 +31,11 @@ func sharedClientForRegion(region string) (*rediscloudApi.Client, error) { return client, nil } - if os.Getenv(RedisCloudUrlEnvVar) == "" || os.Getenv(rediscloudApi.AccessKeyEnvVar) == "" || os.Getenv(rediscloudApi.SecretKeyEnvVar) == "" { - return nil, fmt.Errorf("must provide environment variables %s, %s, %s", RedisCloudUrlEnvVar, rediscloudApi.AccessKeyEnvVar, rediscloudApi.SecretKeyEnvVar) + if os.Getenv(utils.RedisCloudUrlEnvVar) == "" || os.Getenv(rediscloudApi.AccessKeyEnvVar) == "" || os.Getenv(rediscloudApi.SecretKeyEnvVar) == "" { + return nil, fmt.Errorf("must provide environment variables %s, %s, %s", utils.RedisCloudUrlEnvVar, rediscloudApi.AccessKeyEnvVar, rediscloudApi.SecretKeyEnvVar) } - client, err := rediscloudApi.NewClient(rediscloudApi.BaseURL(os.Getenv(RedisCloudUrlEnvVar))) + client, err := rediscloudApi.NewClient(rediscloudApi.BaseURL(os.Getenv(utils.RedisCloudUrlEnvVar))) if err != nil { return nil, err } diff --git a/provider/testdata/testAccDatasourceRedisCloudProDatabase.tf b/provider/testdata/testAccDatasourceRedisCloudProDatabase.tf index 12abc849..0cf33a80 100644 --- a/provider/testdata/testAccDatasourceRedisCloudProDatabase.tf +++ b/provider/testdata/testAccDatasourceRedisCloudProDatabase.tf @@ -15,26 +15,26 @@ data "rediscloud_cloud_account" "account" { name = local.rediscloud_cloud_account } resource "rediscloud_subscription" "example" { - name = local.rediscloud_subscription_name + name = local.rediscloud_subscription_name payment_method_id = data.rediscloud_payment_method.card.id - memory_storage = "ram" + memory_storage = "ram" cloud_provider { - provider = data.rediscloud_cloud_account.account.provider_type + provider = data.rediscloud_cloud_account.account.provider_type cloud_account_id = data.rediscloud_cloud_account.account.id region { - region = "eu-west-1" + region = "eu-west-1" networking_deployment_cidr = "10.0.0.0/24" preferred_availability_zones = ["eu-west-1a"] } } creation_plan { - memory_limit_in_gb = 1 - quantity = 1 - replication=false - support_oss_cluster_api=true - throughput_measurement_by = "operations-per-second" + memory_limit_in_gb = 1 + quantity = 1 + replication = false + support_oss_cluster_api = true + throughput_measurement_by = "operations-per-second" throughput_measurement_value = 1000 - query_performance_factor = "2x" + query_performance_factor = "2x" modules = ["RediSearch"] } } diff --git a/provider/datasource_rediscloud_active_active_transit_gateway.go b/provider/transitgateway/datasource_rediscloud_active_active_transit_gateway.go similarity index 91% rename from provider/datasource_rediscloud_active_active_transit_gateway.go rename to provider/transitgateway/datasource_rediscloud_active_active_transit_gateway.go index 115990fd..12b26122 100644 --- a/provider/datasource_rediscloud_active_active_transit_gateway.go +++ b/provider/transitgateway/datasource_rediscloud_active_active_transit_gateway.go @@ -1,15 +1,17 @@ -package provider +package transitgateway import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/transit_gateway/attachments" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceActiveActiveTransitGateway() *schema.Resource { +func DataSourceActiveActiveTransitGateway() *schema.Resource { return &schema.Resource{ Description: "The Active Active Transit Gateway data source allows access to an available Transit Gateway within your Redis Enterprise Cloud Account.", ReadContext: dataSourceActiveActiveTransitGatewayRead, @@ -71,14 +73,14 @@ func dataSourceActiveActiveTransitGateway() *schema.Resource { func dataSourceActiveActiveTransitGatewayRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } regionId := d.Get("region_id").(int) - tgwTask, err := api.client.TransitGatewayAttachments.GetActiveActive(ctx, subId, regionId) + tgwTask, err := api.Client.TransitGatewayAttachments.GetActiveActive(ctx, subId, regionId) if err != nil { return diag.FromErr(err) } @@ -109,7 +111,7 @@ func dataSourceActiveActiveTransitGatewayRead(ctx context.Context, d *schema.Res tgw := tgws[0] tgwId := redis.IntValue(tgw.Id) - d.SetId(buildResourceId(subId, tgwId)) + d.SetId(utils.BuildResourceId(subId, tgwId)) if err := d.Set("tgw_id", tgwId); err != nil { return diag.FromErr(err) } @@ -128,7 +130,7 @@ func dataSourceActiveActiveTransitGatewayRead(ctx context.Context, d *schema.Res if err := d.Set("aws_account_id", redis.StringValue(tgw.AwsAccountId)); err != nil { return diag.FromErr(err) } - if err := d.Set("cidrs", flattenCidrs(tgw.Cidrs)); err != nil { + if err := d.Set("cidrs", utils.FlattenCidrs(tgw.Cidrs)); err != nil { return diag.FromErr(err) } diff --git a/provider/datasource_rediscloud_transit_gateway.go b/provider/transitgateway/datasource_rediscloud_transit_gateway.go similarity index 80% rename from provider/datasource_rediscloud_transit_gateway.go rename to provider/transitgateway/datasource_rediscloud_transit_gateway.go index 0ef4558a..1638fed5 100644 --- a/provider/datasource_rediscloud_transit_gateway.go +++ b/provider/transitgateway/datasource_rediscloud_transit_gateway.go @@ -1,15 +1,17 @@ -package provider +package transitgateway import ( "context" + "strconv" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/transit_gateway/attachments" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" ) -func dataSourceTransitGateway() *schema.Resource { +func DataSourceTransitGateway() *schema.Resource { return &schema.Resource{ Description: "The Transit Gateway data source allows access to an available Transit Gateway within your Redis Enterprise Cloud Account.", ReadContext: dataSourceTransitGatewayRead, @@ -66,13 +68,13 @@ func dataSourceTransitGateway() *schema.Resource { func dataSourceTransitGatewayRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { return diag.FromErr(err) } - tgwTask, err := api.client.TransitGatewayAttachments.Get(ctx, subId) + tgwTask, err := api.Client.TransitGatewayAttachments.Get(ctx, subId) if err != nil { return diag.FromErr(err) } @@ -103,7 +105,7 @@ func dataSourceTransitGatewayRead(ctx context.Context, d *schema.ResourceData, m tgw := tgws[0] tgwId := redis.IntValue(tgw.Id) - d.SetId(buildResourceId(subId, tgwId)) + d.SetId(utils.BuildResourceId(subId, tgwId)) if err := d.Set("tgw_id", tgwId); err != nil { return diag.FromErr(err) } @@ -122,28 +124,9 @@ func dataSourceTransitGatewayRead(ctx context.Context, d *schema.ResourceData, m if err := d.Set("aws_account_id", redis.StringValue(tgw.AwsAccountId)); err != nil { return diag.FromErr(err) } - if err := d.Set("cidrs", flattenCidrs(tgw.Cidrs)); err != nil { + if err := d.Set("cidrs", utils.FlattenCidrs(tgw.Cidrs)); err != nil { return diag.FromErr(err) } return diags } - -func filterTgwAttachments(getAttachmentsTask *attachments.GetAttachmentsTask, filters []func(tgwa *attachments.TransitGatewayAttachment) bool) []*attachments.TransitGatewayAttachment { - var filtered []*attachments.TransitGatewayAttachment - for _, tgwa := range getAttachmentsTask.Response.Resource.TransitGatewayAttachment { - if filterTgwAttachment(tgwa, filters) { - filtered = append(filtered, tgwa) - } - } - return filtered -} - -func filterTgwAttachment(tgwa *attachments.TransitGatewayAttachment, filters []func(tgwa *attachments.TransitGatewayAttachment) bool) bool { - for _, filter := range filters { - if !filter(tgwa) { - return false - } - } - return true -} diff --git a/provider/resource_rediscloud_active_active_transit_gateway_attachment.go b/provider/transitgateway/resource_rediscloud_active_active_transit_gateway_attachment.go similarity index 89% rename from provider/resource_rediscloud_active_active_transit_gateway_attachment.go rename to provider/transitgateway/resource_rediscloud_active_active_transit_gateway_attachment.go index 5c11e08b..e7383bb2 100644 --- a/provider/resource_rediscloud_active_active_transit_gateway_attachment.go +++ b/provider/transitgateway/resource_rediscloud_active_active_transit_gateway_attachment.go @@ -1,13 +1,15 @@ -package provider +package transitgateway import ( "context" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/transit_gateway/attachments" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" - "time" ) func resourceRedisCloudActiveActiveTransitGatewayAttachment() *schema.Resource { @@ -84,7 +86,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachment() *schema.Resource { } func resourceRedisCloudActiveActiveTransitGatewayAttachmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) regionId, err := strconv.Atoi(d.Get("region_id").(string)) @@ -94,12 +96,12 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentCreate(ctx context.Co } // At this point, cidrs has to be empty. We cannot honour the user's configuration until the invitation has been accepted - cidrs := interfaceToStringSlice(d.Get("cidrs").([]interface{})) + cidrs := utils.InterfaceToStringSlice(d.Get("cidrs").([]interface{})) if len(cidrs) > 0 { return diag.Errorf("Attachment cannot be created with Cidrs provided, it must be accepted first. This resource may then be updated with Cidrs.") } - _, err = api.client.TransitGatewayAttachments.CreateActiveActive(ctx, subscriptionId, regionId, tgwId) + _, err = api.Client.TransitGatewayAttachments.CreateActiveActive(ctx, subscriptionId, regionId, tgwId) if err != nil { return diag.FromErr(err) } @@ -109,7 +111,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentCreate(ctx context.Co func resourceRedisCloudActiveActiveTransitGatewayAttachmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -121,7 +123,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentRead(ctx context.Cont } tgwId := d.Get("tgw_id").(int) - tgwTask, err := api.client.TransitGatewayAttachments.GetActiveActive(ctx, subId, regionId) + tgwTask, err := api.Client.TransitGatewayAttachments.GetActiveActive(ctx, subId, regionId) if err != nil { return diag.FromErr(err) } @@ -142,7 +144,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentRead(ctx context.Cont } tgw := tgws[0] - d.SetId(buildResourceId(subId, tgwId)) + d.SetId(utils.BuildResourceId(subId, tgwId)) if err := d.Set("aws_tgw_uid", redis.StringValue(tgw.AwsTgwUid)); err != nil { return diag.FromErr(err) } @@ -158,7 +160,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentRead(ctx context.Cont if err := d.Set("aws_account_id", redis.StringValue(tgw.AwsAccountId)); err != nil { return diag.FromErr(err) } - if err := d.Set("cidrs", flattenCidrs(tgw.Cidrs)); err != nil { + if err := d.Set("cidrs", utils.FlattenCidrs(tgw.Cidrs)); err != nil { return diag.FromErr(err) } @@ -166,7 +168,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentRead(ctx context.Cont } func resourceRedisCloudActiveActiveTransitGatewayAttachmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) regionId, err := strconv.Atoi(d.Get("region_id").(string)) @@ -175,12 +177,12 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentUpdate(ctx context.Co return diag.FromErr(err) } - cidrs := interfaceToStringSlice(d.Get("cidrs").([]interface{})) + cidrs := utils.InterfaceToStringSlice(d.Get("cidrs").([]interface{})) if len(cidrs) == 0 { cidrs = make([]*string, 0) } - err = api.client.TransitGatewayAttachments.UpdateActiveActive(ctx, subId, tgwId, regionId, cidrs) + err = api.Client.TransitGatewayAttachments.UpdateActiveActive(ctx, subId, tgwId, regionId, cidrs) if err != nil { return diag.FromErr(err) } @@ -190,7 +192,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentUpdate(ctx context.Co func resourceRedisCloudActiveActiveTransitGatewayAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -199,7 +201,7 @@ func resourceRedisCloudActiveActiveTransitGatewayAttachmentDelete(ctx context.Co regionId := d.Get("region_id").(int) tgwId := d.Get("tgw_id").(int) - err = api.client.TransitGatewayAttachments.DeleteActiveActive(ctx, subscriptionId, regionId, tgwId) + err = api.Client.TransitGatewayAttachments.DeleteActiveActive(ctx, subscriptionId, regionId, tgwId) if err != nil { return diag.FromErr(err) } diff --git a/provider/resource_rediscloud_transit_gateway_attachment.go b/provider/transitgateway/resource_rediscloud_transit_gateway_attachment.go similarity index 86% rename from provider/resource_rediscloud_transit_gateway_attachment.go rename to provider/transitgateway/resource_rediscloud_transit_gateway_attachment.go index 2d8e47e7..5836edbf 100644 --- a/provider/resource_rediscloud_transit_gateway_attachment.go +++ b/provider/transitgateway/resource_rediscloud_transit_gateway_attachment.go @@ -1,16 +1,18 @@ -package provider +package transitgateway import ( "context" + "strconv" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" "github.com/RedisLabs/rediscloud-go-api/service/transit_gateway/attachments" + "github.com/RedisLabs/terraform-provider-rediscloud/provider/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "strconv" - "time" ) -func resourceRedisCloudTransitGatewayAttachment() *schema.Resource { +func ResourceRedisCloudTransitGatewayAttachment() *schema.Resource { return &schema.Resource{ Description: "Manages a Transit Gateway Attachment to a Pro/Flexible Subscription in your Redis Enterprise Cloud Account.", CreateContext: resourceRedisCloudTransitGatewayAttachmentCreate, @@ -79,7 +81,7 @@ func resourceRedisCloudTransitGatewayAttachment() *schema.Resource { } func resourceRedisCloudTransitGatewayAttachmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) tgwId := d.Get("tgw_id").(int) @@ -88,12 +90,12 @@ func resourceRedisCloudTransitGatewayAttachmentCreate(ctx context.Context, d *sc } // At this point, cidrs has to be empty. We cannot honour the user's configuration until the invitation has been accepted - cidrs := interfaceToStringSlice(d.Get("cidrs").([]interface{})) + cidrs := utils.InterfaceToStringSlice(d.Get("cidrs").([]interface{})) if len(cidrs) > 0 { return diag.Errorf("Attachment cannot be created with Cidrs provided, it must be accepted first. This resource may then be updated with Cidrs.") } - _, err = api.client.TransitGatewayAttachments.Create(ctx, subscriptionId, tgwId) + _, err = api.Client.TransitGatewayAttachments.Create(ctx, subscriptionId, tgwId) if err != nil { return diag.FromErr(err) } @@ -103,7 +105,7 @@ func resourceRedisCloudTransitGatewayAttachmentCreate(ctx context.Context, d *sc func resourceRedisCloudTransitGatewayAttachmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) if err != nil { @@ -111,7 +113,7 @@ func resourceRedisCloudTransitGatewayAttachmentRead(ctx context.Context, d *sche } tgwId := d.Get("tgw_id").(int) - tgwTask, err := api.client.TransitGatewayAttachments.Get(ctx, subId) + tgwTask, err := api.Client.TransitGatewayAttachments.Get(ctx, subId) if err != nil { return diag.FromErr(err) } @@ -132,7 +134,7 @@ func resourceRedisCloudTransitGatewayAttachmentRead(ctx context.Context, d *sche } tgw := tgws[0] - d.SetId(buildResourceId(subId, tgwId)) + d.SetId(utils.BuildResourceId(subId, tgwId)) if err := d.Set("aws_tgw_uid", redis.StringValue(tgw.AwsTgwUid)); err != nil { return diag.FromErr(err) } @@ -148,7 +150,7 @@ func resourceRedisCloudTransitGatewayAttachmentRead(ctx context.Context, d *sche if err := d.Set("aws_account_id", redis.StringValue(tgw.AwsAccountId)); err != nil { return diag.FromErr(err) } - if err := d.Set("cidrs", flattenCidrs(tgw.Cidrs)); err != nil { + if err := d.Set("cidrs", utils.FlattenCidrs(tgw.Cidrs)); err != nil { return diag.FromErr(err) } @@ -156,7 +158,7 @@ func resourceRedisCloudTransitGatewayAttachmentRead(ctx context.Context, d *sche } func resourceRedisCloudTransitGatewayAttachmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subId, err := strconv.Atoi(d.Get("subscription_id").(string)) tgwId := d.Get("tgw_id").(int) @@ -164,12 +166,12 @@ func resourceRedisCloudTransitGatewayAttachmentUpdate(ctx context.Context, d *sc return diag.FromErr(err) } - cidrs := interfaceToStringSlice(d.Get("cidrs").([]interface{})) + cidrs := utils.InterfaceToStringSlice(d.Get("cidrs").([]interface{})) if len(cidrs) == 0 { cidrs = make([]*string, 0) } - err = api.client.TransitGatewayAttachments.Update(ctx, subId, tgwId, cidrs) + err = api.Client.TransitGatewayAttachments.Update(ctx, subId, tgwId, cidrs) if err != nil { return diag.FromErr(err) } @@ -179,7 +181,7 @@ func resourceRedisCloudTransitGatewayAttachmentUpdate(ctx context.Context, d *sc func resourceRedisCloudTransitGatewayAttachmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - api := meta.(*apiClient) + api := meta.(*utils.ApiClient) subscriptionId, err := strconv.Atoi(d.Get("subscription_id").(string)) tgwId := d.Get("tgw_id").(int) @@ -187,7 +189,7 @@ func resourceRedisCloudTransitGatewayAttachmentDelete(ctx context.Context, d *sc return diag.FromErr(err) } - err = api.client.TransitGatewayAttachments.Delete(ctx, subscriptionId, tgwId) + err = api.Client.TransitGatewayAttachments.Delete(ctx, subscriptionId, tgwId) if err != nil { return diag.FromErr(err) } @@ -196,11 +198,3 @@ func resourceRedisCloudTransitGatewayAttachmentDelete(ctx context.Context, d *sc return diags } - -func flattenCidrs(cidrs []*attachments.Cidr) []string { - cidrStrings := make([]string, 0) - for _, cidr := range cidrs { - cidrStrings = append(cidrStrings, redis.StringValue(cidr.CidrAddress)) - } - return cidrStrings -} diff --git a/provider/transitgateway/utils.go b/provider/transitgateway/utils.go new file mode 100644 index 00000000..e2b2fb55 --- /dev/null +++ b/provider/transitgateway/utils.go @@ -0,0 +1,22 @@ +package transitgateway + +import "github.com/RedisLabs/rediscloud-go-api/service/transit_gateway/attachments" + +func filterTgwAttachments(getAttachmentsTask *attachments.GetAttachmentsTask, filters []func(tgwa *attachments.TransitGatewayAttachment) bool) []*attachments.TransitGatewayAttachment { + var filtered []*attachments.TransitGatewayAttachment + for _, tgwa := range getAttachmentsTask.Response.Resource.TransitGatewayAttachment { + if filterTgwAttachment(tgwa, filters) { + filtered = append(filtered, tgwa) + } + } + return filtered +} + +func filterTgwAttachment(tgwa *attachments.TransitGatewayAttachment, filters []func(tgwa *attachments.TransitGatewayAttachment) bool) bool { + for _, filter := range filters { + if !filter(tgwa) { + return false + } + } + return true +} diff --git a/provider/utils.go b/provider/utils.go deleted file mode 100644 index eeb2b0c1..00000000 --- a/provider/utils.go +++ /dev/null @@ -1,201 +0,0 @@ -package provider - -import ( - "fmt" - "sync" - "time" - - "github.com/RedisLabs/rediscloud-go-api/redis" - "github.com/RedisLabs/rediscloud-go-api/service/latest_backups" - "github.com/RedisLabs/rediscloud-go-api/service/latest_imports" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -// This timeout is an absolute maximum used in some of the waitForStatus operations concerning creation and updating -// Subscriptions and Databases. Reads and Deletions have their own, stricter timeouts because they consistently behave -// well. The Terraform operation-level timeout should kick in way before we hit this and kill the task. -// Unfortunately there's no "time-remaining-before-timeout" utility, or we could use that in the wait blocks. -const safetyTimeout = 6 * time.Hour - -func setToStringSlice(set *schema.Set) []*string { - var ret []*string - for _, s := range set.List() { - ret = append(ret, redis.String(s.(string))) - } - return ret -} - -func interfaceToStringSlice(list []interface{}) []*string { - var ret []*string - for _, i := range list { - if i == nil { - // The user probably entered "" (string's zero-value) but gets read in as nil (interface{}'s zero-value) - ret = append(ret, redis.String("")) - } else { - ret = append(ret, redis.String(i.(string))) - } - } - return ret -} - -type perIdLock struct { - lock sync.Mutex - store map[int]*sync.Mutex -} - -func newPerIdLock() *perIdLock { - return &perIdLock{ - store: map[int]*sync.Mutex{}, - } -} - -func (m *perIdLock) Lock(id int) { - m.get(id).Lock() -} - -func (m *perIdLock) Unlock(id int) { - m.get(id).Unlock() -} - -func (m *perIdLock) get(id int) *sync.Mutex { - m.lock.Lock() - defer m.lock.Unlock() - - if v, ok := m.store[id]; ok { - return v - } - - mutex := &sync.Mutex{} - m.store[id] = mutex - return mutex -} - -// IDs of any resources dependent on a subscription need to be divided by a slash. In this format: /. -func buildResourceId(subId int, id int) string { - return fmt.Sprintf("%d/%d", subId, id) -} - -func isTime() schema.SchemaValidateDiagFunc { - return func(i interface{}, path cty.Path) diag.Diagnostics { - var diags diag.Diagnostics - - v, ok := i.(string) - if !ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Value not a string", - Detail: fmt.Sprintf("Value should be a string rather than %T", i), - }) - } else if _, err := time.Parse("15:04", v); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Value is not a time", - Detail: fmt.Sprintf("Value should be a valid time, got: %q: %s", i, err), - }) - } - - return diags - } -} - -func parseLatestBackupStatus(latestBackupStatus *latest_backups.LatestBackupStatus) ([]map[string]interface{}, error) { - lbs := map[string]interface{}{ - "response": nil, - "error": nil, - } - - if latestBackupStatus.Response.Resource != nil { - res := map[string]interface{}{ - "status": redis.StringValue(latestBackupStatus.Response.Resource.Status), - "last_backup_time": nil, - "failure_reason": redis.StringValue(latestBackupStatus.Response.Resource.FailureReason), - } - if latestBackupStatus.Response.Resource.LastBackupTime != nil { - res["last_backup_time"] = latestBackupStatus.Response.Resource.LastBackupTime.String() - } - lbs["response"] = []map[string]interface{}{res} - } - - if latestBackupStatus.Response.Error != nil { - err := map[string]interface{}{ - "type": redis.StringValue(latestBackupStatus.Response.Error.Type), - "description": redis.StringValue(latestBackupStatus.Response.Error.Description), - "status": redis.StringValue(latestBackupStatus.Response.Error.Status), - } - lbs["error"] = []map[string]interface{}{err} - } - - return []map[string]interface{}{lbs}, nil -} - -func parseLatestImportStatus(latestImportStatus *latest_imports.LatestImportStatus) ([]map[string]interface{}, error) { - lis := map[string]interface{}{ - "response": nil, - "error": nil, - } - - if latestImportStatus.Response.Resource != nil { - res := map[string]interface{}{ - "status": redis.StringValue(latestImportStatus.Response.Resource.Status), - "last_import_time": nil, - "failure_reason": redis.StringValue(latestImportStatus.Response.Resource.FailureReason), - "failure_reason_params": parseFailureReasonParams(latestImportStatus.Response.Resource.FailureReasonParams), - } - if latestImportStatus.Response.Resource.LastImportTime != nil { - res["last_import_time"] = latestImportStatus.Response.Resource.LastImportTime.String() - } - lis["response"] = []map[string]interface{}{res} - } - - if latestImportStatus.Response.Error != nil { - err := map[string]interface{}{ - "type": redis.StringValue(latestImportStatus.Response.Error.Type), - "description": redis.StringValue(latestImportStatus.Response.Error.Description), - "status": redis.StringValue(latestImportStatus.Response.Error.Status), - } - lis["error"] = []map[string]interface{}{err} - } - - return []map[string]interface{}{lis}, nil -} - -func parseFailureReasonParams(params []*latest_imports.FailureReasonParam) []map[string]interface{} { - writableParams := make([]map[string]interface{}, 0) - for _, param := range params { - writableParams = append(writableParams, map[string]interface{}{ - "key": redis.StringValue(param.Key), - "value": redis.StringValue(param.Value), - }) - } - return writableParams -} - -func applyCertificateHints(tlsAuthEnabled bool, d *schema.ResourceData) error { - sslCertificate := d.Get("client_ssl_certificate").(string) - tlsCertificates := interfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) - if tlsAuthEnabled { - if sslCertificate == "" && len(tlsCertificates) == 0 { - // The resource does have SSL/TLS auth enabled, but it was not certified by this template. - if err := d.Set("client_tls_certificates", []interface{}{"Unknown certificate"}); err != nil { - return err - } - } - } else { - if sslCertificate != "" { - // The resource does not have SSL/TLS auth enabled, but this template provides an SSL certificate - if err := d.Set("client_ssl_certificate", ""); err != nil { - return err - } - } - if len(tlsCertificates) >= 0 { - // The resource does not have SSL/TLS auth enabled, but this template provides TLS certificates. - if err := d.Set("client_tls_certificates", []interface{}{}); err != nil { - return err - } - } - } - - return nil -} diff --git a/provider/utils/client.go b/provider/utils/client.go new file mode 100644 index 00000000..9be7f479 --- /dev/null +++ b/provider/utils/client.go @@ -0,0 +1,10 @@ +package utils + +import rediscloudApi "github.com/RedisLabs/rediscloud-go-api" + +// Lock that must be acquired when modifying something related to a subscription as only one _thing_ can modify a subscription and all sub-resources at any time +var SubscriptionMutex = NewPerIdLock() + +type ApiClient struct { + Client *rediscloudApi.Client +} diff --git a/provider/utils/flatten.go b/provider/utils/flatten.go new file mode 100644 index 00000000..25dc15c7 --- /dev/null +++ b/provider/utils/flatten.go @@ -0,0 +1,137 @@ +package utils + +import ( + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/rediscloud-go-api/service/maintenance" + "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/rediscloud-go-api/service/transit_gateway/attachments" +) + +func FlattenCidrs(cidrs []*attachments.Cidr) []string { + cidrStrings := make([]string, 0) + for _, cidr := range cidrs { + cidrStrings = append(cidrStrings, redis.StringValue(cidr.CidrAddress)) + } + return cidrStrings +} + +func FlattenAlerts(alerts []*databases.Alert) []map[string]interface{} { + var tfs = make([]map[string]interface{}, 0) + + for _, alert := range alerts { + tf := map[string]interface{}{ + "name": redis.StringValue(alert.Name), + "value": redis.IntValue(alert.Value), + } + tfs = append(tfs, tf) + } + + return tfs +} + +func FlattenModules(modules []*databases.Module) []map[string]interface{} { + var tfs = make([]map[string]interface{}, 0) + for _, module := range modules { + + tf := map[string]interface{}{ + "name": redis.StringValue(module.Name), + } + tfs = append(tfs, tf) + } + + return tfs +} + +func FlattenRegexRules(rules []*databases.RegexRule) []string { + ret := make([]string, len(rules)) + for _, rule := range rules { + ret[rule.Ordinal] = rule.Pattern + } + + if len(ret) == 2 && ret[0] == ".*\\{(?.*)\\}.*" && ret[1] == "(?.*)" { + // This is the default regex rules - https://docs.redislabs.com/latest/rc/concepts/clustering/#custom-hashing-policy + return []string{} + } + + return ret +} + +func FlattenBackupPlan(backup *databases.Backup, existing []interface{}, periodicBackupPath string) []map[string]interface{} { + if backup == nil || !redis.BoolValue(backup.Enabled) || periodicBackupPath != "" { + return nil + } + + storageType := "" + if len(existing) == 1 { + d := existing[0].(map[string]interface{}) + storageType = d["storage_type"].(string) + } + + return []map[string]interface{}{ + { + "interval": redis.StringValue(backup.Interval), + "time_utc": redis.StringValue(backup.TimeUTC), + "storage_type": storageType, + "storage_path": redis.StringValue(backup.Destination), + }, + } +} + +func FlattenPrivateServiceConnectEndpoints(endpoints []*psc.PrivateServiceConnectEndpoint, + serviceAttachments map[int][]psc.TerraformGCPServiceAttachment) []map[string]interface{} { + + var rl []map[string]interface{} + for _, endpoint := range endpoints { + + endpointMapString := map[string]interface{}{ + "private_service_connect_endpoint_id": redis.IntValue(endpoint.ID), + "gcp_project_id": redis.StringValue(endpoint.GCPProjectID), + "gcp_vpc_name": redis.StringValue(endpoint.GCPVPCName), + "gcp_vpc_subnet_name": redis.StringValue(endpoint.GCPVPCSubnetName), + "endpoint_connection_name": redis.StringValue(endpoint.EndpointConnectionName), + "status": redis.StringValue(endpoint.Status), + "service_attachments": FlattenPrivateServiceConnectEndpointServiceAttachments(serviceAttachments[redis.IntValue(endpoint.ID)]), + } + + rl = append(rl, endpointMapString) + } + + return rl +} + +func FlattenPrivateServiceConnectEndpointServiceAttachments(serviceAttachments []psc.TerraformGCPServiceAttachment) []map[string]interface{} { + var rl []map[string]interface{} + for _, serviceAttachment := range serviceAttachments { + + serviceAttachmentMapString := map[string]interface{}{ + "name": serviceAttachment.Name, + "dns_record": serviceAttachment.DNSRecord, + "ip_address_name": serviceAttachment.IPAddressName, + "forwarding_rule_name": serviceAttachment.ForwardingRuleName, + } + + rl = append(rl, serviceAttachmentMapString) + } + + return rl +} + +func FlattenMaintenance(m *maintenance.Maintenance) []map[string]interface{} { + var windows []map[string]interface{} + for _, w := range m.Windows { + tfw := map[string]interface{}{ + "start_hour": w.StartHour, + "duration_in_hours": w.DurationInHours, + "days": w.Days, + } + windows = append(windows, tfw) + } + + tf := map[string]interface{}{ + "mode": m.Mode, + "window": windows, + } + + return []map[string]interface{}{tf} +} diff --git a/provider/utils/get.go b/provider/utils/get.go new file mode 100644 index 00000000..b4ccaad5 --- /dev/null +++ b/provider/utils/get.go @@ -0,0 +1,30 @@ +package utils + +import ( + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// GetString safely retrieves a string value from schema.ResourceData. +func GetString(d *schema.ResourceData, key string) *string { + if v, ok := d.GetOk(key); ok { + return redis.String(v.(string)) + } + return redis.String("") +} + +// GetBool safely retrieves a bool value from schema.ResourceData. +func GetBool(d *schema.ResourceData, key string) *bool { + if v, ok := d.GetOk(key); ok { + return redis.Bool(v.(bool)) + } + return redis.Bool(false) +} + +// GetInt safely retrieves an int value from schema.ResourceData. +func GetInt(d *schema.ResourceData, key string) *int { + if v, ok := d.GetOk(key); ok { + return redis.Int(v.(int)) + } + return redis.Int(0) +} diff --git a/provider/utils/set.go b/provider/utils/set.go new file mode 100644 index 00000000..196e2f67 --- /dev/null +++ b/provider/utils/set.go @@ -0,0 +1,46 @@ +package utils + +import ( + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func SetStringIfNotEmpty(d *schema.ResourceData, key string, setter func(*string)) { + if v, ok := d.GetOk(key); ok { + if s, valid := v.(string); valid && s != "" { + setter(redis.String(s)) + } + } +} + +func SetIntIfPositive(d *schema.ResourceData, key string, setter func(*int)) { + if v, ok := d.GetOk(key); ok { + if i, valid := v.(int); valid && i > 0 { + setter(redis.Int(i)) + } + } +} + +func SetInt(d *schema.ResourceData, key string, setter func(*int)) { + if v, ok := d.GetOk(key); ok { + if i, valid := v.(int); valid { + setter(redis.Int(i)) + } + } +} + +func SetFloat64(d *schema.ResourceData, key string, setter func(*float64)) { + if v, ok := d.GetOk(key); ok { + if f, valid := v.(float64); valid { + setter(redis.Float64(f)) + } + } +} + +func SetBool(d *schema.ResourceData, key string, setter func(*bool)) { + if v, ok := d.GetOk(key); ok { + if b, valid := v.(bool); valid { + setter(redis.Bool(b)) + } + } +} diff --git a/provider/utils/utils.go b/provider/utils/utils.go index 13a7417e..e34b7e4e 100644 --- a/provider/utils/utils.go +++ b/provider/utils/utils.go @@ -1,70 +1,387 @@ package utils import ( + "fmt" + "strconv" + "strings" + "sync" + "time" + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/rediscloud-go-api/service/latest_backups" + "github.com/RedisLabs/rediscloud-go-api/service/latest_imports" + "github.com/RedisLabs/rediscloud-go-api/service/pricing" + "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + redisTags "github.com/RedisLabs/rediscloud-go-api/service/tags" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "golang.org/x/net/context" ) -// GetString safely retrieves a string value from schema.ResourceData. -func GetString(d *schema.ResourceData, key string) *string { - if v, ok := d.GetOk(key); ok { - return redis.String(v.(string)) +const RedisCloudUrlEnvVar = "REDISCLOUD_URL" + +func SetToStringSlice(set *schema.Set) []*string { + var ret []*string + for _, s := range set.List() { + ret = append(ret, redis.String(s.(string))) + } + return ret +} + +func InterfaceToStringSlice(list []interface{}) []*string { + var ret []*string + for _, i := range list { + if i == nil { + // The user probably entered "" (string's zero-value) but gets read in as nil (interface{}'s zero-value) + ret = append(ret, redis.String("")) + } else { + ret = append(ret, redis.String(i.(string))) + } + } + return ret +} + +type perIdLock struct { + lock sync.Mutex + store map[int]*sync.Mutex +} + +func NewPerIdLock() *perIdLock { + return &perIdLock{ + store: map[int]*sync.Mutex{}, + } +} + +func (m *perIdLock) Lock(id int) { + m.get(id).Lock() +} + +func (m *perIdLock) Unlock(id int) { + m.get(id).Unlock() +} + +func (m *perIdLock) get(id int) *sync.Mutex { + m.lock.Lock() + defer m.lock.Unlock() + + if v, ok := m.store[id]; ok { + return v + } + + mutex := &sync.Mutex{} + m.store[id] = mutex + return mutex +} + +// IDs of any resources dependent on a subscription need to be divided by a slash. In this format: /. +func BuildResourceId(subId int, id int) string { + return fmt.Sprintf("%d/%d", subId, id) +} + +func IsTime() schema.SchemaValidateDiagFunc { + return func(i interface{}, path cty.Path) diag.Diagnostics { + var diags diag.Diagnostics + + v, ok := i.(string) + if !ok { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Value not a string", + Detail: fmt.Sprintf("Value should be a string rather than %T", i), + }) + } else if _, err := time.Parse("15:04", v); err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Value is not a time", + Detail: fmt.Sprintf("Value should be a valid time, got: %q: %s", i, err), + }) + } + + return diags + } +} + +func ParseLatestBackupStatus(latestBackupStatus *latest_backups.LatestBackupStatus) ([]map[string]interface{}, error) { + lbs := map[string]interface{}{ + "response": nil, + "error": nil, + } + + if latestBackupStatus.Response.Resource != nil { + res := map[string]interface{}{ + "status": redis.StringValue(latestBackupStatus.Response.Resource.Status), + "last_backup_time": nil, + "failure_reason": redis.StringValue(latestBackupStatus.Response.Resource.FailureReason), + } + if latestBackupStatus.Response.Resource.LastBackupTime != nil { + res["last_backup_time"] = latestBackupStatus.Response.Resource.LastBackupTime.String() + } + lbs["response"] = []map[string]interface{}{res} + } + + if latestBackupStatus.Response.Error != nil { + err := map[string]interface{}{ + "type": redis.StringValue(latestBackupStatus.Response.Error.Type), + "description": redis.StringValue(latestBackupStatus.Response.Error.Description), + "status": redis.StringValue(latestBackupStatus.Response.Error.Status), + } + lbs["error"] = []map[string]interface{}{err} + } + + return []map[string]interface{}{lbs}, nil +} + +func ParseLatestImportStatus(latestImportStatus *latest_imports.LatestImportStatus) ([]map[string]interface{}, error) { + lis := map[string]interface{}{ + "response": nil, + "error": nil, + } + + if latestImportStatus.Response.Resource != nil { + res := map[string]interface{}{ + "status": redis.StringValue(latestImportStatus.Response.Resource.Status), + "last_import_time": nil, + "failure_reason": redis.StringValue(latestImportStatus.Response.Resource.FailureReason), + "failure_reason_params": ParseFailureReasonParams(latestImportStatus.Response.Resource.FailureReasonParams), + } + if latestImportStatus.Response.Resource.LastImportTime != nil { + res["last_import_time"] = latestImportStatus.Response.Resource.LastImportTime.String() + } + lis["response"] = []map[string]interface{}{res} + } + + if latestImportStatus.Response.Error != nil { + err := map[string]interface{}{ + "type": redis.StringValue(latestImportStatus.Response.Error.Type), + "description": redis.StringValue(latestImportStatus.Response.Error.Description), + "status": redis.StringValue(latestImportStatus.Response.Error.Status), + } + lis["error"] = []map[string]interface{}{err} + } + + return []map[string]interface{}{lis}, nil +} + +func ParseFailureReasonParams(params []*latest_imports.FailureReasonParam) []map[string]interface{} { + writableParams := make([]map[string]interface{}, 0) + for _, param := range params { + writableParams = append(writableParams, map[string]interface{}{ + "key": redis.StringValue(param.Key), + "value": redis.StringValue(param.Value), + }) + } + return writableParams +} + +func ApplyCertificateHints(tlsAuthEnabled bool, d *schema.ResourceData) error { + sslCertificate := d.Get("client_ssl_certificate").(string) + tlsCertificates := InterfaceToStringSlice(d.Get("client_tls_certificates").([]interface{})) + if tlsAuthEnabled { + if sslCertificate == "" && len(tlsCertificates) == 0 { + // The resource does have SSL/TLS auth enabled, but it was not certified by this template. + if err := d.Set("client_tls_certificates", []interface{}{"Unknown certificate"}); err != nil { + return err + } + } + } else { + if sslCertificate != "" { + // The resource does not have SSL/TLS auth enabled, but this template provides an SSL certificate + if err := d.Set("client_ssl_certificate", ""); err != nil { + return err + } + } + if len(tlsCertificates) >= 0 { + // The resource does not have SSL/TLS auth enabled, but this template provides TLS certificates. + if err := d.Set("client_tls_certificates", []interface{}{}); err != nil { + return err + } + } + } + + return nil +} + +func ValidateTagsfunc(tagsRaw interface{}, _ cty.Path) diag.Diagnostics { + tags := tagsRaw.(map[string]interface{}) + invalid := make([]string, 0) + for k, v := range tags { + if k != strings.ToLower(k) { + invalid = append(invalid, k) + } + vStr := v.(string) + if vStr != strings.ToLower(vStr) { + invalid = append(invalid, vStr) + } + } + + if len(invalid) > 0 { + return diag.Errorf("tag keys and values must be lower case, invalid entries: %s", strings.Join(invalid, ", ")) + } + return nil +} + +func ToDatabaseId(id string) (int, int, error) { + parts := strings.Split(id, "/") + + if len(parts) > 2 { + return 0, 0, fmt.Errorf("invalid id: %s", id) + } + + if len(parts) == 1 { + dbId, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, err + } + return 0, dbId, nil + } + + subId, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, 0, err + } + + dbId, err := strconv.Atoi(parts[1]) + if err != nil { + return 0, 0, err } - return redis.String("") + + return subId, dbId, nil } -// GetBool safely retrieves a bool value from schema.ResourceData. -func GetBool(d *schema.ResourceData, key string) *bool { - if v, ok := d.GetOk(key); ok { - return redis.Bool(v.(bool)) +func ReadTags(ctx context.Context, api *ApiClient, subId int, databaseId int, d *schema.ResourceData) error { + tags := make(map[string]string) + tagResponse, err := api.Client.Tags.Get(ctx, subId, databaseId) + if err != nil { + return err } - return redis.Bool(false) + if tagResponse.Tags != nil { + for _, t := range *tagResponse.Tags { + tags[redis.StringValue(t.Key)] = redis.StringValue(t.Value) + } + } + return d.Set("tags", tags) } -// GetInt safely retrieves an int value from schema.ResourceData. -func GetInt(d *schema.ResourceData, key string) *int { - if v, ok := d.GetOk(key); ok { - return redis.Int(v.(int)) +func ReadPaymentMethodID(d *schema.ResourceData) (*int, error) { + pmID := d.Get("payment_method_id").(string) + if pmID != "" { + pmID, err := strconv.Atoi(pmID) + if err != nil { + return nil, err + } + return redis.Int(pmID), nil } - return redis.Int(0) + return nil, nil } -func SetStringIfNotEmpty(d *schema.ResourceData, key string, setter func(*string)) { - if v, ok := d.GetOk(key); ok { - if s, valid := v.(string); valid && s != "" { - setter(redis.String(s)) +func RemoteBackupIntervalSetCorrectly(key string) schema.CustomizeDiffFunc { + // Validate multiple attributes - https://github.com/hashicorp/terraform-plugin-sdk/issues/233 + + return func(ctx context.Context, diff *schema.ResourceDiff, i interface{}) error { + if v, ok := diff.GetOk(key); ok { + backups := v.([]interface{}) + if len(backups) == 1 { + v := backups[0].(map[string]interface{}) + + interval := v["interval"].(string) + timeUtc := v["time_utc"].(string) + + if interval != databases.BackupIntervalEvery12Hours && interval != databases.BackupIntervalEvery24Hours && timeUtc != "" { + return fmt.Errorf("unexpected value at %s.0.time_utc - time_utc can only be set when interval is either %s or %s", key, databases.BackupIntervalEvery24Hours, databases.BackupIntervalEvery12Hours) + } + } } + return nil } + } -func SetIntIfPositive(d *schema.ResourceData, key string, setter func(*int)) { - if v, ok := d.GetOk(key); ok { - if i, valid := v.(int); valid && i > 0 { - setter(redis.Int(i)) +func WriteTags(ctx context.Context, api *ApiClient, subId int, databaseId int, d *schema.ResourceData) error { + tags := make([]*redisTags.Tag, 0) + tState := d.Get("tags").(map[string]interface{}) + for k, v := range tState { + tags = append(tags, &redisTags.Tag{ + Key: redis.String(k), + Value: redis.String(v.(string)), + }) + } + return api.Client.Tags.Put(ctx, subId, databaseId, redisTags.AllTags{Tags: &tags}) +} + +func BuildBackupPlan(data interface{}, periodicBackupPath interface{}) *databases.DatabaseBackupConfig { + var d map[string]interface{} + + switch v := data.(type) { + case []interface{}: + if len(v) != 1 { + if periodicBackupPath == nil { + return &databases.DatabaseBackupConfig{Active: redis.Bool(false)} + } else { + return nil + } } + d = v[0].(map[string]interface{}) + default: + d = v.(map[string]interface{}) + } + + config := databases.DatabaseBackupConfig{ + Active: redis.Bool(true), + Interval: redis.String(d["interval"].(string)), + StorageType: redis.String(d["storage_type"].(string)), + StoragePath: redis.String(d["storage_path"].(string)), + } + + if v := d["time_utc"].(string); v != "" { + config.TimeUTC = redis.String(v) } + + return &config } -func SetInt(d *schema.ResourceData, key string, setter func(*int)) { - if v, ok := d.GetOk(key); ok { - if i, valid := v.(int); valid { - setter(redis.Int(i)) +func FlattenPricing(pricing []*pricing.Pricing) []map[string]interface{} { + var tfs = make([]map[string]interface{}, 0) + for _, p := range pricing { + + tf := map[string]interface{}{ + "database_name": p.DatabaseName, + "type": p.Type, + "type_details": p.TypeDetails, + "quantity": p.Quantity, + "quantity_measurement": p.QuantityMeasurement, + "price_per_unit": p.PricePerUnit, + "price_currency": p.PriceCurrency, + "price_period": p.PricePeriod, + "region": p.Region, } + tfs = append(tfs, tf) } + + return tfs } -func SetFloat64(d *schema.ResourceData, key string, setter func(*float64)) { - if v, ok := d.GetOk(key); ok { - if f, valid := v.(float64); valid { - setter(redis.Float64(f)) +func FilterSubscriptions(subs []*subscriptions.Subscription, filters []func(sub *subscriptions.Subscription) bool) []*subscriptions.Subscription { + var filteredSubs []*subscriptions.Subscription + for _, sub := range subs { + if filterSub(sub, filters) { + filteredSubs = append(filteredSubs, sub) } } + + return filteredSubs } -func SetBool(d *schema.ResourceData, key string, setter func(*bool)) { - if v, ok := d.GetOk(key); ok { - if b, valid := v.(bool); valid { - setter(redis.Bool(b)) +func filterSub(method *subscriptions.Subscription, filters []func(method *subscriptions.Subscription) bool) bool { + for _, f := range filters { + if !f(method) { + return false } } + return true +} + +func BuildPrivateServiceConnectActiveActiveId(subId int, regionId int, pscServiceId int) string { + return fmt.Sprintf("%d/%d/%d", subId, regionId, pscServiceId) } diff --git a/provider/utils_test.go b/provider/utils/utils_test.go similarity index 63% rename from provider/utils_test.go rename to provider/utils/utils_test.go index 3a2a9f3f..8d49e719 100644 --- a/provider/utils_test.go +++ b/provider/utils/utils_test.go @@ -1,9 +1,9 @@ -package provider +package utils import ( - "github.com/stretchr/testify/assert" - "os" "testing" + + "github.com/stretchr/testify/assert" ) func TestIsTime(t *testing.T) { @@ -22,16 +22,8 @@ func TestIsTime(t *testing.T) { for _, test := range tests { t.Run(test.input, func(t *testing.T) { - actual := isTime()(test.input, nil) + actual := IsTime()(test.input, nil) assert.Equal(t, test.errors, actual.HasError(), "%+v", actual) }) } } - -func testAccRequiresEnvVar(t *testing.T, envVarName string) string { - envVarValue := os.Getenv(envVarName) - if envVarValue == "" || envVarValue == "false" { - t.Skipf("Skipping test because %s is not set.", envVarName) - } - return envVarValue -} diff --git a/provider/utils/waits.go b/provider/utils/waits.go new file mode 100644 index 00000000..d46c780f --- /dev/null +++ b/provider/utils/waits.go @@ -0,0 +1,257 @@ +package utils + +import ( + "context" + "log" + "time" + + "github.com/RedisLabs/rediscloud-go-api/redis" + "github.com/RedisLabs/rediscloud-go-api/service/access_control_lists/redis_rules" + "github.com/RedisLabs/rediscloud-go-api/service/databases" + "github.com/RedisLabs/rediscloud-go-api/service/psc" + "github.com/RedisLabs/rediscloud-go-api/service/subscriptions" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +// This timeout is an absolute maximum used in some of the waitForStatus operations concerning creation and updating +// Subscriptions and Databases. Reads and Deletions have their own, stricter timeouts because they consistently behave +// well. The Terraform operation-level timeout should kick in way before we hit this and kill the task. +// Unfortunately there's no "time-remaining-before-timeout" utility, or we could use that in the wait blocks. +const SafetyTimeout = 6 * time.Hour + +func WaitForSubscriptionToBeActive(ctx context.Context, id int, api *ApiClient) error { + wait := &retry.StateChangeConf{ + Pending: []string{subscriptions.SubscriptionStatusPending}, + Target: []string{subscriptions.SubscriptionStatusActive}, + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for subscription %d to be %s", id, subscriptions.SubscriptionStatusActive) + + subscription, err := api.Client.Subscription.Get(ctx, id) + if err != nil { + return nil, "", err + } + + return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func WaitForSubscriptionToBeEncryptionKeyPending(ctx context.Context, id int, api *ApiClient) error { + wait := &retry.StateChangeConf{ + Pending: []string{subscriptions.SubscriptionStatusPending}, + Target: []string{subscriptions.SubscriptionStatusEncryptionKeyPending, subscriptions.SubscriptionStatusActive}, + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for subscription %d to be %s", id, subscriptions.SubscriptionStatusEncryptionKeyPending) + + subscription, err := api.Client.Subscription.Get(ctx, id) + if err != nil { + return nil, "", err + } + + return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func WaitForSubscriptionToBeDeleted(ctx context.Context, id int, api *ApiClient) error { + wait := &retry.StateChangeConf{ + Pending: []string{subscriptions.SubscriptionStatusDeleting}, + Target: []string{"deleted"}, // TODO: update this with deleted field in SDK + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for subscription %d to be deleted", id) + + subscription, err := api.Client.Subscription.Get(ctx, id) + if err != nil { + if _, ok := err.(*subscriptions.NotFound); ok { + return "deleted", "deleted", nil + } // TODO: update this with deleted field in SDK + return nil, "", err + } + + return redis.StringValue(subscription.Status), redis.StringValue(subscription.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func WaitForDatabaseToBeActive(ctx context.Context, subId, id int, api *ApiClient) error { + wait := &retry.StateChangeConf{ + Pending: []string{ + databases.StatusDraft, + databases.StatusPending, + databases.StatusActiveChangePending, + databases.StatusRCPActiveChangeDraft, + databases.StatusActiveChangeDraft, + databases.StatusRCPDraft, + databases.StatusRCPChangePending, + databases.StatusProxyPolicyChangePending, + databases.StatusProxyPolicyChangeDraft, + databases.StatusDynamicEndpointsCreationPending, + databases.StatusActiveUpgradePending, + }, + Target: []string{databases.StatusActive}, + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for database %d to be active", id) + + database, err := api.Client.Database.Get(ctx, subId, id) + if err != nil { + return nil, "", err + } + + return redis.StringValue(database.Status), redis.StringValue(database.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func WaitForPrivateServiceConnectServiceToBeActive(ctx context.Context, refreshFunc func() (result interface{}, state string, err error)) error { + wait := &retry.StateChangeConf{ + Pending: []string{ + psc.ServiceStatusCreateQueued, + psc.ServiceStatusInitialized, + psc.ServiceStatusCreatePending}, + Target: []string{psc.ServiceStatusActive}, + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: refreshFunc, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +const PlaceholderStatusDisappear = "disappeared" + +func WaitForPrivateServiceConnectServiceEndpointDisappear(ctx context.Context, refreshFunc func() (result interface{}, state string, err error)) error { + wait := &retry.StateChangeConf{ + Pending: []string{ + psc.EndpointStatusProcessing, + psc.EndpointStatusPending, + psc.EndpointStatusAcceptPending, + psc.EndpointStatusActive, + psc.EndpointStatusDeleted, + psc.EndpointStatusRejected, + psc.EndpointStatusRejectPending, + psc.EndpointStatusFailed, + }, + Target: []string{PlaceholderStatusDisappear}, + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: refreshFunc, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func WaitForPrivateServiceConnectServiceEndpointToBePending(ctx context.Context, refreshFunc func(targetStatus string) (result interface{}, state string, err error)) error { + targetStatus := psc.EndpointStatusPending + return waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx, func() (result interface{}, state string, err error) { + return refreshFunc(targetStatus) + }, targetStatus, []string{ + psc.EndpointStatusInitialized, + psc.EndpointStatusProcessing}) +} + +func WaitForPrivateServiceConnectServiceEndpointToBeActive(ctx context.Context, refreshFunc func(targetStatus string) (result interface{}, state string, err error)) error { + targetStatus := psc.EndpointStatusActive + return waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx, func() (result interface{}, state string, err error) { + return refreshFunc(targetStatus) + }, targetStatus, []string{ + psc.EndpointStatusPending, + psc.EndpointStatusAcceptPending}) +} + +func WaitForPrivateServiceConnectServiceEndpointToBeRejected(ctx context.Context, refreshFunc func(targetStatus string) (result interface{}, state string, err error)) error { + targetStatus := psc.EndpointStatusRejected + return waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx, func() (result interface{}, state string, err error) { + return refreshFunc(targetStatus) + }, targetStatus, []string{ + psc.EndpointStatusPending, + psc.EndpointStatusRejectPending}) +} + +func waitForPrivateServiceConnectServiceEndpointToBeInStatus(ctx context.Context, + refreshFunc func() (result interface{}, state string, err error), status string, pendingStatus []string) error { + wait := &retry.StateChangeConf{ + Pending: pendingStatus, + Target: []string{status}, + Timeout: SafetyTimeout, + Delay: 10 * time.Second, + PollInterval: 30 * time.Second, + + Refresh: refreshFunc, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +} + +func WaitForAclRuleToBeActive(ctx context.Context, id int, api *ApiClient) error { + wait := &retry.StateChangeConf{ + Delay: 5 * time.Second, + Pending: []string{redis_rules.StatusPending}, + Target: []string{redis_rules.StatusActive}, + Timeout: 5 * time.Minute, + + Refresh: func() (result interface{}, state string, err error) { + log.Printf("[DEBUG] Waiting for rule %d to be active", id) + + rule, err := api.Client.RedisRules.Get(ctx, id) + if err != nil { + return nil, "", err + } + + return redis.StringValue(rule.Status), redis.StringValue(rule.Status), nil + }, + } + if _, err := wait.WaitForStateContext(ctx); err != nil { + return err + } + + return nil +}