From 6086287ba2ec6a7b7dbe5c165b6da65e50b15515 Mon Sep 17 00:00:00 2001 From: Murad Biashimov Date: Mon, 9 Dec 2024 14:55:08 +0100 Subject: [PATCH] feat(alloydbomni): add service and dependant resources (#1900) --- .github/workflows/acceptance-tests.yml | 17 +- CHANGELOG.md | 6 + docs/data-sources/alloydbomni.md | 286 ++++ docs/data-sources/alloydbomni_database.md | 33 + docs/data-sources/alloydbomni_user.md | 36 + docs/resources/alloydbomni.md | 308 +++++ docs/resources/alloydbomni_database.md | 48 + docs/resources/alloydbomni_user.md | 51 + go.mod | 3 + go.sum | 6 + internal/acctest/acctest.go | 5 + internal/common/error.go | 7 +- internal/schemautil/common.go | 16 + internal/schemautil/schemautil.go | 12 +- internal/schemautil/service.go | 37 +- internal/schemautil/wait.go | 15 +- internal/sdkprovider/provider/provider.go | 20 +- .../service/alloydbomni/alloydbomni.go | 274 ++++ .../alloydbomni/alloydbomni_data_source.go | 15 + .../alloydbomni/alloydbomni_database.go | 128 ++ .../alloydbomni_database_data_source.go | 29 + .../alloydbomni/alloydbomni_database_test.go | 156 +++ .../alloydbomni/alloydbomni_import_test.go | 108 ++ .../service/alloydbomni/alloydbomni_test.go | 1219 +++++++++++++++++ .../service/alloydbomni/alloydbomni_user.go | 199 +++ .../alloydbomni_user_data_source.go | 29 + .../alloydbomni/alloydbomni_user_test.go | 307 +++++ .../service_account_credentials_validator.go | 106 ++ ...vice_account_credentials_validator_test.go | 66 + .../sdkprovider/service/alloydbomni/sweep.go | 9 + .../service/connectionpool/sweep.go | 4 +- internal/sdkprovider/service/pg/pg_user.go | 13 +- .../userconfig/service/alloydbomni.go | 575 ++++++++ .../sdkprovider/userconfig/service/service.go | 11 +- main.go | 2 +- 35 files changed, 4135 insertions(+), 21 deletions(-) create mode 100644 docs/data-sources/alloydbomni.md create mode 100644 docs/data-sources/alloydbomni_database.md create mode 100644 docs/data-sources/alloydbomni_user.md create mode 100644 docs/resources/alloydbomni.md create mode 100644 docs/resources/alloydbomni_database.md create mode 100644 docs/resources/alloydbomni_user.md create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_data_source.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_database.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_database_data_source.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_database_test.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_import_test.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_test.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_user.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_user_data_source.go create mode 100644 internal/sdkprovider/service/alloydbomni/alloydbomni_user_test.go create mode 100644 internal/sdkprovider/service/alloydbomni/service_account_credentials_validator.go create mode 100644 internal/sdkprovider/service/alloydbomni/service_account_credentials_validator_test.go create mode 100644 internal/sdkprovider/service/alloydbomni/sweep.go create mode 100644 internal/sdkprovider/userconfig/service/alloydbomni.go diff --git a/.github/workflows/acceptance-tests.yml b/.github/workflows/acceptance-tests.yml index 0ec24d82e..0a073f318 100644 --- a/.github/workflows/acceptance-tests.yml +++ b/.github/workflows/acceptance-tests.yml @@ -40,30 +40,31 @@ jobs: fail-fast: false matrix: pkg: [ - kafka, - kafkatopic, - kafkaschema, account, + alloydbomni, cassandra, clickhouse, connectionpool, + dragonfly, flink, - pg, grafana, influxdb, + kafka, + kafkaschema, + kafkatopic, m3db, mysql, opensearch, organization, + pg, project, redis, servicecomponent, - staticip, serviceintegration, - vpc, - dragonfly, + staticip, thanos, - valkey + valkey, + vpc, ] steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index dec196ec8..50018cda8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ nav_order: 1 +## [MAJOR.MINOR.PATCH] - YYYY-MM-DD + +- Add `alloydbomni` BETA resource and datasource +- Add `aiven_alloydbomni_user` BETA resource and datasource +- Add `aiven_alloydbomni_database` BETA resource and datasource + ## [4.30.0] - 2024-12-05 - Add `aiven_kafka_native_acl` resource diff --git a/docs/data-sources/alloydbomni.md b/docs/data-sources/alloydbomni.md new file mode 100644 index 000000000..7c0f45e44 --- /dev/null +++ b/docs/data-sources/alloydbomni.md @@ -0,0 +1,286 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_alloydbomni Data Source - terraform-provider-aiven" +subcategory: "" +description: |- + Gets information about an Aiven for AlloyDB Omni service. + This resource is in the beta stage and may change without notice. Set + the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource. +--- + +# aiven_alloydbomni (Data Source) + +Gets information about an Aiven for AlloyDB Omni service. + +**This resource is in the beta stage and may change without notice.** Set +the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource. + + + + +## Schema + +### Required + +- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `service_name` (String) Specifies the actual name of the service. The name cannot be changed later without destroying and re-creating the service so name should be picked based on intended service usage rather than current attributes. + +### Read-Only + +- `additional_disk_space` (String) Add [disk storage](https://aiven.io/docs/platform/howto/add-storage-space) in increments of 30 GiB to scale your service. The maximum value depends on the service type and cloud provider. Removing additional storage causes the service nodes to go through a rolling restart and there might be a short downtime for services with no HA capabilities. +- `alloydbomni` (List of Object, Sensitive) Values provided by the AlloyDB Omni server. (see [below for nested schema](#nestedatt--alloydbomni)) +- `alloydbomni_user_config` (List of Object) Alloydbomni user configurable settings. **Warning:** There's no way to reset advanced configuration options to default. Options that you add cannot be removed later (see [below for nested schema](#nestedatt--alloydbomni_user_config)) +- `cloud_name` (String) Defines where the cloud provider and region where the service is hosted in. This can be changed freely after service is created. Changing the value will trigger a potentially lengthy migration process for the service. Format is cloud provider name (`aws`, `azure`, `do` `google`, `upcloud`, etc.), dash, and the cloud provider specific region name. These are documented on each Cloud provider's own support articles, like [here for Google](https://cloud.google.com/compute/docs/regions-zones/) and [here for AWS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html). +- `components` (List of Object) Service component information objects (see [below for nested schema](#nestedatt--components)) +- `disk_space` (String) Service disk space. Possible values depend on the service type, the cloud provider and the project. Therefore, reducing will result in the service rebalancing. +- `disk_space_cap` (String) The maximum disk space of the service, possible values depend on the service type, the cloud provider and the project. +- `disk_space_default` (String) The default disk space of the service, possible values depend on the service type, the cloud provider and the project. Its also the minimum value for `disk_space` +- `disk_space_step` (String) The default disk space step of the service, possible values depend on the service type, the cloud provider and the project. `disk_space` needs to increment from `disk_space_default` by increments of this size. +- `disk_space_used` (String) Disk space that service is currently using +- `id` (String) The ID of this resource. +- `maintenance_window_dow` (String) Day of week when maintenance operations should be performed. One monday, tuesday, wednesday, etc. +- `maintenance_window_time` (String) Time of day when maintenance operations should be performed. UTC time in HH:mm:ss format. +- `plan` (String) Defines what kind of computing resources are allocated for the service. It can be changed after creation, though there are some restrictions when going to a smaller plan such as the new plan must have sufficient amount of disk space to store all current data and switching to a plan with fewer nodes might not be supported. The basic plan names are `hobbyist`, `startup-x`, `business-x` and `premium-x` where `x` is (roughly) the amount of memory on each node (also other attributes like number of CPUs and amount of disk space varies but naming is based on memory). The available options can be seen from the [Aiven pricing page](https://aiven.io/pricing). +- `project_vpc_id` (String) Specifies the VPC the service should run in. If the value is not set the service is not run inside a VPC. When set, the value should be given as a reference to set up dependencies correctly and the VPC must be in the same cloud and region as the service itself. Project can be freely moved to and from VPC after creation but doing so triggers migration to new servers so the operation can take significant amount of time to complete if the service has a lot of data. +- `service_account_credentials` (String, Sensitive) Your [Google service account key](https://cloud.google.com/iam/docs/service-account-creds#key-types) in JSON format. +- `service_host` (String) The hostname of the service. +- `service_integrations` (List of Object) Service integrations to specify when creating a service. Not applied after initial service creation (see [below for nested schema](#nestedatt--service_integrations)) +- `service_password` (String, Sensitive) Password used for connecting to the service, if applicable +- `service_port` (Number) The port of the service +- `service_type` (String) Aiven internal service type code +- `service_uri` (String, Sensitive) URI for connecting to the service. Service specific info is under "kafka", "pg", etc. +- `service_username` (String) Username used for connecting to the service, if applicable +- `state` (String) Service state. One of `POWEROFF`, `REBALANCING`, `REBUILDING` or `RUNNING` +- `static_ips` (Set of String) Static IPs that are going to be associated with this service. Please assign a value using the 'toset' function. Once a static ip resource is in the 'assigned' state it cannot be unbound from the node again +- `tag` (Set of Object) Tags are key-value pairs that allow you to categorize services. (see [below for nested schema](#nestedatt--tag)) +- `tech_emails` (Set of Object) The email addresses for [service contacts](https://aiven.io/docs/platform/howto/technical-emails), who will receive important alerts and updates about this service. You can also set email contacts at the project level. (see [below for nested schema](#nestedatt--tech_emails)) +- `termination_protection` (Boolean) Prevents the service from being deleted. It is recommended to set this to `true` for all production services to prevent unintentional service deletion. This does not shield against deleting databases or topics but for services with backups much of the content can at least be restored from backup in case accidental deletion is done. + + +### Nested Schema for `alloydbomni` + +Read-Only: + +- `bouncer` (String) +- `dbname` (String) +- `host` (String) +- `max_connections` (Number) +- `params` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni--params)) +- `password` (String) +- `port` (Number) +- `replica_uri` (String) +- `sslmode` (String) +- `standby_uris` (List of String) +- `syncing_uris` (List of String) +- `uri` (String) +- `uris` (List of String) +- `user` (String) + + +### Nested Schema for `alloydbomni.params` + +Read-Only: + +- `database_name` (String) +- `host` (String) +- `password` (String) +- `port` (Number) +- `sslmode` (String) +- `user` (String) + + + + +### Nested Schema for `alloydbomni_user_config` + +Read-Only: + +- `additional_backup_regions` (List of String) +- `admin_password` (String) +- `admin_username` (String) +- `alloydbomni_version` (String) +- `backup_hour` (Number) +- `backup_minute` (Number) +- `enable_ipv6` (Boolean) +- `google_columnar_engine_enabled` (Boolean) +- `google_columnar_engine_memory_size_percentage` (Number) +- `ip_filter` (Set of String) +- `ip_filter_object` (Set of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--ip_filter_object)) +- `ip_filter_string` (Set of String) +- `pg` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--pg)) +- `pg_read_replica` (Boolean) +- `pg_service_to_fork_from` (String) +- `pg_version` (String) +- `pgbouncer` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--pgbouncer)) +- `pglookout` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--pglookout)) +- `private_access` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--private_access)) +- `privatelink_access` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--privatelink_access)) +- `project_to_fork_from` (String) +- `public_access` (List of Object) (see [below for nested schema](#nestedobjatt--alloydbomni_user_config--public_access)) +- `recovery_target_time` (String) +- `service_log` (Boolean) +- `service_to_fork_from` (String) +- `shared_buffers_percentage` (Number) +- `static_ips` (Boolean) +- `synchronous_replication` (String) +- `variant` (String) +- `work_mem` (Number) + + +### Nested Schema for `alloydbomni_user_config.ip_filter_object` + +Read-Only: + +- `description` (String) +- `network` (String) + + + +### Nested Schema for `alloydbomni_user_config.pg` + +Read-Only: + +- `autovacuum_analyze_scale_factor` (Number) +- `autovacuum_analyze_threshold` (Number) +- `autovacuum_freeze_max_age` (Number) +- `autovacuum_max_workers` (Number) +- `autovacuum_naptime` (Number) +- `autovacuum_vacuum_cost_delay` (Number) +- `autovacuum_vacuum_cost_limit` (Number) +- `autovacuum_vacuum_scale_factor` (Number) +- `autovacuum_vacuum_threshold` (Number) +- `bgwriter_delay` (Number) +- `bgwriter_flush_after` (Number) +- `bgwriter_lru_maxpages` (Number) +- `bgwriter_lru_multiplier` (Number) +- `deadlock_timeout` (Number) +- `default_toast_compression` (String) +- `idle_in_transaction_session_timeout` (Number) +- `jit` (Boolean) +- `log_autovacuum_min_duration` (Number) +- `log_error_verbosity` (String) +- `log_line_prefix` (String) +- `log_min_duration_statement` (Number) +- `log_temp_files` (Number) +- `max_files_per_process` (Number) +- `max_locks_per_transaction` (Number) +- `max_logical_replication_workers` (Number) +- `max_parallel_workers` (Number) +- `max_parallel_workers_per_gather` (Number) +- `max_pred_locks_per_transaction` (Number) +- `max_prepared_transactions` (Number) +- `max_replication_slots` (Number) +- `max_slot_wal_keep_size` (Number) +- `max_stack_depth` (Number) +- `max_standby_archive_delay` (Number) +- `max_standby_streaming_delay` (Number) +- `max_wal_senders` (Number) +- `max_worker_processes` (Number) +- `pg_partman_bgw__dot__interval` (Number) +- `pg_partman_bgw__dot__role` (String) +- `pg_stat_statements__dot__track` (String) +- `temp_file_limit` (Number) +- `timezone` (String) +- `track_activity_query_size` (Number) +- `track_commit_timestamp` (String) +- `track_functions` (String) +- `track_io_timing` (String) +- `wal_sender_timeout` (Number) +- `wal_writer_delay` (Number) + + + +### Nested Schema for `alloydbomni_user_config.pgbouncer` + +Read-Only: + +- `autodb_idle_timeout` (Number) +- `autodb_max_db_connections` (Number) +- `autodb_pool_mode` (String) +- `autodb_pool_size` (Number) +- `ignore_startup_parameters` (List of String) +- `max_prepared_statements` (Number) +- `min_pool_size` (Number) +- `server_idle_timeout` (Number) +- `server_lifetime` (Number) +- `server_reset_query_always` (Boolean) + + + +### Nested Schema for `alloydbomni_user_config.pglookout` + +Read-Only: + +- `max_failover_replication_time_lag` (Number) + + + +### Nested Schema for `alloydbomni_user_config.private_access` + +Read-Only: + +- `pg` (Boolean) +- `pgbouncer` (Boolean) +- `prometheus` (Boolean) + + + +### Nested Schema for `alloydbomni_user_config.privatelink_access` + +Read-Only: + +- `pg` (Boolean) +- `pgbouncer` (Boolean) +- `prometheus` (Boolean) + + + +### Nested Schema for `alloydbomni_user_config.public_access` + +Read-Only: + +- `pg` (Boolean) +- `pgbouncer` (Boolean) +- `prometheus` (Boolean) + + + + +### Nested Schema for `components` + +Read-Only: + +- `component` (String) +- `connection_uri` (String) +- `host` (String) +- `kafka_authentication_method` (String) +- `port` (Number) +- `route` (String) +- `ssl` (Boolean) +- `usage` (String) + + + +### Nested Schema for `service_integrations` + +Read-Only: + +- `integration_type` (String) +- `source_service_name` (String) + + + +### Nested Schema for `tag` + +Read-Only: + +- `key` (String) +- `value` (String) + + + +### Nested Schema for `tech_emails` + +Read-Only: + +- `email` (String) diff --git a/docs/data-sources/alloydbomni_database.md b/docs/data-sources/alloydbomni_database.md new file mode 100644 index 000000000..b462da890 --- /dev/null +++ b/docs/data-sources/alloydbomni_database.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_alloydbomni_database Data Source - terraform-provider-aiven" +subcategory: "" +description: |- + Gets information about a database in an Aiven for AlloyDB Omni service. + This resource is in the beta stage and may change without notice. Set + the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource. +--- + +# aiven_alloydbomni_database (Data Source) + +Gets information about a database in an Aiven for AlloyDB Omni service. + +**This resource is in the beta stage and may change without notice.** Set +the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource. + + + + +## Schema + +### Required + +- `database_name` (String) The name of the service database. Changing this property forces recreation of the resource. +- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `service_name` (String) The name of the service that this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. + +### Read-Only + +- `id` (String) The ID of this resource. +- `lc_collate` (String) Default string sort order (`LC_COLLATE`) of the database. The default value is `en_US.UTF-8`. Changing this property forces recreation of the resource. +- `lc_ctype` (String) Default character classification (`LC_CTYPE`) of the database. The default value is `en_US.UTF-8`. Changing this property forces recreation of the resource. diff --git a/docs/data-sources/alloydbomni_user.md b/docs/data-sources/alloydbomni_user.md new file mode 100644 index 000000000..9e4328335 --- /dev/null +++ b/docs/data-sources/alloydbomni_user.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_alloydbomni_user Data Source - terraform-provider-aiven" +subcategory: "" +description: |- + Gets information about an Aiven for AlloyDB Omni service user. + This resource is in the beta stage and may change without notice. Set + the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource. +--- + +# aiven_alloydbomni_user (Data Source) + +Gets information about an Aiven for AlloyDB Omni service user. + +**This resource is in the beta stage and may change without notice.** Set +the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource. + + + + +## Schema + +### Required + +- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `service_name` (String) The name of the service that this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `username` (String) The name of the service user for this service. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. + +### Read-Only + +- `access_cert` (String, Sensitive) The access certificate for the servie user. +- `access_key` (String, Sensitive) The access certificate key for the service user. +- `id` (String) The ID of this resource. +- `password` (String, Sensitive) The password of the service user. +- `pg_allow_replication` (Boolean) Allows replication. For the default avnadmin user this attribute is required and is always `true`. +- `type` (String) The service user account type, either primary or regular. diff --git a/docs/resources/alloydbomni.md b/docs/resources/alloydbomni.md new file mode 100644 index 000000000..f7ec8efcb --- /dev/null +++ b/docs/resources/alloydbomni.md @@ -0,0 +1,308 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_alloydbomni Resource - terraform-provider-aiven" +subcategory: "" +description: |- + Creates and manages an Aiven for AlloyDB Omni service. + This resource is in the beta stage and may change without notice. Set + the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource. +--- + +# aiven_alloydbomni (Resource) + +Creates and manages an Aiven for AlloyDB Omni service. + +**This resource is in the beta stage and may change without notice.** Set +the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource. + + + + +## Schema + +### Required + +- `plan` (String) Defines what kind of computing resources are allocated for the service. It can be changed after creation, though there are some restrictions when going to a smaller plan such as the new plan must have sufficient amount of disk space to store all current data and switching to a plan with fewer nodes might not be supported. The basic plan names are `hobbyist`, `startup-x`, `business-x` and `premium-x` where `x` is (roughly) the amount of memory on each node (also other attributes like number of CPUs and amount of disk space varies but naming is based on memory). The available options can be seen from the [Aiven pricing page](https://aiven.io/pricing). +- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `service_name` (String) Specifies the actual name of the service. The name cannot be changed later without destroying and re-creating the service so name should be picked based on intended service usage rather than current attributes. + +### Optional + +- `additional_disk_space` (String) Add [disk storage](https://aiven.io/docs/platform/howto/add-storage-space) in increments of 30 GiB to scale your service. The maximum value depends on the service type and cloud provider. Removing additional storage causes the service nodes to go through a rolling restart and there might be a short downtime for services with no HA capabilities. +- `alloydbomni` (Block List, Max: 1) Values provided by the AlloyDB Omni server. (see [below for nested schema](#nestedblock--alloydbomni)) +- `alloydbomni_user_config` (Block List, Max: 1) Alloydbomni user configurable settings. **Warning:** There's no way to reset advanced configuration options to default. Options that you add cannot be removed later (see [below for nested schema](#nestedblock--alloydbomni_user_config)) +- `cloud_name` (String) Defines where the cloud provider and region where the service is hosted in. This can be changed freely after service is created. Changing the value will trigger a potentially lengthy migration process for the service. Format is cloud provider name (`aws`, `azure`, `do` `google`, `upcloud`, etc.), dash, and the cloud provider specific region name. These are documented on each Cloud provider's own support articles, like [here for Google](https://cloud.google.com/compute/docs/regions-zones/) and [here for AWS](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html). +- `disk_space` (String, Deprecated) Service disk space. Possible values depend on the service type, the cloud provider and the project. Therefore, reducing will result in the service rebalancing. +- `maintenance_window_dow` (String) Day of week when maintenance operations should be performed. One monday, tuesday, wednesday, etc. +- `maintenance_window_time` (String) Time of day when maintenance operations should be performed. UTC time in HH:mm:ss format. +- `project_vpc_id` (String) Specifies the VPC the service should run in. If the value is not set the service is not run inside a VPC. When set, the value should be given as a reference to set up dependencies correctly and the VPC must be in the same cloud and region as the service itself. Project can be freely moved to and from VPC after creation but doing so triggers migration to new servers so the operation can take significant amount of time to complete if the service has a lot of data. +- `service_account_credentials` (String, Sensitive) Your [Google service account key](https://cloud.google.com/iam/docs/service-account-creds#key-types) in JSON format. +- `service_integrations` (Block List) Service integrations to specify when creating a service. Not applied after initial service creation (see [below for nested schema](#nestedblock--service_integrations)) +- `static_ips` (Set of String) Static IPs that are going to be associated with this service. Please assign a value using the 'toset' function. Once a static ip resource is in the 'assigned' state it cannot be unbound from the node again +- `tag` (Block Set) Tags are key-value pairs that allow you to categorize services. (see [below for nested schema](#nestedblock--tag)) +- `tech_emails` (Block Set) The email addresses for [service contacts](https://aiven.io/docs/platform/howto/technical-emails), who will receive important alerts and updates about this service. You can also set email contacts at the project level. (see [below for nested schema](#nestedblock--tech_emails)) +- `termination_protection` (Boolean) Prevents the service from being deleted. It is recommended to set this to `true` for all production services to prevent unintentional service deletion. This does not shield against deleting databases or topics but for services with backups much of the content can at least be restored from backup in case accidental deletion is done. +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) + +### Read-Only + +- `components` (List of Object) Service component information objects (see [below for nested schema](#nestedatt--components)) +- `disk_space_cap` (String) The maximum disk space of the service, possible values depend on the service type, the cloud provider and the project. +- `disk_space_default` (String) The default disk space of the service, possible values depend on the service type, the cloud provider and the project. Its also the minimum value for `disk_space` +- `disk_space_step` (String) The default disk space step of the service, possible values depend on the service type, the cloud provider and the project. `disk_space` needs to increment from `disk_space_default` by increments of this size. +- `disk_space_used` (String, Deprecated) Disk space that service is currently using +- `id` (String) The ID of this resource. +- `service_host` (String) The hostname of the service. +- `service_password` (String, Sensitive) Password used for connecting to the service, if applicable +- `service_port` (Number) The port of the service +- `service_type` (String) Aiven internal service type code +- `service_uri` (String, Sensitive) URI for connecting to the service. Service specific info is under "kafka", "pg", etc. +- `service_username` (String) Username used for connecting to the service, if applicable +- `state` (String) Service state. One of `POWEROFF`, `REBALANCING`, `REBUILDING` or `RUNNING` + + +### Nested Schema for `alloydbomni` + +Optional: + +- `standby_uris` (List of String, Sensitive) AlloyDB Omni standby connection URIs. +- `syncing_uris` (List of String, Sensitive) AlloyDB Omni syncing connection URIs. +- `uri` (String, Sensitive) AlloyDB Omni primary connection URI. +- `uris` (List of String, Sensitive) AlloyDB Omni primary connection URIs. + +Read-Only: + +- `bouncer` (String, Sensitive, Deprecated) PgBouncer connection details for [connection pooling](https://aiven.io/docs/products/postgresql/concepts/pg-connection-pooling). +- `dbname` (String, Sensitive) Primary AlloyDB Omni database name. +- `host` (String, Sensitive) AlloyDB Omni primary node host IP or name. +- `max_connections` (Number, Sensitive) The [number of allowed connections](https://aiven.io/docs/products/postgresql/reference/pg-connection-limits). Varies based on the service plan. +- `params` (Block List) AlloyDB Omni connection parameters. (see [below for nested schema](#nestedblock--alloydbomni--params)) +- `password` (String, Sensitive) AlloyDB Omni admin user password. +- `port` (Number, Sensitive) AlloyDB Omni port. +- `replica_uri` (String, Sensitive) AlloyDB Omni replica URI for services with a replica. +- `sslmode` (String, Sensitive) AlloyDB Omni SSL mode setting. +- `user` (String, Sensitive) AlloyDB Omni admin user name. + + +### Nested Schema for `alloydbomni.params` + +Read-Only: + +- `database_name` (String, Sensitive) Primary AlloyDB Omni database name. +- `host` (String, Sensitive) AlloyDB Omni host IP or name. +- `password` (String, Sensitive) AlloyDB Omni admin user password. +- `port` (Number, Sensitive) AlloyDB Omni port. +- `sslmode` (String, Sensitive) AlloyDB Omni SSL mode setting. +- `user` (String, Sensitive) AlloyDB Omni admin user name. + + + + +### Nested Schema for `alloydbomni_user_config` + +Optional: + +- `additional_backup_regions` (List of String) Additional Cloud Regions for Backup Replication. +- `admin_password` (String, Sensitive) Custom password for admin user. Defaults to random string. This must be set only when a new service is being created. +- `admin_username` (String) Custom username for admin user. This must be set only when a new service is being created. Example: `avnadmin`. +- `alloydbomni_version` (String) Enum: `15`, and newer. PostgreSQL major version. +- `backup_hour` (Number) The hour of day (in UTC) when backup for the service is started. New backup is only started if previous backup has already completed. Example: `3`. +- `backup_minute` (Number) The minute of an hour when backup for the service is started. New backup is only started if previous backup has already completed. Example: `30`. +- `enable_ipv6` (Boolean) Register AAAA DNS records for the service, and allow IPv6 packets to service ports. +- `google_columnar_engine_enabled` (Boolean) Enables or disables the columnar engine. When enabled, it accelerates SQL query processing. Default: `true`. +- `google_columnar_engine_memory_size_percentage` (Number) Allocate the amount of RAM to store columnar data. Default: `10`. +- `ip_filter` (Set of String, Deprecated) Allow incoming connections from CIDR address block, e.g. `10.20.0.0/16`. +- `ip_filter_object` (Block Set, Max: 1024) Allow incoming connections from CIDR address block, e.g. `10.20.0.0/16` (see [below for nested schema](#nestedblock--alloydbomni_user_config--ip_filter_object)) +- `ip_filter_string` (Set of String) Allow incoming connections from CIDR address block, e.g. `10.20.0.0/16`. +- `pg` (Block List, Max: 1) postgresql.conf configuration values (see [below for nested schema](#nestedblock--alloydbomni_user_config--pg)) +- `pg_read_replica` (Boolean) Should the service which is being forked be a read replica (deprecated, use read_replica service integration instead). +- `pg_service_to_fork_from` (String) Name of the PG Service from which to fork (deprecated, use service_to_fork_from). This has effect only when a new service is being created. Example: `anotherservicename`. +- `pg_version` (String) Enum: `15`, and newer. PostgreSQL major version. +- `pgbouncer` (Block List, Max: 1) PGBouncer connection pooling settings (see [below for nested schema](#nestedblock--alloydbomni_user_config--pgbouncer)) +- `pglookout` (Block List, Max: 1) System-wide settings for pglookout (see [below for nested schema](#nestedblock--alloydbomni_user_config--pglookout)) +- `private_access` (Block List, Max: 1) Allow access to selected service ports from private networks (see [below for nested schema](#nestedblock--alloydbomni_user_config--private_access)) +- `privatelink_access` (Block List, Max: 1) Allow access to selected service components through Privatelink (see [below for nested schema](#nestedblock--alloydbomni_user_config--privatelink_access)) +- `project_to_fork_from` (String) Name of another project to fork a service from. This has effect only when a new service is being created. Example: `anotherprojectname`. +- `public_access` (Block List, Max: 1) Allow access to selected service ports from the public Internet (see [below for nested schema](#nestedblock--alloydbomni_user_config--public_access)) +- `recovery_target_time` (String) Recovery target time when forking a service. This has effect only when a new service is being created. Example: `2019-01-01 23:34:45`. +- `service_log` (Boolean) Store logs for the service so that they are available in the HTTP API and console. +- `service_to_fork_from` (String) Name of another service to fork from. This has effect only when a new service is being created. Example: `anotherservicename`. +- `shared_buffers_percentage` (Number) Percentage of total RAM that the database server uses for shared memory buffers. Valid range is 20-60 (float), which corresponds to 20% - 60%. This setting adjusts the shared_buffers configuration value. Example: `41.5`. +- `static_ips` (Boolean) Use static public IP addresses. +- `synchronous_replication` (String) Enum: `off`, `quorum`. Synchronous replication type. Note that the service plan also needs to support synchronous replication. +- `variant` (String) Enum: `aiven`, `timescale`. Variant of the PostgreSQL service, may affect the features that are exposed by default. +- `work_mem` (Number) Sets the maximum amount of memory to be used by a query operation (such as a sort or hash table) before writing to temporary disk files, in MB. Default is 1MB + 0.075% of total RAM (up to 32MB). Example: `4`. + + +### Nested Schema for `alloydbomni_user_config.ip_filter_object` + +Required: + +- `network` (String) CIDR address block. Example: `10.20.0.0/16`. + +Optional: + +- `description` (String) Description for IP filter list entry. Example: `Production service IP range`. + + + +### Nested Schema for `alloydbomni_user_config.pg` + +Optional: + +- `autovacuum_analyze_scale_factor` (Number) Specifies a fraction of the table size to add to autovacuum_analyze_threshold when deciding whether to trigger an ANALYZE. The default is 0.2 (20% of table size). +- `autovacuum_analyze_threshold` (Number) Specifies the minimum number of inserted, updated or deleted tuples needed to trigger an ANALYZE in any one table. The default is 50 tuples. +- `autovacuum_freeze_max_age` (Number) Specifies the maximum age (in transactions) that a table's pg_class.relfrozenxid field can attain before a VACUUM operation is forced to prevent transaction ID wraparound within the table. Note that the system will launch autovacuum processes to prevent wraparound even when autovacuum is otherwise disabled. This parameter will cause the server to be restarted. Example: `200000000`. +- `autovacuum_max_workers` (Number) Specifies the maximum number of autovacuum processes (other than the autovacuum launcher) that may be running at any one time. The default is three. This parameter can only be set at server start. +- `autovacuum_naptime` (Number) Specifies the minimum delay between autovacuum runs on any given database. The delay is measured in seconds, and the default is one minute. +- `autovacuum_vacuum_cost_delay` (Number) Specifies the cost delay value that will be used in automatic VACUUM operations. If -1 is specified, the regular vacuum_cost_delay value will be used. The default value is 20 milliseconds. +- `autovacuum_vacuum_cost_limit` (Number) Specifies the cost limit value that will be used in automatic VACUUM operations. If -1 is specified (which is the default), the regular vacuum_cost_limit value will be used. +- `autovacuum_vacuum_scale_factor` (Number) Specifies a fraction of the table size to add to autovacuum_vacuum_threshold when deciding whether to trigger a VACUUM. The default is 0.2 (20% of table size). +- `autovacuum_vacuum_threshold` (Number) Specifies the minimum number of updated or deleted tuples needed to trigger a VACUUM in any one table. The default is 50 tuples. +- `bgwriter_delay` (Number) Specifies the delay between activity rounds for the background writer in milliseconds. Default is 200. Example: `200`. +- `bgwriter_flush_after` (Number) Whenever more than bgwriter_flush_after bytes have been written by the background writer, attempt to force the OS to issue these writes to the underlying storage. Specified in kilobytes, default is 512. Setting of 0 disables forced writeback. Example: `512`. +- `bgwriter_lru_maxpages` (Number) In each round, no more than this many buffers will be written by the background writer. Setting this to zero disables background writing. Default is 100. Example: `100`. +- `bgwriter_lru_multiplier` (Number) The average recent need for new buffers is multiplied by bgwriter_lru_multiplier to arrive at an estimate of the number that will be needed during the next round, (up to bgwriter_lru_maxpages). 1.0 represents a “just in time” policy of writing exactly the number of buffers predicted to be needed. Larger values provide some cushion against spikes in demand, while smaller values intentionally leave writes to be done by server processes. The default is 2.0. Example: `2.0`. +- `deadlock_timeout` (Number) This is the amount of time, in milliseconds, to wait on a lock before checking to see if there is a deadlock condition. Example: `1000`. +- `default_toast_compression` (String) Enum: `lz4`, `pglz`. Specifies the default TOAST compression method for values of compressible columns (the default is lz4). +- `idle_in_transaction_session_timeout` (Number) Time out sessions with open transactions after this number of milliseconds. +- `jit` (Boolean) Controls system-wide use of Just-in-Time Compilation (JIT). +- `log_autovacuum_min_duration` (Number) Causes each action executed by autovacuum to be logged if it ran for at least the specified number of milliseconds. Setting this to zero logs all autovacuum actions. Minus-one (the default) disables logging autovacuum actions. +- `log_error_verbosity` (String) Enum: `DEFAULT`, `TERSE`, `VERBOSE`. Controls the amount of detail written in the server log for each message that is logged. +- `log_line_prefix` (String) Enum: `'%m [%p] %q[user=%u,db=%d,app=%a] '`, `'%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h '`, `'pid=%p,user=%u,db=%d,app=%a,client=%h '`, `'pid=%p,user=%u,db=%d,app=%a,client=%h,txid=%x,qid=%Q '`. Choose from one of the available log formats. +- `log_min_duration_statement` (Number) Log statements that take more than this number of milliseconds to run, -1 disables. +- `log_temp_files` (Number) Log statements for each temporary file created larger than this number of kilobytes, -1 disables. +- `max_files_per_process` (Number) PostgreSQL maximum number of files that can be open per process. +- `max_locks_per_transaction` (Number) PostgreSQL maximum locks per transaction. +- `max_logical_replication_workers` (Number) PostgreSQL maximum logical replication workers (taken from the pool of max_parallel_workers). +- `max_parallel_workers` (Number) Sets the maximum number of workers that the system can support for parallel queries. +- `max_parallel_workers_per_gather` (Number) Sets the maximum number of workers that can be started by a single Gather or Gather Merge node. +- `max_pred_locks_per_transaction` (Number) PostgreSQL maximum predicate locks per transaction. +- `max_prepared_transactions` (Number) PostgreSQL maximum prepared transactions. +- `max_replication_slots` (Number) PostgreSQL maximum replication slots. +- `max_slot_wal_keep_size` (Number) PostgreSQL maximum WAL size (MB) reserved for replication slots. Default is -1 (unlimited). wal_keep_size minimum WAL size setting takes precedence over this. +- `max_stack_depth` (Number) Maximum depth of the stack in bytes. +- `max_standby_archive_delay` (Number) Max standby archive delay in milliseconds. +- `max_standby_streaming_delay` (Number) Max standby streaming delay in milliseconds. +- `max_wal_senders` (Number) PostgreSQL maximum WAL senders. +- `max_worker_processes` (Number) Sets the maximum number of background processes that the system can support. +- `pg_partman_bgw__dot__interval` (Number) Sets the time interval to run pg_partman's scheduled tasks. Example: `3600`. +- `pg_partman_bgw__dot__role` (String) Controls which role to use for pg_partman's scheduled background tasks. Example: `myrolename`. +- `pg_stat_statements__dot__track` (String) Enum: `all`, `none`, `top`. Controls which statements are counted. Specify top to track top-level statements (those issued directly by clients), all to also track nested statements (such as statements invoked within functions), or none to disable statement statistics collection. The default value is top. +- `temp_file_limit` (Number) PostgreSQL temporary file limit in KiB, -1 for unlimited. Example: `5000000`. +- `timezone` (String) PostgreSQL service timezone. Example: `Europe/Helsinki`. +- `track_activity_query_size` (Number) Specifies the number of bytes reserved to track the currently executing command for each active session. Example: `1024`. +- `track_commit_timestamp` (String) Enum: `off`, `on`. Record commit time of transactions. +- `track_functions` (String) Enum: `all`, `none`, `pl`. Enables tracking of function call counts and time used. +- `track_io_timing` (String) Enum: `off`, `on`. Enables timing of database I/O calls. This parameter is off by default, because it will repeatedly query the operating system for the current time, which may cause significant overhead on some platforms. +- `wal_sender_timeout` (Number) Terminate replication connections that are inactive for longer than this amount of time, in milliseconds. Setting this value to zero disables the timeout. Example: `60000`. +- `wal_writer_delay` (Number) WAL flush interval in milliseconds. Note that setting this value to lower than the default 200ms may negatively impact performance. Example: `50`. + + + +### Nested Schema for `alloydbomni_user_config.pgbouncer` + +Optional: + +- `autodb_idle_timeout` (Number) If the automatically created database pools have been unused this many seconds, they are freed. If 0 then timeout is disabled. (seconds). Default: `3600`. +- `autodb_max_db_connections` (Number) Do not allow more than this many server connections per database (regardless of user). Setting it to 0 means unlimited. Example: `0`. +- `autodb_pool_mode` (String) Enum: `session`, `statement`, `transaction`. PGBouncer pool mode. Default: `transaction`. +- `autodb_pool_size` (Number) If non-zero then create automatically a pool of that size per user when a pool doesn't exist. Default: `0`. +- `ignore_startup_parameters` (List of String) List of parameters to ignore when given in startup packet. +- `max_prepared_statements` (Number) PgBouncer tracks protocol-level named prepared statements related commands sent by the client in transaction and statement pooling modes when max_prepared_statements is set to a non-zero value. Setting it to 0 disables prepared statements. max_prepared_statements defaults to 100, and its maximum is 3000. Default: `100`. +- `min_pool_size` (Number) Add more server connections to pool if below this number. Improves behavior when usual load comes suddenly back after period of total inactivity. The value is effectively capped at the pool size. Default: `0`. +- `server_idle_timeout` (Number) If a server connection has been idle more than this many seconds it will be dropped. If 0 then timeout is disabled. (seconds). Default: `600`. +- `server_lifetime` (Number) The pooler will close an unused server connection that has been connected longer than this. (seconds). Default: `3600`. +- `server_reset_query_always` (Boolean) Run server_reset_query (DISCARD ALL) in all pooling modes. Default: `false`. + + + +### Nested Schema for `alloydbomni_user_config.pglookout` + +Optional: + +- `max_failover_replication_time_lag` (Number) Number of seconds of master unavailability before triggering database failover to standby. Default: `60`. + + + +### Nested Schema for `alloydbomni_user_config.private_access` + +Optional: + +- `pg` (Boolean) Allow clients to connect to pg with a DNS name that always resolves to the service's private IP addresses. Only available in certain network locations. +- `pgbouncer` (Boolean) Allow clients to connect to pgbouncer with a DNS name that always resolves to the service's private IP addresses. Only available in certain network locations. +- `prometheus` (Boolean) Allow clients to connect to prometheus with a DNS name that always resolves to the service's private IP addresses. Only available in certain network locations. + + + +### Nested Schema for `alloydbomni_user_config.privatelink_access` + +Optional: + +- `pg` (Boolean) Enable pg. +- `pgbouncer` (Boolean) Enable pgbouncer. +- `prometheus` (Boolean) Enable prometheus. + + + +### Nested Schema for `alloydbomni_user_config.public_access` + +Optional: + +- `pg` (Boolean) Allow clients to connect to pg from the public internet for service nodes that are in a project VPC or another type of private network. +- `pgbouncer` (Boolean) Allow clients to connect to pgbouncer from the public internet for service nodes that are in a project VPC or another type of private network. +- `prometheus` (Boolean) Allow clients to connect to prometheus from the public internet for service nodes that are in a project VPC or another type of private network. + + + + +### Nested Schema for `service_integrations` + +Required: + +- `integration_type` (String) Type of the service integration. The only supported value at the moment is `read_replica` +- `source_service_name` (String) Name of the source service + + + +### Nested Schema for `tag` + +Required: + +- `key` (String) Service tag key +- `value` (String) Service tag value + + + +### Nested Schema for `tech_emails` + +Required: + +- `email` (String) An email address to contact for technical issues + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `default` (String) +- `delete` (String) +- `read` (String) +- `update` (String) + + + +### Nested Schema for `components` + +Read-Only: + +- `component` (String) +- `connection_uri` (String) +- `host` (String) +- `kafka_authentication_method` (String) +- `port` (Number) +- `route` (String) +- `ssl` (Boolean) +- `usage` (String) diff --git a/docs/resources/alloydbomni_database.md b/docs/resources/alloydbomni_database.md new file mode 100644 index 000000000..c10f6e9e6 --- /dev/null +++ b/docs/resources/alloydbomni_database.md @@ -0,0 +1,48 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_alloydbomni_database Resource - terraform-provider-aiven" +subcategory: "" +description: |- + Creates and manages a database in an Aiven for AlloyDB Omni service. + This resource is in the beta stage and may change without notice. Set + the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource. +--- + +# aiven_alloydbomni_database (Resource) + +Creates and manages a database in an Aiven for AlloyDB Omni service. + +**This resource is in the beta stage and may change without notice.** Set +the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource. + + + + +## Schema + +### Required + +- `database_name` (String) The name of the service database. Changing this property forces recreation of the resource. +- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `service_name` (String) The name of the service that this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. + +### Optional + +- `lc_collate` (String) Default string sort order (`LC_COLLATE`) of the database. The default value is `en_US.UTF-8`. Changing this property forces recreation of the resource. +- `lc_ctype` (String) Default character classification (`LC_CTYPE`) of the database. The default value is `en_US.UTF-8`. Changing this property forces recreation of the resource. +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `default` (String) +- `delete` (String) +- `read` (String) +- `update` (String) diff --git a/docs/resources/alloydbomni_user.md b/docs/resources/alloydbomni_user.md new file mode 100644 index 000000000..07c6fb1a4 --- /dev/null +++ b/docs/resources/alloydbomni_user.md @@ -0,0 +1,51 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "aiven_alloydbomni_user Resource - terraform-provider-aiven" +subcategory: "" +description: |- + Creates and manages an Aiven for AlloyDB Omni service user. + This resource is in the beta stage and may change without notice. Set + the PROVIDER_AIVEN_ENABLE_BETA environment variable to use the resource. +--- + +# aiven_alloydbomni_user (Resource) + +Creates and manages an Aiven for AlloyDB Omni service user. + +**This resource is in the beta stage and may change without notice.** Set +the `PROVIDER_AIVEN_ENABLE_BETA` environment variable to use the resource. + + + + +## Schema + +### Required + +- `project` (String) The name of the project this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `service_name` (String) The name of the service that this resource belongs to. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. +- `username` (String) The name of the service user for this service. To set up proper dependencies please refer to this variable as a reference. Changing this property forces recreation of the resource. + +### Optional + +- `password` (String, Sensitive) The password of the service user. +- `pg_allow_replication` (Boolean) Allows replication. For the default avnadmin user this attribute is required and is always `true`. +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) + +### Read-Only + +- `access_cert` (String, Sensitive) The access certificate for the servie user. +- `access_key` (String, Sensitive) The access certificate key for the service user. +- `id` (String) The ID of this resource. +- `type` (String) The service user account type, either primary or regular. + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) +- `default` (String) +- `delete` (String) +- `read` (String) +- `update` (String) diff --git a/go.mod b/go.mod index b7892412e..e4150bf2d 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/samber/lo v1.47.0 github.com/stoewer/go-strcase v1.3.0 github.com/stretchr/testify v1.10.0 + github.com/xeipuuv/gojsonschema v1.2.0 golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c golang.org/x/tools v0.26.0 gopkg.in/yaml.v3 v3.0.1 @@ -45,6 +46,8 @@ require ( github.com/rs/zerolog v1.33.0 // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect go.opentelemetry.io/otel v1.22.0 // indirect diff --git a/go.sum b/go.sum index 3f4dc326f..2c9ebc79d 100644 --- a/go.sum +++ b/go.sum @@ -586,6 +586,12 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 35d233d71..bac597e25 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -176,6 +176,7 @@ func TestAccCheckAivenServiceResourceDestroy(s *terraform.State) error { r := func() []string { return []string{ + "aiven_alloydbomni", "aiven_influxdb", "aiven_grafana", "aiven_mysql", @@ -190,6 +191,10 @@ func TestAccCheckAivenServiceResourceDestroy(s *terraform.State) error { "aiven_kafka_connector", "aiven_kafka_connect", "aiven_clickhouse", + "aiven_dragonfly", + "aiven_m3aggregator", + "aiven_thanos", + "aiven_valkey", } } if sort.SearchStrings(r(), rs.Type) > 0 { diff --git a/internal/common/error.go b/internal/common/error.go index f009f56a4..cfd4bdad0 100644 --- a/internal/common/error.go +++ b/internal/common/error.go @@ -1,8 +1,11 @@ package common -import "github.com/aiven/aiven-go-client/v2" +import ( + "github.com/aiven/aiven-go-client/v2" + avngen "github.com/aiven/go-client-codegen" +) // IsCritical returns true if the given error is critical func IsCritical(err error) bool { - return !(err == nil || aiven.IsNotFound(err)) + return !(err == nil || aiven.IsNotFound(err) || avngen.IsNotFound(err)) } diff --git a/internal/schemautil/common.go b/internal/schemautil/common.go index 87f7cfb4a..51a136cf0 100644 --- a/internal/schemautil/common.go +++ b/internal/schemautil/common.go @@ -1,8 +1,10 @@ package schemautil import ( + "context" "regexp" + "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" @@ -73,3 +75,17 @@ func PointerValueOrDefault[T comparable](v *T, d T) T { } return *v } + +// ComposeContexts composes multiple context (create, update, read or delete) functions into one. +// So instead of chaining them, they can be composed +func ComposeContexts(funcs ...func(context.Context, *schema.ResourceData, any) diag.Diagnostics) func(context.Context, *schema.ResourceData, any) diag.Diagnostics { + return func(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + for _, f := range funcs { + err := f(ctx, d, m) + if err != nil { + return err + } + } + return nil + } +} diff --git a/internal/schemautil/schemautil.go b/internal/schemautil/schemautil.go index 73fb772e3..e8991ebcc 100644 --- a/internal/schemautil/schemautil.go +++ b/internal/schemautil/schemautil.go @@ -483,7 +483,17 @@ func serializeSet(s map[string]*schema.Schema, m map[string]any) map[string]any return m } -// RenameAliases renames field names on object top level +// RenameAlias renames field names terraform name -> dto name +// Example: RenameAlias("hasFoo", "wantBar", "hasBaz", "wantEgg") +func RenameAlias(keys ...string) KVModifier { + m := make(map[string]string, len(keys)/2) + for i := 0; i < len(keys); i += 2 { + m[keys[i]] = keys[i+1] + } + return RenameAliases(m) +} + +// RenameAliases renames field names terraform name -> dto name func RenameAliases(aliases map[string]string) KVModifier { return func(k string, v any) (string, any) { alias, ok := aliases[k] diff --git a/internal/schemautil/service.go b/internal/schemautil/service.go index db00759b3..45609cbee 100644 --- a/internal/schemautil/service.go +++ b/internal/schemautil/service.go @@ -6,11 +6,13 @@ import ( "encoding/json" "errors" "fmt" + "net/http" "strconv" "strings" "time" "github.com/aiven/aiven-go-client/v2" + avngen "github.com/aiven/go-client-codegen" "github.com/aiven/go-client-codegen/handler/service" "github.com/aiven/go-client-codegen/handler/staticip" "github.com/docker/go-units" @@ -37,6 +39,7 @@ func DefaultResourceTimeouts() *schema.ResourceTimeout { } const ( + ServiceTypeAlloyDBOmni = "alloydbomni" ServiceTypePG = "pg" ServiceTypeCassandra = "cassandra" ServiceTypeOpenSearch = "opensearch" @@ -826,7 +829,7 @@ func copyConnectionInfoFromAPIResponseToTerraform( setProp(props, "connect_uri", connectionInfo.KafkaConnectUri) setProp(props, "rest_uri", connectionInfo.KafkaRestUri) setProp(props, "schema_registry_uri", connectionInfo.SchemaRegistryUri) - case ServiceTypePG: + case ServiceTypeAlloyDBOmni, ServiceTypePG: // For compatibility with the old schema, we only set the first URI. // TODO: Remove this block in the next major version. Keep `uris` key only, see below. if len(connectionInfo.Pg) > 0 { @@ -955,16 +958,42 @@ func setProp[T comparable](m map[string]any, k string, v *T) { } } +// NewNotFound creates a new not found error +// There are lots of endpoints that return a list of objects which might not contain the object we are looking for. +// In this case, we should still return 404. +func NewNotFound(msg string, args ...any) error { + return aiven.Error{Status: http.StatusNotFound, Message: fmt.Sprintf(msg, args...)} +} + +func IsNotFound(err error) bool { + return aiven.IsNotFound(err) || avngen.IsNotFound(err) +} + +func OmitNotFound(err error) error { + if IsNotFound(err) { + return nil + } + return err +} + // IsUnknownRole checks if the database returned an error because of an unknown role // to make deletions idempotent func IsUnknownRole(err error) bool { - var e aiven.Error - return errors.As(err, &e) && strings.Contains(e.Message, "Code: 511") + var oldError aiven.Error + var newError avngen.Error + var msg string + switch { + case errors.As(err, &oldError): + msg = oldError.Message + case errors.As(err, &newError): + msg = newError.Message + } + return strings.Contains(msg, "Code: 511") } // IsUnknownResource is a function to handle errors that we want to treat as "Not Found" func IsUnknownResource(err error) bool { - return aiven.IsNotFound(err) || IsUnknownRole(err) + return IsNotFound(err) || IsUnknownRole(err) } func ResourceReadHandleNotFound(err error, d *schema.ResourceData) error { diff --git a/internal/schemautil/wait.go b/internal/schemautil/wait.go index 694bf9786..9b4884358 100644 --- a/internal/schemautil/wait.go +++ b/internal/schemautil/wait.go @@ -12,6 +12,7 @@ import ( avngen "github.com/aiven/go-client-codegen" "github.com/aiven/go-client-codegen/handler/service" "github.com/aiven/go-client-codegen/handler/staticip" + retryGo "github.com/avast/retry-go" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -244,7 +245,7 @@ func grafanaReady(s *service.ServiceGetOut) bool { func backupsReady(s *service.ServiceGetOut) bool { switch s.ServiceType { - case ServiceTypePG, ServiceTypeInfluxDB, ServiceTypeRedis, ServiceTypeDragonfly: + case ServiceTypeAlloyDBOmni, ServiceTypePG, ServiceTypeInfluxDB, ServiceTypeRedis, ServiceTypeDragonfly: // See https://github.com/aiven/terraform-provider-aiven/issues/756 switch "off" { case s.UserConfig["redis_persistence"], s.UserConfig["dragonfly_persistence"]: @@ -357,3 +358,15 @@ L: func staticIpsForServiceFromSchema(d *schema.ResourceData) []string { return FlattenToString(d.Get("static_ips").(*schema.Set).List()) } + +// WaitUntilNotFound retries the given retryableFunc until it returns 404 +// To stop the retrying, the function should return retryGo.Unrecoverable +func WaitUntilNotFound(ctx context.Context, retryableFunc retryGo.RetryableFunc) error { + return retryGo.Do( + func() error { + return OmitNotFound(retryableFunc()) + }, + retryGo.Context(ctx), + retryGo.Delay(common.DefaultStateChangeDelay), + ) +} diff --git a/internal/sdkprovider/provider/provider.go b/internal/sdkprovider/provider/provider.go index 654564c69..5f35201be 100644 --- a/internal/sdkprovider/provider/provider.go +++ b/internal/sdkprovider/provider/provider.go @@ -13,6 +13,7 @@ import ( "github.com/aiven/terraform-provider-aiven/internal/plugin/util" "github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig" "github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/account" + "github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/alloydbomni" "github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/cassandra" "github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/clickhouse" "github.com/aiven/terraform-provider-aiven/internal/sdkprovider/service/connectionpool" @@ -77,6 +78,11 @@ func Provider(version string) (*schema.Provider, error) { "aiven_pg_user": pg.DatasourcePGUser(), "aiven_pg_database": pg.DatasourcePGDatabase(), + // alloydbomni + "aiven_alloydbomni": alloydbomni.DatasourceAlloyDBOmni(), + "aiven_alloydbomni_user": alloydbomni.DatasourceAlloyDBOmniUser(), + "aiven_alloydbomni_database": alloydbomni.DatasourceAlloyDBOmniDatabase(), + // cassandra "aiven_cassandra": cassandra.DatasourceCassandra(), "aiven_cassandra_user": cassandra.DatasourceCassandraUser(), @@ -186,6 +192,11 @@ func Provider(version string) (*schema.Provider, error) { "aiven_pg_user": pg.ResourcePGUser(), "aiven_pg_database": pg.ResourcePGDatabase(), + // alloydbomni + "aiven_alloydbomni": alloydbomni.ResourceAlloyDBOmni(), + "aiven_alloydbomni_user": alloydbomni.ResourceAlloyDBOmniUser(), + "aiven_alloydbomni_database": alloydbomni.ResourceAlloyDBOmniDatabase(), + // cassandra "aiven_cassandra": cassandra.ResourceCassandra(), "aiven_cassandra_user": cassandra.ResourceCassandraUser(), @@ -278,9 +289,16 @@ func Provider(version string) (*schema.Provider, error) { } // Adds "beta" warning to the description - betaResources := []string{} + betaResources := []string{ + "aiven_alloydbomni", + "aiven_alloydbomni_user", + "aiven_alloydbomni_database", + } betaDataSources := []string{ + "aiven_alloydbomni", + "aiven_alloydbomni_user", + "aiven_alloydbomni_database", "aiven_organization_user_list", } diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni.go b/internal/sdkprovider/service/alloydbomni/alloydbomni.go new file mode 100644 index 000000000..c06b6970f --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni.go @@ -0,0 +1,274 @@ +package alloydbomni + +import ( + "context" + "encoding/json" + "fmt" + + avngen "github.com/aiven/go-client-codegen" + "github.com/aiven/go-client-codegen/handler/alloydbomni" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/aiven/terraform-provider-aiven/internal/common" + "github.com/aiven/terraform-provider-aiven/internal/schemautil" +) + +const serviceAccountCredentials = "service_account_credentials" + +func aivenAlloyDBOmniSchema() map[string]*schema.Schema { + s := schemautil.ServiceCommonSchemaWithUserConfig(schemautil.ServiceTypeAlloyDBOmni) + s[serviceAccountCredentials] = &schema.Schema{ + Description: "Your [Google service account key](https://cloud.google.com/iam/docs/service-account-creds#key-types) in JSON format.", + Optional: true, + Sensitive: true, + Type: schema.TypeString, + ValidateDiagFunc: validateServiceAccountCredentials, + } + s[schemautil.ServiceTypeAlloyDBOmni] = &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Computed: true, + Description: "Values provided by the AlloyDB Omni server.", + Optional: true, + Sensitive: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // TODO: Remove `uri` in the next major version. + "uri": { + Type: schema.TypeString, + Computed: true, + Description: "AlloyDB Omni primary connection URI.", + Optional: true, + Sensitive: true, + }, + "uris": { + Type: schema.TypeList, + Computed: true, + Description: "AlloyDB Omni primary connection URIs.", + Optional: true, + Sensitive: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Sensitive: true, + }, + }, + "bouncer": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "PgBouncer connection details for [connection pooling](https://aiven.io/docs/products/postgresql/concepts/pg-connection-pooling).", + Deprecated: "This field was added by mistake and has never worked. It will be removed in future versions.", + }, + // TODO: Remove `host` in the next major version. + "host": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni primary node host IP or name.", + }, + // TODO: Remove `port` in the next major version. + "port": { + Type: schema.TypeInt, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni port.", + }, + // TODO: Remove `sslmode` in the next major version. + "sslmode": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni SSL mode setting.", + }, + // TODO: Remove `user` in the next major version. + "user": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni admin user name.", + }, + // TODO: Remove `password` in the next major version. + "password": { + Type: schema.TypeString, + Computed: true, + Description: "AlloyDB Omni admin user password.", + Sensitive: true, + }, + // TODO: Remove `dbname` in the next major version. + "dbname": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "Primary AlloyDB Omni database name.", + }, + "params": { + Type: schema.TypeList, + Computed: true, + Description: "AlloyDB Omni connection parameters.", + Optional: true, + Sensitive: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "host": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni host IP or name.", + }, + "port": { + Type: schema.TypeInt, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni port.", + }, + "sslmode": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni SSL mode setting.", + }, + "user": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni admin user name.", + }, + "password": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "AlloyDB Omni admin user password.", + }, + "database_name": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + Description: "Primary AlloyDB Omni database name.", + }, + }, + }, + }, + "replica_uri": { + Type: schema.TypeString, + Computed: true, + Description: "AlloyDB Omni replica URI for services with a replica.", + Sensitive: true, + }, + "standby_uris": { + Type: schema.TypeList, + Computed: true, + Description: "AlloyDB Omni standby connection URIs.", + Optional: true, + Sensitive: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Sensitive: true, + }, + }, + "syncing_uris": { + Type: schema.TypeList, + Computed: true, + Description: "AlloyDB Omni syncing connection URIs.", + Optional: true, + Sensitive: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + Sensitive: true, + }, + }, + // TODO: This isn't in the connection info, but it's in the metadata. + // We should move this to the other part of the schema in the next major version. + "max_connections": { + Type: schema.TypeInt, + Computed: true, + Sensitive: true, + Description: "The [number of allowed connections](https://aiven.io/docs/products/postgresql/reference/pg-connection-limits). Varies based on the service plan.", + }, + }, + }, + } + return s +} + +func ResourceAlloyDBOmni() *schema.Resource { + return &schema.Resource{ + Description: "Creates and manages an Aiven for AlloyDB Omni service.", + CreateContext: schemautil.ComposeContexts( + schemautil.ResourceServiceCreateWrapper(schemautil.ServiceTypeAlloyDBOmni), + common.WithGenClient(serviceAccountCredentialsUpsert), + ), + ReadContext: schemautil.ComposeContexts( + schemautil.ResourceServiceRead, + common.WithGenClient(serviceAccountCredentialsRead), + ), + UpdateContext: schemautil.ComposeContexts( + schemautil.ResourceServiceUpdate, + common.WithGenClient(serviceAccountCredentialsUpsert), + ), + DeleteContext: schemautil.ResourceServiceDelete, + CustomizeDiff: schemautil.CustomizeDiffGenericService(schemautil.ServiceTypeAlloyDBOmni), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: schemautil.DefaultResourceTimeouts(), + Schema: aivenAlloyDBOmniSchema(), + SchemaVersion: 1, + } +} + +// serviceAccountCredentialsUpsert sets, updates and removes service account credentials +func serviceAccountCredentialsUpsert(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + projectName, serviceName, err := schemautil.SplitResourceID2(d.Id()) + if err != nil { + return err + } + + privateKey, ok := d.GetOk(serviceAccountCredentials) + if ok { + req := &alloydbomni.AlloyDbOmniGoogleCloudPrivateKeySetIn{PrivateKey: privateKey.(string)} + _, err = client.AlloyDbOmniGoogleCloudPrivateKeySet(ctx, projectName, serviceName, req) + } else { + _, err = client.AlloyDbOmniGoogleCloudPrivateKeyRemove(ctx, projectName, serviceName) + } + return err +} + +// serviceAccountCredentialsRead reads remote service account credentials and compares with the local value +// This function is unnecessary for the most cases: +// terraform will handle all the plan and apply logic for add/change/delete. +// It covers just one case: when the remote value is different from the local value. +// For instance, when it was modified or removed in the Console. +func serviceAccountCredentialsRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + projectName, serviceName, err := schemautil.SplitResourceID2(d.Id()) + if err != nil { + return err + } + + // Compares remote and local private keys + // When they differ, the local value is set to an empty string to enforce plan to show the change + remote, err := client.AlloyDbOmniGoogleCloudPrivateKeyIdentify(ctx, projectName, serviceName) + if err != nil { + // Something terrible happened, we should return the error + // This endpoint doesn't return 404, if service exists, it will return the DTO with null values + return err + } + + // Unmarshals local private key into DTO to compare with the remote value + local := new(alloydbomni.AlloyDbOmniGoogleCloudPrivateKeyIdentifyOut) + if v, ok := d.GetOk(serviceAccountCredentials); ok { + err = json.Unmarshal([]byte(v.(string)), local) + if err != nil { + return fmt.Errorf("failed to unmarshal config service_account_credentials: %w", err) + } + } + + if !cmp.Equal(remote, local) { + // 1. Remote key does not exist, or + // 2. Remote key is different from the local value + // This will enforce the plan to show the change + return d.Set(serviceAccountCredentials, "") + } + + return nil +} diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni_data_source.go b/internal/sdkprovider/service/alloydbomni/alloydbomni_data_source.go new file mode 100644 index 000000000..9fac7bdd5 --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni_data_source.go @@ -0,0 +1,15 @@ +package alloydbomni + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/aiven/terraform-provider-aiven/internal/schemautil" +) + +func DatasourceAlloyDBOmni() *schema.Resource { + return &schema.Resource{ + ReadContext: schemautil.DatasourceServiceRead, + Description: "Gets information about an Aiven for AlloyDB Omni service.", + Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenAlloyDBOmniSchema(), "project", "service_name"), + } +} diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni_database.go b/internal/sdkprovider/service/alloydbomni/alloydbomni_database.go new file mode 100644 index 000000000..83fbc9311 --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni_database.go @@ -0,0 +1,128 @@ +package alloydbomni + +import ( + "context" + + avngen "github.com/aiven/go-client-codegen" + "github.com/aiven/go-client-codegen/handler/service" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/aiven/terraform-provider-aiven/internal/common" + "github.com/aiven/terraform-provider-aiven/internal/schemautil" + "github.com/aiven/terraform-provider-aiven/internal/schemautil/userconfig" +) + +const defaultLC = "en_US.UTF-8" + +var aivenAlloyDBOmniDatabaseSchema = map[string]*schema.Schema{ + "project": schemautil.CommonSchemaProjectReference, + "service_name": schemautil.CommonSchemaServiceNameReference, + "database_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: userconfig.Desc("The name of the service database.").ForceNew().Build(), + }, + "lc_ctype": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: defaultLC, + Description: userconfig.Desc("Default character classification (`LC_CTYPE`) of the database.").DefaultValue(defaultLC).ForceNew().Build(), + }, + "lc_collate": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: defaultLC, + Description: userconfig.Desc("Default string sort order (`LC_COLLATE`) of the database.").DefaultValue(defaultLC).ForceNew().Build(), + }, +} + +func ResourceAlloyDBOmniDatabase() *schema.Resource { + return &schema.Resource{ + Description: "Creates and manages a database in an Aiven for AlloyDB Omni service.", + CreateContext: common.WithGenClient(resourceAlloyDBOmniDatabaseCreate), + ReadContext: common.WithGenClient(resourceAlloyDBOmniDatabaseRead), + DeleteContext: common.WithGenClient(resourceAlloyDBOmniDatabaseDelete), + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Timeouts: schemautil.DefaultResourceTimeouts(), + Schema: aivenAlloyDBOmniDatabaseSchema, + } +} + +func resourceAlloyDBOmniDatabaseCreate(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + projectName := d.Get("project").(string) + serviceName := d.Get("service_name").(string) + + req := new(service.ServiceDatabaseCreateIn) + err := schemautil.ResourceDataGet(d, req, schemautil.RenameAlias("database_name", "database")) + if err != nil { + return err + } + + err = client.ServiceDatabaseCreate(ctx, projectName, serviceName, req) + if err != nil { + return err + } + + d.SetId(schemautil.BuildResourceID(projectName, serviceName, req.Database)) + return resourceAlloyDBOmniDatabaseRead(ctx, d, client) +} + +func resourceAlloyDBOmniDatabaseRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + projectName, serviceName, dbName, err := schemautil.SplitResourceID3(d.Id()) + if err != nil { + return err + } + + db, err := getDatabase(ctx, client, projectName, serviceName, dbName) + if err != nil { + return err + } + + if err := d.Set("project", projectName); err != nil { + return err + } + + if err := d.Set("service_name", serviceName); err != nil { + return err + } + + return schemautil.ResourceDataSet(aivenAlloyDBOmniDatabaseSchema, d, db) +} + +func resourceAlloyDBOmniDatabaseDelete(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + projectName, serviceName, dbName, err := schemautil.SplitResourceID3(d.Id()) + if err != nil { + return err + } + + err = client.ServiceDatabaseDelete(ctx, projectName, serviceName, dbName) + if err != nil { + return err + } + + // Waits until database is deleted + return schemautil.WaitUntilNotFound(ctx, func() error { + _, err = getDatabase(ctx, client, projectName, serviceName, dbName) + return err + }) +} + +func getDatabase(ctx context.Context, client avngen.Client, projectName, serviceName, dbName string) (*service.DatabaseOut, error) { + list, err := client.ServiceDatabaseList(ctx, projectName, serviceName) + if err != nil { + return nil, err + } + + for _, db := range list { + if db.DatabaseName == dbName { + return &db, nil + } + } + + return nil, schemautil.NewNotFound("service database %q not found", dbName) +} diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni_database_data_source.go b/internal/sdkprovider/service/alloydbomni/alloydbomni_database_data_source.go new file mode 100644 index 000000000..6c5c75125 --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni_database_data_source.go @@ -0,0 +1,29 @@ +package alloydbomni + +import ( + "context" + + avngen "github.com/aiven/go-client-codegen" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/aiven/terraform-provider-aiven/internal/common" + "github.com/aiven/terraform-provider-aiven/internal/schemautil" +) + +func DatasourceAlloyDBOmniDatabase() *schema.Resource { + return &schema.Resource{ + ReadContext: common.WithGenClient(datasourceDatabaseRead), + Description: "Gets information about a database in an Aiven for AlloyDB Omni service.", + Schema: schemautil.ResourceSchemaAsDatasourceSchema(aivenAlloyDBOmniDatabaseSchema, + "project", "service_name", "database_name"), + } +} + +func datasourceDatabaseRead(ctx context.Context, d *schema.ResourceData, client avngen.Client) error { + projectName := d.Get("project").(string) + serviceName := d.Get("service_name").(string) + databaseName := d.Get("database_name").(string) + + d.SetId(schemautil.BuildResourceID(projectName, serviceName, databaseName)) + return resourceAlloyDBOmniDatabaseRead(ctx, d, client) +} diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni_database_test.go b/internal/sdkprovider/service/alloydbomni/alloydbomni_database_test.go new file mode 100644 index 000000000..e87efde30 --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni_database_test.go @@ -0,0 +1,156 @@ +package alloydbomni_test + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + "testing" + + "github.com/aiven/aiven-go-client/v2" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + acc "github.com/aiven/terraform-provider-aiven/internal/acctest" + "github.com/aiven/terraform-provider-aiven/internal/schemautil" +) + +func TestAccAivenAlloyDBOmniDatabase_basic(t *testing.T) { + resourceName := "aiven_alloydbomni_database.foo" + projectName := os.Getenv("AIVEN_PROJECT_NAME") + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: testAccCheckAivenAlloyDBOmniDatabaseResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniDatabaseResource(projectName, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "project", projectName), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "database_name", fmt.Sprintf("test-acc-db-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "lc_ctype", "en_US.UTF-8"), + resource.TestCheckResourceAttr(resourceName, "lc_collate", "en_US.UTF-8"), + ), + }, + { + Config: testAccAlloyDBOmniDatabaseResource(projectName, rName), + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("expected resource '%s' to be present in the state", resourceName) + } + if _, ok := rs.Primary.Attributes["database_name"]; !ok { + return "", fmt.Errorf("expected resource '%s' to have 'database_name' attribute", resourceName) + } + return rs.Primary.ID, nil + }, + ImportStateCheck: func(s []*terraform.InstanceState) error { + if len(s) != 1 { + return fmt.Errorf("expected only one instance to be imported, state: %#v", s) + } + attributes := s[0].Attributes + if !strings.EqualFold(attributes["project"], projectName) { + return fmt.Errorf("expected project to match '%s', got: '%s'", projectName, attributes["project_name"]) + } + databaseName, ok := attributes["database_name"] + if !ok { + return errors.New("expected 'database_name' field to be set") + } + if _, ok := attributes["lc_ctype"]; !ok { + return errors.New("expected 'lc_ctype' field to be set") + } + if _, ok := attributes["lc_collate"]; !ok { + return errors.New("expected 'lc_collate' field to be set") + } + expectedID := fmt.Sprintf("%s/test-acc-sr-%s/%s", projectName, rName, databaseName) + if !strings.EqualFold(s[0].ID, expectedID) { + return fmt.Errorf("expected ID to match '%s', but got: %s", expectedID, s[0].ID) + } + return nil + }, + }, + }, + }) +} + +func testAccCheckAivenAlloyDBOmniDatabaseResourceDestroy(s *terraform.State) error { + c := acc.GetTestAivenClient() + + ctx := context.Background() + + // loop through the resources in state, verifying each database is destroyed + for _, rs := range s.RootModule().Resources { + if rs.Type != "aiven_alloydbomni_database" { + continue + } + + projectName, serviceName, databaseName, err := schemautil.SplitResourceID3(rs.Primary.ID) + if err != nil { + return err + } + + db, err := c.Databases.Get(ctx, projectName, serviceName, databaseName) + if err != nil { + var e aiven.Error + if errors.As(err, &e) && e.Status != 404 { + return err + } + } + + if db != nil { + return fmt.Errorf("database (%s) still exists", rs.Primary.ID) + } + } + + return nil +} + +func testAccAlloyDBOmniDatabaseResource(project string, name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + } + } +} + +resource "aiven_alloydbomni_database" "foo" { + project = aiven_alloydbomni.bar.project + service_name = aiven_alloydbomni.bar.service_name + database_name = "test-acc-db-%s" + lc_ctype = "en_US.UTF-8" + lc_collate = "en_US.UTF-8" +} + +data "aiven_alloydbomni_database" "database" { + project = aiven_alloydbomni_database.foo.project + service_name = aiven_alloydbomni_database.foo.service_name + database_name = aiven_alloydbomni_database.foo.database_name + + depends_on = [aiven_alloydbomni_database.foo] +}`, project, name, name) +} diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni_import_test.go b/internal/sdkprovider/service/alloydbomni/alloydbomni_import_test.go new file mode 100644 index 000000000..c19791e14 --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni_import_test.go @@ -0,0 +1,108 @@ +package alloydbomni_test + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stretchr/testify/assert" + + acc "github.com/aiven/terraform-provider-aiven/internal/acctest" +) + +func TestAccAivenAlloyDBOmni_import(t *testing.T) { + resourceName := "aiven_alloydbomni.main" + projectName := os.Getenv("AIVEN_PROJECT_NAME") + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniImportResource(projectName, rName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "project", projectName), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s-main", rName)), + ), + }, + { + Config: testAccAlloyDBOmniImportResource(projectName, rName), + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("expected resource '%s' to be present in the state", resourceName) + } + return rs.Primary.ID, nil + }, + ImportStateCheck: func(s []*terraform.InstanceState) error { + assert := assert.New(t) + if !assert.Len(s, 1, "expected only one instance to be imported") { + return fmt.Errorf("state: %#v", s) + } + attributes := s[0].Attributes + assert.Equal(projectName, attributes["project"]) + assert.Equal("google-europe-west1", attributes["cloud_name"]) + assert.Equal("startup-4", attributes["plan"]) + assert.Equal(fmt.Sprintf("test-acc-sr-%s-main", rName), attributes["service_name"]) + assert.Equal("monday", attributes["maintenance_window_dow"]) + assert.Equal("10:00:00", attributes["maintenance_window_time"]) + assert.Equal("30GiB", attributes["additional_disk_space"]) + assert.Equal("alloydbomniimporttest@aiven.io", attributes["tech_emails.0.email"]) + assert.Equal("test-key", attributes["tag.0.key"]) + assert.Equal("test-value", attributes["tag.0.value"]) + assert.Equal(fmt.Sprintf("%s/test-acc-sr-%s-main", projectName, rName), s[0].ID) + return nil + }, + }, + }, + }) +} + +func testAccAlloyDBOmniImportResource(project string, name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "main" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s-main" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + additional_disk_space = "30GiB" + + tech_emails { + email = "alloydbomniimporttest@aiven.io" + } + + tag { + key = "test-key" + value = "test-value" + } +} + +resource "aiven_alloydbomni" "read_replica" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s-read-replica" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + additional_disk_space = "30GiB" + + service_integrations { + source_service_name = aiven_alloydbomni.main.service_name + integration_type = "read_replica" + } +} +`, project, name, name) +} diff --git a/internal/sdkprovider/service/alloydbomni/alloydbomni_test.go b/internal/sdkprovider/service/alloydbomni/alloydbomni_test.go new file mode 100644 index 000000000..b65b6d9c9 --- /dev/null +++ b/internal/sdkprovider/service/alloydbomni/alloydbomni_test.go @@ -0,0 +1,1219 @@ +package alloydbomni_test + +import ( + "context" + "fmt" + "os" + "regexp" + "strings" + "testing" + + "github.com/aiven/aiven-go-client/v2" + "github.com/aiven/go-client-codegen/handler/alloydbomni" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + acc "github.com/aiven/terraform-provider-aiven/internal/acctest" + "github.com/aiven/terraform-provider-aiven/internal/schemautil" +) + +func TestAccAivenAlloyDBOmni_invalid_disk_size(t *testing.T) { + expectErrorRegexBadString := regexp.MustCompile(regexp.QuoteMeta("configured string must match ^[1-9][0-9]*(G|GiB)")) + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // bad strings + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "abc"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "01MiB"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "1234"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "5TiB"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, " 1Gib "), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "1 GiB"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + // bad disk sizes + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "1GiB"), + PlanOnly: true, + ExpectError: regexp.MustCompile("requested disk size is too small"), + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "100000GiB"), + PlanOnly: true, + ExpectError: regexp.MustCompile("requested disk size is too large"), + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "127GiB"), + PlanOnly: true, + ExpectError: regexp.MustCompile("requested disk size has to increase from: '.*' in increments of '.*'"), + }, + { + Config: testAccAlloyDBOmniResourceWithAdditionalDiskSize(rName, "127GiB"), + PlanOnly: true, + ExpectError: regexp.MustCompile("requested disk size has to increase from: '.*' in increments of '.*'"), + }, + { + Config: testAccAlloyDBOmniResourceWithAdditionalDiskSize(rName, "100000GiB"), + PlanOnly: true, + ExpectError: regexp.MustCompile("requested disk size is too large"), + }, + { + Config: testAccAlloyDBOmniResourceWithAdditionalDiskSize(rName, "abc"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithAdditionalDiskSize(rName, "01MiB"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniResourceWithAdditionalDiskSize(rName, "1234"), + PlanOnly: true, + ExpectError: expectErrorRegexBadString, + }, + { + Config: testAccAlloyDBOmniDoubleTagResource(rName), + PlanOnly: true, + ExpectNonEmptyPlan: true, + ExpectError: regexp.MustCompile("tag keys should be unique"), + }, + }, + }) +} + +func TestAccAivenAlloyDBOmni_static_ips(t *testing.T) { + resourceName := "aiven_alloydbomni.bar" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniWithStaticIps(rName, 2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + resource.TestCheckResourceAttrSet(resourceName, "service_username"), + resource.TestCheckResourceAttrSet(resourceName, "service_password"), + resource.TestCheckResourceAttrSet(resourceName, "service_host"), + resource.TestCheckResourceAttrSet(resourceName, "service_port"), + resource.TestCheckResourceAttrSet(resourceName, "service_uri"), + resource.TestCheckResourceAttr(resourceName, "static_ips.#", "2"), + ), + }, + { + Config: testAccAlloyDBOmniWithStaticIps(rName, 3), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "static_ips.#", "3"), + ), + }, + { + Config: testAccAlloyDBOmniWithStaticIps(rName, 4), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "static_ips.#", "4"), + ), + }, + { + Config: testAccAlloyDBOmniWithStaticIps(rName, 3), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "static_ips.#", "3"), + ), + }, + { + Config: testAccAlloyDBOmniWithStaticIps(rName, 4), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "static_ips.#", "4"), + ), + }, + { + Config: testAccAlloyDBOmniWithStaticIps(rName, 2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "static_ips.#", "2"), + ), + }, + }, + }) +} + +func TestAccAivenAlloyDBOmni_changing_plan(t *testing.T) { + resourceName := "aiven_alloydbomni.bar" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniResourcePlanChange(rName, "business-8"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + resource.TestCheckResourceAttrSet(resourceName, "disk_space_used"), + ), + }, + { + Config: testAccAlloyDBOmniResourcePlanChange(rName, "business-4"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + resource.TestCheckResourceAttrSet(resourceName, "disk_space_used"), + ), + }, + }, + }) +} + +func TestAccAivenAlloyDBOmni_deleting_additional_disk_size(t *testing.T) { + resourceName := "aiven_alloydbomni.bar" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniResourceWithAdditionalDiskSize(rName, "20GiB"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "disk_space_used", "100GiB"), + resource.TestCheckResourceAttr(resourceName, "disk_space_default", "80GiB"), + resource.TestCheckResourceAttr(resourceName, "additional_disk_space", "20GiB"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + { + Config: testAccAlloyDBOmniResourceWithoutDiskSize(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "disk_space_default", "80GiB"), + resource.TestCheckResourceAttr(resourceName, "disk_space_used", "80GiB"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + }, + }) +} + +func TestAccAivenAlloyDBOmni_deleting_disk_size(t *testing.T) { + resourceName := "aiven_alloydbomni.bar" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "90GiB"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "disk_space", "90GiB"), + resource.TestCheckResourceAttr(resourceName, "disk_space_used", "90GiB"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + { + Config: testAccAlloyDBOmniResourceWithoutDiskSize(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "disk_space", ""), + resource.TestCheckResourceAttrSet(resourceName, "disk_space_used"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + }, + }) +} + +func TestAccAivenAlloyDBOmni_changing_disk_size(t *testing.T) { + resourceName := "aiven_alloydbomni.bar" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "90GiB"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "disk_space", "90GiB"), + resource.TestCheckResourceAttr(resourceName, "disk_space_used", "90GiB"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + { + Config: testAccAlloyDBOmniResourceWithDiskSize(rName, "100GiB"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttrSet(resourceName, "state"), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "service_type", "alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "disk_space", "100GiB"), + resource.TestCheckResourceAttr(resourceName, "disk_space_used", "100GiB"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + }, + }) +} + +func testAccAlloyDBOmniWithStaticIps(name string, count int) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_static_ip" "ips" { + count = %d + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + static_ips = toset(aiven_static_ip.ips[*].static_ip_address_id) + + alloydbomni_user_config { + static_ips = true + } +} + +data "aiven_alloydbomni" "common" { + service_name = aiven_alloydbomni.bar.service_name + project = aiven_alloydbomni.bar.project + + depends_on = [aiven_alloydbomni.bar] +}`, os.Getenv("AIVEN_PROJECT_NAME"), count, name) +} + +func testAccAlloyDBOmniResourceWithDiskSize(name, diskSize string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + disk_space = "%s" + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + log_min_duration_statement = -1 + } + } +} + +data "aiven_alloydbomni" "common" { + service_name = aiven_alloydbomni.bar.service_name + project = aiven_alloydbomni.bar.project + + depends_on = [aiven_alloydbomni.bar] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name, diskSize) +} + +func testAccAlloyDBOmniResourceWithAdditionalDiskSize(name, diskSize string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + additional_disk_space = "%s" + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + log_min_duration_statement = -1 + } + } +} + +data "aiven_alloydbomni" "common" { + service_name = aiven_alloydbomni.bar.service_name + project = aiven_alloydbomni.bar.project + + depends_on = [aiven_alloydbomni.bar] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name, diskSize) +} + +func testAccAlloyDBOmniResourceWithoutDiskSize(name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + tag { + key = "test" + value = "val" + } + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + log_min_duration_statement = -1 + } + } +} + +data "aiven_alloydbomni" "common" { + service_name = aiven_alloydbomni.bar.service_name + project = aiven_alloydbomni.bar.project + + depends_on = [aiven_alloydbomni.bar] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name) +} + +func testAccAlloyDBOmniResourcePlanChange(name, plan string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "%s" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + tag { + key = "test" + value = "val" + } + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + log_min_duration_statement = -1 + } + } +} + +data "aiven_alloydbomni" "common" { + service_name = aiven_alloydbomni.bar.service_name + project = aiven_alloydbomni.bar.project + + depends_on = [aiven_alloydbomni.bar] +}`, os.Getenv("AIVEN_PROJECT_NAME"), plan, name) +} + +func testAccAlloyDBOmniDoubleTagResource(name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar" { + project = data.aiven_project.foo.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + tag { + key = "test" + value = "val" + } + tag { + key = "test" + value = "val2" + } + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + log_min_duration_statement = -1 + } + } +} + +data "aiven_alloydbomni" "common" { + service_name = aiven_alloydbomni.bar.service_name + project = aiven_alloydbomni.bar.project + + depends_on = [aiven_alloydbomni.bar] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name) +} + +// TestAccAivenAlloyDBOmni_admin_creds tests admin creds in user_config +func TestAccAivenAlloyDBOmni_admin_creds(t *testing.T) { + resourceName := "aiven_alloydbomni.alloydbomni" + prefix := "test-tf-acc-" + acctest.RandString(7) + project := os.Getenv("AIVEN_PROJECT_NAME") + expectedURLPrefix := fmt.Sprintf("postgres://root:%s-password", prefix) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniResourceAdminCreds(prefix, project), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrWith(resourceName, "service_uri", func(value string) error { + if !strings.HasPrefix(value, expectedURLPrefix) { + return fmt.Errorf("invalid service_uri, doesn't contain admin_username: %q", value) + } + return nil + }), + resource.TestCheckResourceAttr(resourceName, "alloydbomni_user_config.0.admin_username", "root"), + resource.TestCheckResourceAttr(resourceName, "alloydbomni_user_config.0.admin_password", prefix+"-password"), + ), + }, + }, + }) +} + +// testAccAlloyDBOmniResourceAdminCreds returns config TestAccAivenAlloyDBOmni_admin_creds +func testAccAlloyDBOmniResourceAdminCreds(prefix, project string) string { + return fmt.Sprintf(` +resource "aiven_alloydbomni" "alloydbomni" { + project = %[2]q + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "%[1]s-alloydbomni" + + alloydbomni_user_config { + admin_username = "root" + admin_password = "%[1]s-password" + } +} + `, prefix, project) +} + +// AlloyDBOmni service tests +func TestAccAivenServiceAlloyDBOmni_basic(t *testing.T) { + resourceName := "aiven_alloydbomni.bar-alloydbomni" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniServiceResource(rName), + Check: resource.ComposeTestCheckFunc( + acc.TestAccCheckAivenServiceCommonAttributes("data.aiven_alloydbomni.common-alloydbomni"), + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common-alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "state", "RUNNING"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + }, + }) +} + +func TestAccAivenServiceAlloyDBOmni_termination_protection(t *testing.T) { + resourceName := "aiven_alloydbomni.bar-alloydbomni" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniTerminationProtectionServiceResource(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAivenServiceTerminationProtection("data.aiven_alloydbomni.common-alloydbomni"), + acc.TestAccCheckAivenServiceCommonAttributes("data.aiven_alloydbomni.common-alloydbomni"), + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common-alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "state", "RUNNING"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "true"), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccAivenServiceAlloyDBOmni_read_replica(t *testing.T) { + resourceName := "aiven_alloydbomni.bar-alloydbomni" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniReadReplicaServiceResource(rName), + PreventPostDestroyRefresh: true, + Check: resource.ComposeTestCheckFunc( + acc.TestAccCheckAivenServiceCommonAttributes("data.aiven_alloydbomni.common-alloydbomni"), + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common-alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "state", "RUNNING"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + }, + }) +} + +func TestAccAivenServiceAlloyDBOmni_custom_timeouts(t *testing.T) { + resourceName := "aiven_alloydbomni.bar-alloydbomni" + rName := acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAlloyDBOmniServiceCustomTimeoutsResource(rName), + Check: resource.ComposeTestCheckFunc( + acc.TestAccCheckAivenServiceCommonAttributes("data.aiven_alloydbomni.common-alloydbomni"), + testAccCheckAivenServiceAlloyDBOmniAttributes("data.aiven_alloydbomni.common-alloydbomni"), + resource.TestCheckResourceAttr(resourceName, "service_name", fmt.Sprintf("test-acc-sr-%s", rName)), + resource.TestCheckResourceAttr(resourceName, "project", os.Getenv("AIVEN_PROJECT_NAME")), + resource.TestCheckResourceAttr(resourceName, "cloud_name", "google-europe-west1"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_dow", "monday"), + resource.TestCheckResourceAttr(resourceName, "maintenance_window_time", "10:00:00"), + resource.TestCheckResourceAttr(resourceName, "state", "RUNNING"), + resource.TestCheckResourceAttr(resourceName, "termination_protection", "false"), + ), + }, + }, + }) +} + +func testAccAlloyDBOmniServiceResource(name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo-alloydbomni" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar-alloydbomni" { + project = data.aiven_project.foo-alloydbomni.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + } + } +} + +data "aiven_alloydbomni" "common-alloydbomni" { + service_name = aiven_alloydbomni.bar-alloydbomni.service_name + project = aiven_alloydbomni.bar-alloydbomni.project + + depends_on = [aiven_alloydbomni.bar-alloydbomni] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name) +} + +func testAccAlloyDBOmniServiceCustomTimeoutsResource(name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo-alloydbomni" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar-alloydbomni" { + project = data.aiven_project.foo-alloydbomni.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + timeouts { + create = "25m" + update = "30m" + } + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + } + } +} + +data "aiven_alloydbomni" "common-alloydbomni" { + service_name = aiven_alloydbomni.bar-alloydbomni.service_name + project = aiven_alloydbomni.bar-alloydbomni.project + + depends_on = [aiven_alloydbomni.bar-alloydbomni] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name) +} + +func testAccAlloyDBOmniTerminationProtectionServiceResource(name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo-alloydbomni" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar-alloydbomni" { + project = data.aiven_project.foo-alloydbomni.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + termination_protection = true + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + } + } +} + +data "aiven_alloydbomni" "common-alloydbomni" { + service_name = aiven_alloydbomni.bar-alloydbomni.service_name + project = aiven_alloydbomni.bar-alloydbomni.project + + depends_on = [aiven_alloydbomni.bar-alloydbomni] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name) +} + +func testAccAlloyDBOmniReadReplicaServiceResource(name string) string { + return fmt.Sprintf(` +data "aiven_project" "foo-alloydbomni" { + project = "%s" +} + +resource "aiven_alloydbomni" "bar-alloydbomni" { + project = data.aiven_project.foo-alloydbomni.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + alloydbomni_user_config { + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + } + } +} + +resource "aiven_alloydbomni" "bar-replica" { + project = data.aiven_project.foo-alloydbomni.project + cloud_name = "google-europe-west1" + plan = "startup-4" + service_name = "test-acc-sr-repica-%s" + maintenance_window_dow = "monday" + maintenance_window_time = "10:00:00" + + alloydbomni_user_config { + backup_hour = 19 + backup_minute = 30 + public_access { + pg = true + prometheus = false + } + + pg { + idle_in_transaction_session_timeout = 900 + } + } + + service_integrations { + integration_type = "read_replica" + source_service_name = aiven_alloydbomni.bar-alloydbomni.service_name + } + + depends_on = [aiven_alloydbomni.bar-alloydbomni] +} + +resource "aiven_service_integration" "alloydbomni-readreplica" { + project = data.aiven_project.foo-alloydbomni.project + integration_type = "read_replica" + source_service_name = aiven_alloydbomni.bar-alloydbomni.service_name + destination_service_name = aiven_alloydbomni.bar-replica.service_name + + depends_on = [aiven_alloydbomni.bar-replica] +} + +data "aiven_alloydbomni" "common-alloydbomni" { + service_name = aiven_alloydbomni.bar-alloydbomni.service_name + project = aiven_alloydbomni.bar-alloydbomni.project + + depends_on = [aiven_alloydbomni.bar-alloydbomni] +}`, os.Getenv("AIVEN_PROJECT_NAME"), name, name) +} + +func testAccCheckAivenServiceTerminationProtection(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + projectName, serviceName, err := schemautil.SplitResourceID2(a["id"]) + if err != nil { + return err + } + + c := acc.GetTestAivenClient() + + ctx := context.Background() + + service, err := c.Services.Get(ctx, projectName, serviceName) + if err != nil { + return fmt.Errorf("cannot get service %s err: %w", serviceName, err) + } + + if service.TerminationProtection == false { + return fmt.Errorf("expected to get a termination_protection=true from Aiven") + } + + // try to delete Aiven service with termination_protection enabled + // should be an error from Aiven API + err = c.Services.Delete(ctx, projectName, serviceName) + if err == nil { + return fmt.Errorf("termination_protection enabled should prevent from deletion of a service, deletion went OK") + } + + // set service termination_protection to false to make Terraform Destroy plan work + _, err = c.Services.Update( + ctx, + projectName, + service.Name, + aiven.UpdateServiceRequest{ + Cloud: service.CloudName, + MaintenanceWindow: &service.MaintenanceWindow, + Plan: service.Plan, + ProjectVPCID: service.ProjectVPCID, + Powered: true, + TerminationProtection: false, + UserConfig: service.UserConfig, + }, + ) + + if err != nil { + return fmt.Errorf("unable to update Aiven service to set termination_protection=false err: %w", err) + } + + return nil + } +} + +func testAccCheckAivenServiceAlloyDBOmniAttributes(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r := s.RootModule().Resources[n] + a := r.Primary.Attributes + + if !strings.Contains(a["service_type"], "alloydbomni") { + return fmt.Errorf("expected to get a correct service_type from Aiven, got :%s", a["service_type"]) + } + + if a["alloydbomni_user_config.0.pg.0.idle_in_transaction_session_timeout"] != "900" { + return fmt.Errorf("expected to get a correct idle_in_transaction_session_timeout from Aiven") + } + + if a["alloydbomni_user_config.0.public_access.0.pg"] != "true" { + return fmt.Errorf("expected to get a correct public_access.alloydbomni from Aiven") + } + + if a["alloydbomni_user_config.0.public_access.0.pgbouncer"] != "false" { + return fmt.Errorf("expected to get a correct public_access.alloydbomnibouncer from Aiven") + } + + if a["alloydbomni_user_config.0.public_access.0.prometheus"] != "false" { + return fmt.Errorf("expected to get a correct public_access.prometheus from Aiven") + } + + if a["alloydbomni_user_config.0.service_to_fork_from"] != "" { + return fmt.Errorf("expected to get a service_to_fork_from not set to any value") + } + + if a["alloydbomni.0.uri"] == "" { + return fmt.Errorf("expected to get a correct uri from Aiven") + } + + if a["alloydbomni.0.uris.#"] == "" { + return fmt.Errorf("expected to get uris from Aiven") + } + + if a["alloydbomni.0.host"] == "" { + return fmt.Errorf("expected to get a correct host from Aiven") + } + + if a["alloydbomni.0.port"] == "" { + return fmt.Errorf("expected to get a correct port from Aiven") + } + + if a["alloydbomni.0.sslmode"] != "require" { + return fmt.Errorf("expected to get a correct sslmode from Aiven") + } + + if a["alloydbomni.0.user"] != "avnadmin" { + return fmt.Errorf("expected to get a correct user from Aiven") + } + + if a["alloydbomni.0.password"] == "" { + return fmt.Errorf("expected to get a correct password from Aiven") + } + + if a["alloydbomni.0.dbname"] != "defaultdb" { + return fmt.Errorf("expected to get a correct dbname from Aiven") + } + + if a["alloydbomni.0.params.#"] == "" { + return fmt.Errorf("expected to get params from Aiven") + } + + if a["alloydbomni.0.params.0.host"] == "" { + return fmt.Errorf("expected to get a correct host from Aiven") + } + + if a["alloydbomni.0.params.0.port"] == "" { + return fmt.Errorf("expected to get a correct port from Aiven") + } + + if a["alloydbomni.0.params.0.sslmode"] != "require" { + return fmt.Errorf("expected to get a correct sslmode from Aiven") + } + + if a["alloydbomni.0.params.0.user"] != "avnadmin" { + return fmt.Errorf("expected to get a correct user from Aiven") + } + + if a["alloydbomni.0.params.0.password"] == "" { + return fmt.Errorf("expected to get a correct password from Aiven") + } + + if a["alloydbomni.0.params.0.database_name"] != "defaultdb" { + return fmt.Errorf("expected to get a correct database_name from Aiven") + } + + if a["alloydbomni.0.max_connections"] != "100" && a["alloydbomni.0.max_connections"] != "200" { + return fmt.Errorf("expected to get a correct max_connections from Aiven") + } + + return nil + } +} + +func TestAccAivenServiceAlloyDBOmni_service_account_credentials(t *testing.T) { + ctx := context.Background() + client, err := acc.GetTestGenAivenClient() + if err != nil { + t.Skipf("cannot get aiven client: %s", err) + } + + project := os.Getenv("AIVEN_PROJECT_NAME") + resourceName := "aiven_alloydbomni.foo" + serviceName := fmt.Sprintf("test-acc-sr-%s", acc.RandStr()) + + // Service account credentials are managed by its own API + // When Terraform fails to create a service because of this field, + // the whole resource is tainted, and must be replaced + serviceAccountCredentialsInvalid := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( + project, serviceName, + `{ + "private_key": "-----BEGIN PRIVATE KEY--.........----END PRIVATE KEY-----\n", + "client_email": "example@aiven.io", + "client_id": "example_user_id", + "type": "service_account", + "project_id": "example_project_id" + }`, + ) + serviceAccountCredentialsEmpty := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( + project, serviceName, "", + ) + serviceAccountCredentialsValid := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( + project, serviceName, getTestServiceAccountCredentials("foo"), + ) + serviceAccountCredentialsValidModified := testAccAivenServiceAlloyDBOmniServiceAccountCredentials( + project, serviceName, getTestServiceAccountCredentials("bar"), + ) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + ProtoV6ProviderFactories: acc.TestProtoV6ProviderFactories, + CheckDestroy: acc.TestAccCheckAivenServiceResourceDestroy, + Steps: []resource.TestStep{ + { + // 0. Invalid credentials + Config: serviceAccountCredentialsInvalid, + ExpectError: regexp.MustCompile(`private_key_id is required`), + }, + { + // 1. No credential initially + Config: serviceAccountCredentialsEmpty, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr(resourceName, "service_account_credentials"), + ), + }, + { + // 2. Credentials are set + Config: serviceAccountCredentialsValid, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "service_account_credentials"), + ), + }, + { + // 3. Updates the credentials + Config: serviceAccountCredentialsValidModified, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "service_account_credentials"), + // Validates they key has been updated + func(_ *terraform.State) error { + rsp, err := client.AlloyDbOmniGoogleCloudPrivateKeyIdentify(ctx, project, serviceName) + if err != nil { + return fmt.Errorf("cannot get alloydbomni service account credentials: %w", err) + } + if rsp.PrivateKeyId != "bar" { + return fmt.Errorf(`invalid private_key_id: expected "bar", got %q`, rsp.PrivateKeyId) + } + return nil + }, + ), + }, + { + // 4. Removes the credentials + Config: serviceAccountCredentialsEmpty, + Check: resource.ComposeTestCheckFunc( + // It looks like TF can't unset an attribute, when it was set. + // So I can't use TestCheckNoResourceAttr here. + resource.TestCheckResourceAttr(resourceName, "service_account_credentials", ""), + // Validates they key has been deleted + func(_ *terraform.State) error { + rsp, err := client.AlloyDbOmniGoogleCloudPrivateKeyIdentify(ctx, project, serviceName) + if err != nil { + return fmt.Errorf("cannot get alloydbomni service account credentials: %w", err) + } + if rsp.PrivateKeyId != "" { + return fmt.Errorf(`invalid private_key_id: expected empty, got %q`, rsp.PrivateKeyId) + } + return nil + }, + ), + }, + { + // 5. Re-applies the credential, so we can check unexpected remote state changes in the next step + Config: serviceAccountCredentialsValidModified, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "service_account_credentials"), + ), + }, + { + // 6. Same config. Modifies the remove state, expects non-empty plan + Config: serviceAccountCredentialsValidModified, + PlanOnly: true, + ExpectNonEmptyPlan: true, + PreConfig: func() { + // Modifies remote state to simulate a change + req := &alloydbomni.AlloyDbOmniGoogleCloudPrivateKeySetIn{ + PrivateKey: getTestServiceAccountCredentials("egg"), + } + _, err = client.AlloyDbOmniGoogleCloudPrivateKeySet(ctx, project, serviceName, req) + if err != nil { + t.Fatal(err) + } + }, + }, + }, + }) +} + +func getTestServiceAccountCredentials(privateKeyID string) string { + return fmt.Sprintf(`{ + "private_key_id": %q, + "private_key": "-----BEGIN PRIVATE KEY--.........----END PRIVATE KEY-----\n", + "client_email": "example@aiven.io", + "client_id": "example_user_id", + "type": "service_account", + "project_id": "example_project_id" + }`, privateKeyID) +} + +func testAccAivenServiceAlloyDBOmniServiceAccountCredentials(project, name, privateKey string) string { + var p string + if privateKey != "" { + p = fmt.Sprintf(` + service_account_credentials = <