From 621e72a9b534f653eed6741f90d6dc583535caf5 Mon Sep 17 00:00:00 2001 From: John Sharpe Date: Fri, 30 Aug 2024 16:14:30 +0100 Subject: [PATCH] Added tags read/write to resources and read to data sources (#563) * Added tags read/write to resources and read to data sources * Added documentation * Lower-case keys and values, no single-character keys/values --- ...oud_active_active_subscription_database.md | 1 + docs/data-sources/rediscloud_database.md | 1 + .../rediscloud_essentials_database.md | 1 + ...oud_active_active_subscription_database.md | 7 +- .../rediscloud_essentials_database.md | 6 ++ .../rediscloud_subscription_database.md | 7 +- go.mod | 16 ++--- go.sum | 36 +++++----- ...ource_rediscloud_active_active_database.go | 12 ++++ ...tasource_rediscloud_essentials_database.go | 12 ++++ .../datasource_rediscloud_pro_database.go | 12 ++++ .../rediscloud_active_active_database_test.go | 11 ++++ .../rediscloud_essentials_database_test.go | 59 +++++++++++++++++ ...ource_rediscloud_active_active_database.go | 18 +++++ ...resource_rediscloud_essentials_database.go | 46 +++++++++++++ provider/resource_rediscloud_pro_database.go | 65 +++++++++++++++++++ .../resource_rediscloud_pro_database_test.go | 9 +++ 17 files changed, 291 insertions(+), 28 deletions(-) diff --git a/docs/data-sources/rediscloud_active_active_subscription_database.md b/docs/data-sources/rediscloud_active_active_subscription_database.md index 63c26b30..b2905a62 100644 --- a/docs/data-sources/rediscloud_active_active_subscription_database.md +++ b/docs/data-sources/rediscloud_active_active_subscription_database.md @@ -52,6 +52,7 @@ data "rediscloud_active_active_subscription_database" "example" { * `private_endpoint` - Private endpoint to access the database. * `latest_backup_statuses` A list of latest_backup_status objects, documented below. * `latest_import_status` - A latest_import_status object, documented below.` +* `tags` - A string/string map of all Tags associated with this database. The `latest_backup_status` object and `latest_import_status` block contains: diff --git a/docs/data-sources/rediscloud_database.md b/docs/data-sources/rediscloud_database.md index 2f133638..54220ff2 100644 --- a/docs/data-sources/rediscloud_database.md +++ b/docs/data-sources/rediscloud_database.md @@ -63,6 +63,7 @@ data "rediscloud_database" "example" { * `enable_default_user` - When `true` enables connecting to the database with the default user. Default `true`. * `latest_backup_status` - A latest_backup_status object, documented below. * `latest_import_status` - A latest_import_status object, documented below. +* `tags` - A string/string map of all Tags associated with this database. The `alert` block supports: diff --git a/docs/data-sources/rediscloud_essentials_database.md b/docs/data-sources/rediscloud_essentials_database.md index 421f3341..f2e36ca9 100644 --- a/docs/data-sources/rediscloud_essentials_database.md +++ b/docs/data-sources/rediscloud_essentials_database.md @@ -59,6 +59,7 @@ data "rediscloud_essentials_database" "example" { * `modules` A list of database modules, documented below. * `latest_backup_status` - A latest_backup_status object, documented below. * `latest_import_status` - A latest_import_status object, documented below. +* `tags` - A string/string map of all Tags associated with this database. * `memory_limit_in_gb` - **Only relevant with Pay-As-You-Go databases.** Maximum memory usage for this specific database. * `support_oss_cluster_api` - **Only relevant with Pay-As-You-Go databases.** Support Redis open-source (OSS) Cluster API. * `external_endpoint_for_oss_cluster_api` - **Only relevant with Pay-As-You-Go databases.** Should use the external endpoint for open-source (OSS) Cluster API. diff --git a/docs/resources/rediscloud_active_active_subscription_database.md b/docs/resources/rediscloud_active_active_subscription_database.md index dbbc8e73..3ba54869 100644 --- a/docs/resources/rediscloud_active_active_subscription_database.md +++ b/docs/resources/rediscloud_active_active_subscription_database.md @@ -69,6 +69,11 @@ resource "rediscloud_active_active_subscription_database" "database-resource" { value = 60 } } + + tags = { + "environment" = "production" + "cost_center" = "0700" + } } output "us-east-1-public-endpoints" { @@ -101,7 +106,7 @@ The following arguments are supported: * `global_resp_version` - (Optional) Either 'resp2' or 'resp3'. Resp version for Crdb databases within the AA database. Must be compatible with Redis version. * `port` - (Optional) TCP port on which the database is available - must be between 10000 and 19999. **Modifying this attribute will force creation of a new resource.** * `override_region` - (Optional) Override region specific configuration, documented below - +* `tags` - (Optional) A string/string map of tags to associate with this database. Note that all keys and values must be lowercase. The `override_region` block supports: diff --git a/docs/resources/rediscloud_essentials_database.md b/docs/resources/rediscloud_essentials_database.md index 6680f24f..145b15c1 100644 --- a/docs/resources/rediscloud_essentials_database.md +++ b/docs/resources/rediscloud_essentials_database.md @@ -43,6 +43,11 @@ resource "rediscloud_essentials_database" "database-resource" { name = "throughput-higher-than" value = 80 } + + tags = { + "env" = "dev" + "priority" = "2" + } } ``` @@ -64,6 +69,7 @@ The following arguments are supported: * `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`. * `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.** * `enable_payg_features` - (Optional) Whether to enable features restricted to Pay-As-You-Go legacy databases. It is not supported for new databases. Default `false`. * `memory_limit_in_gb` - (Optional) **Only used with Pay-As-You-Go databases.** Maximum memory usage for the database. diff --git a/docs/resources/rediscloud_subscription_database.md b/docs/resources/rediscloud_subscription_database.md index f492a526..778e809a 100644 --- a/docs/resources/rediscloud_subscription_database.md +++ b/docs/resources/rediscloud_subscription_database.md @@ -67,6 +67,10 @@ resource "rediscloud_subscription_database" "database-resource" { name = "dataset-size" value = 40 } + + tags = { + "market" = "emea" + } } ``` @@ -105,7 +109,8 @@ The following arguments are supported: * `enable_tls` - (Optional) Use TLS for authentication. Default: ‘false’ * `port` - (Optional) TCP port on which the database is available - must be between 10000 and 19999. **Modifying this attribute will force creation of a new resource.** * `remote_backup` (Optional) Specifies the backup options for the database, documented below -* `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`. +* `tags` - (Optional) A string/string map of Tags to associate with this database. Note that all keys and values must be lowercase. The `alert` block supports: diff --git a/go.mod b/go.mod index 476b71f8..3ddbc05b 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.19.0 + github.com/RedisLabs/rediscloud-go-api v0.20.1 github.com/bflad/tfproviderlint v0.30.0 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 @@ -52,13 +52,13 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.14.4 // indirect - golang.org/x/crypto v0.25.0 // indirect - golang.org/x/mod v0.19.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/tools v0.23.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/grpc v1.63.2 // indirect diff --git a/go.sum b/go.sum index aa5992c6..412e60c4 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.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= -github.com/RedisLabs/rediscloud-go-api v0.19.0 h1:ItgQzOwKT1kF+1buLS80kWLZsilW4DscB/WeuNQ8rbY= -github.com/RedisLabs/rediscloud-go-api v0.19.0/go.mod h1:Euh81mfkoCDTH2gQT+rNnvI7rwCDe05zdvkxp9oKQsM= +github.com/RedisLabs/rediscloud-go-api v0.20.1 h1:imYAb7qi7XCHHQ8IeBo4CUr8KWjiZ8Njj0P84YmqhSI= +github.com/RedisLabs/rediscloud-go-api v0.20.1/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= @@ -155,23 +155,23 @@ github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRK golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -185,25 +185,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200214201135-548b770e2dfa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg= -golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/provider/datasource_rediscloud_active_active_database.go b/provider/datasource_rediscloud_active_active_database.go index 34457f13..1b08b5a8 100644 --- a/provider/datasource_rediscloud_active_active_database.go +++ b/provider/datasource_rediscloud_active_active_database.go @@ -214,6 +214,14 @@ func dataSourceRedisCloudActiveActiveDatabase() *schema.Resource { }, }, }, + "tags": { + Description: "Tags for database management", + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, }, } } @@ -341,6 +349,10 @@ func dataSourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema return diag.FromErr(err) } + if err := readTags(ctx, api, subId, dbId, d); err != nil { + return diag.FromErr(err) + } + return diags } diff --git a/provider/datasource_rediscloud_essentials_database.go b/provider/datasource_rediscloud_essentials_database.go index 30c2924c..97769de3 100644 --- a/provider/datasource_rediscloud_essentials_database.go +++ b/provider/datasource_rediscloud_essentials_database.go @@ -353,6 +353,14 @@ func dataSourceRedisCloudEssentialsDatabase() *schema.Resource { Type: schema.TypeBool, Computed: true, }, + "tags": { + Description: "Tags for database management", + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, }, } } @@ -546,6 +554,10 @@ func dataSourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.R } } + if err := readFixedTags(ctx, api, subId, databaseId, d); err != nil { + return diag.FromErr(err) + } + return diags } diff --git a/provider/datasource_rediscloud_pro_database.go b/provider/datasource_rediscloud_pro_database.go index 32ced11b..5b0a56ac 100644 --- a/provider/datasource_rediscloud_pro_database.go +++ b/provider/datasource_rediscloud_pro_database.go @@ -287,6 +287,14 @@ func dataSourceRedisCloudProDatabase() *schema.Resource { }, }, }, + "tags": { + Description: "Tags for database management", + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Computed: true, + }, }, } } @@ -455,6 +463,10 @@ func dataSourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.Resource return diag.FromErr(err) } + if err := readTags(ctx, api, subId, dbId, d); err != nil { + return diag.FromErr(err) + } + return diags } diff --git a/provider/rediscloud_active_active_database_test.go b/provider/rediscloud_active_active_database_test.go index b72357ff..9b675992 100644 --- a/provider/rediscloud_active_active_database_test.go +++ b/provider/rediscloud_active_active_database_test.go @@ -69,6 +69,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "override_region.1.override_global_alert.#", "0"), resource.TestCheckResourceAttr(resourceName, "override_region.1.override_source_ips.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.deployment_family", "blue"), + resource.TestCheckResourceAttr(resourceName, "tags.priority", "code-2"), + // Test databases exist func(s *terraform.State) error { r := s.RootModule().Resources[subscriptionResourceName] @@ -111,6 +114,9 @@ func TestAccResourceRedisCloudActiveActiveDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(datasourceName, "data_eviction", "volatile-lru"), resource.TestCheckResourceAttr(datasourceName, "global_modules.#", "1"), resource.TestCheckResourceAttr(datasourceName, "global_modules.0", "RedisJSON"), + + resource.TestCheckResourceAttr(datasourceName, "tags.deployment_family", "blue"), + resource.TestCheckResourceAttr(datasourceName, "tags.priority", "code-2"), ), }, // Test database is updated successfully, including updates to both global and local alerts and clearing modules @@ -304,6 +310,11 @@ resource "rediscloud_active_active_subscription_database" "example" { name = "us-east-2" } + tags = { + "deployment_family" = "blue" + "priority" = "code-2" + } + } data "rediscloud_active_active_subscription_database" "example" { diff --git a/provider/rediscloud_essentials_database_test.go b/provider/rediscloud_essentials_database_test.go index e35e29ae..960c76f5 100644 --- a/provider/rediscloud_essentials_database_test.go +++ b/provider/rediscloud_essentials_database_test.go @@ -55,6 +55,10 @@ func TestAccRedisCloudEssentialsDatabase_BasicCRUDI(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "regex_rules.#", "0"), resource.TestCheckResourceAttr(resourceName, "enable_tls", "false"), + resource.TestCheckResourceAttr(resourceName, "tags.environment", "production"), + resource.TestCheckResourceAttr(resourceName, "tags.cost_center", "0700"), + resource.TestCheckResourceAttr(resourceName, "tags.department", "finance"), + // Test the datasource resource.TestMatchResourceAttr(datasourceName, "id", regexp.MustCompile("^\\d+/\\d+$")), resource.TestCheckResourceAttrSet(datasourceName, "subscription_id"), @@ -83,8 +87,16 @@ func TestAccRedisCloudEssentialsDatabase_BasicCRUDI(t *testing.T) { resource.TestCheckResourceAttr(datasourceName, "enable_database_clustering", "false"), resource.TestCheckResourceAttr(datasourceName, "regex_rules.#", "0"), resource.TestCheckResourceAttr(datasourceName, "enable_tls", "false"), + + resource.TestCheckResourceAttr(datasourceName, "tags.environment", "production"), + resource.TestCheckResourceAttr(datasourceName, "tags.cost_center", "0700"), + resource.TestCheckResourceAttr(datasourceName, "tags.department", "finance"), ), }, + { + Config: fmt.Sprintf(testAccResourceRedisCloudEssentialsDatabaseBasicWithUpperCaseTagKey, subscriptionName, databaseName), + ExpectError: regexp.MustCompile("tag keys and values must be lower case, invalid entries: UpperCaseKey"), + }, { Config: fmt.Sprintf(testAccResourceRedisCloudEssentialsDatabaseBasic, subscriptionName, databaseNameUpdated), Check: resource.ComposeAggregateTestCheckFunc( @@ -187,6 +199,53 @@ resource "rediscloud_essentials_database" "example" { name = "throughput-higher-than" value = 80 } + + tags = { + "environment" = "production" + "cost_center" = "0700" + "department" = "finance" + } +} +data "rediscloud_essentials_database" "example" { + subscription_id = rediscloud_essentials_subscription.example.id + name = rediscloud_essentials_database.example.name +} +` + +const testAccResourceRedisCloudEssentialsDatabaseBasicWithUpperCaseTagKey = ` +data "rediscloud_payment_method" "card" { + card_type = "Visa" +} +data "rediscloud_essentials_plan" "example" { + name = "250MB" + cloud_provider = "AWS" + region = "eu-west-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 +} +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 = { + "UpperCaseKey" = "invalid" + "environment" = "production" + "cost_center" = "0700" + "department" = "finance" + } } data "rediscloud_essentials_database" "example" { subscription_id = rediscloud_essentials_subscription.example.id diff --git a/provider/resource_rediscloud_active_active_database.go b/provider/resource_rediscloud_active_active_database.go index c93485c9..68cc4f1d 100644 --- a/provider/resource_rediscloud_active_active_database.go +++ b/provider/resource_rediscloud_active_active_database.go @@ -437,6 +437,15 @@ func resourceRedisCloudActiveActiveDatabase() *schema.Resource { }, }, }, + "tags": { + Description: "Tags for database management", + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ValidateDiagFunc: validateTagsfunc, + }, }, } } @@ -715,6 +724,10 @@ func resourceRedisCloudActiveActiveDatabaseRead(ctx context.Context, d *schema.R return diag.FromErr(err) } + if err := readTags(ctx, api, subId, dbId, d); err != nil { + return diag.FromErr(err) + } + return diags } @@ -888,6 +901,11 @@ func resourceRedisCloudActiveActiveDatabaseUpdate(ctx context.Context, d *schema 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 { + return diag.FromErr(err) + } + return resourceRedisCloudActiveActiveDatabaseRead(ctx, d, meta) } diff --git a/provider/resource_rediscloud_essentials_database.go b/provider/resource_rediscloud_essentials_database.go index b2cb4487..3309a04c 100644 --- a/provider/resource_rediscloud_essentials_database.go +++ b/provider/resource_rediscloud_essentials_database.go @@ -4,6 +4,7 @@ import ( "context" "github.com/RedisLabs/rediscloud-go-api/redis" 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" "log" @@ -410,6 +411,15 @@ func resourceRedisCloudEssentialsDatabase() *schema.Resource { Optional: true, DiffSuppressFunc: suppressIfPaygDisabled, }, + "tags": { + Description: "Tags for database management", + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ValidateDiagFunc: validateTagsfunc, + }, }, } } @@ -540,6 +550,7 @@ func resourceRedisCloudEssentialsDatabaseCreate(ctx context.Context, d *schema.R // 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) return resourceRedisCloudEssentialsDatabaseUpdate(ctx, d, meta) } @@ -721,6 +732,10 @@ func resourceRedisCloudEssentialsDatabaseRead(ctx context.Context, d *schema.Res } } + if err := readFixedTags(ctx, api, subId, databaseId, d); err != nil { + return diag.FromErr(err) + } + return diags } @@ -832,6 +847,11 @@ func resourceRedisCloudEssentialsDatabaseUpdate(ctx context.Context, d *schema.R return diag.FromErr(err) } + // The Tags API is synchronous so we shouldn't need to do any more waiting + if err := writeFixedTags(ctx, api, subId, databaseId, d); err != nil { + return diag.FromErr(err) + } + return resourceRedisCloudEssentialsDatabaseRead(ctx, d, meta) } @@ -917,3 +937,29 @@ func suppressIfPaygDisabled(_, _, _ string, d *schema.ResourceData) bool { // If payg is disabled, suppress diff checks on payg attributes return !d.Get("enable_payg_features").(bool) } + +func readFixedTags(ctx context.Context, api *apiClient, subId int, databaseId int, d *schema.ResourceData) error { + t := make(map[string]string) + tagResponse, err := api.client.Tags.GetFixed(ctx, subId, databaseId) + if err != nil { + return err + } + if tagResponse.Tags != nil { + for _, tag := range *tagResponse.Tags { + t[redis.StringValue(tag.Key)] = redis.StringValue(tag.Value) + } + } + return d.Set("tags", t) +} + +func writeFixedTags(ctx context.Context, api *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 { + t = append(t, &tags.Tag{ + Key: redis.String(k), + Value: redis.String(v.(string)), + }) + } + return api.client.Tags.PutFixed(ctx, subId, databaseId, tags.AllTags{Tags: &t}) +} diff --git a/provider/resource_rediscloud_pro_database.go b/provider/resource_rediscloud_pro_database.go index 44ea780b..5be6654b 100644 --- a/provider/resource_rediscloud_pro_database.go +++ b/provider/resource_rediscloud_pro_database.go @@ -3,6 +3,8 @@ package provider import ( "context" "fmt" + redisTags "github.com/RedisLabs/rediscloud-go-api/service/tags" + "github.com/hashicorp/go-cty/cty" "strconv" "strings" "time" @@ -427,6 +429,15 @@ func resourceRedisCloudProDatabase() *schema.Resource { }, }, }, + "tags": { + Description: "Tags for database management", + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + Optional: true, + ValidateDiagFunc: validateTagsfunc, + }, }, } } @@ -704,6 +715,10 @@ func resourceRedisCloudProDatabaseRead(ctx context.Context, d *schema.ResourceDa return diag.FromErr(err) } + if err := readTags(ctx, api, subId, dbId, d); err != nil { + return diag.FromErr(err) + } + return diags } @@ -863,6 +878,11 @@ func resourceRedisCloudProDatabaseUpdate(ctx context.Context, d *schema.Resource 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 { + return diag.FromErr(err) + } + subscriptionMutex.Unlock(subId) return resourceRedisCloudProDatabaseRead(ctx, d, meta) } @@ -994,3 +1014,48 @@ func remoteBackupIntervalSetCorrectly(key string) schema.CustomizeDiffFunc { } } + +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_database_test.go b/provider/resource_rediscloud_pro_database_test.go index d98c0d40..9fa75b0d 100644 --- a/provider/resource_rediscloud_pro_database_test.go +++ b/provider/resource_rediscloud_pro_database_test.go @@ -56,6 +56,10 @@ func TestAccResourceRedisCloudProDatabase_CRUDI(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "modules.#", "1"), resource.TestCheckResourceAttr(resourceName, "modules.0.name", "RedisBloom"), resource.TestCheckResourceAttr(resourceName, "enable_default_user", "true"), + + resource.TestCheckResourceAttr(resourceName, "tags.market", "emea"), + resource.TestCheckResourceAttr(resourceName, "tags.material", "cardboard"), + // Replica tests resource.TestCheckResourceAttr(replicaResourceName, "name", "example-replica"), // should be the value specified in the replica config, rather than the primary database @@ -366,6 +370,11 @@ resource "rediscloud_subscription_database" "example" { name = "RedisBloom" } ] + + tags = { + "market" = "emea" + "material" = "cardboard" + } } `