diff --git a/.github/workflows/terraform_provider_pr.yml b/.github/workflows/terraform_provider_pr.yml index eb6b320a..96ecc32b 100644 --- a/.github/workflows/terraform_provider_pr.yml +++ b/.github/workflows/terraform_provider_pr.yml @@ -126,6 +126,18 @@ jobs: - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudEssentialsDatabase_CRUDI"' + go_test_smoke_essentials_sub: + name: go test smoke essentials sub + needs: [go_build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 + with: + go-version-file: go.mod + - run: EXECUTE_TESTS=true make testacc TESTARGS='-run="TestAccResourceRedisCloudEssentialsSubscription"' + + go_test_smoke_pro_db: name: go test smoke pro db needs: [go_build] diff --git a/CHANGELOG.md b/CHANGELOG.md index f6c7d121..cb9a05e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ 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.1.5 (1st July 2025) + +### Added + +Feature: Support Marketplace as a payment method for Essentials subscription +Feature: Add TLS certificate to databases’ data sources + +### Fixed: + +Unexpected state `dynamic-endpoints-creation-pending' +Can not disable default user on essentials db + +# 2.1.4 (22nd May 2025) + +### Added + +- Documentation for `rediscloud_active_active_subscription_regions` added. +- Schema documentation amended to match documentation above. + # 2.1.3 (21st May 2025) ### Added diff --git a/docs/data-sources/rediscloud_active_active_subscription_database.md b/docs/data-sources/rediscloud_active_active_subscription_database.md index 6e9d92d8..86409140 100644 --- a/docs/data-sources/rediscloud_active_active_subscription_database.md +++ b/docs/data-sources/rediscloud_active_active_subscription_database.md @@ -47,6 +47,7 @@ data "rediscloud_active_active_subscription_database" "example" { * `support_oss_cluster_api` - Supports the Redis open-source (OSS) Cluster API. * `external_endpoint_for_oss_cluster_api` - Use the external endpoint for open-source (OSS) Cluster API. * `enable_tls` - Enable TLS for database. +* `tls_certificate` - TLS certificate used for authentication. * `data_eviction` - The data items eviction policy. * `global_modules` - A list of modules to be enabled on all deployments of this database. * `public_endpoint` - Public endpoint to access the database. diff --git a/docs/data-sources/rediscloud_essentials_subscription.md b/docs/data-sources/rediscloud_essentials_subscription.md index 50ad40e0..4f3e1410 100644 --- a/docs/data-sources/rediscloud_essentials_subscription.md +++ b/docs/data-sources/rediscloud_essentials_subscription.md @@ -33,5 +33,6 @@ output "rediscloud_essentials_subscription" { * `status` - The current status of the subscription * `plan_id` - The plan to which this subscription belongs +* `payment_method` - Payment method for the requested subscription. If `credit-card` is specified, the payment method id must be defined. This information is only used when creating a new subscription and any changes will be ignored after this. * `payment_method_id` - A valid payment method pre-defined in the current account * `creation_date` - When the subscription was created diff --git a/docs/resources/rediscloud_essentials_database.md b/docs/resources/rediscloud_essentials_database.md index 3635740c..eff00d6b 100644 --- a/docs/resources/rediscloud_essentials_database.md +++ b/docs/resources/rediscloud_essentials_database.md @@ -67,7 +67,7 @@ The following arguments are supported: * `replica` - (Optional) If specified, this database will be a replica of the specified Redis databases provided, documented below. * `client_tls_certificates` - (Optional) A list of TLS/SSL certificates (public keys) with new line characters replaced by \n. * `password` - (Optional) Password to access the database. If not specified, a random 32 character long alphanumeric password will be automatically generated. -* `enable_default_user` - (Optional) When `true` enables connecting to the database with the default user. Default `true`. +* `enable_default_user` - (Optional) When `true` enables connecting to the database with the default user. Default `true`. If set to `false`, any value for `password` will be ignored. * `alert` - (Optional) A block defining Redis database alert. Can be specified multiple times. Documented below. * `tags` - (Optional) A string/string map of tags to associate with this database. Note that all keys and values must be lowercase. * `modules` - (Optional) A list of modules objects, documented below. **Modifying this attribute will force creation of a new resource.** diff --git a/go.mod b/go.mod index 282bc8c0..e3a6ceee 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/RedisLabs/terraform-provider-rediscloud go 1.22.4 require ( - github.com/RedisLabs/rediscloud-go-api v0.26.0 + github.com/RedisLabs/rediscloud-go-api v0.29.0 github.com/bflad/tfproviderlint v0.31.0 github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 @@ -66,3 +66,6 @@ require ( google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +// for local development, uncomment this +//replace github.com/RedisLabs/rediscloud-go-api => ../rediscloud-go-api diff --git a/go.sum b/go.sum index abd0f506..a065a2d8 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RedisLabs/rediscloud-go-api v0.26.0 h1:ka6CN2O+Ti6igkfH8lDT9Ua1/ksEh2H5dj1GF/pnKKQ= -github.com/RedisLabs/rediscloud-go-api v0.26.0/go.mod h1:3/oVb71rv2OstFRYEc65QCIbfwnJTgZeQhtPCcdHook= +github.com/RedisLabs/rediscloud-go-api v0.29.0 h1:XLVBMSgHwaaHFmf+TXrsU2veQ67J+e5Xrz54FggnwTY= +github.com/RedisLabs/rediscloud-go-api v0.29.0/go.mod h1:3/oVb71rv2OstFRYEc65QCIbfwnJTgZeQhtPCcdHook= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= diff --git a/provider/datasource_rediscloud_active_active_database.go b/provider/datasource_rediscloud_active_active_database.go index eea5e95e..98dfe697 100644 --- a/provider/datasource_rediscloud_active_active_database.go +++ b/provider/datasource_rediscloud_active_active_database.go @@ -58,6 +58,11 @@ func dataSourceRedisCloudActiveActiveDatabase() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "tls_certificate": { + Description: "TLS certificate used for authentication.", + Type: schema.TypeString, + Computed: true, + }, "data_eviction": { Description: "Data eviction items policy", Type: schema.TypeString, @@ -362,9 +367,30 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema return diag.FromErr(err) } + if dbTlsCertificate, err := getCertificateData(ctx, api, subId, dbId); err != nil { + return diag.FromErr(err) + } else if dbTlsCertificate != nil { + if err := d.Set("tls_certificate", dbTlsCertificate.PublicCertificatePEMString); err != nil { + return diag.FromErr(err) + } + } + 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) + + if err != nil { + return nil, err + } + + if dbTlsCertificate == nil { + return nil, fmt.Errorf("no certificate found for database %d", dbId) + } + return dbTlsCertificate, nil +} + func filterAADatabases(list *databases.ListActiveActiveDatabase, filters []func(db *databases.ActiveActiveDatabase) bool) ([]*databases.ActiveActiveDatabase, error) { var filtered []*databases.ActiveActiveDatabase for list.Next() { diff --git a/provider/datasource_rediscloud_essentials_subscription.go b/provider/datasource_rediscloud_essentials_subscription.go index 10e91f56..e6accacd 100644 --- a/provider/datasource_rediscloud_essentials_subscription.go +++ b/provider/datasource_rediscloud_essentials_subscription.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/RedisLabs/rediscloud-go-api/redis" - fixedSubscriptions "github.com/RedisLabs/rediscloud-go-api/service/fixed/subscriptions" + fs "github.com/RedisLabs/rediscloud-go-api/service/fixed/subscriptions" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -60,16 +60,16 @@ func dataSourceRedisCloudEssentialsSubscriptionRead(ctx context.Context, d *sche return diag.FromErr(err) } - var filters []func(method *fixedSubscriptions.FixedSubscription) bool + var filters []func(method *fs.FixedSubscriptionResponse) bool if id, ok := d.GetOk("id"); ok { - filters = append(filters, func(sub *fixedSubscriptions.FixedSubscription) bool { + filters = append(filters, func(sub *fs.FixedSubscriptionResponse) bool { return redis.IntValue(sub.ID) == id }) } if name, ok := d.GetOk("name"); ok { - filters = append(filters, func(sub *fixedSubscriptions.FixedSubscription) bool { + filters = append(filters, func(sub *fs.FixedSubscriptionResponse) bool { return redis.StringValue(sub.Name) == name }) } @@ -109,8 +109,8 @@ func dataSourceRedisCloudEssentialsSubscriptionRead(ctx context.Context, d *sche return diags } -func filterFixedSubscriptions(subs []*fixedSubscriptions.FixedSubscription, filters []func(sub *fixedSubscriptions.FixedSubscription) bool) []*fixedSubscriptions.FixedSubscription { - var filteredSubs []*fixedSubscriptions.FixedSubscription +func filterFixedSubscriptions(subs []*fs.FixedSubscriptionResponse, filters []func(sub *fs.FixedSubscriptionResponse) bool) []*fs.FixedSubscriptionResponse { + var filteredSubs []*fs.FixedSubscriptionResponse for _, sub := range subs { if filterFixedSub(sub, filters) { filteredSubs = append(filteredSubs, sub) @@ -120,7 +120,7 @@ func filterFixedSubscriptions(subs []*fixedSubscriptions.FixedSubscription, filt return filteredSubs } -func filterFixedSub(method *fixedSubscriptions.FixedSubscription, filters []func(method *fixedSubscriptions.FixedSubscription) bool) bool { +func filterFixedSub(method *fs.FixedSubscriptionResponse, filters []func(method *fs.FixedSubscriptionResponse) bool) bool { for _, f := range filters { if !f(method) { return false diff --git a/provider/rediscloud_active_active_database_test.go b/provider/rediscloud_active_active_database_test.go index 3b69ba2a..40d0f1ec 100644 --- a/provider/rediscloud_active_active_database_test.go +++ b/provider/rediscloud_active_active_database_test.go @@ -112,6 +112,8 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(datasourceName, "support_oss_cluster_api", "false"), resource.TestCheckResourceAttr(datasourceName, "external_endpoint_for_oss_cluster_api", "false"), resource.TestCheckResourceAttr(datasourceName, "enable_tls", "false"), + resource.TestCheckResourceAttrSet(datasourceName, "tls_certificate"), + resource.TestCheckResourceAttr(datasourceName, "data_eviction", "volatile-lru"), resource.TestCheckResourceAttr(datasourceName, "global_modules.#", "1"), resource.TestCheckResourceAttr(datasourceName, "global_modules.0", "RedisJSON"), diff --git a/provider/rediscloud_active_active_subscription_test.go b/provider/rediscloud_active_active_subscription_test.go index 63c7d701..6c89f4b4 100644 --- a/provider/rediscloud_active_active_subscription_test.go +++ b/provider/rediscloud_active_active_subscription_test.go @@ -24,7 +24,7 @@ var activeActiveMarketplaceFlag = flag.Bool("activeActiveMarketplace", false, // Also checks active-active subscription regions. func TestAccResourceRedisCloudActiveActiveSubscription_CRUDI(t *testing.T) { - //testAccRequiresEnvVar(t, "EXECUTE_TESTS") + testAccRequiresEnvVar(t, "EXECUTE_TESTS") name := acctest.RandomWithPrefix(testResourcePrefix) const resourceName = "rediscloud_active_active_subscription.example" diff --git a/provider/rediscloud_essentials_subscription_test.go b/provider/rediscloud_essentials_subscription_test.go index 5c4dad4c..21d8b638 100644 --- a/provider/rediscloud_essentials_subscription_test.go +++ b/provider/rediscloud_essentials_subscription_test.go @@ -2,16 +2,21 @@ package provider import ( "context" + "flag" "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" + "regexp" "strconv" "testing" ) -func TestAccResourceRedisCloudEssentialsSubscription_FreeCRUDI(t *testing.T) { +var essentialsMarketplaceFlag = flag.Bool("essentialsMarketplace", false, + "Add this flag '-essentialsMarketplace' to run tests for marketplace associated accounts") + +func TestAccResourceRedisCloudEssentialsSubscription_Free_CRUDI(t *testing.T) { testAccRequiresEnvVar(t, "EXECUTE_TESTS") @@ -76,7 +81,7 @@ func TestAccResourceRedisCloudEssentialsSubscription_FreeCRUDI(t *testing.T) { }) } -func TestAccResourceRedisCloudEssentialsSubscription_PaidCRUDI(t *testing.T) { +func TestAccResourceRedisCloudEssentialsSubscription_Paid_CreditCard_CRUDI(t *testing.T) { testAccRequiresEnvVar(t, "EXECUTE_TESTS") @@ -92,13 +97,14 @@ func TestAccResourceRedisCloudEssentialsSubscription_PaidCRUDI(t *testing.T) { CheckDestroy: testAccCheckEssentialsSubscriptionDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccResourceRedisCloudPaidEssentialsSubscription, subscriptionName), + Config: fmt.Sprintf(testAccResourceRedisCloudPaidCreditCardEssentialsSubscription, subscriptionName), Check: resource.ComposeAggregateTestCheckFunc( // Test the resource resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttr(resourceName, "name", subscriptionName), resource.TestCheckResourceAttr(resourceName, "status", "active"), resource.TestCheckResourceAttrSet(resourceName, "plan_id"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method"), resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), resource.TestCheckResourceAttrSet(resourceName, "creation_date"), @@ -112,13 +118,151 @@ func TestAccResourceRedisCloudEssentialsSubscription_PaidCRUDI(t *testing.T) { ), }, { - Config: fmt.Sprintf(testAccResourceRedisCloudPaidEssentialsSubscription, subscriptionNameUpdated), + Config: fmt.Sprintf(testAccResourceRedisCloudPaidCreditCardEssentialsSubscription, subscriptionNameUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + // Test the resource + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "name", subscriptionNameUpdated), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttrSet(resourceName, "plan_id"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + + // Test the datasource + resource.TestCheckResourceAttrSet(datasourceName, "id"), + resource.TestCheckResourceAttr(datasourceName, "name", subscriptionNameUpdated), + resource.TestCheckResourceAttr(datasourceName, "status", "active"), + resource.TestCheckResourceAttrSet(datasourceName, "plan_id"), + resource.TestCheckResourceAttrSet(datasourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(datasourceName, "creation_date"), + ), + }, + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidCreditCardEssentialsSubscription, subscriptionNameUpdated), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccResourceRedisCloudEssentialsSubscription_Paid_NoPaymentType_CRUDI(t *testing.T) { + + testAccRequiresEnvVar(t, "EXECUTE_TESTS") + + subscriptionName := acctest.RandomWithPrefix(testResourcePrefix) + subscriptionNameUpdated := subscriptionName + "-updated" + + const resourceName = "rediscloud_essentials_subscription.example" + const datasourceName = "data.rediscloud_essentials_subscription.example" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckEssentialsSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidNoPaymentTypeEssentialsSubscription, subscriptionName), + Check: resource.ComposeAggregateTestCheckFunc( + // Test the resource + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "name", subscriptionName), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttrSet(resourceName, "plan_id"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + + // Test the datasource + resource.TestCheckResourceAttrSet(datasourceName, "id"), + resource.TestCheckResourceAttr(datasourceName, "name", subscriptionName), + resource.TestCheckResourceAttr(datasourceName, "status", "active"), + resource.TestCheckResourceAttrSet(datasourceName, "plan_id"), + resource.TestCheckResourceAttrSet(datasourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(datasourceName, "creation_date"), + ), + }, + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidNoPaymentTypeEssentialsSubscription, subscriptionNameUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + // Test the resource + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "name", subscriptionNameUpdated), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttrSet(resourceName, "plan_id"), + resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + + // Test the datasource + resource.TestCheckResourceAttrSet(datasourceName, "id"), + resource.TestCheckResourceAttr(datasourceName, "name", subscriptionNameUpdated), + resource.TestCheckResourceAttr(datasourceName, "status", "active"), + resource.TestCheckResourceAttrSet(datasourceName, "plan_id"), + resource.TestCheckResourceAttrSet(datasourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(datasourceName, "creation_date"), + ), + }, + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidNoPaymentTypeEssentialsSubscription, subscriptionNameUpdated), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccResourceRedisCloudEssentialsSubscription_Paid_Marketplace_CRUDI(t *testing.T) { + // Only the qa environment has access to the marketplace, so this test will normally fail. + // Leaving this in the test suite for manual runs + testAccRequiresEnvVar(t, "EXECUTE_TESTS") + + if !*essentialsMarketplaceFlag { + t.Skip("The '-essentialsMarketplace' parameter wasn't provided in the test command.") + } + + subscriptionName := acctest.RandomWithPrefix(testResourcePrefix) + subscriptionNameUpdated := subscriptionName + "-updated" + + const resourceName = "rediscloud_essentials_subscription.example" + const datasourceName = "data.rediscloud_essentials_subscription.example" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckEssentialsSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidMarketplaceEssentialsSubscription, subscriptionName), + Check: resource.ComposeAggregateTestCheckFunc( + // Test the resource + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "name", subscriptionName), + resource.TestCheckResourceAttr(resourceName, "status", "active"), + resource.TestCheckResourceAttrSet(resourceName, "plan_id"), + resource.TestCheckNoResourceAttr(datasourceName, "payment_method_id"), + //resource.TestCheckResourceAttr(resourceName, "payment_method", "marketplace"), // empty from API? + resource.TestCheckResourceAttrSet(resourceName, "creation_date"), + + // Test the datasource + resource.TestCheckResourceAttrSet(datasourceName, "id"), + resource.TestCheckResourceAttr(datasourceName, "name", subscriptionName), + resource.TestCheckResourceAttr(datasourceName, "status", "active"), + resource.TestCheckResourceAttrSet(datasourceName, "plan_id"), + resource.TestCheckNoResourceAttr(datasourceName, "payment_method_id"), + resource.TestCheckResourceAttrSet(datasourceName, "creation_date"), + ), + }, + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidMarketplaceEssentialsSubscription, subscriptionNameUpdated), Check: resource.ComposeAggregateTestCheckFunc( // Test the resource resource.TestCheckResourceAttrSet(resourceName, "id"), resource.TestCheckResourceAttr(resourceName, "name", subscriptionNameUpdated), resource.TestCheckResourceAttr(resourceName, "status", "active"), resource.TestCheckResourceAttrSet(resourceName, "plan_id"), + resource.TestCheckResourceAttr(resourceName, "payment_method", "credit-card"), resource.TestCheckResourceAttrSet(resourceName, "payment_method_id"), resource.TestCheckResourceAttrSet(resourceName, "creation_date"), @@ -132,7 +276,7 @@ func TestAccResourceRedisCloudEssentialsSubscription_PaidCRUDI(t *testing.T) { ), }, { - Config: fmt.Sprintf(testAccResourceRedisCloudPaidEssentialsSubscription, subscriptionNameUpdated), + Config: fmt.Sprintf(testAccResourceRedisCloudPaidMarketplaceEssentialsSubscription, subscriptionNameUpdated), ResourceName: resourceName, ImportState: true, ImportStateVerify: true, @@ -141,6 +285,24 @@ func TestAccResourceRedisCloudEssentialsSubscription_PaidCRUDI(t *testing.T) { }) } +func TestAccResourceRedisCloudEssentialsSubscription_Incorrect_PaymentIdForType(t *testing.T) { + testAccRequiresEnvVar(t, "EXECUTE_TESTS") + + subscriptionName := acctest.RandomWithPrefix(testResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: testAccCheckEssentialsSubscriptionDestroy, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccResourceRedisCloudPaidIncorrectPaymentTypeEssentialsSubscription, subscriptionName), + ExpectError: regexp.MustCompile("payment methods aside from credit-card cannot have a payment ID"), + }, + }, + }) +} + const testAccResourceRedisCloudFreeEssentialsSubscription = ` data "rediscloud_essentials_plan" "example" { name = "30MB" @@ -158,7 +320,25 @@ data "rediscloud_essentials_subscription" "example" { } ` -const testAccResourceRedisCloudPaidEssentialsSubscription = ` +const testAccResourceRedisCloudPaidMarketplaceEssentialsSubscription = ` +data "rediscloud_essentials_plan" "example" { + name = "250MB" + cloud_provider = "AWS" + region = "us-east-1" +} + +resource "rediscloud_essentials_subscription" "example" { + name = "%s" + plan_id = data.rediscloud_essentials_plan.example.id + payment_method = "marketplace" +} + +data "rediscloud_essentials_subscription" "example" { + name = rediscloud_essentials_subscription.example.name +} +` + +const testAccResourceRedisCloudPaidCreditCardEssentialsSubscription = ` data "rediscloud_payment_method" "card" { card_type = "Visa" } @@ -173,6 +353,49 @@ resource "rediscloud_essentials_subscription" "example" { name = "%s" plan_id = data.rediscloud_essentials_plan.example.id payment_method_id = data.rediscloud_payment_method.card.id + payment_method = "credit-card" +} + +data "rediscloud_essentials_subscription" "example" { + name = rediscloud_essentials_subscription.example.name +} +` + +// doesn't contain credit-card, tests for default +const testAccResourceRedisCloudPaidNoPaymentTypeEssentialsSubscription = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_essentials_plan" "example" { + name = "250MB" + cloud_provider = "AWS" + region = "us-east-1" +} + +resource "rediscloud_essentials_subscription" "example" { + name = "%s" + plan_id = data.rediscloud_essentials_plan.example.id + payment_method_id = data.rediscloud_payment_method.card.id +} + +data "rediscloud_essentials_subscription" "example" { + name = rediscloud_essentials_subscription.example.name +} +` + +const testAccResourceRedisCloudPaidIncorrectPaymentTypeEssentialsSubscription = ` +data "rediscloud_essentials_plan" "example" { + name = "250MB" + cloud_provider = "AWS" + region = "us-east-1" +} + +resource "rediscloud_essentials_subscription" "example" { + name = "%s" + plan_id = data.rediscloud_essentials_plan.example.id + payment_method = "marketplace" + payment_method_id = 999999999 } data "rediscloud_essentials_subscription" "example" { diff --git a/provider/resource_rediscloud_essentials_database.go b/provider/resource_rediscloud_essentials_database.go index c69f2432..483aee16 100644 --- a/provider/resource_rediscloud_essentials_database.go +++ b/provider/resource_rediscloud_essentials_database.go @@ -652,6 +652,11 @@ func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.R updateDatabaseRequest.Password = redis.String(password) } + // can't update the password and disable the default user at once + if !d.Get("enable_default_user").(bool) { + updateDatabaseRequest.Password = nil + } + createAlerts := make([]*databases.Alert, 0) alerts := d.Get("alert").(*schema.Set) for _, alert := range alerts.List() { @@ -738,6 +743,7 @@ func waitForEssentialsDatabaseToBeActive(ctx context.Context, subId, id int, api databases.StatusRCPChangePending, databases.StatusProxyPolicyChangePending, databases.StatusProxyPolicyChangeDraft, + databases.StatusDynamicEndpointsCreationPending, }, Target: []string{databases.StatusActive}, Timeout: safetyTimeout, diff --git a/provider/resource_rediscloud_essentials_database_test.go b/provider/resource_rediscloud_essentials_database_test.go index 564bae91..a2229dcf 100644 --- a/provider/resource_rediscloud_essentials_database_test.go +++ b/provider/resource_rediscloud_essentials_database_test.go @@ -208,6 +208,7 @@ resource "rediscloud_essentials_database" "example" { "department" = "finance" } } + data "rediscloud_essentials_database" "example" { subscription_id = rediscloud_essentials_subscription.example.id name = rediscloud_essentials_database.example.name @@ -249,8 +250,157 @@ resource "rediscloud_essentials_database" "example" { "department" = "finance" } } + data "rediscloud_essentials_database" "example" { subscription_id = rediscloud_essentials_subscription.example.id name = rediscloud_essentials_database.example.name } ` + +// there was a bug where removing the default user would cause issues with passwords +func TestAccResourceRedisCloudEssentialsDatabase_DisableDefaultUser(t *testing.T) { + testAccRequiresEnvVar(t, "EXECUTE_TESTS") + + subscriptionName := acctest.RandomWithPrefix(testResourcePrefix) + databaseName := subscriptionName + "-db" + databaseNameUpdated := databaseName + "-updated" + + const resourceName = "rediscloud_essentials_database.example" + const datasourceName = "data.rediscloud_essentials_database.example" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + CheckDestroy: testAccCheckEssentialsSubscriptionDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserCreate, subscriptionName, databaseName), + Check: resource.ComposeAggregateTestCheckFunc( + // Test creating resource + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile("^\\d+/\\d+$")), + resource.TestCheckResourceAttrSet(resourceName, "subscription_id"), + resource.TestCheckResourceAttrSet(resourceName, "db_id"), + resource.TestCheckResourceAttr(resourceName, "name", databaseName), + resource.TestCheckResourceAttr(resourceName, "enable_default_user", "true"), + resource.TestCheckResourceAttr(resourceName, "password", "j43589rhe39f"), + + // Test the datasource + resource.TestMatchResourceAttr(datasourceName, "id", regexp.MustCompile("^\\d+/\\d+$")), + resource.TestCheckResourceAttrSet(datasourceName, "subscription_id"), + resource.TestCheckResourceAttrSet(datasourceName, "db_id"), + resource.TestCheckResourceAttr(datasourceName, "name", databaseName), + resource.TestCheckResourceAttr(datasourceName, "enable_default_user", "true"), + resource.TestCheckResourceAttr(datasourceName, "password", "j43589rhe39f"), + ), + }, + { + // test update + Config: fmt.Sprintf(testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserUpdate, subscriptionName, databaseNameUpdated), + Check: resource.ComposeAggregateTestCheckFunc( + // Test the resource + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile("^\\d+/\\d+$")), + resource.TestCheckResourceAttrSet(resourceName, "subscription_id"), + resource.TestCheckResourceAttrSet(resourceName, "db_id"), + resource.TestCheckResourceAttr(resourceName, "name", databaseNameUpdated), + resource.TestCheckResourceAttr(resourceName, "enable_default_user", "false"), + resource.TestCheckResourceAttr(resourceName, "password", ""), + + // Test the datasource + resource.TestMatchResourceAttr(datasourceName, "id", regexp.MustCompile("^\\d+/\\d+$")), + resource.TestCheckResourceAttrSet(datasourceName, "subscription_id"), + resource.TestCheckResourceAttrSet(datasourceName, "db_id"), + resource.TestCheckResourceAttr(datasourceName, "name", databaseNameUpdated), + resource.TestCheckResourceAttr(datasourceName, "enable_default_user", "false"), + ), + }, + { + Config: fmt.Sprintf(testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserUpdate, subscriptionName, databaseName), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password", "enable_payg_features"}, + }, + }, + }) +} + +const testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserCreate = ` + +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_essentials_plan" "example" { + name = "Single-Zone_1GB" + cloud_provider = "AWS" + region = "eu-west-1" +} + +data "rediscloud_essentials_database" "example" { + subscription_id = rediscloud_essentials_subscription.example.id + name = rediscloud_essentials_database.example.name +} + +resource "rediscloud_essentials_subscription" "example" { + name = "%s" + plan_id = data.rediscloud_essentials_plan.example.id + payment_method_id = data.rediscloud_payment_method.card.id +} + +resource "rediscloud_essentials_database" "example" { + subscription_id = rediscloud_essentials_subscription.example.id + name = "%s" + enable_default_user = true + password = "j43589rhe39f" + + data_persistence = "none" + replication = false + + alert { + name = "throughput-higher-than" + value = 80 + } + tags = { + "envaaaa" = "qaaaa" + } +} +` + +const testAccResourceRedisCloudEssentialsDatabaseDisableDefaultUserUpdate = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} + +data "rediscloud_essentials_plan" "example" { + name = "Single-Zone_1GB" + cloud_provider = "AWS" + region = "eu-west-1" +} + +data "rediscloud_essentials_database" "example" { + subscription_id = rediscloud_essentials_subscription.example.id + name = rediscloud_essentials_database.example.name +} + +resource "rediscloud_essentials_subscription" "example" { + name = "%s" + plan_id = data.rediscloud_essentials_plan.example.id + payment_method_id = data.rediscloud_payment_method.card.id +} + +resource "rediscloud_essentials_database" "example" { + subscription_id = rediscloud_essentials_subscription.example.id + name = "%s" + enable_default_user = false + data_persistence = "none" + replication = false + + alert { + name = "throughput-higher-than" + value = 80 + } + tags = { + "envaaaa" = "qaaaa" + } +} +` diff --git a/provider/resource_rediscloud_essentials_subscription.go b/provider/resource_rediscloud_essentials_subscription.go index b93a5388..d293669b 100644 --- a/provider/resource_rediscloud_essentials_subscription.go +++ b/provider/resource_rediscloud_essentials_subscription.go @@ -2,7 +2,10 @@ package provider import ( "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "log" + "regexp" "strconv" "time" @@ -50,6 +53,20 @@ func resourceRedisCloudEssentialsSubscription() *schema.Resource { Type: schema.TypeInt, Required: true, }, + "payment_method": { + Description: "Payment method for the requested subscription. If credit-card is specified, the payment method id must be defined. This information is only used when creating a new subscription and any changes will be ignored after this.", + Type: schema.TypeString, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringMatch(regexp.MustCompile("^(credit-card|marketplace)$"), "must be 'credit-card' or 'marketplace'")), + Optional: true, + Default: "credit-card", + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.Id() == "" { + // We don't want to ignore the block if the resource is about to be created. + return false + } + return true + }, + }, "payment_method_id": { Description: "The identifier of the method which will be charged for this subscription. Not required for free plans", Type: schema.TypeInt, @@ -68,11 +85,20 @@ func resourceRedisCloudEssentialsSubscriptionCreate(ctx context.Context, d *sche var diags diag.Diagnostics api := meta.(*apiClient) - createSubscriptionRequest := fixedSubscriptions.FixedSubscription{ + createSubscriptionRequest := fixedSubscriptions.FixedSubscriptionRequest{ Name: redis.String(d.Get("name").(string)), PlanId: redis.Int(d.Get("plan_id").(int)), } + // payment_method_id only matters if it is a credit card + if d.Get("payment_method").(string) != "credit-card" && d.Get("payment_method_id") != 0 { + return diag.FromErr(errors.New("payment methods aside from credit-card cannot have a payment ID")) + } + + if v, ok := d.GetOk("payment_method"); ok { + createSubscriptionRequest.PaymentMethod = redis.String(v.(string)) + } + if v, ok := d.GetOk("payment_method_id"); ok { createSubscriptionRequest.PaymentMethodID = redis.Int(v.(int)) } @@ -124,6 +150,9 @@ func resourceRedisCloudEssentialsSubscriptionRead(ctx context.Context, d *schema if err := d.Set("payment_method_id", redis.IntValue(subscription.PaymentMethodID)); err != nil { return diag.FromErr(err) } + if err := d.Set("payment_method", redis.StringValue(subscription.PaymentMethod)); err != nil { + return diag.FromErr(err) + } if err := d.Set("creation_date", redis.TimeValue(subscription.CreationDate).String()); err != nil { return diag.FromErr(err) } @@ -149,11 +178,15 @@ func resourceRedisCloudEssentialsSubscriptionUpdate(ctx context.Context, d *sche return diags } - updateSubscriptionRequest := fixedSubscriptions.FixedSubscription{ + updateSubscriptionRequest := fixedSubscriptions.FixedSubscriptionRequest{ Name: redis.String(d.Get("name").(string)), PlanId: redis.Int(d.Get("plan_id").(int)), } + if v, ok := d.GetOk("payment_method"); ok { + updateSubscriptionRequest.PaymentMethod = redis.String(v.(string)) + } + if v, ok := d.GetOk("payment_method_id"); ok { updateSubscriptionRequest.PaymentMethodID = redis.Int(v.(int)) } diff --git a/provider/resource_rediscloud_pro_subscription.go b/provider/resource_rediscloud_pro_subscription.go index 1d2e8e03..d5ced2cd 100644 --- a/provider/resource_rediscloud_pro_subscription.go +++ b/provider/resource_rediscloud_pro_subscription.go @@ -1022,6 +1022,7 @@ func waitForDatabaseToBeActive(ctx context.Context, subId, id int, api *apiClien databases.StatusRCPChangePending, databases.StatusProxyPolicyChangePending, databases.StatusProxyPolicyChangeDraft, + databases.StatusDynamicEndpointsCreationPending, }, Target: []string{databases.StatusActive}, Timeout: safetyTimeout,