From c496aea2a56f79dd75db9570d595385b4db96b2a Mon Sep 17 00:00:00 2001 From: Bobby Iliev Date: Wed, 10 Jan 2024 16:29:46 +0200 Subject: [PATCH] Cloud Resources Refactor (#351) * WIP: cloud resources Terraform Docs WIP: manage frontegg users [WIP]: Cloud Resources Terraform Docs Add tests for app password Terraform Docs Fix failing tests Add region datasource Add integration tests for user resource Minor changes Terraform Docs Minor changes Add token refresh func * Port the IDs state migraton * Fix failing tests * Update the index tmpl file * Terraform Docs * Add unit tests for the region datasource * Fix failing tests and add region data schema * Terraform Docs * Add more tests * Remove some comments * Minor improvements * Update token refresh func * Add link to SO for deep clone * Prepare the changelog --------- Co-authored-by: bobbyiliev --- .github/workflows/acceptance.yml | 9 +- CHANGELOG.md | 42 +++ CONTRIBUTING.md | 24 +- docker-compose.yml | 12 + docs/data-sources/cluster.md | 1 + docs/data-sources/cluster_replica.md | 1 + docs/data-sources/connection.md | 1 + docs/data-sources/current_cluster.md | 1 + docs/data-sources/current_database.md | 1 + docs/data-sources/database.md | 1 + docs/data-sources/egress_ips.md | 1 + docs/data-sources/index.md | 1 + docs/data-sources/materialized_view.md | 1 + docs/data-sources/region.md | 40 +++ docs/data-sources/role.md | 1 + docs/data-sources/schema.md | 1 + docs/data-sources/secret.md | 1 + docs/data-sources/sink.md | 1 + docs/data-sources/source.md | 1 + docs/data-sources/table.md | 1 + docs/data-sources/type.md | 1 + docs/data-sources/view.md | 1 + docs/developer/design-v2-2023-11-11.md | 199 ++++++++++++ docs/index.md | 15 +- docs/resources/app_password.md | 43 +++ docs/resources/cluster.md | 1 + docs/resources/cluster_grant.md | 4 + .../cluster_grant_default_privilege.md | 4 + docs/resources/cluster_replica | 1 + docs/resources/cluster_replica.md | 1 + docs/resources/connection_aws_privatelink.md | 1 + .../connection_confluent_schema_registry.md | 1 + docs/resources/connection_grant.md | 4 + .../connection_grant_default_privilege.md | 1 + docs/resources/connection_kafka.md | 1 + docs/resources/connection_postgres.md | 1 + docs/resources/connection_ssh_tunnel.md | 1 + docs/resources/database.md | 1 + docs/resources/database_grant.md | 4 + .../database_grant_default_privilege.md | 4 + docs/resources/grant_system_privilege.md | 4 + docs/resources/index.md | 1 + docs/resources/materialized_view.md | 1 + docs/resources/materialized_view_grant.md | 4 + docs/resources/role.md | 1 + docs/resources/role_grant.md | 4 + docs/resources/schema.md | 1 + docs/resources/schema_grant.md | 4 + .../schema_grant_default_privilege.md | 1 + docs/resources/secret.md | 1 + docs/resources/secret_grant.md | 4 + .../secret_grant_default_privilege.md | 1 + docs/resources/sink_kafka.md | 1 + docs/resources/source_grant.md | 4 + docs/resources/source_kafka.md | 1 + docs/resources/source_load_generator.md | 1 + docs/resources/source_postgres.md | 1 + docs/resources/source_webhook.md | 1 + docs/resources/table.md | 1 + docs/resources/table_grant.md | 4 + .../table_grant_default_privilege.md | 1 + docs/resources/type.md | 1 + docs/resources/type_grant.md | 4 + .../resources/type_grant_default_privilege.md | 1 + docs/resources/user.md | 44 +++ docs/resources/view.md | 1 + docs/resources/view_grant.md | 4 + .../materialize_region/data-source.tf | 5 + examples/provider/provider.tf | 7 +- .../materialize_app_password/import.sh | 2 + .../materialize_app_password/resource.tf | 3 + examples/resources/materialize_user/import.sh | 2 + .../resources/materialize_user/resource.tf | 4 + go.mod | 1 + go.sum | 2 + integration/app_password.tf | 4 + integration/main.tf | 11 +- integration/region.tf | 5 + integration/user.tf | 5 + mocks/cloud/Dockerfile | 30 ++ mocks/cloud/go.mod | 5 + mocks/cloud/go.sum | 2 + mocks/cloud/mock_server.go | 90 ++++++ mocks/frontegg/Dockerfile | 30 ++ mocks/frontegg/go.mod | 3 + mocks/frontegg/go.sum | 0 mocks/frontegg/mock_server.go | 298 +++++++++++++++++ pkg/clients/cloud_client.go | 164 ++++++++++ pkg/clients/cloud_client_test.go | 219 +++++++++++++ pkg/clients/db_client.go | 53 ++++ pkg/clients/db_client_test.go | 27 ++ pkg/clients/frontegg_client.go | 245 ++++++++++++++ pkg/clients/frontegg_client_test.go | 133 ++++++++ pkg/clients/helpers.go | 47 +++ pkg/clients/helpers_test.go | 44 +++ pkg/datasources/datasource_cluster.go | 10 +- pkg/datasources/datasource_cluster_replica.go | 10 +- .../datasource_cluster_replica_test.go | 4 +- pkg/datasources/datasource_cluster_test.go | 4 +- pkg/datasources/datasource_connection.go | 11 +- pkg/datasources/datasource_connection_test.go | 4 +- pkg/datasources/datasource_current_cluster.go | 10 +- .../datasource_current_cluster_test.go | 4 +- .../datasource_current_database.go | 10 +- .../datasource_current_database_test.go | 4 +- pkg/datasources/datasource_database.go | 11 +- pkg/datasources/datasource_database_test.go | 4 +- pkg/datasources/datasource_egress_ips.go | 10 +- pkg/datasources/datasource_egress_ips_test.go | 4 +- pkg/datasources/datasource_index.go | 11 +- pkg/datasources/datasource_index_test.go | 4 +- .../datasource_materialized_view.go | 11 +- .../datasource_materialized_view_test.go | 4 +- pkg/datasources/datasource_region.go | 99 ++++++ pkg/datasources/datasource_region_test.go | 64 ++++ pkg/datasources/datasource_role.go | 10 +- pkg/datasources/datasource_role_test.go | 4 +- pkg/datasources/datasource_schema.go | 12 +- pkg/datasources/datasource_schema_test.go | 4 +- pkg/datasources/datasource_secret.go | 11 +- pkg/datasources/datasource_secret_test.go | 4 +- pkg/datasources/datasource_sink.go | 11 +- pkg/datasources/datasource_sink_test.go | 4 +- pkg/datasources/datasource_source.go | 11 +- pkg/datasources/datasource_source_test.go | 4 +- pkg/datasources/datasource_table.go | 11 +- pkg/datasources/datasource_table_test.go | 4 +- pkg/datasources/datasource_type.go | 11 +- pkg/datasources/datasource_type_test.go | 4 +- pkg/datasources/datasource_view.go | 11 +- pkg/datasources/datasource_view_test.go | 4 +- pkg/datasources/utils.go | 12 +- pkg/materialize/cluster_replica.go | 2 +- .../acceptance_cluster_replica_test.go | 15 +- pkg/provider/acceptance_cluster_test.go | 15 +- ...nnection_confluent_schema_registry_test.go | 15 +- .../acceptance_connection_kafka_test.go | 15 +- .../acceptance_connection_postgres_test.go | 15 +- .../acceptance_connection_ssh_tunnel_test.go | 15 +- pkg/provider/acceptance_database_test.go | 15 +- .../acceptance_grant_system_privilege_test.go | 18 +- pkg/provider/acceptance_index_test.go | 23 +- .../acceptance_materialized_view_test.go | 15 +- pkg/provider/acceptance_role_grant_test.go | 16 +- pkg/provider/acceptance_role_test.go | 15 +- pkg/provider/acceptance_schema_test.go | 17 +- pkg/provider/acceptance_secret_test.go | 15 +- pkg/provider/acceptance_sink_kafka_test.go | 23 +- pkg/provider/acceptance_source_kafka_test.go | 15 +- .../acceptance_source_load_generator_test.go | 23 +- .../acceptance_source_postgres_test.go | 15 +- .../acceptance_source_webhook_test.go | 15 +- pkg/provider/acceptance_table_test.go | 15 +- pkg/provider/acceptance_type_test.go | 15 +- pkg/provider/acceptance_view_test.go | 15 +- pkg/provider/provider.go | 137 +++++--- pkg/provider/provider_test.go | 46 ++- pkg/resources/resource_app_password.go | 280 ++++++++++++++++ pkg/resources/resource_app_password_test.go | 95 ++++++ pkg/resources/resource_cluster.go | 43 ++- pkg/resources/resource_cluster_replica.go | 35 +- .../resource_cluster_replica_test.go | 9 +- pkg/resources/resource_cluster_test.go | 11 +- pkg/resources/resource_connection.go | 25 +- .../resource_connection_aws_privatelink.go | 34 +- ...esource_connection_aws_privatelink_test.go | 8 +- ...ce_connection_confluent_schema_registry.go | 16 +- ...nnection_confluent_schema_registry_test.go | 4 +- pkg/resources/resource_connection_kafka.go | 16 +- .../resource_connection_kafka_test.go | 4 +- pkg/resources/resource_connection_postgres.go | 16 +- .../resource_connection_postgres_test.go | 4 +- .../resource_connection_ssh_tunnel.go | 34 +- .../resource_connection_ssh_tunnel_test.go | 8 +- pkg/resources/resource_connection_test.go | 9 +- pkg/resources/resource_database.go | 42 ++- pkg/resources/resource_database_test.go | 8 +- pkg/resources/resource_grant.go | 13 +- pkg/resources/resource_grant_cluster.go | 22 +- ...esource_grant_cluster_default_privilege.go | 22 +- ...ce_grant_cluster_default_privilege_test.go | 7 +- pkg/resources/resource_grant_cluster_test.go | 7 +- pkg/resources/resource_grant_connection.go | 22 +- ...urce_grant_connection_default_privilege.go | 26 +- ...grant_connection_default_privilege_test.go | 7 +- .../resource_grant_connection_test.go | 7 +- pkg/resources/resource_grant_database.go | 22 +- ...source_grant_database_default_privilege.go | 22 +- ...e_grant_database_default_privilege_test.go | 7 +- pkg/resources/resource_grant_database_test.go | 9 +- .../resource_grant_default_privilege.go | 10 +- .../resource_grant_default_privilege_test.go | 5 +- .../resource_grant_materialized_view.go | 22 +- .../resource_grant_materialized_view_test.go | 7 +- pkg/resources/resource_grant_role.go | 31 +- pkg/resources/resource_grant_role_test.go | 11 +- pkg/resources/resource_grant_schema.go | 22 +- ...resource_grant_schema_default_privilege.go | 24 +- ...rce_grant_schema_default_privilege_test.go | 7 +- pkg/resources/resource_grant_schema_test.go | 7 +- pkg/resources/resource_grant_secret.go | 22 +- ...resource_grant_secret_default_privilege.go | 26 +- ...rce_grant_secret_default_privilege_test.go | 7 +- pkg/resources/resource_grant_secret_test.go | 7 +- pkg/resources/resource_grant_source.go | 22 +- pkg/resources/resource_grant_source_test.go | 7 +- .../resource_grant_system_privilege.go | 33 +- .../resource_grant_system_privilege_test.go | 11 +- pkg/resources/resource_grant_table.go | 22 +- .../resource_grant_table_default_privilege.go | 26 +- ...urce_grant_table_default_privilege_test.go | 7 +- pkg/resources/resource_grant_table_test.go | 7 +- pkg/resources/resource_grant_test.go | 5 +- pkg/resources/resource_grant_type.go | 22 +- .../resource_grant_type_default_privilege.go | 26 +- ...ource_grant_type_default_privilege_test.go | 7 +- pkg/resources/resource_grant_type_test.go | 7 +- pkg/resources/resource_grant_view.go | 22 +- pkg/resources/resource_grant_view_test.go | 8 +- pkg/resources/resource_index.go | 38 ++- pkg/resources/resource_index_test.go | 8 +- pkg/resources/resource_materialized_view.go | 40 ++- .../resource_materialized_view_test.go | 10 +- pkg/resources/resource_role.go | 38 ++- pkg/resources/resource_role_test.go | 8 +- pkg/resources/resource_schema.go | 38 ++- pkg/resources/resource_schema_test.go | 8 +- pkg/resources/resource_secret.go | 42 ++- pkg/resources/resource_secret_test.go | 11 +- pkg/resources/resource_sink.go | 27 +- pkg/resources/resource_sink_kafka.go | 16 +- pkg/resources/resource_sink_kafka_test.go | 4 +- pkg/resources/resource_sink_test.go | 8 +- pkg/resources/resource_source.go | 29 +- pkg/resources/resource_source_kafka.go | 16 +- pkg/resources/resource_source_kafka_test.go | 4 +- .../resource_source_load_generator.go | 16 +- .../resource_source_load_generator_test.go | 4 +- pkg/resources/resource_source_postgres.go | 28 +- .../resource_source_postgres_test.go | 8 +- pkg/resources/resource_source_test.go | 8 +- pkg/resources/resource_source_webhook.go | 16 +- pkg/resources/resource_source_webhook_test.go | 4 +- pkg/resources/resource_table.go | 46 ++- pkg/resources/resource_table_test.go | 9 +- pkg/resources/resource_type.go | 38 ++- pkg/resources/resource_type_test.go | 8 +- pkg/resources/resource_user.go | 300 ++++++++++++++++++ pkg/resources/resource_user_test.go | 65 ++++ pkg/resources/resource_view.go | 40 ++- pkg/resources/resource_view_test.go | 10 +- pkg/resources/schema.go | 9 + pkg/testhelpers/helpers.go | 202 +++++++++++- pkg/testhelpers/helpers_test.go | 129 ++++++++ pkg/utils/ids_migration_helper.go | 57 ---- pkg/utils/ids_migration_helper_test.go | 34 -- pkg/utils/provider_meta.go | 107 +++++++ pkg/utils/provider_meta_test.go | 85 +++++ templates/index.md.tmpl | 8 +- 259 files changed, 4951 insertions(+), 898 deletions(-) create mode 100644 docs/data-sources/region.md create mode 100644 docs/developer/design-v2-2023-11-11.md create mode 100644 docs/resources/app_password.md create mode 100644 docs/resources/user.md create mode 100644 examples/data-sources/materialize_region/data-source.tf create mode 100644 examples/resources/materialize_app_password/import.sh create mode 100644 examples/resources/materialize_app_password/resource.tf create mode 100644 examples/resources/materialize_user/import.sh create mode 100644 examples/resources/materialize_user/resource.tf create mode 100644 integration/app_password.tf create mode 100644 integration/region.tf create mode 100644 integration/user.tf create mode 100644 mocks/cloud/Dockerfile create mode 100644 mocks/cloud/go.mod create mode 100644 mocks/cloud/go.sum create mode 100644 mocks/cloud/mock_server.go create mode 100644 mocks/frontegg/Dockerfile create mode 100644 mocks/frontegg/go.mod create mode 100644 mocks/frontegg/go.sum create mode 100644 mocks/frontegg/mock_server.go create mode 100644 pkg/clients/cloud_client.go create mode 100644 pkg/clients/cloud_client_test.go create mode 100644 pkg/clients/db_client.go create mode 100644 pkg/clients/db_client_test.go create mode 100644 pkg/clients/frontegg_client.go create mode 100644 pkg/clients/frontegg_client_test.go create mode 100644 pkg/clients/helpers.go create mode 100644 pkg/clients/helpers_test.go create mode 100644 pkg/datasources/datasource_region.go create mode 100644 pkg/datasources/datasource_region_test.go create mode 100644 pkg/resources/resource_app_password.go create mode 100644 pkg/resources/resource_app_password_test.go create mode 100644 pkg/resources/resource_user.go create mode 100644 pkg/resources/resource_user_test.go create mode 100644 pkg/testhelpers/helpers_test.go delete mode 100644 pkg/utils/ids_migration_helper.go delete mode 100644 pkg/utils/ids_migration_helper_test.go create mode 100644 pkg/utils/provider_meta.go create mode 100644 pkg/utils/provider_meta_test.go diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 4420b046..f7d689f6 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -22,13 +22,16 @@ jobs: - name: Docker Compose Up run: docker compose up --build -d + - name: Configure hosts file + run: echo "127.0.0.1 materialized frontegg cloud" | sudo tee -a /etc/hosts + - name: make testacc run: make testacc env: - MZ_HOST: localhost - MZ_USER: mz_system + MZ_ENDPOINT: http://localhost:3000 + MZ_CLOUD_ENDPOINT: http://localhost:3001 + MZ_PASSWORD: mzp_1b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b MZ_SSLMODE: disable - MZ_PORT: 6877 - name: Docker Compose Down run: docker compose down diff --git a/CHANGELOG.md b/CHANGELOG.md index cff46346..f37eb78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,48 @@ ## Unreleased +### Features +* Introduced a unified interface for managing both global and regional resources. +* Implemented single authentication using an app password for all operations. +* Added dynamic client allocation for managing different resource types. +* Enhanced provider configuration with parameters for default settings and optional endpoint overrides. +* New resources: + * App passwords: `materialize_app_password`. + * User management `materialize_user`. +* Added data sources for fetching region details (`materialize_region`). +* Implemented support for establishing SQL connections across multiple regions. +* Introduced a new `region` parameter in all resource and data source configurations. This allows users to specify the region for resource creation and data retrieval. + +### Breaking Changes +* **Provider Configuration Changes**: + * Deprecated the `host`, `port`, and `user` parameters in the provider configuration. These details are now derived from the app password. + * Retained only the `password` definition in the provider configuration. This password is used to fetch all necessary connection information. +* **New `region` Configuration**: + * Introduced a new `default_region` parameter in the provider configuration. This allows users to specify the default region for resource creation. + * The `default_region` parameter can be overridden in specific resource configurations if a particular resource needs to be created in a non-default region. + + ```hcl + provider "materialize" { + password = var.materialize_app_password + default_region = "aws/us-east-1" + } + + resource "materialize_cluster" "cluster" { + name = "cluster" + region = "aws/us-west-2" + } + ``` + +### Misc +* Mock Services for Testing: + * Added a new mocks directory, which includes mock services for the Cloud API and the FrontEgg API. + * These mocks are intended for local testing and CI, facilitating development and testing without the need for a live backend. + +### Migration Guide +* Before upgrading to `v0.5.0`, users should ensure that they have upgraded to `v0.4.x` which introduced the Terraform state migration necessary for `v0.5.0`. After upgrading to `v0.4.x`, users should run `terraform plan` to ensure that the state migration has completed successfully. +* Users upgrading to `v0.5.0` should update their provider configurations to remove the `host`, `port`, and `user` parameters and ensure that the `password` parameter is set with the app password. +* For managing resources across multiple regions, users should specify the `default_region` parameter in their provider configuration or override it in specific resource blocks as needed using the `region` parameter. + ## 0.4.3 - 2024-01-08 ### Breaking Changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bc91067..fdd6a3b7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,15 +40,16 @@ make test To run the acceptance tests which will simulate running Terraform commands you will need to set the necessary envrionment variables and start the docker compose: ```bash -export MZ_HOST=localhost -export MZ_USER=mz_system -export MZ_SSLMODE=disable -export MZ_PORT=6877 - # Start all containers docker-compose up -d --build ``` +Add the following to your `hosts` file so that the provider can connect to the mock services: + +``` +127.0.0.1 materialized frontegg cloud +``` + You can then run the acceptance tests: ```bash @@ -125,6 +126,7 @@ var clusterSchema = map[string]*schema.Schema{ Description: "The size of the cluster.", Optional: true, }, + "region": RegionSchema(), } ``` @@ -147,8 +149,12 @@ If the resource can be updated we would also have to change the update context ` ```go if d.HasChange("size") { + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } _, newSize := d.GetChange("size") - b := materialize.NewClusterBuilder(meta.(*sqlx.DB), o) + b := materialize.NewClusterBuilder(metaDb, o) if err := b.Resize(newSize.(string)); err != nil { return diag.FromErr(err) } @@ -182,6 +188,10 @@ Schema: map[string]*schema.Schema{ }, }, }, + "region": { + Type: schema.TypeString, + Computed: true, + }, }, ``` @@ -201,7 +211,7 @@ for _, p := range dataSource { ## Cutting a release -To cut a new release of the provider, create a new tag and push that tag. This will trigger a Github Action to generate the artifacts necessary for the Terraform Registry. +To cut a new release of the provider, create a new tag and push that tag. This will trigger a GitHub Action to generate the artifacts necessary for the Terraform Registry. ```bash git tag -a vX.Y.Z -m vX.Y.Z diff --git a/docker-compose.yml b/docker-compose.yml index d058122c..00caf4e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,3 +87,15 @@ services: TF_LOG: INFO command: > sh -c "tail -F /dev/null" + + frontegg: + container_name: frontegg + build: mocks/frontegg + ports: + - "3000:3000" + + cloud: + container_name: cloud + build: mocks/cloud + ports: + - "3001:3001" diff --git a/docs/data-sources/cluster.md b/docs/data-sources/cluster.md index 2803827c..193a6d73 100644 --- a/docs/data-sources/cluster.md +++ b/docs/data-sources/cluster.md @@ -23,6 +23,7 @@ data "materialize_cluster" "all" {} - `clusters` (List of Object) The clusters in the account (see [below for nested schema](#nestedatt--clusters)) - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. ### Nested Schema for `clusters` diff --git a/docs/data-sources/cluster_replica.md b/docs/data-sources/cluster_replica.md index ee6f1972..937270f0 100644 --- a/docs/data-sources/cluster_replica.md +++ b/docs/data-sources/cluster_replica.md @@ -23,6 +23,7 @@ data "materialize_cluster_replica" "all" {} - `cluster_replicas` (List of Object) The cluster replicas in the account (see [below for nested schema](#nestedatt--cluster_replicas)) - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. ### Nested Schema for `cluster_replicas` diff --git a/docs/data-sources/connection.md b/docs/data-sources/connection.md index d56bd3f7..5c19d5d3 100644 --- a/docs/data-sources/connection.md +++ b/docs/data-sources/connection.md @@ -37,6 +37,7 @@ data "materialize_connection" "materialize_schema" { - `connections` (List of Object) The connections in the account (see [below for nested schema](#nestedatt--connections)) - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. ### Nested Schema for `connections` diff --git a/docs/data-sources/current_cluster.md b/docs/data-sources/current_cluster.md index d0222e30..1cfc57fb 100644 --- a/docs/data-sources/current_cluster.md +++ b/docs/data-sources/current_cluster.md @@ -27,3 +27,4 @@ output "cluster_name" { - `id` (String) The ID of this resource. - `name` (String) +- `region` (String) The region in which the resource is located. diff --git a/docs/data-sources/current_database.md b/docs/data-sources/current_database.md index 8da234be..1f91792b 100644 --- a/docs/data-sources/current_database.md +++ b/docs/data-sources/current_database.md @@ -27,3 +27,4 @@ output "database_name" { - `id` (String) The ID of this resource. - `name` (String) +- `region` (String) The region in which the resource is located. diff --git a/docs/data-sources/database.md b/docs/data-sources/database.md index 2b39cda2..d3e18a32 100644 --- a/docs/data-sources/database.md +++ b/docs/data-sources/database.md @@ -23,6 +23,7 @@ data "materialize_database" "all" {} - `databases` (List of Object) The databases in the account (see [below for nested schema](#nestedatt--databases)) - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. ### Nested Schema for `databases` diff --git a/docs/data-sources/egress_ips.md b/docs/data-sources/egress_ips.md index 697df62f..d0247fbd 100644 --- a/docs/data-sources/egress_ips.md +++ b/docs/data-sources/egress_ips.md @@ -27,3 +27,4 @@ output "ips" { - `egress_ips` (List of String) The egress IPs in the account - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. diff --git a/docs/data-sources/index.md b/docs/data-sources/index.md index cfe141cc..ae1041bf 100644 --- a/docs/data-sources/index.md +++ b/docs/data-sources/index.md @@ -37,6 +37,7 @@ data "materialize_index" "materialize_schema" { - `id` (String) The ID of this resource. - `indexes` (List of Object) The indexes in the account (see [below for nested schema](#nestedatt--indexes)) +- `region` (String) The region in which the resource is located. ### Nested Schema for `indexes` diff --git a/docs/data-sources/materialized_view.md b/docs/data-sources/materialized_view.md index b7b988b6..72a8357c 100644 --- a/docs/data-sources/materialized_view.md +++ b/docs/data-sources/materialized_view.md @@ -37,6 +37,7 @@ data "materialize_materialized_view" "materialize_schema" { - `id` (String) The ID of this resource. - `materialized_views` (List of Object) The materialized views in the account (see [below for nested schema](#nestedatt--materialized_views)) +- `region` (String) The region in which the resource is located. ### Nested Schema for `materialized_views` diff --git a/docs/data-sources/region.md b/docs/data-sources/region.md new file mode 100644 index 00000000..21a8a9da --- /dev/null +++ b/docs/data-sources/region.md @@ -0,0 +1,40 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "materialize_region Data Source - terraform-provider-materialize" +subcategory: "" +description: |- + +--- + +# materialize_region (Data Source) + + + +## Example Usage + +```terraform +data "materialize_region" "all" {} + +output "region" { + value = data.materialize_region.all +} +``` + + +## Schema + +### Read-Only + +- `id` (String) The ID of this resource. +- `regions` (List of Object) (see [below for nested schema](#nestedatt--regions)) + + +### Nested Schema for `regions` + +Read-Only: + +- `cloud_provider` (String) +- `host` (String) +- `id` (String) +- `name` (String) +- `url` (String) diff --git a/docs/data-sources/role.md b/docs/data-sources/role.md index 13f8d69f..93171c43 100644 --- a/docs/data-sources/role.md +++ b/docs/data-sources/role.md @@ -22,6 +22,7 @@ data "materialize_role" "all" {} ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `roles` (List of Object) The roles in the account (see [below for nested schema](#nestedatt--roles)) diff --git a/docs/data-sources/schema.md b/docs/data-sources/schema.md index 979c4552..20cfe09d 100644 --- a/docs/data-sources/schema.md +++ b/docs/data-sources/schema.md @@ -30,6 +30,7 @@ data "materialize_schema" "materialize" { ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `schemas` (List of Object) The schemas in the account (see [below for nested schema](#nestedatt--schemas)) diff --git a/docs/data-sources/secret.md b/docs/data-sources/secret.md index b3781e43..721d8d93 100644 --- a/docs/data-sources/secret.md +++ b/docs/data-sources/secret.md @@ -36,6 +36,7 @@ data "materialize_secret" "materialize_schema" { ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `secrets` (List of Object) The secrets in the account (see [below for nested schema](#nestedatt--secrets)) diff --git a/docs/data-sources/sink.md b/docs/data-sources/sink.md index 13afded1..183bf8f1 100644 --- a/docs/data-sources/sink.md +++ b/docs/data-sources/sink.md @@ -36,6 +36,7 @@ data "materialize_sink" "materialize_schema" { ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `sinks` (List of Object) The sinks in the account (see [below for nested schema](#nestedatt--sinks)) diff --git a/docs/data-sources/source.md b/docs/data-sources/source.md index 177ea98e..5fe1ea81 100644 --- a/docs/data-sources/source.md +++ b/docs/data-sources/source.md @@ -36,6 +36,7 @@ data "materialize_source" "materialize_schema" { ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `sources` (List of Object) The sources in the account (see [below for nested schema](#nestedatt--sources)) diff --git a/docs/data-sources/table.md b/docs/data-sources/table.md index 09da5ecc..0fc25c21 100644 --- a/docs/data-sources/table.md +++ b/docs/data-sources/table.md @@ -23,6 +23,7 @@ description: |- ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `tables` (List of Object) The tables in the account (see [below for nested schema](#nestedatt--tables)) diff --git a/docs/data-sources/type.md b/docs/data-sources/type.md index 1c907389..e33c03b9 100644 --- a/docs/data-sources/type.md +++ b/docs/data-sources/type.md @@ -23,6 +23,7 @@ description: |- ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `types` (List of Object) The types in the account (see [below for nested schema](#nestedatt--types)) diff --git a/docs/data-sources/view.md b/docs/data-sources/view.md index 50c08774..ebfce43f 100644 --- a/docs/data-sources/view.md +++ b/docs/data-sources/view.md @@ -36,6 +36,7 @@ data "materialize_view" "materialize_schema" { ### Read-Only - `id` (String) The ID of this resource. +- `region` (String) The region in which the resource is located. - `views` (List of Object) The views in the account (see [below for nested schema](#nestedatt--views)) diff --git a/docs/developer/design-v2-2023-11-11.md b/docs/developer/design-v2-2023-11-11.md new file mode 100644 index 00000000..58783e30 --- /dev/null +++ b/docs/developer/design-v2-2023-11-11.md @@ -0,0 +1,199 @@ +# Design Document for Materialize Terraform Provider + +The proposed updates to the Materialize Terraform provider aim to streamline the management of resources within Materialize, offering a unified interface for global and regional resources. The provider will leverage a single authentication method, an app password, to derive the necessary configurations for seamless interaction with various services. It will also incorporate a dynamic client allocation mechanism to handle different resource types and their requirements. + +## Provider Overview + +### Objectives + +- To provide a singular Terraform provider capable of interfacing with multiple backends and services. +- To facilitate the management of global resources and regional deployments through a consistent workflow. +- To enable a high degree of automation in resource provisioning and management, thereby reducing manual overhead and potential for errors. + +### Features + +- **Single Authentication**: Utilizing an app password to access and manage resources without the need for multiple credentials. +- **Global and Regional Resource Management**: Allowing users to manage resources that are not bound by regional constraints, as well as those that are region-specific. +- **Dynamic Client Allocation**: Depending on the resource's requirement, the appropriate client (DB, Frontegg, Cloud API and etc.) will be allocated dynamically. + +## Architecture + +### Provider Configuration + +The provider will be initialized with parameters that determine its behavior and default settings: + +```hcl +provider "materialize" { + app_password = var.materialize_app_password + default_region = var.materialize_default_region +} +``` + +Users will specify the app password and the default region for operations. Optionally, the endpoint can be overridden for testing or staging purposes by defining a `MZ_ENDPOINT` environment variable or "endpoint" parameter in the provider configuration block. + +The `MZ_ENDPOINT` environment variable will be hidden from the provider's documentation and will be used only for testing and development purposes. + +### Multi-Client Structure + +The provider will maintain a map of clients for database operations across different regions and a client for interfacing with the Frontegg API. + +```hcl +type ProviderMeta struct { + DB map[Region]*clients.DBClient + Frontegg *clients.FronteggClient + // Future: CloudAPI *clients.CloudAPIClient +} +``` + +This structure allows for scalability and extension of the provider to incorporate additional clients as necessary. + +## Resources and Operations + +### Region Resource + +Responsible for managing the availability of Materialize regions. It will interact with the Cloud API to enable or retrieve regions as requested by the user. + +```hcl +resource "materialize_region" "aws_us_east_1" { + region = "aws/us-east-1" +} +``` + +Operations: + +- **Enable Region**: Uses the Cloud API to enable a new region. +- **Read Region**: Retrieves the status of a region from the Cloud API. +- **Disable Region**: Noop. Regions cannot be disabled once enabled. + +### User Resource + +Manages the lifecycle of a user within the Frontegg platform. + +```hcl +resource "materialize_user" "example_user" { + email = "user@example.com" + auth_provider = "local" + organization_roles = ["admin", "member"] + # Additional attributes... +} +``` + +Operations: + +- **Create User**: Invokes the Frontegg API to create/invite a user. +- **Read User**: Retrieves user details from Frontegg. +- **Update User**: Attaches or detaches roles from the user. +- **Delete User**: Removes the user from Frontegg. + +### Organization Data Source? + +Gets the associated organization for the provided app password. + +```hcl +data "materialize_app_organization" "current" {} +``` + +Operations: + +- **List Organizations**: Fetches all available organizations from Frontegg. + +### Role Data Source + +Generates a list of available roles from Frontegg. + +```hcl +data "materialize_organization_roles" "all" {} +``` + +Operations: + +- **List Roles**: Fetches all available roles from Frontegg. + +## Testing Strategy + +Along the existing unit tests described in the [design document](../../DESIGN.md), the provider will include a comprehensive suite of tests for all resources and operations, including: + +- **Unit Tests**: Will run against a test Materialize environment or a mock service to ensure that the provider functions correctly in isolation. +- **Integration Tests**: Will run against staging instance to confirm that the provider interacts correctly with the actual services. +- **Acceptance Tests**: То be determined. + +## State Management + +The provider will ensure that the Terraform state reflects the true state of resources in Materialize Cloud and Frontegg, using Terraform's state management capabilities. + +## User Experience and Documentation + +Users should be able to easily upgrade to the new provider version without having to make significant changes to their existing configurations. + +The release should include clear documentation and examples to help users understand the new features and how to use them. + +This should be clearly communicated to users through release notes and other channels. + +Clear and detailed documentation will accompany the provider, explaining usage, resource configuration, and troubleshooting. + +### Edge Case Management + +Further exploration of how the provider will handle edge cases, such as delays in region enablement or Frontegg API failures, will be included in the design document. + +## Database Resource + +### Integration with Frontegg and Global API + +The Materialize Terraform provider will integrate with the Frontegg API to dynamically fetch the necessary credentials and regional endpoint information. + +This approach streamlines the process of establishing SQL connections across various Materialize regions using a single app password. + +### Resource Definitions + +#### Cluster in an Existing Region + +To create a cluster in a region that has been previously enabled, users can define a `materialize_cluster` resource without creating a dependency on the region's resource within Terraform. This is useful when operating in regions activated outside the scope of the current Terraform configuration. + +```hcl +resource "materialize_cluster" "eu_west_1_cluster" { + region = "aws/eu-west-1" + # Additional configuration options... +} +``` + +#### Cluster in the Default Provider Region + +For convenience, users can create clusters without specifying a region, in which case the provider uses the default region specified in the provider's configuration block. This simplifies resource definitions and maintains consistency across Terraform configurations. + +```hcl +resource "materialize_cluster" "default_region_cluster" { + # The provider will infer the region from the default settings. + # Additional configuration options... +} +``` + +#### Cluster in a Terraform-Enabled Region + +When a region is enabled within Terraform, resource definitions can reference the `materialize_region` resource. This ensures that Terraform manages dependencies correctly, creating the cluster only after the region is fully enabled. + +```hcl +resource "materialize_cluster" "us_east_1_cluster" { + region = materialize_region.aws_us_east_1.region + # Additional configuration options... +} +``` + +### Enhanced Workflow + +The provider will handle the following workflow: + +1. **Authentication**: Utilize the app password to retrieve the username associated with the provided token. +2. **Regional Endpoint Retrieval**: Query the global API to obtain the hostname and port for the specified region. +3. **SQL Connection Establishment**: Connect to the desired region's SQL interface using the retrieved credentials and endpoint information. +4. **Resource Management**: Facilitate the creation, updating, and deletion of resources within the connected region using the established SQL connection. + +### Error Handling and Region Validation + +The provider will have error handling to provide clear and actionable feedback when: + +- A specified region is not enabled or in the process of being disabled. +- There are network or authentication issues while establishing the SQL connection. + +### Simplifying Configuration + +By abstracting away the complexities of managing credentials and endpoints, the provider allows users to focus on the high-level definitions of their Materialize infrastructure, promoting a simplified and more efficient configuration process. diff --git a/docs/index.md b/docs/index.md index d77ded7c..23a6e8ce 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,21 +14,16 @@ Configure the provider by adding the following block to your Terraform project: ```terraform # Configuration-based authentication provider "materialize" { - host = var.materialize_host # optionally use MZ_HOST env var - user = var.materialize_user # optionally use MZ_USER env var - password = var.materialize_password # optionally use MZ_PASSWORD env var - port = var.materialize_port # optionally use MZ_PORT env var - database = var.materialize_database # optionally use MZ_DATABASE env var + password = var.materialize_password # optionally use MZ_PASSWORD env var + default_region = "aws/us-east-1" # optionally use MZ_REGION env var } ``` ## Schema -* `host` (String) Materialize host. Can also come from the `MZ_HOST` environment variable. -* `user` (String) Materialize user. Can also come from the `MZ_USER` environment variable. -* `password` (String, Sensitive) Materialize host. Can also come from the `MZ_PASSWORD` environment variable. -* `port` (Number) The Materialize port number to connect to at the server host. Can also come from the `MZ_PORT` environment variable. Defaults to 6875. -* `database` (String) The Materialize database. Can also come from the `MZ_DATABASE` environment variable. Defaults to `materialize`. +* `password` (String, Sensitive) Materialize App Password. Can also come from the `MZ_PASSWORD` environment variable. +* `database` (String, Optional) The Materialize database. Can also come from the `MZ_DATABASE` environment variable. Defaults to `materialize`. +* `default_region` (String, Optional) The Materialize AWS region. Can also come from the `MZ_DEFAULT_REGION` environment variable. Defaults to `aws/us-east-1`. ## Order precedence diff --git a/docs/resources/app_password.md b/docs/resources/app_password.md new file mode 100644 index 00000000..3c34b80d --- /dev/null +++ b/docs/resources/app_password.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "materialize_app_password Resource - terraform-provider-materialize" +subcategory: "" +description: |- + +--- + +# materialize_app_password (Resource) + + + +## Example Usage + +```terraform +resource "materialize_app_password" "example_app_password" { + name = "example_app_password_name" +} +``` + + +## Schema + +### Required + +- `name` (String) + +### Read-Only + +- `created_at` (String) +- `id` (String) The ID of this resource. +- `owner` (String) +- `password` (String, Sensitive) +- `secret` (String, Sensitive) + +## Import + +Import is supported using the following syntax: + +```shell +# App passwords can be imported using the app password id: +terraform import materialize_app_password.example_app_password +``` diff --git a/docs/resources/cluster.md b/docs/resources/cluster.md index c19662a2..7f09fa1f 100644 --- a/docs/resources/cluster.md +++ b/docs/resources/cluster.md @@ -33,6 +33,7 @@ resource "materialize_cluster" "example_cluster" { - `introspection_debugging` (Boolean) Whether to introspect the gathering of the introspection data. - `introspection_interval` (String) The interval at which to collect introspection data. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `replication_factor` (Number) The number of replicas of each dataflow-powered object to maintain. - `size` (String) The size of the managed cluster. diff --git a/docs/resources/cluster_grant.md b/docs/resources/cluster_grant.md index 4a85ecd2..30f3b1ef 100644 --- a/docs/resources/cluster_grant.md +++ b/docs/resources/cluster_grant.md @@ -30,6 +30,10 @@ resource "materialize_cluster_grant" "cluster_grant_usage" { - `privilege` (String) The privilege to grant to the object. - `role_name` (String) The name of the role to grant privilege to. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/cluster_grant_default_privilege.md b/docs/resources/cluster_grant_default_privilege.md index de988c2f..14852f60 100644 --- a/docs/resources/cluster_grant_default_privilege.md +++ b/docs/resources/cluster_grant_default_privilege.md @@ -30,6 +30,10 @@ resource "materialize_cluster_grant_default_privilege" "example" { - `privilege` (String) The privilege to grant to the object. - `target_role_name` (String) The default privilege will apply to objects created by this role. If this is left blank, then the current role is assumed. Use the `PUBLIC` pseudo-role to target objects created by all roles. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/cluster_replica b/docs/resources/cluster_replica index 23b759ef..8cbb5871 100644 --- a/docs/resources/cluster_replica +++ b/docs/resources/cluster_replica @@ -39,6 +39,7 @@ resource "materialize_cluster_replica" "example_cluster_replica" { - `idle_arrangement_merge_effort` (Number) The amount of effort to exert compacting arrangements during idle periods. This is an unstable option! It may be changed or removed at any time. - `introspection_debugging` (Boolean) Whether to introspect the gathering of the introspection data. - `introspection_interval` (String) The interval at which to collect introspection data. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ## Import diff --git a/docs/resources/cluster_replica.md b/docs/resources/cluster_replica.md index d0d35da8..7e45bf00 100644 --- a/docs/resources/cluster_replica.md +++ b/docs/resources/cluster_replica.md @@ -37,6 +37,7 @@ resource "materialize_cluster_replica" "example_cluster_replica" { - `idle_arrangement_merge_effort` (Number) The amount of effort to exert compacting arrangements during idle periods. This is an unstable option! It may be changed or removed at any time. - `introspection_debugging` (Boolean) Whether to introspect the gathering of the introspection data. - `introspection_interval` (String) The interval at which to collect introspection data. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ### Read-Only diff --git a/docs/resources/connection_aws_privatelink.md b/docs/resources/connection_aws_privatelink.md index 9cc8d165..5bde1296 100644 --- a/docs/resources/connection_aws_privatelink.md +++ b/docs/resources/connection_aws_privatelink.md @@ -43,6 +43,7 @@ resource "materialize_connection_aws_privatelink" "example_privatelink_connectio - `comment` (String) **Private Preview** Comment on an object in the database. - `database_name` (String) The identifier for the connection database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the connection schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/connection_confluent_schema_registry.md b/docs/resources/connection_confluent_schema_registry.md index b33949e4..28517511 100644 --- a/docs/resources/connection_confluent_schema_registry.md +++ b/docs/resources/connection_confluent_schema_registry.md @@ -49,6 +49,7 @@ resource "materialize_connection_confluent_schema_registry" "example_confluent_s - `database_name` (String) The identifier for the connection database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. - `password` (Block List, Max: 1) The password for the Confluent Schema Registry. (see [below for nested schema](#nestedblock--password)) +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the connection schema. Defaults to `public`. - `ssh_tunnel` (Block List, Max: 1) The SSH tunnel configuration for the Confluent Schema Registry. (see [below for nested schema](#nestedblock--ssh_tunnel)) - `ssl_certificate` (Block List, Max: 1) The client certificate for the Confluent Schema Registry.. Can be supplied as either free text using `text` or reference to a secret object using `secret`. (see [below for nested schema](#nestedblock--ssl_certificate)) diff --git a/docs/resources/connection_grant.md b/docs/resources/connection_grant.md index bf945f7e..c960b5a5 100644 --- a/docs/resources/connection_grant.md +++ b/docs/resources/connection_grant.md @@ -34,6 +34,10 @@ resource "materialize_connection_grant" "connection_grant_usage" { - `role_name` (String) The name of the role to grant privilege to. - `schema_name` (String) The schema that the connection being to. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/connection_grant_default_privilege.md b/docs/resources/connection_grant_default_privilege.md index 3d46adf1..bdf0b921 100644 --- a/docs/resources/connection_grant_default_privilege.md +++ b/docs/resources/connection_grant_default_privilege.md @@ -35,6 +35,7 @@ resource "materialize_connection_grant_default_privilege" "example" { ### Optional - `database_name` (String) The default privilege will apply only to objects created in this database, if specified. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The default privilege will apply only to objects created in this schema, if specified. ### Read-Only diff --git a/docs/resources/connection_kafka.md b/docs/resources/connection_kafka.md index 3443642d..c0aa39d5 100644 --- a/docs/resources/connection_kafka.md +++ b/docs/resources/connection_kafka.md @@ -85,6 +85,7 @@ resource "materialize_connection_kafka" "example_kafka_connection_multiple_broke - `database_name` (String) The identifier for the connection database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. - `progress_topic` (String) The name of a topic that Kafka sinks can use to track internal consistency metadata. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `sasl_mechanisms` (String) The SASL mechanism for the Kafka broker. - `sasl_password` (Block List, Max: 1) The SASL password for the Kafka broker. (see [below for nested schema](#nestedblock--sasl_password)) - `sasl_username` (Block List, Max: 1) The SASL username for the Kafka broker.. Can be supplied as either free text using `text` or reference to a secret object using `secret`. (see [below for nested schema](#nestedblock--sasl_username)) diff --git a/docs/resources/connection_postgres.md b/docs/resources/connection_postgres.md index ad8f0ed3..a33e5f6b 100644 --- a/docs/resources/connection_postgres.md +++ b/docs/resources/connection_postgres.md @@ -90,6 +90,7 @@ resource "materialize_connection_postgres" "example_postgres_connection" { - `ownership_role` (String) The owernship role of the object. - `password` (Block List, Max: 1) The Postgres database password. (see [below for nested schema](#nestedblock--password)) - `port` (Number) The Postgres database port. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the connection schema. Defaults to `public`. - `ssh_tunnel` (Block List, Max: 1) The SSH tunnel configuration for the Postgres database. (see [below for nested schema](#nestedblock--ssh_tunnel)) - `ssl_certificate` (Block List, Max: 1) The client certificate for the Postgres database.. Can be supplied as either free text using `text` or reference to a secret object using `secret`. (see [below for nested schema](#nestedblock--ssl_certificate)) diff --git a/docs/resources/connection_ssh_tunnel.md b/docs/resources/connection_ssh_tunnel.md index 959e81b9..38ec9942 100644 --- a/docs/resources/connection_ssh_tunnel.md +++ b/docs/resources/connection_ssh_tunnel.md @@ -44,6 +44,7 @@ resource "materialize_connection_ssh_tunnel" "example_ssh_connection" { - `comment` (String) **Private Preview** Comment on an object in the database. - `database_name` (String) The identifier for the connection database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the connection schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/database.md b/docs/resources/database.md index 520bdfbf..a1504399 100644 --- a/docs/resources/database.md +++ b/docs/resources/database.md @@ -29,6 +29,7 @@ resource "materialize_database" "example_database" { - `comment` (String) **Private Preview** Comment on an object in the database. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ### Read-Only diff --git a/docs/resources/database_grant.md b/docs/resources/database_grant.md index b6beb3f1..a10ed659 100644 --- a/docs/resources/database_grant.md +++ b/docs/resources/database_grant.md @@ -30,6 +30,10 @@ resource "materialize_database_grant" "database_grant_usage" { - `privilege` (String) The privilege to grant to the object. - `role_name` (String) The name of the role to grant privilege to. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/database_grant_default_privilege.md b/docs/resources/database_grant_default_privilege.md index 29a1814c..43d6d032 100644 --- a/docs/resources/database_grant_default_privilege.md +++ b/docs/resources/database_grant_default_privilege.md @@ -30,6 +30,10 @@ resource "materialize_database_grant_default_privilege" "example" { - `privilege` (String) The privilege to grant to the object. - `target_role_name` (String) The default privilege will apply to objects created by this role. If this is left blank, then the current role is assumed. Use the `PUBLIC` pseudo-role to target objects created by all roles. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/grant_system_privilege.md b/docs/resources/grant_system_privilege.md index f6ab0ea7..397920bd 100644 --- a/docs/resources/grant_system_privilege.md +++ b/docs/resources/grant_system_privilege.md @@ -28,6 +28,10 @@ resource "materialize_grant_system_privilege" "role_createdb" { - `privilege` (String) The system privilege to grant. - `role_name` (String) The name of the role to grant privilege to. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/index.md b/docs/resources/index.md index a2819a58..653772f9 100644 --- a/docs/resources/index.md +++ b/docs/resources/index.md @@ -46,6 +46,7 @@ resource "materialize_index" "loadgen_index" { - `default` (Boolean) Creates a default index using all inferred columns are used. - `method` (String) The name of the index method to use. - `name` (String) The identifier for the index. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ### Read-Only diff --git a/docs/resources/materialized_view.md b/docs/resources/materialized_view.md index 73139b93..a90f628b 100644 --- a/docs/resources/materialized_view.md +++ b/docs/resources/materialized_view.md @@ -52,6 +52,7 @@ resource "materialize_materialized_view" "simple_materialized_view" { - `database_name` (String) The identifier for the materialized view database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `not_null_assertion` (List of String) **Private Preview** A list of columns for which to create non-null assertions. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the materialized view schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/materialized_view_grant.md b/docs/resources/materialized_view_grant.md index 8ea1daf2..84e118a0 100644 --- a/docs/resources/materialized_view_grant.md +++ b/docs/resources/materialized_view_grant.md @@ -34,6 +34,10 @@ resource "materialize_materialized_view_grant" "materialized_view_grant_select" - `role_name` (String) The name of the role to grant privilege to. - `schema_name` (String) The schema that the materialized view being to. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/role.md b/docs/resources/role.md index b46d62dc..10e80913 100644 --- a/docs/resources/role.md +++ b/docs/resources/role.md @@ -28,6 +28,7 @@ resource "materialize_role" "example_role" { ### Optional - `comment` (String) **Private Preview** Comment on an object in the database. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ### Read-Only diff --git a/docs/resources/role_grant.md b/docs/resources/role_grant.md index 7996ddd9..61bfdbd8 100644 --- a/docs/resources/role_grant.md +++ b/docs/resources/role_grant.md @@ -28,6 +28,10 @@ resource "materialize_role_grant" "role_grant_user" { - `member_name` (String) The role name to add to role_name as a member. - `role_name` (String) The role name to add member_name as a member. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/schema.md b/docs/resources/schema.md index 7d0a8ef2..9c126913 100644 --- a/docs/resources/schema.md +++ b/docs/resources/schema.md @@ -31,6 +31,7 @@ resource "materialize_schema" "example_schema" { - `comment` (String) **Private Preview** Comment on an object in the database. - `database_name` (String) The identifier for the schema database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ### Read-Only diff --git a/docs/resources/schema_grant.md b/docs/resources/schema_grant.md index 5085f617..8f5b0c1c 100644 --- a/docs/resources/schema_grant.md +++ b/docs/resources/schema_grant.md @@ -32,6 +32,10 @@ resource "materialize_schema_grant" "schema_grant_usage" { - `role_name` (String) The name of the role to grant privilege to. - `schema_name` (String) The schema that is being granted on. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/schema_grant_default_privilege.md b/docs/resources/schema_grant_default_privilege.md index 01633e41..0a197366 100644 --- a/docs/resources/schema_grant_default_privilege.md +++ b/docs/resources/schema_grant_default_privilege.md @@ -34,6 +34,7 @@ resource "materialize_schema_grant_default_privilege" "example" { ### Optional - `database_name` (String) The default privilege will apply only to objects created in this database, if specified. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. ### Read-Only diff --git a/docs/resources/secret.md b/docs/resources/secret.md index 37584fb2..05b8543a 100644 --- a/docs/resources/secret.md +++ b/docs/resources/secret.md @@ -32,6 +32,7 @@ resource "materialize_secret" "example_secret" { - `comment` (String) **Private Preview** Comment on an object in the database. - `database_name` (String) The identifier for the secret database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the secret schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/secret_grant.md b/docs/resources/secret_grant.md index 69e39fb0..9ee9abbb 100644 --- a/docs/resources/secret_grant.md +++ b/docs/resources/secret_grant.md @@ -34,6 +34,10 @@ resource "materialize_secret_grant" "secret_grant_usage" { - `schema_name` (String) The schema that the secret being to. - `secret_name` (String) The secret that is being granted on. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/secret_grant_default_privilege.md b/docs/resources/secret_grant_default_privilege.md index d7153f32..a35cf79b 100644 --- a/docs/resources/secret_grant_default_privilege.md +++ b/docs/resources/secret_grant_default_privilege.md @@ -35,6 +35,7 @@ resource "materialize_secret_grant_default_privilege" "example" { ### Optional - `database_name` (String) The default privilege will apply only to objects created in this database, if specified. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The default privilege will apply only to objects created in this schema, if specified. ### Read-Only diff --git a/docs/resources/sink_kafka.md b/docs/resources/sink_kafka.md index 72c7f0fe..350d93b4 100644 --- a/docs/resources/sink_kafka.md +++ b/docs/resources/sink_kafka.md @@ -67,6 +67,7 @@ resource "materialize_sink_kafka" "example_sink_kafka" { - `key` (List of String) An optional list of columns to use for the Kafka key. If unspecified, the Kafka key is left unset. - `key_not_enforced` (Boolean) Disable Materialize's validation of the key's uniqueness. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the sink schema. Defaults to `public`. - `size` (String) The size of the sink. If not specified, the `cluster_name` option must be specified. - `snapshot` (Boolean) Whether to emit the consolidated results of the query before the sink was created at the start of the sink. diff --git a/docs/resources/source_grant.md b/docs/resources/source_grant.md index 250c8346..b34ec884 100644 --- a/docs/resources/source_grant.md +++ b/docs/resources/source_grant.md @@ -34,6 +34,10 @@ resource "materialize_source_grant" "source_grant_select" { - `schema_name` (String) The schema that the view being to. - `source_name` (String) The source that is being granted on. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/source_kafka.md b/docs/resources/source_kafka.md index 04e821f3..f6b1abac 100644 --- a/docs/resources/source_kafka.md +++ b/docs/resources/source_kafka.md @@ -72,6 +72,7 @@ resource "materialize_source_kafka" "example_source_kafka" { - `include_timestamp_alias` (String) Provide an alias for the timestamp column. - `key_format` (Block List, Max: 1) Set the key format explicitly. (see [below for nested schema](#nestedblock--key_format)) - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the source schema. Defaults to `public`. - `size` (String) The size of the source. If not specified, the `cluster_name` option must be specified. - `start_offset` (List of Number) Read partitions from the specified offset. diff --git a/docs/resources/source_load_generator.md b/docs/resources/source_load_generator.md index 68063895..6c41ec68 100644 --- a/docs/resources/source_load_generator.md +++ b/docs/resources/source_load_generator.md @@ -50,6 +50,7 @@ resource "materialize_source_load_generator" "example_source_load_generator" { - `expose_progress` (Block List, Max: 1) The name of the progress subsource for the source. If this is not specified, the subsource will be named `_progress`. (see [below for nested schema](#nestedblock--expose_progress)) - `marketing_options` (Block List, Max: 1) Marketing Options. (see [below for nested schema](#nestedblock--marketing_options)) - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the source schema. Defaults to `public`. - `size` (String) The size of the source. If not specified, the `cluster_name` option must be specified. - `tpch_options` (Block List, Max: 1) TPCH Options. (see [below for nested schema](#nestedblock--tpch_options)) diff --git a/docs/resources/source_postgres.md b/docs/resources/source_postgres.md index e9a4c75f..290bbff9 100644 --- a/docs/resources/source_postgres.md +++ b/docs/resources/source_postgres.md @@ -59,6 +59,7 @@ resource "materialize_source_postgres" "example_source_postgres" { - `database_name` (String) The identifier for the source database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `expose_progress` (Block List, Max: 1) The name of the progress subsource for the source. If this is not specified, the subsource will be named `_progress`. (see [below for nested schema](#nestedblock--expose_progress)) - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema` (List of String) Creates subsources for specific schemas. If neither table or schema is specified, will default to ALL TABLES - `schema_name` (String) The identifier for the source schema. Defaults to `public`. - `size` (String) The size of the source. If not specified, the `cluster_name` option must be specified. diff --git a/docs/resources/source_webhook.md b/docs/resources/source_webhook.md index 45933c77..2d681d94 100644 --- a/docs/resources/source_webhook.md +++ b/docs/resources/source_webhook.md @@ -67,6 +67,7 @@ resource "materialize_source_webhook" "example_webhook" { - `include_header` (Block List) Map a header value from a request into a column. (see [below for nested schema](#nestedblock--include_header)) - `include_headers` (Block List, Max: 1) Include headers in the webhook. (see [below for nested schema](#nestedblock--include_headers)) - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the source schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/table.md b/docs/resources/table.md index 8f2ab959..db702ead 100644 --- a/docs/resources/table.md +++ b/docs/resources/table.md @@ -48,6 +48,7 @@ resource "materialize_table" "simple_table" { - `comment` (String) **Private Preview** Comment on an object in the database. - `database_name` (String) The identifier for the table database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the table schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/table_grant.md b/docs/resources/table_grant.md index 35a39365..4d834aa2 100644 --- a/docs/resources/table_grant.md +++ b/docs/resources/table_grant.md @@ -35,6 +35,10 @@ resource "materialize_table_grant" "table_grant_usage" { - `schema_name` (String) The schema that the table being to. - `table_name` (String) The table that is being granted on. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/table_grant_default_privilege.md b/docs/resources/table_grant_default_privilege.md index cf43994d..1e4e4e5d 100644 --- a/docs/resources/table_grant_default_privilege.md +++ b/docs/resources/table_grant_default_privilege.md @@ -36,6 +36,7 @@ resource "materialize_table_grant_default_privilege" "example" { ### Optional - `database_name` (String) The default privilege will apply only to objects created in this database, if specified. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The default privilege will apply only to objects created in this schema, if specified. ### Read-Only diff --git a/docs/resources/type.md b/docs/resources/type.md index b12f116b..76b8c8bc 100644 --- a/docs/resources/type.md +++ b/docs/resources/type.md @@ -49,6 +49,7 @@ resource "materialize_type" "map_type" { - `list_properties` (Block List, Max: 1) List properties. (see [below for nested schema](#nestedblock--list_properties)) - `map_properties` (Block List, Max: 1) Map properties. (see [below for nested schema](#nestedblock--map_properties)) - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `row_properties` (Block List) Row properties. (see [below for nested schema](#nestedblock--row_properties)) - `schema_name` (String) The identifier for the type schema. Defaults to `public`. diff --git a/docs/resources/type_grant.md b/docs/resources/type_grant.md index 42083a5c..bf463577 100644 --- a/docs/resources/type_grant.md +++ b/docs/resources/type_grant.md @@ -34,6 +34,10 @@ resource "materialize_type_grant" "type_grant_usage" { - `schema_name` (String) The schema that the type being to. - `type_name` (String) The type that is being granted on. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/docs/resources/type_grant_default_privilege.md b/docs/resources/type_grant_default_privilege.md index c01a0d9b..c42c2064 100644 --- a/docs/resources/type_grant_default_privilege.md +++ b/docs/resources/type_grant_default_privilege.md @@ -35,6 +35,7 @@ resource "materialize_type_grant_default_privilege" "example" { ### Optional - `database_name` (String) The default privilege will apply only to objects created in this database, if specified. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The default privilege will apply only to objects created in this schema, if specified. ### Read-Only diff --git a/docs/resources/user.md b/docs/resources/user.md new file mode 100644 index 00000000..507d54ab --- /dev/null +++ b/docs/resources/user.md @@ -0,0 +1,44 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "materialize_user Resource - terraform-provider-materialize" +subcategory: "" +description: |- + +--- + +# materialize_user (Resource) + + + +## Example Usage + +```terraform +resource "materialize_user" "example_user" { + email = "example-user@example.com" + roles = ["Member", "Admin"] +} +``` + + +## Schema + +### Required + +- `email` (String) The email address of the user. This must be unique across all users in the organization. +- `roles` (List of String) The roles to assign to the user. Allowed values are 'Member' and 'Admin'. + +### Read-Only + +- `auth_provider` (String) The authentication provider for the user. +- `id` (String) The ID of this resource. +- `metadata` (String) +- `verified` (Boolean) + +## Import + +Import is supported using the following syntax: + +```shell +# Users can be imported using the user id: +terraform import materialize_user.example_user +``` diff --git a/docs/resources/view.md b/docs/resources/view.md index 514bc322..6b55d8ef 100644 --- a/docs/resources/view.md +++ b/docs/resources/view.md @@ -50,6 +50,7 @@ resource "materialize_view" "simple_view" { - `comment` (String) **Private Preview** Comment on an object in the database. - `database_name` (String) The identifier for the view database. Defaults to `MZ_DATABASE` environment variable if set or `materialize` if environment variable is not set. - `ownership_role` (String) The owernship role of the object. +- `region` (String) The region to use for the resource connection. If not set, the default region is used. - `schema_name` (String) The identifier for the view schema. Defaults to `public`. ### Read-Only diff --git a/docs/resources/view_grant.md b/docs/resources/view_grant.md index 002d2585..2f3615b1 100644 --- a/docs/resources/view_grant.md +++ b/docs/resources/view_grant.md @@ -34,6 +34,10 @@ resource "materialize_view_grant" "view_grant_select" { - `schema_name` (String) The schema that the view being to. - `view_name` (String) The view that is being granted on. +### Optional + +- `region` (String) The region to use for the resource connection. If not set, the default region is used. + ### Read-Only - `id` (String) The ID of this resource. diff --git a/examples/data-sources/materialize_region/data-source.tf b/examples/data-sources/materialize_region/data-source.tf new file mode 100644 index 00000000..e08c1375 --- /dev/null +++ b/examples/data-sources/materialize_region/data-source.tf @@ -0,0 +1,5 @@ +data "materialize_region" "all" {} + +output "region" { + value = data.materialize_region.all +} diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index e2d0e95b..d37ea23c 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,8 +1,5 @@ # Configuration-based authentication provider "materialize" { - host = var.materialize_host # optionally use MZ_HOST env var - user = var.materialize_user # optionally use MZ_USER env var - password = var.materialize_password # optionally use MZ_PASSWORD env var - port = var.materialize_port # optionally use MZ_PORT env var - database = var.materialize_database # optionally use MZ_DATABASE env var + password = var.materialize_password # optionally use MZ_PASSWORD env var + default_region = "aws/us-east-1" # optionally use MZ_REGION env var } diff --git a/examples/resources/materialize_app_password/import.sh b/examples/resources/materialize_app_password/import.sh new file mode 100644 index 00000000..62c873bb --- /dev/null +++ b/examples/resources/materialize_app_password/import.sh @@ -0,0 +1,2 @@ +# App passwords can be imported using the app password id: +terraform import materialize_app_password.example_app_password diff --git a/examples/resources/materialize_app_password/resource.tf b/examples/resources/materialize_app_password/resource.tf new file mode 100644 index 00000000..ca5893ed --- /dev/null +++ b/examples/resources/materialize_app_password/resource.tf @@ -0,0 +1,3 @@ +resource "materialize_app_password" "example_app_password" { + name = "example_app_password_name" +} diff --git a/examples/resources/materialize_user/import.sh b/examples/resources/materialize_user/import.sh new file mode 100644 index 00000000..e8a8ea6c --- /dev/null +++ b/examples/resources/materialize_user/import.sh @@ -0,0 +1,2 @@ +# Users can be imported using the user id: +terraform import materialize_user.example_user diff --git a/examples/resources/materialize_user/resource.tf b/examples/resources/materialize_user/resource.tf new file mode 100644 index 00000000..412721fe --- /dev/null +++ b/examples/resources/materialize_user/resource.tf @@ -0,0 +1,4 @@ +resource "materialize_user" "example_user" { + email = "example-user@example.com" + roles = ["Member", "Admin"] +} diff --git a/go.mod b/go.mod index a53390bd..67e4b19f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.20 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 + github.com/golang-jwt/jwt/v5 v5.1.0 github.com/hashicorp/terraform-plugin-docs v0.16.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.31.0 github.com/hashicorp/terraform-plugin-testing v1.5.1 diff --git a/go.sum b/go.sum index a46b88ae..cdbc1078 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= diff --git a/integration/app_password.tf b/integration/app_password.tf new file mode 100644 index 00000000..c91e04cf --- /dev/null +++ b/integration/app_password.tf @@ -0,0 +1,4 @@ +resource "materialize_app_password" "example_password" { + for_each = toset(["1", "2", "3", "4", "5"]) + name = "example_password_${each.key}" +} diff --git a/integration/main.tf b/integration/main.tf index 5fd18b5c..c31adabc 100644 --- a/integration/main.tf +++ b/integration/main.tf @@ -7,10 +7,9 @@ terraform { } provider "materialize" { - host = "materialized" - user = "mz_system" - password = "password" - port = 6877 - database = "materialize" - sslmode = "disable" + endpoint = "http://frontegg:3000" + cloud_endpoint = "http://cloud:3001" + password = "mzp_1b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b" + database = "materialize" + sslmode = "disable" } diff --git a/integration/region.tf b/integration/region.tf new file mode 100644 index 00000000..e08c1375 --- /dev/null +++ b/integration/region.tf @@ -0,0 +1,5 @@ +data "materialize_region" "all" {} + +output "region" { + value = data.materialize_region.all +} diff --git a/integration/user.tf b/integration/user.tf new file mode 100644 index 00000000..6fe069e3 --- /dev/null +++ b/integration/user.tf @@ -0,0 +1,5 @@ +resource "materialize_user" "example_user" { + for_each = toset(["1", "2", "3", "4", "5"]) + email = "example-user${each.key}@example.com" + roles = ["Member", "Admin"] +} diff --git a/mocks/cloud/Dockerfile b/mocks/cloud/Dockerfile new file mode 100644 index 00000000..1bda83d2 --- /dev/null +++ b/mocks/cloud/Dockerfile @@ -0,0 +1,30 @@ +# Start from the official Golang base image +FROM golang:1.20 as builder + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mockserver . + +# Start a new stage from scratch +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +# Copy the Pre-built binary file from the previous stage +COPY --from=builder /app/mockserver . + +# Command to run the executable +CMD ["./mockserver"] diff --git a/mocks/cloud/go.mod b/mocks/cloud/go.mod new file mode 100644 index 00000000..44e380d8 --- /dev/null +++ b/mocks/cloud/go.mod @@ -0,0 +1,5 @@ +module cloud-mockserver + +go 1.20 + +require github.com/golang-jwt/jwt/v5 v5.1.0 // indirect diff --git a/mocks/cloud/go.sum b/mocks/cloud/go.sum new file mode 100644 index 00000000..45e080f9 --- /dev/null +++ b/mocks/cloud/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= diff --git a/mocks/cloud/mock_server.go b/mocks/cloud/mock_server.go new file mode 100644 index 00000000..16fe549f --- /dev/null +++ b/mocks/cloud/mock_server.go @@ -0,0 +1,90 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" +) + +type Region struct { + ID string `json:"id"` + Name string `json:"name"` + CloudProvider string `json:"cloudProvider"` + URL string `json:"url"` + RegionInfo *RegionInfo `json:"regionInfo,omitempty"` +} + +type RegionInfo struct { + SqlAddress string `json:"sqlAddress"` + HttpAddress string `json:"httpAddress"` + Resolvable bool `json:"resolvable"` + EnabledAt string `json:"enabledAt"` +} + +type CloudRegion struct { + RegionInfo *RegionInfo `json:"regionInfo"` +} + +type CloudProviderResponse struct { + Data []Region `json:"data"` + NextCursor string `json:"nextCursor,omitempty"` +} + +// Mock data +var regions = []Region{ + { + ID: "aws/us-east-1", + Name: "us-east-1", + CloudProvider: "aws", + URL: "http://cloud:3001", + RegionInfo: &RegionInfo{ + SqlAddress: "materialized:6877", + HttpAddress: "materialized:6875", + Resolvable: true, + EnabledAt: "2023-01-01T00:00:00Z", + }, + }, + // Add more mock regions if needed later +} + +func main() { + http.HandleFunc("/api/region", regionHandler) + http.HandleFunc("/api/cloud-regions", cloudRegionsHandler) + + fmt.Println("Mock Cloud API server is running at http://localhost:3001") + log.Fatal(http.ListenAndServe(":3001", nil)) +} + +func regionHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + mockRegion := CloudRegion{ + RegionInfo: &RegionInfo{ + SqlAddress: "materialized:6877", + HttpAddress: "materialized:6875", + Resolvable: true, + EnabledAt: "2023-01-01T00:00:00Z", + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(mockRegion) + case http.MethodPatch: + w.WriteHeader(http.StatusOK) + case http.MethodDelete: + w.WriteHeader(http.StatusAccepted) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func cloudRegionsHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + response := CloudProviderResponse{ + Data: regions, + } + json.NewEncoder(w).Encode(response) +} diff --git a/mocks/frontegg/Dockerfile b/mocks/frontegg/Dockerfile new file mode 100644 index 00000000..1bda83d2 --- /dev/null +++ b/mocks/frontegg/Dockerfile @@ -0,0 +1,30 @@ +# Start from the official Golang base image +FROM golang:1.20 as builder + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mockserver . + +# Start a new stage from scratch +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +# Copy the Pre-built binary file from the previous stage +COPY --from=builder /app/mockserver . + +# Command to run the executable +CMD ["./mockserver"] diff --git a/mocks/frontegg/go.mod b/mocks/frontegg/go.mod new file mode 100644 index 00000000..16aec118 --- /dev/null +++ b/mocks/frontegg/go.mod @@ -0,0 +1,3 @@ +module frontegg-mockserver + +go 1.20 diff --git a/mocks/frontegg/go.sum b/mocks/frontegg/go.sum new file mode 100644 index 00000000..e69de29b diff --git a/mocks/frontegg/mock_server.go b/mocks/frontegg/mock_server.go new file mode 100644 index 00000000..c47d76ae --- /dev/null +++ b/mocks/frontegg/mock_server.go @@ -0,0 +1,298 @@ +package main + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strings" + "sync" + "time" +) + +type AppPassword struct { + ClientID string `json:"clientId"` + Secret string `json:"secret"` + Description string `json:"description"` + Owner string `json:"owner"` + CreatedAt time.Time `json:"created_at"` +} + +type User struct { + ID string `json:"id"` + Email string `json:"email"` + ProfilePictureURL string `json:"profilePictureUrl"` + Verified bool `json:"verified"` + Metadata string `json:"metadata"` +} + +type FronteggRole struct { + ID string `json:"id"` + Name string `json:"name"` +} + +type FronteggRolesResponse struct { + Items []FronteggRole `json:"items"` + Metadata struct { + TotalItems int `json:"totalItems"` + TotalPages int `json:"totalPages"` + } `json:"_metadata"` +} + +var ( + appPasswords = make(map[string]AppPassword) + users = make(map[string]User) + mutex = &sync.Mutex{} +) + +func main() { + http.HandleFunc("/identity/resources/auth/v1/api-token", handleTokenRequest) + http.HandleFunc("/identity/resources/users/api-tokens/v1", handleAppPasswords) + http.HandleFunc("/identity/resources/users/v1/", handleUserRequest) + http.HandleFunc("/identity/resources/users/v2", handleUserRequest) + http.HandleFunc("/identity/resources/roles/v2", handleRolesRequest) + + fmt.Println("Mock Frontegg server is running at http://localhost:3000") + log.Fatal(http.ListenAndServe(":3000", nil)) +} + +func handleUserRequest(w http.ResponseWriter, r *http.Request) { + logRequest(r) + switch r.Method { + case http.MethodGet: + getUser(w, r) + case http.MethodDelete: + deleteUser(w, r) + case http.MethodPost: + createUser(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func getUser(w http.ResponseWriter, r *http.Request) { + userID := strings.TrimPrefix(r.URL.Path, "/identity/resources/users/v1/") + if userID == "" { + http.Error(w, "User ID is required", http.StatusBadRequest) + return + } + + user, ok := users[userID] + if !ok { + http.Error(w, "User not found", http.StatusNotFound) + return + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(user) +} + +func createUser(w http.ResponseWriter, r *http.Request) { + var newUser User + if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + userID := generateUserID() + newUser.ID = userID + + // Store the new user + mutex.Lock() + users[userID] = newUser + mutex.Unlock() + + w.WriteHeader(http.StatusCreated) + // Return the created user + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(newUser) +} + +func generateUserID() string { + return fmt.Sprintf("user-%d", time.Now().UnixNano()) +} + +func deleteUser(w http.ResponseWriter, r *http.Request) { + userID := strings.TrimPrefix(r.URL.Path, "/identity/resources/users/v1/") + if userID == "" { + http.Error(w, "User ID is required", http.StatusBadRequest) + return + } + + _, ok := users[userID] + if !ok { + http.Error(w, "User not found", http.StatusNotFound) + return + } + + delete(users, userID) + w.WriteHeader(http.StatusOK) +} + +func handleTokenRequest(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var payload struct { + ClientId string `json:"clientId"` + Secret string `json:"secret"` + } + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + if payload.ClientId == "1b2a3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" && payload.Secret == "7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b" { + mockToken := createMockJWTToken() + response := map[string]string{ + "accessToken": mockToken, + "email": "mz_system", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + } else { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + } +} + +func createMockJWTToken() string { + header := base64UrlEncode([]byte(`{"alg":"HS256","typ":"JWT"}`)) + payload := base64UrlEncode([]byte(`{"email":"mz_system","exp":1700000000}`)) + signature := base64UrlEncode([]byte(`signature`)) + return fmt.Sprintf("%s.%s.%s", header, payload, signature) +} + +func base64UrlEncode(input []byte) string { + encoded := base64.StdEncoding.EncodeToString(input) + encoded = strings.ReplaceAll(encoded, "+", "-") + encoded = strings.ReplaceAll(encoded, "/", "_") + encoded = strings.TrimRight(encoded, "=") + return encoded +} + +func handleAppPasswords(w http.ResponseWriter, r *http.Request) { + logRequest(r) + switch r.Method { + case http.MethodPost: + createAppPassword(w, r) + case http.MethodGet: + listAppPasswords(w, r) + case http.MethodDelete: + deleteAppPassword(w, r) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func handleRolesRequest(w http.ResponseWriter, r *http.Request) { + logRequest(r) + roles := []FronteggRole{ + {ID: "1", Name: "Organization Admin"}, + {ID: "2", Name: "Organization Member"}, + } + + response := FronteggRolesResponse{ + Items: roles, + Metadata: struct { + TotalItems int `json:"totalItems"` + TotalPages int `json:"totalPages"` + }{ + TotalItems: len(roles), + TotalPages: 1, + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) +} + +func createAppPassword(w http.ResponseWriter, r *http.Request) { + logRequest(r) + var req struct { + Description string `json:"description"` + } + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + // Generate a new app password + newAppPassword := AppPassword{ + ClientID: generateClientID(), + Secret: generateSecret(), + Description: req.Description, + Owner: "mockOwner", + CreatedAt: time.Now(), + } + + // Store the new app password + mutex.Lock() + appPasswords[newAppPassword.ClientID] = newAppPassword + mutex.Unlock() + + // Send the response back + sendResponse(w, http.StatusCreated, newAppPassword) +} + +func listAppPasswords(w http.ResponseWriter, r *http.Request) { + mutex.Lock() + passwords := make([]AppPassword, 0, len(appPasswords)) + for _, password := range appPasswords { + passwords = append(passwords, password) + } + mutex.Unlock() + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(passwords) +} + +func deleteAppPassword(w http.ResponseWriter, r *http.Request) { + clientID := r.URL.Query().Get("clientId") + if clientID == "" { + http.Error(w, "Client ID is required", http.StatusBadRequest) + return + } + + mutex.Lock() + delete(appPasswords, clientID) + mutex.Unlock() + + w.WriteHeader(http.StatusOK) +} + +// generateClientID generates a unique client ID. +func generateClientID() string { + return fmt.Sprintf("client-%d", time.Now().UnixNano()) +} + +// generateSecret generates a secret. +func generateSecret() string { + return fmt.Sprintf("secret-%d", time.Now().UnixNano()) +} + +func sendResponse(w http.ResponseWriter, statusCode int, payload interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + if payload != nil { + responseBytes, _ := json.Marshal(payload) + fmt.Printf("Response body: %s\n", string(responseBytes)) + w.Write(responseBytes) + } +} + +func logRequest(r *http.Request) { + fmt.Printf("Received request: %s %s\n", r.Method, r.URL.Path) + if r.Body != nil { + bodyBytes, err := io.ReadAll(r.Body) + if err == nil { + fmt.Printf("Request body: %s\n", string(bodyBytes)) + // Important: Restore the body for further reading + r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) + } + } +} diff --git a/pkg/clients/cloud_client.go b/pkg/clients/cloud_client.go new file mode 100644 index 00000000..68fd22f1 --- /dev/null +++ b/pkg/clients/cloud_client.go @@ -0,0 +1,164 @@ +package clients + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strconv" + "strings" +) + +// RegionInfo holds the detailed information about a region from the Cloud API +type RegionInfo struct { + SqlAddress string `json:"sqlAddress"` + HttpAddress string `json:"httpAddress"` + Resolvable bool `json:"resolvable"` + EnabledAt string `json:"enabledAt"` +} + +// Region holds the connection details for an active region +type CloudRegion struct { + RegionInfo *RegionInfo `json:"regionInfo"` +} + +// CloudProvider contains the information about a cloud provider and its region +type CloudProvider struct { + ID string `json:"id"` + Name string `json:"name"` + Url string `json:"url"` + CloudProvider string `json:"cloudProvider"` +} + +// CloudProviderResponse represents the response for listing cloud providers +type CloudProviderResponse struct { + Data []CloudProvider `json:"data"` + NextCursor string `json:"nextCursor,omitempty"` +} + +// CloudAPIClient is a client for interacting with the Materialize Cloud API +type CloudAPIClient struct { + HTTPClient *http.Client + FronteggClient *FronteggClient + Endpoint string +} + +// NewCloudAPIClient creates a new Cloud API client +func NewCloudAPIClient(fronteggClient *FronteggClient, cloudAPIEndpoint string) *CloudAPIClient { + return &CloudAPIClient{ + HTTPClient: &http.Client{}, + FronteggClient: fronteggClient, + Endpoint: cloudAPIEndpoint, + } +} + +// ListCloudProviders fetches the list of cloud providers and their regions +func (c *CloudAPIClient) ListCloudProviders(ctx context.Context) ([]CloudProvider, error) { + providersEndpoint := fmt.Sprintf("%s/api/cloud-regions", c.Endpoint) + + // Reuse the FronteggClient's HTTPClient which already includes the Authorization token. + resp, err := c.FronteggClient.HTTPClient.Get(providersEndpoint) + if err != nil { + return nil, fmt.Errorf("error listing cloud providers: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + return nil, fmt.Errorf("cloud API returned non-200 status code: %d, body: %s", resp.StatusCode, string(body)) + } + + var response CloudProviderResponse + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, err + } + + log.Printf("[DEBUG] Cloud providers response body: %+v\n", response) + + return response.Data, nil +} + +// GetRegionDetails fetches the details for a given region +func (c *CloudAPIClient) GetRegionDetails(ctx context.Context, provider CloudProvider) (*CloudRegion, error) { + regionEndpoint := fmt.Sprintf("%s/api/region", provider.Url) + + resp, err := c.FronteggClient.HTTPClient.Get(regionEndpoint) + if err != nil { + return nil, fmt.Errorf("error retrieving region details: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %v", err) + } + return nil, fmt.Errorf("cloud API returned non-200 status code: %d, body: %s", resp.StatusCode, string(body)) + } + + log.Printf("[DEBUG] Region details response body: %+v\n", resp.Body) + + var region CloudRegion + if err := json.NewDecoder(resp.Body).Decode(®ion); err != nil { + return nil, err + } + + log.Printf("[DEBUG] Region details response body: %+v\n", region) + + return ®ion, nil +} + +// GetHost retrieves the SQL address for a specified region +func (c *CloudAPIClient) GetHost(ctx context.Context, regionID string) (string, error) { + providers, err := c.ListCloudProviders(ctx) + if err != nil { + return "", err + } + + var provider *CloudProvider + for _, p := range providers { + if p.ID == regionID { + provider = &p + break + } + } + + if provider == nil { + return "", fmt.Errorf("provider for region '%s' not found", regionID) + } + + region, err := c.GetRegionDetails(ctx, *provider) + if err != nil { + return "", err + } + + if region.RegionInfo == nil || !region.RegionInfo.Resolvable { + return "", fmt.Errorf("region '%s' is not enabled", regionID) + } + + return region.RegionInfo.SqlAddress, nil +} + +func SplitHostPort(hostPortStr string) (host string, port int, err error) { + parts := strings.Split(hostPortStr, ":") + switch len(parts) { + case 1: + // Only host is provided, return the default port + return parts[0], 6875, nil + case 2: + // Both host and port are provided, return both + port, err := strconv.Atoi(parts[1]) + if err != nil { + return "", 0, fmt.Errorf("invalid port: %v", err) + } + return parts[0], port, nil + default: + // Invalid format + return "", 0, fmt.Errorf("invalid host:port format") + } +} diff --git a/pkg/clients/cloud_client_test.go b/pkg/clients/cloud_client_test.go new file mode 100644 index 00000000..adf8d14f --- /dev/null +++ b/pkg/clients/cloud_client_test.go @@ -0,0 +1,219 @@ +package clients + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +type MockFronteggService struct { + MockResponseStatus int +} + +func (m *MockFronteggService) RoundTrip(req *http.Request) (*http.Response, error) { + // Check the requested URL and return a response accordingly + if strings.HasSuffix(req.URL.Path, "/api/cloud-regions") { + // Mock response data + data := CloudProviderResponse{ + Data: []CloudProvider{ + {ID: "aws/us-east-1", Name: "us-east-1", Url: "http://mockendpoint", CloudProvider: "aws"}, + {ID: "aws/eu-west-1", Name: "eu-west-1", Url: "http://mockendpoint", CloudProvider: "aws"}, + }, + } + + // Convert response data to JSON + respData, _ := json.Marshal(data) + + // Create a new HTTP response with the JSON data and the specified status code + return &http.Response{ + StatusCode: m.MockResponseStatus, + Body: io.NopCloser(bytes.NewReader(respData)), + Header: make(http.Header), + }, nil + } else if strings.HasSuffix(req.URL.Path, "/api/region") { + // Return mock response for GetRegionDetails + details := CloudRegion{ + RegionInfo: &RegionInfo{ + SqlAddress: "sql.materialize.com", + HttpAddress: "http.materialize.com", + Resolvable: true, + EnabledAt: "2021-01-01T00:00:00Z", + }, + } + respData, _ := json.Marshal(details) + return &http.Response{ + StatusCode: m.MockResponseStatus, + Body: io.NopCloser(bytes.NewReader(respData)), + Header: make(http.Header), + }, nil + } + return nil, fmt.Errorf("no mock available for the requested endpoint") +} + +func TestCloudAPIClient_ListCloudProviders(t *testing.T) { + mockService := &MockFronteggService{ + MockResponseStatus: http.StatusOK, + } + mockClient := &http.Client{Transport: mockService} + apiClient := &CloudAPIClient{ + FronteggClient: &FronteggClient{HTTPClient: mockClient}, + Endpoint: "http://mockendpoint.com", + } + + // Call the method to test + providers, err := apiClient.ListCloudProviders(context.Background()) + if err != nil { + t.Fatalf("ListCloudProviders() error: %v", err) + } + + // Verify the results + wantProviderCount := 2 + if len(providers) != wantProviderCount { + t.Errorf("ListCloudProviders() got %v providers, want %v", len(providers), wantProviderCount) + } +} + +func TestCloudAPIClient_GetRegionDetails(t *testing.T) { + mockService := &MockFronteggService{ + MockResponseStatus: http.StatusOK, + } + mockClient := &http.Client{Transport: mockService} + apiClient := &CloudAPIClient{ + FronteggClient: &FronteggClient{HTTPClient: mockClient}, + Endpoint: "http://mockendpoint.com", + } + + provider := CloudProvider{ + ID: "aws/us-east-1", + Name: "us-east-1", + Url: "http://mockendpoint.com/api/region", + } + + // Call the method to test + region, err := apiClient.GetRegionDetails(context.Background(), provider) + if err != nil { + t.Fatalf("GetRegionDetails() error: %v", err) + } + + // Verify the results + wantSqlAddress := "sql.materialize.com" + if region.RegionInfo.SqlAddress != wantSqlAddress { + t.Errorf("GetRegionDetails() got SqlAddress = %v, want %v", region.RegionInfo.SqlAddress, wantSqlAddress) + } +} + +func TestCloudAPIClient_GetHost(t *testing.T) { + mockService := &MockFronteggService{ + MockResponseStatus: http.StatusOK, + } + mockClient := &http.Client{Transport: mockService} + apiClient := &CloudAPIClient{ + FronteggClient: &FronteggClient{HTTPClient: mockClient}, + Endpoint: "http://mockendpoint.com", + } + + regionID := "aws/us-east-1" + + sqlAddress, err := apiClient.GetHost(context.Background(), regionID) + if err != nil { + t.Fatalf("GetHost() error: %v", err) + } + + // Verify the results + wantSqlAddress := "sql.materialize.com" + if sqlAddress != wantSqlAddress { + t.Errorf("GetHost() got SqlAddress = %v, want %v", sqlAddress, wantSqlAddress) + } +} + +func TestCloudAPIClient_ListCloudProviders_ErrorResponse(t *testing.T) { + mockService := &MockFronteggService{ + // Mock the HTTP response to return an error status code: + MockResponseStatus: http.StatusInternalServerError, + } + mockClient := &http.Client{Transport: mockService} + apiClient := &CloudAPIClient{ + FronteggClient: &FronteggClient{HTTPClient: mockClient}, + Endpoint: "http://mockendpoint.com", + } + + // Call the method to test + _, err := apiClient.ListCloudProviders(context.Background()) + + // Verify that an error is returned when the server responds with an error status code + require.Error(t, err) +} + +func TestCloudAPIClient_GetRegionDetails_ErrorResponse(t *testing.T) { + mockService := &MockFronteggService{ + // Mock the HTTP response to return an error status code + MockResponseStatus: http.StatusInternalServerError, + } + mockClient := &http.Client{Transport: mockService} + apiClient := &CloudAPIClient{ + FronteggClient: &FronteggClient{HTTPClient: mockClient}, + Endpoint: "http://mockendpoint.com", + } + provider := CloudProvider{ + ID: "aws/us-east-1", + Name: "us-east-1", + Url: "http://mockendpoint.com/api/region", + } + + // Call the method to test + _, err := apiClient.GetRegionDetails(context.Background(), provider) + + // Verify that an error is returned when the server responds with an error status code + require.Error(t, err) +} + +func TestCloudAPIClient_GetHost_RegionNotFound(t *testing.T) { + mockService := &MockFronteggService{ + MockResponseStatus: http.StatusOK, + } + mockClient := &http.Client{Transport: mockService} + apiClient := &CloudAPIClient{ + FronteggClient: &FronteggClient{HTTPClient: mockClient}, + Endpoint: "http://mockendpoint.com", + } + regionID := "non-existent-region" + + // Call the method to test + _, err := apiClient.GetHost(context.Background(), regionID) + + // Verify that an error is returned when the region is not found + require.Error(t, err) + require.Contains(t, err.Error(), "provider for region 'non-existent-region' not found") +} + +func TestNewCloudAPIClient(t *testing.T) { + // Create a FronteggClient instance for testing + fronteggClient := &FronteggClient{} + + // Call the NewCloudAPIClient function with a custom API endpoint + customEndpoint := "http://custom-endpoint.com/api" + cloudAPIClient := NewCloudAPIClient(fronteggClient, customEndpoint) + + // Assert that the returned CloudAPIClient has the expected properties + require.NotNil(t, cloudAPIClient) + require.Equal(t, fronteggClient, cloudAPIClient.FronteggClient) + require.NotNil(t, cloudAPIClient.HTTPClient) + require.Equal(t, customEndpoint, cloudAPIClient.Endpoint) + + // Call the NewCloudAPIClient function with a different custom API endpoint + anotherCustomEndpoint := "http://another-custom-endpoint.com/api" + cloudAPIClient = NewCloudAPIClient(fronteggClient, anotherCustomEndpoint) + + // Assert that the returned CloudAPIClient has the updated custom endpoint + require.NotNil(t, cloudAPIClient) + require.Equal(t, fronteggClient, cloudAPIClient.FronteggClient) + require.NotNil(t, cloudAPIClient.HTTPClient) + require.Equal(t, anotherCustomEndpoint, cloudAPIClient.Endpoint) +} diff --git a/pkg/clients/db_client.go b/pkg/clients/db_client.go new file mode 100644 index 00000000..132543fd --- /dev/null +++ b/pkg/clients/db_client.go @@ -0,0 +1,53 @@ +package clients + +import ( + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/jmoiron/sqlx" +) + +type DBClient struct { + *sqlx.DB +} + +func NewDBClient(host, user, password string, port int, database, application_name_suffix, version, sslmode string) (*DBClient, diag.Diagnostics) { + var diags diag.Diagnostics + + application_name := fmt.Sprintf("terraform-provider-materialize v%s", version) + if application_name_suffix != "" { + application_name += " " + application_name_suffix + } + + connStr := buildConnectionString(host, user, password, port, database, sslmode, application_name) + db, err := sqlx.Open("pgx", connStr) + if err != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Unable to create database client", + Detail: fmt.Sprintf("Unable to authenticate user for database: %s", err), + }) + return nil, diags + } + return &DBClient{DB: db}, diags +} + +func buildConnectionString(host, user, password string, port int, database, sslmode, application_name string) string { + url := &url.URL{ + Scheme: "postgres", + User: url.UserPassword(user, password), + Host: fmt.Sprintf("%s:%d", host, port), + Path: database, + RawQuery: url.Values{ + "application_name": {application_name}, + "sslmode": {sslmode}, + }.Encode(), + } + + return url.String() +} + +func (c *DBClient) SQLX() *sqlx.DB { + return c.DB +} diff --git a/pkg/clients/db_client_test.go b/pkg/clients/db_client_test.go new file mode 100644 index 00000000..2929cdcf --- /dev/null +++ b/pkg/clients/db_client_test.go @@ -0,0 +1,27 @@ +package clients + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestConnectionString(t *testing.T) { + r := require.New(t) + c := buildConnectionString("host", "user", "pass", 6875, "database", "require", "tf") + r.Equal(`postgres://user:pass@host:6875/database?application_name=tf&sslmode=require`, c) +} + +func TestConnectionStringTesting(t *testing.T) { + r := require.New(t) + c := buildConnectionString("host", "user", "pass", 6875, "database", "disable", "tf") + r.Equal(`postgres://user:pass@host:6875/database?application_name=tf&sslmode=disable`, c) +} + +func TestNewDBClientFailure(t *testing.T) { + r := require.New(t) + + client, diags := NewDBClient("localhost", "user", "pass", 6875, "database", "tf-provider", "v0.1.0", "invalid-sslmode") + r.NotEmpty(diags) + r.Nil(client) +} diff --git a/pkg/clients/frontegg_client.go b/pkg/clients/frontegg_client.go new file mode 100644 index 00000000..c7530ecc --- /dev/null +++ b/pkg/clients/frontegg_client.go @@ -0,0 +1,245 @@ +package clients + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "net/http" + "net/url" + "regexp" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +// FronteggClient struct to encapsulate the http.Client with additional properties +type FronteggClient struct { + HTTPClient *http.Client + Token string + Email string + Endpoint string + TokenExpiry time.Time + Password string +} + +// NewFronteggClient function for initializing a new Frontegg client with an auth token +func NewFronteggClient(ctx context.Context, password, endpoint string) (*FronteggClient, error) { + token, email, tokenExpiry, err := getToken(ctx, password, endpoint) + if err != nil { + return nil, fmt.Errorf("failed to get token: %v", err) + } + + transport := &tokenTransport{ + Token: token, + Transport: http.DefaultTransport, + } + + client := &http.Client{Transport: transport} + + return &FronteggClient{ + HTTPClient: client, + Token: token, + Email: email, + Endpoint: endpoint, + TokenExpiry: tokenExpiry.Add(-time.Duration(0.5*float64(time.Until(tokenExpiry).Nanoseconds())) * time.Nanosecond), + Password: password, + }, nil +} + +// tokenTransport struct to add the Authorization header to each request +type tokenTransport struct { + Token string + Transport http.RoundTripper +} + +// RoundTrip method to execute the request with the token +func (t *tokenTransport) RoundTrip(req *http.Request) (*http.Response, error) { + req2 := cloneRequest(req) + req2.Header.Set("Authorization", "Bearer "+t.Token) + return t.Transport.RoundTrip(req2) +} + +// GetToken function to authenticate with the Frontegg API and retrieve a token +func getToken(ctx context.Context, password string, endpoint string) (string, string, time.Time, error) { + clientId, secretKey, err := parseAppPassword(password) + if err != nil { + return "", "", time.Time{}, err + } + + adminEndpoint := fmt.Sprintf("%s/identity/resources/auth/v1/api-token", endpoint) + + payload := map[string]string{ + "clientId": clientId, + "secret": secretKey, + } + payloadBytes, err := json.Marshal(payload) + if err != nil { + return "", "", time.Time{}, err + } + + req, err := http.NewRequestWithContext(ctx, "POST", adminEndpoint, bytes.NewBuffer(payloadBytes)) + if err != nil { + return "", "", time.Time{}, err + } + req.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", "", time.Time{}, err + } + defer resp.Body.Close() + + // Read the response body into the 'body' variable + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", "", time.Time{}, err + } + + if resp.StatusCode != http.StatusOK { + return "", "", time.Time{}, fmt.Errorf("authentication failed: %s", string(body)) + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return "", "", time.Time{}, err + } + + tokenString, ok := result["accessToken"].(string) + if !ok { + return "", "", time.Time{}, errors.New("access token not found in the response") + } + + // Parse the token without verifying the signature. + token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) + if err != nil { + return "", "", time.Time{}, fmt.Errorf("error parsing token: %v", err) + } + + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return "", "", time.Time{}, errors.New("invalid token claims") + } + + email, ok := claims["email"].(string) + if !ok { + return "", "", time.Time{}, errors.New("email claim not found in token") + } + + var tokenExpiry time.Time + if expiresIn, ok := result["expiresIn"].(float64); ok { + tokenExpiry = time.Now().Add(time.Duration(expiresIn) * time.Second) + } else { + // Default expiry time if not provided in the response + tokenExpiry = time.Now().Add(1 * time.Hour) + } + + return tokenString, email, tokenExpiry, nil +} + +// Get the token from the FronteggClient +func (c *FronteggClient) GetToken() string { + return c.Token +} + +// Get the email from the FronteggClient +func (c *FronteggClient) GetEmail() string { + return c.Email +} + +// Get the endpoint from the FronteggClient +func (c *FronteggClient) GetEndpoint() string { + return c.Endpoint +} + +// Get the token expiry from the FronteggClient +func (c *FronteggClient) GetTokenExpiry() time.Time { + return c.TokenExpiry +} + +// Get the password from the FronteggClient +func (c *FronteggClient) GetPassword() string { + return c.Password +} + +// cloneRequest creates a deep copy of an HTTP request to enable safe modifications +// while preserving concurrency safety, immutability, and reusability. +// https://stackoverflow.com/questions/62017146/http-request-clone-is-not-deep-clone +func cloneRequest(r *http.Request) *http.Request { + // Deep copy the request + r2 := new(http.Request) + *r2 = *r + + // Deep copy the URL + r2.URL = new(url.URL) + *r2.URL = *r.URL + + // Deep copy the Header + r2.Header = make(http.Header, len(r.Header)) + for k, s := range r.Header { + r2.Header[k] = append([]string(nil), s...) + } + return r2 +} + +func parseAppPassword(password string) (string, string, error) { + strippedPassword := strings.TrimPrefix(password, "mzp_") + var clientId, secretKey string + + re := regexp.MustCompile("[^0-9a-fA-F]") + filteredChars := re.ReplaceAllString(strippedPassword, "") + + if len(filteredChars) < 64 { + return "", "", fmt.Errorf("invalid app password length: %d", len(filteredChars)) + } + + clientId = formatDashlessUuid(filteredChars[0:32]) + secretKey = formatDashlessUuid(filteredChars[32:]) + + return clientId, secretKey, nil +} + +func formatDashlessUuid(dashlessUuid string) string { + parts := []string{ + dashlessUuid[0:8], + dashlessUuid[8:12], + dashlessUuid[12:16], + dashlessUuid[16:20], + dashlessUuid[20:], + } + return strings.Join(parts, "-") +} + +func (c *FronteggClient) NeedsTokenRefresh() error { + if time.Now().After(c.TokenExpiry) { + return fmt.Errorf("token expired and needs refresh") + } + return nil +} + +func (c *FronteggClient) RefreshToken() error { + log.Printf("[DEBUG] Refreshing Frontegg: %v\n", c) + + token, email, tokenExpiry, err := getToken(context.Background(), c.Password, c.Endpoint) + if err != nil { + return fmt.Errorf("failed to get token: %v", err) + } + + transport := &tokenTransport{ + Token: token, + Transport: http.DefaultTransport, + } + + client := &http.Client{Transport: transport} + + c.HTTPClient = client + c.Token = token + c.Email = email + c.TokenExpiry = tokenExpiry.Add(-time.Duration(0.5*float64(time.Until(tokenExpiry).Nanoseconds())) * time.Nanosecond) + + return nil +} diff --git a/pkg/clients/frontegg_client_test.go b/pkg/clients/frontegg_client_test.go new file mode 100644 index 00000000..121257a0 --- /dev/null +++ b/pkg/clients/frontegg_client_test.go @@ -0,0 +1,133 @@ +package clients + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/stretchr/testify/require" +) + +func TestNewFronteggClient(t *testing.T) { + // Start a local HTTP server to mock the Frontegg API + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/identity/resources/auth/v1/api-token" { + w.Header().Set("Content-Type", "application/json") + response := map[string]string{ + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im16X3N5c3RlbSIsImV4cCI6MTcwMDAwMDAwMH0.c2lnbmF0dXJl", + } + json.NewEncoder(w).Encode(response) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + // Use the server's URL as the endpoint + endpoint := server.URL + password := "mzp_" + strings.Repeat("a", 64) + + // Create the Frontegg client using the mocked server and context + fronteggClient, err := NewFronteggClient(context.Background(), password, endpoint) + require.NoError(t, err, "Error should be nil") + require.NotNil(t, fronteggClient, "Frontegg client should not be nil") + + // The token should be set correctly in the Frontegg client + require.Equal(t, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im16X3N5c3RlbSIsImV4cCI6MTcwMDAwMDAwMH0.c2lnbmF0dXJl", fronteggClient.Token, "Token should be set correctly in the Frontegg client") +} + +func TestFronteggClient_AuthenticationError(t *testing.T) { + // Start a local HTTP server to mock the Frontegg API with an error response + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) // Simulate an authentication error + })) + defer server.Close() + + // Use the server's URL as the endpoint + endpoint := server.URL + password := "mzp_" + strings.Repeat("a", 64) + + // Create the Frontegg client using the mocked server and context + _, err := NewFronteggClient(context.Background(), password, endpoint) + require.Error(t, err, "Authentication error should result in an error") +} + +func TestFronteggClient_TokenRefresh(t *testing.T) { + // Create a mock HTTP server to simulate the Frontegg API + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/identity/resources/auth/v1/api-token" { + // Generate a valid JWT token + token := generateValidJWTToken() + + // Simulate a successful token refresh response with the generated JWT token + w.Header().Set("Content-Type", "application/json") + response := map[string]string{ + "accessToken": token, + } + json.NewEncoder(w).Encode(response) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + defer server.Close() + + // Use the mock server's URL as the endpoint + endpoint := server.URL + password := "mzp_" + strings.Repeat("a", 64) + + // Create the Frontegg client using the mock server and context + fronteggClient, err := NewFronteggClient(context.Background(), password, endpoint) + require.NoError(t, err, "Error should be nil") + require.NotNil(t, fronteggClient, "Frontegg client should not be nil") + + // The token should be initially set correctly in the Frontegg client + require.NotEmpty(t, fronteggClient.Token, "Token should be set correctly in the Frontegg client") + + // Verify that the client does not detect the need for token refresh immediately + require.NoError(t, fronteggClient.NeedsTokenRefresh(), "Token should not be considered expired") +} + +func generateValidJWTToken() string { + // Create a JWT token with the correct format + token := jwt.New(jwt.SigningMethodHS256) + claims := token.Claims.(jwt.MapClaims) + claims["email"] = "test@example.com" // Add relevant claims + claims["exp"] = time.Now().Add(time.Hour).Unix() // Set expiration time + + // Sign the token with a secret key (you can use a random key for testing) + secretKey := []byte("your-secret-key") + tokenString, _ := token.SignedString(secretKey) + + return tokenString +} + +func TestFronteggClient_NeedsTokenRefresh(t *testing.T) { + // Create a Frontegg client with an expired token + fronteggClient := &FronteggClient{ + Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6Im16X3N5c3RlbSIsImV4cCI6MTYwMDAwMDAwMH0.c2lnbmF0dXJl", + Email: "test@example.com", + Endpoint: "http://mockedendpoint", + TokenExpiry: time.Now().Add(-time.Hour), // Expired token + Password: "mzp_" + strings.Repeat("a", 64), + } + + // Verify that the client correctly detects the need for token refresh + require.Error(t, fronteggClient.NeedsTokenRefresh(), "Token should be considered expired and require refresh") +} + +func TestParseAppPassword(t *testing.T) { + validPassword := "mzp_" + strings.Repeat("a", 64) + clientId, secretKey, err := parseAppPassword(validPassword) + require.NoError(t, err, "Parsing valid password should not result in an error") + require.Equal(t, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", clientId, "Client ID should match") + require.Equal(t, "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", secretKey, "Secret Key should match") + + invalidPassword := "invalid_password" + _, _, err = parseAppPassword(invalidPassword) + require.Error(t, err, "Parsing invalid password should result in an error") +} diff --git a/pkg/clients/helpers.go b/pkg/clients/helpers.go new file mode 100644 index 00000000..64a0723a --- /dev/null +++ b/pkg/clients/helpers.go @@ -0,0 +1,47 @@ +package clients + +import ( + "fmt" + "strings" + "time" +) + +type AppPassword struct { + ClientID string `json:"clientId"` + Secret string `json:"secret"` + Description string `json:"description"` + Owner string `json:"owner"` + CreatedAt time.Time `json:"created_at"` +} + +type Region string + +// Role represents the Frontegg role structure. +type Role struct { + ID string `json:"id"` + VendorID string `json:"vendorId"` + TenantID *string `json:"tenantId,omitempty"` + Key string `json:"key"` + Name string `json:"name"` + Description string `json:"description"` + IsDefault bool `json:"isDefault"` + FirstUserRole bool `json:"firstUserRole"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Permissions []string `json:"permissions"` + Level int `json:"level"` +} + +// Helper function to construct app password from clientId and secret. +func ConstructAppPassword(clientID, secret string) string { + // Remove dashes and concatenate with "mzp_" prefix. + clientIDClean := strings.ReplaceAll(clientID, "-", "") + secretClean := strings.ReplaceAll(secret, "-", "") + return fmt.Sprintf("mzp_%s%s", clientIDClean, secretClean) +} + +const ( + AwsUsEast1 Region = "aws/us-east-1" + AwsUsWest2 Region = "aws/us-west-2" + AwsEuWest1 Region = "aws/eu-west-1" +) diff --git a/pkg/clients/helpers_test.go b/pkg/clients/helpers_test.go new file mode 100644 index 00000000..ff8dc146 --- /dev/null +++ b/pkg/clients/helpers_test.go @@ -0,0 +1,44 @@ +package clients + +import ( + "strings" + "testing" +) + +// TestConstructAppPassword tests the ConstructAppPassword function. +func TestConstructAppPassword(t *testing.T) { + tests := []struct { + name string + clientID string + secret string + wantPrefix string + }{ + { + name: "normal IDs without dashes", + clientID: "1b2a3c", + secret: "4d5e6f", + wantPrefix: "mzp_1b2a3c4d5e6f", + }, + { + name: "IDs with dashes", + clientID: "1b2a-3c4d-5e6f", + secret: "7a8b-9c0d-1e2f", + wantPrefix: "mzp_1b2a3c4d5e6f7a8b9c0d1e2f", + }, + { + name: "long IDs", + clientID: "1b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d", + secret: "7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", + wantPrefix: "mzp_1b2a3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := ConstructAppPassword(tt.clientID, tt.secret) + if !strings.HasPrefix(got, tt.wantPrefix) { + t.Errorf("ConstructAppPassword() = %v, want prefix %v", got, tt.wantPrefix) + } + }) + } +} diff --git a/pkg/datasources/datasource_cluster.go b/pkg/datasources/datasource_cluster.go index 5a610c4c..91bdf823 100644 --- a/pkg/datasources/datasource_cluster.go +++ b/pkg/datasources/datasource_cluster.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Cluster() *schema.Resource { @@ -48,6 +47,7 @@ func Cluster() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -55,7 +55,11 @@ func Cluster() *schema.Resource { func clusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - dataSource, err := materialize.ListClusters(meta.(*sqlx.DB)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListClusters(metaDb) if err != nil { return diag.FromErr(err) } @@ -78,6 +82,6 @@ func clusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion("clusters")) + d.SetId(utils.TransformIdWithRegion(string(region), "clusters")) return diags } diff --git a/pkg/datasources/datasource_cluster_replica.go b/pkg/datasources/datasource_cluster_replica.go index 35384cbb..948cbc39 100644 --- a/pkg/datasources/datasource_cluster_replica.go +++ b/pkg/datasources/datasource_cluster_replica.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func ClusterReplica() *schema.Resource { @@ -48,6 +47,7 @@ func ClusterReplica() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -55,7 +55,11 @@ func ClusterReplica() *schema.Resource { func clusterReplicaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - dataSource, err := materialize.ListClusterReplicas(meta.(*sqlx.DB)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListClusterReplicas(metaDb) if err != nil { return diag.FromErr(err) } @@ -78,6 +82,6 @@ func clusterReplicaRead(ctx context.Context, d *schema.ResourceData, meta interf return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion("cluster_replicas")) + d.SetId(utils.TransformIdWithRegion(string(region), "cluster_replicas")) return diags } diff --git a/pkg/datasources/datasource_cluster_replica_test.go b/pkg/datasources/datasource_cluster_replica_test.go index bec7eb10..64716a0b 100644 --- a/pkg/datasources/datasource_cluster_replica_test.go +++ b/pkg/datasources/datasource_cluster_replica_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestClusterReplicaDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, ClusterReplica().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { testhelpers.MockClusterReplicaScan(mock, "") if err := clusterReplicaRead(context.TODO(), d, db); err != nil { diff --git a/pkg/datasources/datasource_cluster_test.go b/pkg/datasources/datasource_cluster_test.go index c31a0737..66624681 100644 --- a/pkg/datasources/datasource_cluster_test.go +++ b/pkg/datasources/datasource_cluster_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestClusterDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Cluster().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { testhelpers.MockClusterScan(mock, "") if err := clusterRead(context.TODO(), d, db); err != nil { diff --git a/pkg/datasources/datasource_connection.go b/pkg/datasources/datasource_connection.go index 41ef4df6..f851f6df 100644 --- a/pkg/datasources/datasource_connection.go +++ b/pkg/datasources/datasource_connection.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Connection() *schema.Resource { @@ -54,6 +54,7 @@ func Connection() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -64,7 +65,11 @@ func connectionRead(ctx context.Context, d *schema.ResourceData, meta interface{ var diags diag.Diagnostics - dataSource, err := materialize.ListConnections(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListConnections(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -86,6 +91,6 @@ func connectionRead(ctx context.Context, d *schema.ResourceData, meta interface{ return diag.FromErr(err) } - SetId("connections", databaseName, schemaName, d) + SetId(string(region), "connections", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_connection_test.go b/pkg/datasources/datasource_connection_test.go index 86274262..ea25fe8f 100644 --- a/pkg/datasources/datasource_connection_test.go +++ b/pkg/datasources/datasource_connection_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestConnectionDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Connection().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockConnectionScan(mock, p) diff --git a/pkg/datasources/datasource_current_cluster.go b/pkg/datasources/datasource_current_cluster.go index a25c105a..49821eae 100644 --- a/pkg/datasources/datasource_current_cluster.go +++ b/pkg/datasources/datasource_current_cluster.go @@ -6,7 +6,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func CurrentCluster() *schema.Resource { @@ -17,6 +16,7 @@ func CurrentCluster() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "region": RegionSchema(), }, } } @@ -24,12 +24,16 @@ func CurrentCluster() *schema.Resource { func currentClusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*sqlx.DB) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + conn := metaDb var name string conn.QueryRow("SHOW CLUSTER;").Scan(&name) d.Set("name", name) - d.SetId(utils.TransformIdWithRegion("current_cluster")) + d.SetId(utils.TransformIdWithRegion(string(region), "current_cluster")) return diags } diff --git a/pkg/datasources/datasource_current_cluster_test.go b/pkg/datasources/datasource_current_cluster_test.go index 94342b41..f9e1a149 100644 --- a/pkg/datasources/datasource_current_cluster_test.go +++ b/pkg/datasources/datasource_current_cluster_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestCurrentClusterDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, CurrentCluster().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { ir := mock.NewRows([]string{"cluster"}).AddRow("quickstart") mock.ExpectQuery(`SHOW CLUSTER;`).WillReturnRows(ir) diff --git a/pkg/datasources/datasource_current_database.go b/pkg/datasources/datasource_current_database.go index 9f53d01a..70d88dd2 100644 --- a/pkg/datasources/datasource_current_database.go +++ b/pkg/datasources/datasource_current_database.go @@ -6,7 +6,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func CurrentDatabase() *schema.Resource { @@ -17,6 +16,7 @@ func CurrentDatabase() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "region": RegionSchema(), }, } } @@ -24,12 +24,16 @@ func CurrentDatabase() *schema.Resource { func currentDatabaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*sqlx.DB) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + conn := metaDb var name string conn.QueryRow("SHOW DATABASE;").Scan(&name) d.Set("name", name) - d.SetId(utils.TransformIdWithRegion("current_database")) + d.SetId(utils.TransformIdWithRegion(string(region), "current_database")) return diags } diff --git a/pkg/datasources/datasource_current_database_test.go b/pkg/datasources/datasource_current_database_test.go index e04d9be4..ecc0df1f 100644 --- a/pkg/datasources/datasource_current_database_test.go +++ b/pkg/datasources/datasource_current_database_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestCurrentDatabaseDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, CurrentDatabase().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { ir := mock.NewRows([]string{"database"}).AddRow("materialize") mock.ExpectQuery(`SHOW DATABASE;`).WillReturnRows(ir) diff --git a/pkg/datasources/datasource_database.go b/pkg/datasources/datasource_database.go index 343854ae..cbbd9ebb 100644 --- a/pkg/datasources/datasource_database.go +++ b/pkg/datasources/datasource_database.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Database() *schema.Resource { @@ -32,6 +31,7 @@ func Database() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -39,7 +39,12 @@ func Database() *schema.Resource { func databaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - dataSource, err := materialize.ListDatabases(meta.(*sqlx.DB)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + dataSource, err := materialize.ListDatabases(metaDb) if err != nil { return diag.FromErr(err) } @@ -58,6 +63,6 @@ func databaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion("databases")) + d.SetId(utils.TransformIdWithRegion(string(region), "databases")) return diags } diff --git a/pkg/datasources/datasource_database_test.go b/pkg/datasources/datasource_database_test.go index 40438779..05e72357 100644 --- a/pkg/datasources/datasource_database_test.go +++ b/pkg/datasources/datasource_database_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestDatabaseDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Database().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { testhelpers.MockDatabaseScan(mock, "") if err := databaseRead(context.TODO(), d, db); err != nil { diff --git a/pkg/datasources/datasource_egress_ips.go b/pkg/datasources/datasource_egress_ips.go index eba3db4b..ca813e9c 100644 --- a/pkg/datasources/datasource_egress_ips.go +++ b/pkg/datasources/datasource_egress_ips.go @@ -10,7 +10,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func EgressIps() *schema.Resource { @@ -23,6 +22,7 @@ func EgressIps() *schema.Resource { Description: "The egress IPs in the account", Elem: &schema.Schema{Type: schema.TypeString}, }, + "region": RegionSchema(), }, } } @@ -30,7 +30,11 @@ func EgressIps() *schema.Resource { func EgressIpsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*sqlx.DB) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + conn := metaDb q := materialize.ReadEgressIpsDatasource() @@ -65,7 +69,7 @@ func EgressIpsRead(ctx context.Context, d *schema.ResourceData, meta interface{} return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion("egress_ips")) + d.SetId(utils.TransformIdWithRegion(string(region), "egress_ips")) return diags } diff --git a/pkg/datasources/datasource_egress_ips_test.go b/pkg/datasources/datasource_egress_ips_test.go index e61af701..08c07c5c 100644 --- a/pkg/datasources/datasource_egress_ips_test.go +++ b/pkg/datasources/datasource_egress_ips_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestEgressIpsDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, EgressIps().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { ir := mock.NewRows([]string{"egress_ip"}). AddRow("egress_ip") mock.ExpectQuery(`SELECT egress_ip FROM materialize.mz_catalog.mz_egress_ips;`).WillReturnRows(ir) diff --git a/pkg/datasources/datasource_index.go b/pkg/datasources/datasource_index.go index 81ec8a8b..7f2e05b2 100644 --- a/pkg/datasources/datasource_index.go +++ b/pkg/datasources/datasource_index.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Index() *schema.Resource { @@ -54,6 +54,7 @@ func Index() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -64,7 +65,11 @@ func indexRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di var diags diag.Diagnostics - dataSource, err := materialize.ListIndexes(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListIndexes(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -86,6 +91,6 @@ func indexRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di return diag.FromErr(err) } - SetId("indexes", databaseName, schemaName, d) + SetId(string(region), "indexes", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_index_test.go b/pkg/datasources/datasource_index_test.go index fdf6bd9e..1a713181 100644 --- a/pkg/datasources/datasource_index_test.go +++ b/pkg/datasources/datasource_index_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestIndexDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Index().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := ` WHERE mz_databases.name = 'database' AND mz_objects.type IN \('source', 'view', 'materialized-view'\) diff --git a/pkg/datasources/datasource_materialized_view.go b/pkg/datasources/datasource_materialized_view.go index b6963b81..d74b642a 100644 --- a/pkg/datasources/datasource_materialized_view.go +++ b/pkg/datasources/datasource_materialized_view.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func MaterializedView() *schema.Resource { @@ -50,6 +50,7 @@ func MaterializedView() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -60,7 +61,11 @@ func materializedViewRead(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics - dataSource, err := materialize.ListMaterializedViews(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListMaterializedViews(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -81,6 +86,6 @@ func materializedViewRead(ctx context.Context, d *schema.ResourceData, meta inte return diag.FromErr(err) } - SetId("materialized_views", databaseName, schemaName, d) + SetId(string(region), "materialized_views", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_materialized_view_test.go b/pkg/datasources/datasource_materialized_view_test.go index 7650e6d5..8a2f325d 100644 --- a/pkg/datasources/datasource_materialized_view_test.go +++ b/pkg/datasources/datasource_materialized_view_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestMaterializedViewDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, MaterializedView().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockMaterializeViewScan(mock, p) diff --git a/pkg/datasources/datasource_region.go b/pkg/datasources/datasource_region.go new file mode 100644 index 00000000..a3b57a82 --- /dev/null +++ b/pkg/datasources/datasource_region.go @@ -0,0 +1,99 @@ +package datasources + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func Region() *schema.Resource { + return &schema.Resource{ + ReadContext: RegionRead, + Schema: map[string]*schema.Schema{ + "regions": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the region.", + }, + "url": { + Type: schema.TypeString, + Computed: true, + Description: "The URL at which the Region API can be reached.", + }, + "cloud_provider": { + Type: schema.TypeString, + Computed: true, + Description: "The cloud provider of the region. Currently, only AWS is supported.", + }, + "host": { + Type: schema.TypeString, + Computed: true, + Description: "The SQL host of the region. This is the hostname of the Materialize cluster in the region.", + }, + }, + }, + }, + }, + } +} + +func RegionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + client := providerMeta.CloudAPI + + providers, err := client.ListCloudProviders(ctx) + if err != nil { + return diag.FromErr(err) + } + + var regions []map[string]interface{} + for _, provider := range providers { + host, err := client.GetHost(ctx, provider.ID) + if err != nil { + if strings.Contains(err.Error(), "non-200 status code: 204") { + log.Printf("[WARN] No host available for region %s, skipping", provider.ID) + continue + } + return diag.FromErr(fmt.Errorf("error fetching host for region %s: %s", provider.ID, err)) + } + + region := createRegionMap(provider, host) + regions = append(regions, region) + } + + if err := d.Set("regions", regions); err != nil { + return diag.FromErr(err) + } + + d.SetId("regions") + return nil +} + +// createRegionMap creates a map of region details +func createRegionMap(provider clients.CloudProvider, host string) map[string]interface{} { + return map[string]interface{}{ + "id": provider.ID, + "name": provider.Name, + "url": provider.Url, + "cloud_provider": provider.CloudProvider, + "host": host, + } +} diff --git a/pkg/datasources/datasource_region_test.go b/pkg/datasources/datasource_region_test.go new file mode 100644 index 00000000..a76dc9f3 --- /dev/null +++ b/pkg/datasources/datasource_region_test.go @@ -0,0 +1,64 @@ +package datasources + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/require" +) + +func TestRegionRead(t *testing.T) { + r := require.New(t) + + // Set up the mock cloud server + testhelpers.WithMockCloudServer(t, func(serverURL string) { + // Create an http.Client that uses the mock transport + mockClient := &http.Client{ + Transport: &testhelpers.MockCloudService{}, + } + + fronteggClient := &clients.FronteggClient{ + Endpoint: serverURL, + HTTPClient: mockClient, + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } + // Create a mock cloud client + mockCloudClient := &clients.CloudAPIClient{ + FronteggClient: fronteggClient, + Endpoint: serverURL, + } + + // Create a provider meta with the mock cloud client + providerMeta := &utils.ProviderMeta{ + CloudAPI: mockCloudClient, + Frontegg: fronteggClient, + } + + // Create a test resource data with the Region schema + d := schema.TestResourceDataRaw(t, Region().Schema, nil) + d.SetId("regions") + + // Call the RegionRead function + diags := RegionRead(context.TODO(), d, providerMeta) + + // Print error messages in diagnostics + for _, diag := range diags { + t.Logf("Error: %s", diag.Summary) + t.Logf("Details: %s", diag.Detail) + } + + // Check for errors within the diagnostics + r.False(diags.HasError()) + r.Equal("aws/us-east-1", d.Get("regions.0.id")) + r.Equal("us-east-1", d.Get("regions.0.name")) + r.Equal("http://mockendpoint", d.Get("regions.0.url")) + r.Equal("aws", d.Get("regions.0.cloud_provider")) + r.Equal("sql.materialize.com", d.Get("regions.0.host")) + }) +} diff --git a/pkg/datasources/datasource_role.go b/pkg/datasources/datasource_role.go index ac829671..3e3f5365 100644 --- a/pkg/datasources/datasource_role.go +++ b/pkg/datasources/datasource_role.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Role() *schema.Resource { @@ -32,6 +31,7 @@ func Role() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -39,7 +39,11 @@ func Role() *schema.Resource { func roleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - dataSource, err := materialize.ListRoles(meta.(*sqlx.DB)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListRoles(metaDb) if err != nil { return diag.FromErr(err) } @@ -58,6 +62,6 @@ func roleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion("roles")) + d.SetId(utils.TransformIdWithRegion(string(region), "roles")) return diags } diff --git a/pkg/datasources/datasource_role_test.go b/pkg/datasources/datasource_role_test.go index 65557c13..2c7371e2 100644 --- a/pkg/datasources/datasource_role_test.go +++ b/pkg/datasources/datasource_role_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -19,7 +19,7 @@ func TestRoleDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Role().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { testhelpers.MockRoleScan(mock, "") if err := roleRead(context.TODO(), d, db); err != nil { diff --git a/pkg/datasources/datasource_schema.go b/pkg/datasources/datasource_schema.go index e80ccdad..290716f4 100644 --- a/pkg/datasources/datasource_schema.go +++ b/pkg/datasources/datasource_schema.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Schema() *schema.Resource { @@ -42,6 +41,7 @@ func Schema() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -51,7 +51,11 @@ func schemaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d var diags diag.Diagnostics - dataSource, err := materialize.ListSchemas(meta.(*sqlx.DB), databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListSchemas(metaDb, databaseName) if err != nil { return diag.FromErr(err) } @@ -73,9 +77,9 @@ func schemaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d if databaseName != "" { id := fmt.Sprintf("%s|schemas", databaseName) - d.SetId(utils.TransformIdWithRegion(id)) + d.SetId(utils.TransformIdWithRegion(string(region), id)) } else { - d.SetId(utils.TransformIdWithRegion("schemas")) + d.SetId(utils.TransformIdWithRegion(string(region), "schemas")) } return diags diff --git a/pkg/datasources/datasource_schema_test.go b/pkg/datasources/datasource_schema_test.go index a2a4eb73..7bad1d9d 100644 --- a/pkg/datasources/datasource_schema_test.go +++ b/pkg/datasources/datasource_schema_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -21,7 +21,7 @@ func TestSchemaDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Schema().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database'` testhelpers.MockSchemaScan(mock, p) diff --git a/pkg/datasources/datasource_secret.go b/pkg/datasources/datasource_secret.go index 298c01c6..119be621 100644 --- a/pkg/datasources/datasource_secret.go +++ b/pkg/datasources/datasource_secret.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Secret() *schema.Resource { @@ -50,6 +50,7 @@ func Secret() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -60,7 +61,11 @@ func secretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d var diags diag.Diagnostics - dataSource, err := materialize.ListSecrets(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListSecrets(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -81,6 +86,6 @@ func secretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(err) } - SetId("secrets", databaseName, schemaName, d) + SetId(string(region), "secrets", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_secret_test.go b/pkg/datasources/datasource_secret_test.go index c4f78485..d6977b23 100644 --- a/pkg/datasources/datasource_secret_test.go +++ b/pkg/datasources/datasource_secret_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestSecretDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Secret().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockSecretScan(mock, p) diff --git a/pkg/datasources/datasource_sink.go b/pkg/datasources/datasource_sink.go index fb0e5b63..472396da 100644 --- a/pkg/datasources/datasource_sink.go +++ b/pkg/datasources/datasource_sink.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Sink() *schema.Resource { @@ -70,6 +70,7 @@ func Sink() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -80,7 +81,11 @@ func sinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia var diags diag.Diagnostics - dataSource, err := materialize.ListSinks(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListSinks(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -106,7 +111,7 @@ func sinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - SetId("sinks", databaseName, schemaName, d) + SetId(string(region), "sinks", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_sink_test.go b/pkg/datasources/datasource_sink_test.go index 5e3a7237..d4372450 100644 --- a/pkg/datasources/datasource_sink_test.go +++ b/pkg/datasources/datasource_sink_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestSinkDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Sink().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockSinkScan(mock, p) diff --git a/pkg/datasources/datasource_source.go b/pkg/datasources/datasource_source.go index 8a2955bc..ccfb0d08 100644 --- a/pkg/datasources/datasource_source.go +++ b/pkg/datasources/datasource_source.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Source() *schema.Resource { @@ -70,6 +70,7 @@ func Source() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -80,7 +81,11 @@ func sourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d var diags diag.Diagnostics - dataSource, err := materialize.ListSources(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListSources(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -106,7 +111,7 @@ func sourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(err) } - SetId("sources", databaseName, schemaName, d) + SetId(string(region), "sources", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_source_test.go b/pkg/datasources/datasource_source_test.go index 481ac1e4..d4845608 100644 --- a/pkg/datasources/datasource_source_test.go +++ b/pkg/datasources/datasource_source_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestSourceDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Source().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockSourceScan(mock, p) diff --git a/pkg/datasources/datasource_table.go b/pkg/datasources/datasource_table.go index da2b7d10..699ab894 100644 --- a/pkg/datasources/datasource_table.go +++ b/pkg/datasources/datasource_table.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Table() *schema.Resource { @@ -50,6 +50,7 @@ func Table() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -60,7 +61,11 @@ func tableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di var diags diag.Diagnostics - dataSource, err := materialize.ListTables(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListTables(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -81,7 +86,7 @@ func tableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di return diag.FromErr(err) } - SetId("tables", databaseName, schemaName, d) + SetId(string(region), "tables", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_table_test.go b/pkg/datasources/datasource_table_test.go index 19c8b15f..2997a8aa 100644 --- a/pkg/datasources/datasource_table_test.go +++ b/pkg/datasources/datasource_table_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestTableDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Table().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockTableScan(mock, p) diff --git a/pkg/datasources/datasource_type.go b/pkg/datasources/datasource_type.go index dc420d43..2483eb6d 100644 --- a/pkg/datasources/datasource_type.go +++ b/pkg/datasources/datasource_type.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func Type() *schema.Resource { @@ -54,6 +54,7 @@ func Type() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -64,7 +65,11 @@ func typeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia var diags diag.Diagnostics - dataSource, err := materialize.ListTypes(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListTypes(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -86,7 +91,7 @@ func typeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - SetId("types", databaseName, schemaName, d) + SetId(string(region), "types", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_type_test.go b/pkg/datasources/datasource_type_test.go index 8f3aa008..23b8fb7a 100644 --- a/pkg/datasources/datasource_type_test.go +++ b/pkg/datasources/datasource_type_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestTypesDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, Type().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockTypeScan(mock, p) diff --git a/pkg/datasources/datasource_view.go b/pkg/datasources/datasource_view.go index 722fe5a9..a42d7901 100644 --- a/pkg/datasources/datasource_view.go +++ b/pkg/datasources/datasource_view.go @@ -4,10 +4,10 @@ import ( "context" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func View() *schema.Resource { @@ -50,6 +50,7 @@ func View() *schema.Resource { }, }, }, + "region": RegionSchema(), }, } } @@ -60,7 +61,11 @@ func viewRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia var diags diag.Diagnostics - dataSource, err := materialize.ListViews(meta.(*sqlx.DB), schemaName, databaseName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + dataSource, err := materialize.ListViews(metaDb, schemaName, databaseName) if err != nil { return diag.FromErr(err) } @@ -81,7 +86,7 @@ func viewRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - SetId("views", databaseName, schemaName, d) + SetId(string(region), "views", databaseName, schemaName, d) return diags } diff --git a/pkg/datasources/datasource_view_test.go b/pkg/datasources/datasource_view_test.go index 90d3ea46..f781b315 100644 --- a/pkg/datasources/datasource_view_test.go +++ b/pkg/datasources/datasource_view_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestViewDatasource(t *testing.T) { d := schema.TestResourceDataRaw(t, View().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { p := `WHERE mz_databases.name = 'database' AND mz_schemas.name = 'schema'` testhelpers.MockViewScan(mock, p) diff --git a/pkg/datasources/utils.go b/pkg/datasources/utils.go index 7c2b391a..000767bc 100644 --- a/pkg/datasources/utils.go +++ b/pkg/datasources/utils.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func SetId(resource, databaseName, schemaName string, d *schema.ResourceData) { +func SetId(region, resource, databaseName, schemaName string, d *schema.ResourceData) { var id string if databaseName != "" && schemaName != "" { id = fmt.Sprintf("%s|%s|%s", databaseName, schemaName, resource) @@ -17,5 +17,13 @@ func SetId(resource, databaseName, schemaName string, d *schema.ResourceData) { id = resource } - d.SetId(utils.TransformIdWithRegion(id)) + d.SetId(utils.TransformIdWithRegion(region, id)) +} + +func RegionSchema() *schema.Schema { + return &schema.Schema{ + Description: "The region in which the resource is located.", + Type: schema.TypeString, + Computed: true, + } } diff --git a/pkg/materialize/cluster_replica.go b/pkg/materialize/cluster_replica.go index 19dc645f..3805bd09 100644 --- a/pkg/materialize/cluster_replica.go +++ b/pkg/materialize/cluster_replica.go @@ -74,7 +74,7 @@ func (b *ClusterReplicaBuilder) Create() error { } if b.disk { - i := fmt.Sprintf(` DISK`) + i := " DISK" p = append(p, i) } diff --git a/pkg/provider/acceptance_cluster_replica_test.go b/pkg/provider/acceptance_cluster_replica_test.go index 9d37dc13..34f0d206 100644 --- a/pkg/provider/acceptance_cluster_replica_test.go +++ b/pkg/provider/acceptance_cluster_replica_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccClusterReplica_basic(t *testing.T) { @@ -133,18 +132,26 @@ func testAccClusterReplicaWithComment(clusterName, clusterReplica, comment strin func testAccCheckClusterReplicaExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("cluster replica not found: %s", name) } - _, err := materialize.ScanClusterReplica(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanClusterReplica(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllClusterReplicaDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_cluster_replica" { diff --git a/pkg/provider/acceptance_cluster_test.go b/pkg/provider/acceptance_cluster_test.go index 80829ce1..a1e1a1c8 100644 --- a/pkg/provider/acceptance_cluster_test.go +++ b/pkg/provider/acceptance_cluster_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccCluster_basic(t *testing.T) { @@ -372,18 +371,26 @@ func testAccClusterManagedResource( func testAccCheckClusterExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("cluster not found: %s", name) } - _, err := materialize.ScanCluster(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanCluster(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllClusterDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_cluster" { diff --git a/pkg/provider/acceptance_connection_confluent_schema_registry_test.go b/pkg/provider/acceptance_connection_confluent_schema_registry_test.go index 6ab80c82..a1665009 100644 --- a/pkg/provider/acceptance_connection_confluent_schema_registry_test.go +++ b/pkg/provider/acceptance_connection_confluent_schema_registry_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccConnConfluentSchemaRegistry_basic(t *testing.T) { @@ -126,18 +125,26 @@ resource "materialize_connection_confluent_schema_registry" "test_role" { func testAccCheckConnConfluentSchemaRegistryExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("connection confluent schema registry not found: %s", name) } - _, err := materialize.ScanConnection(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanConnection(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllConnConfluentSchemaRegistryDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_connection_confluent_schema_registry" { diff --git a/pkg/provider/acceptance_connection_kafka_test.go b/pkg/provider/acceptance_connection_kafka_test.go index 5e099dfc..857cb30d 100644 --- a/pkg/provider/acceptance_connection_kafka_test.go +++ b/pkg/provider/acceptance_connection_kafka_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccConnKafka_basic(t *testing.T) { @@ -264,18 +263,26 @@ func testAccConnKafkaSshResource(connectionName string) string { func testAccCheckConnKafkaExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("connection kafka not found: %s", name) } - _, err := materialize.ScanConnection(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanConnection(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllConnKafkaDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_connection_kafka" { diff --git a/pkg/provider/acceptance_connection_postgres_test.go b/pkg/provider/acceptance_connection_postgres_test.go index 1d38e3bc..419a1c59 100644 --- a/pkg/provider/acceptance_connection_postgres_test.go +++ b/pkg/provider/acceptance_connection_postgres_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccConnPostgres_basic(t *testing.T) { @@ -162,18 +161,26 @@ resource "materialize_connection_postgres" "test_role" { func testAccCheckConnPostgresExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("connection postgres not found: %s", name) } - _, err := materialize.ScanConnection(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanConnection(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllConnPostgresDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_connection_postgres" { diff --git a/pkg/provider/acceptance_connection_ssh_tunnel_test.go b/pkg/provider/acceptance_connection_ssh_tunnel_test.go index 64fa0678..6de0d5d8 100644 --- a/pkg/provider/acceptance_connection_ssh_tunnel_test.go +++ b/pkg/provider/acceptance_connection_ssh_tunnel_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccConnSshTunnel_basic(t *testing.T) { @@ -136,18 +135,26 @@ resource "materialize_connection_ssh_tunnel" "test_role" { func testAccCheckConnSshTunnelExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("connection ssh tunnel not found: %s", name) } - _, err := materialize.ScanConnectionSshTunnel(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanConnectionSshTunnel(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllConnSshTunnelDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_connection_ssh_tunnel" { diff --git a/pkg/provider/acceptance_database_test.go b/pkg/provider/acceptance_database_test.go index 50b5449c..0e686607 100644 --- a/pkg/provider/acceptance_database_test.go +++ b/pkg/provider/acceptance_database_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccDatabase_basic(t *testing.T) { @@ -130,18 +129,26 @@ func testAccDatabaseResource(roleName, databaseName, databse2Name, databaseOwner func testAccCheckDatabaseExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("database not found: %s", name) } - _, err := materialize.ScanDatabase(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanDatabase(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllDatabasesDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_database" { diff --git a/pkg/provider/acceptance_grant_system_privilege_test.go b/pkg/provider/acceptance_grant_system_privilege_test.go index d1cb5967..addc074b 100644 --- a/pkg/provider/acceptance_grant_system_privilege_test.go +++ b/pkg/provider/acceptance_grant_system_privilege_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "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/jmoiron/sqlx" ) func TestAccGrantSystemPrivilege_basic(t *testing.T) { @@ -71,7 +71,11 @@ resource "materialize_grant_system_privilege" "test" { func testAccCheckGrantSystemPrivilegeExists(grantName, roleName, privilege string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } _, ok := s.RootModule().Resources[grantName] if !ok { return fmt.Errorf("grant not found") @@ -82,7 +86,7 @@ func testAccCheckGrantSystemPrivilegeExists(grantName, roleName, privilege strin // return err // } - _, err := materialize.ScanSystemPrivileges(db) + _, err = materialize.ScanSystemPrivileges(db) if err != nil { return err } @@ -93,8 +97,12 @@ func testAccCheckGrantSystemPrivilegeExists(grantName, roleName, privilege strin func testAccCheckGrantSystemPrivilegeRevoked(roleName, privilege string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`REVOKE %[1]s ON SYSTEM FROM %[2]s;`, roleName, privilege)) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`REVOKE %[1]s ON SYSTEM FROM %[2]s;`, roleName, privilege)) return err } } diff --git a/pkg/provider/acceptance_index_test.go b/pkg/provider/acceptance_index_test.go index bdc96079..51493a14 100644 --- a/pkg/provider/acceptance_index_test.go +++ b/pkg/provider/acceptance_index_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccIndex_basic(t *testing.T) { @@ -156,26 +155,38 @@ func testAccIndexWithComment(viewName, indexName, comment string) string { func testAccCheckIndexExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("index not found: %s", name) } - _, err := materialize.ScanIndex(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanIndex(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckIndexDisappears(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`DROP INDEX "%s" RESTRICT;`, name)) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`DROP INDEX "%s" RESTRICT;`, name)) return err } } func testAccCheckAllIndexDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_index" { diff --git a/pkg/provider/acceptance_materialized_view_test.go b/pkg/provider/acceptance_materialized_view_test.go index 5cff2015..30edf652 100644 --- a/pkg/provider/acceptance_materialized_view_test.go +++ b/pkg/provider/acceptance_materialized_view_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccMaterializedView_basic(t *testing.T) { @@ -140,18 +139,26 @@ func testAccMaterializedViewResource(roleName, materializeViewName, materializeV func testAccCheckMaterializedViewExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Materialized View not found: %s", name) } - _, err := materialize.ScanMaterializedView(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanMaterializedView(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllMaterializedViewsDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_materialized_view" { diff --git a/pkg/provider/acceptance_role_grant_test.go b/pkg/provider/acceptance_role_grant_test.go index f6eaa920..f084cdee 100644 --- a/pkg/provider/acceptance_role_grant_test.go +++ b/pkg/provider/acceptance_role_grant_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "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/jmoiron/sqlx" ) func TestAccGrantRole_basic(t *testing.T) { @@ -85,7 +85,11 @@ resource "materialize_role_grant" "test" { func testAccCheckGrantRoleExists(grantName, roleName, granteeName string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } _, ok := s.RootModule().Resources[grantName] if !ok { return fmt.Errorf("grant not found") @@ -112,8 +116,12 @@ func testAccCheckGrantRoleExists(grantName, roleName, granteeName string) resour func testAccCheckGrantRoleRevoked(roleName, granteeName string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`REVOKE %[1]s FROM %[2]s;`, roleName, granteeName)) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`REVOKE %[1]s FROM %[2]s;`, roleName, granteeName)) return err } } diff --git a/pkg/provider/acceptance_role_test.go b/pkg/provider/acceptance_role_test.go index 3041b039..ed781e22 100644 --- a/pkg/provider/acceptance_role_test.go +++ b/pkg/provider/acceptance_role_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccRole_basic(t *testing.T) { @@ -107,18 +106,26 @@ resource "materialize_role" "test" { func testAccCheckRoleExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("role not found: %s", name) } - _, err := materialize.ScanRole(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanRole(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllRolesDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_role" { diff --git a/pkg/provider/acceptance_schema_test.go b/pkg/provider/acceptance_schema_test.go index 5271a460..b9b15e90 100644 --- a/pkg/provider/acceptance_schema_test.go +++ b/pkg/provider/acceptance_schema_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccSchema_basic(t *testing.T) { @@ -129,25 +128,33 @@ func testAccSchemaResource(roleName, schemaName, schema2Name, schemaOwner, comme func testAccCheckSchemaExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Schema not found: %s", name) } - _, err := materialize.ScanSchema(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSchema(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllSchemasDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_schema" { continue } - _, err := materialize.ScanSchema(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSchema(db, utils.ExtractId(r.Primary.ID)) if err == nil { return fmt.Errorf("Schema %v still exists", utils.ExtractId(r.Primary.ID)) } else if err != sql.ErrNoRows { diff --git a/pkg/provider/acceptance_secret_test.go b/pkg/provider/acceptance_secret_test.go index deefe202..8f214126 100644 --- a/pkg/provider/acceptance_secret_test.go +++ b/pkg/provider/acceptance_secret_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccSecret_basic(t *testing.T) { @@ -137,18 +136,26 @@ func testAccSecretResource(roleName, secretName, secretValue, secret2Name, secre func testAccCheckSecretExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("secret not found: %s", name) } - _, err := materialize.ScanSecret(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSecret(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllSecretsDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_secret" { diff --git a/pkg/provider/acceptance_sink_kafka_test.go b/pkg/provider/acceptance_sink_kafka_test.go index a5f2a213..29bba666 100644 --- a/pkg/provider/acceptance_sink_kafka_test.go +++ b/pkg/provider/acceptance_sink_kafka_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccSinkKafka_basic(t *testing.T) { @@ -336,26 +335,38 @@ func testAccSinkKafkaAvroResource(sinkName string) string { func testAccCheckSinkKafkaExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("sink kafka not found: %s", name) } - _, err := materialize.ScanSink(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSink(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckSinkKafkaDisappears(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`DROP SINK "%s";`, name)) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`DROP SINK "%s";`, name)) return err } } func testAccCheckAllSinkKafkaDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_sink_kafka" { diff --git a/pkg/provider/acceptance_source_kafka_test.go b/pkg/provider/acceptance_source_kafka_test.go index 3ba42191..3d4845ae 100644 --- a/pkg/provider/acceptance_source_kafka_test.go +++ b/pkg/provider/acceptance_source_kafka_test.go @@ -11,7 +11,6 @@ import ( "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/jmoiron/sqlx" ) // Initialize a topic used by Kafka Testacc against the docker compose @@ -333,18 +332,26 @@ func testAccSourceKafkaResourceAvro(sourceName string) string { func testAccCheckSourceKafkaExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("source kafka not found: %s", name) } - _, err := materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllSourceKafkaDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_source_kafka" { diff --git a/pkg/provider/acceptance_source_load_generator_test.go b/pkg/provider/acceptance_source_load_generator_test.go index a05de269..be11e835 100644 --- a/pkg/provider/acceptance_source_load_generator_test.go +++ b/pkg/provider/acceptance_source_load_generator_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccSourceLoadGeneratorCounter_basic(t *testing.T) { @@ -313,26 +312,38 @@ func testAccSourceLoadGeneratorTPCHResource(sourceName string) string { func testAccCheckSourceLoadGeneratorExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("SourceLoadGenerator not found: %s", name) } - _, err := materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckSourceLoadGeneratorDisappears(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`DROP SOURCE "%s";`, name)) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`DROP SOURCE "%s";`, name)) return err } } func testAccCheckAllSourceLoadGeneratorsDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_source_load_generator" { diff --git a/pkg/provider/acceptance_source_postgres_test.go b/pkg/provider/acceptance_source_postgres_test.go index 8994c53c..eda4c933 100644 --- a/pkg/provider/acceptance_source_postgres_test.go +++ b/pkg/provider/acceptance_source_postgres_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccSourcePostgres_basic(t *testing.T) { @@ -416,18 +415,26 @@ func testAccSourcePostgresResourceSchema(sourceName string) string { func testAccCheckSourcePostgresExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("source postgres not found: %s", name) } - _, err := materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllSourcePostgresDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_source_postgres" { diff --git a/pkg/provider/acceptance_source_webhook_test.go b/pkg/provider/acceptance_source_webhook_test.go index 2c44867b..59edbbc7 100644 --- a/pkg/provider/acceptance_source_webhook_test.go +++ b/pkg/provider/acceptance_source_webhook_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccSourceWebhook_basic(t *testing.T) { @@ -336,18 +335,26 @@ func testAccSourceWebhookRudderstackResource(sourceName string) string { func testAccCheckSourceWebhookExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("source webhook not found: %s", name) } - _, err := materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanSource(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllSourceWebhookDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_source_webhook" { diff --git a/pkg/provider/acceptance_table_test.go b/pkg/provider/acceptance_table_test.go index 2aee214c..9c064db2 100644 --- a/pkg/provider/acceptance_table_test.go +++ b/pkg/provider/acceptance_table_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccTable_basic(t *testing.T) { @@ -220,18 +219,26 @@ func testAccTableResource(roleName, tableName, tableRoleName, tableOwnership, co func testAccCheckTableExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Table not found: %s", name) } - _, err := materialize.ScanTable(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanTable(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllTablesDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_table" { diff --git a/pkg/provider/acceptance_type_test.go b/pkg/provider/acceptance_type_test.go index c3460f17..f033d34f 100644 --- a/pkg/provider/acceptance_type_test.go +++ b/pkg/provider/acceptance_type_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccTypeList_basic(t *testing.T) { @@ -225,18 +224,26 @@ func testAccTypeMapResource(typeName string) string { func testAccCheckTypeExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("Type not found: %s", name) } - _, err := materialize.ScanType(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanType(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllTypesDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_type" { diff --git a/pkg/provider/acceptance_view_test.go b/pkg/provider/acceptance_view_test.go index 2a59293b..61195ff4 100644 --- a/pkg/provider/acceptance_view_test.go +++ b/pkg/provider/acceptance_view_test.go @@ -10,7 +10,6 @@ import ( "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/jmoiron/sqlx" ) func TestAccView_basic(t *testing.T) { @@ -139,18 +138,26 @@ func testAccViewResource(roleName, viewName, view2Name, viewOwner, comment strin func testAccCheckViewExists(name string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } r, ok := s.RootModule().Resources[name] if !ok { return fmt.Errorf("View not found: %s", name) } - _, err := materialize.ScanView(db, utils.ExtractId(r.Primary.ID)) + _, err = materialize.ScanView(db, utils.ExtractId(r.Primary.ID)) return err } } func testAccCheckAllViewsDestroyed(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } for _, r := range s.RootModule().Resources { if r.Type != "materialize_view" { diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 3ebaf258..9f36eefe 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -3,8 +3,9 @@ package provider import ( "context" "fmt" - "net/url" + "log" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" "github.com/MaterializeInc/terraform-provider-materialize/pkg/datasources" "github.com/MaterializeInc/terraform-provider-materialize/pkg/resources" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" @@ -12,24 +13,11 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" _ "github.com/jackc/pgx/stdlib" - "github.com/jmoiron/sqlx" ) func Provider(version string) *schema.Provider { return &schema.Provider{ Schema: map[string]*schema.Schema{ - "host": { - Type: schema.TypeString, - Optional: true, - Description: "Materialize host. Can also come from the `MZ_HOST` environment variable.", - DefaultFunc: schema.EnvDefaultFunc("MZ_HOST", nil), - }, - "user": { - Type: schema.TypeString, - Optional: true, - Description: "Materialize user. Can also come from the `MZ_USER` environment variable.", - DefaultFunc: schema.EnvDefaultFunc("MZ_USER", nil), - }, "password": { Type: schema.TypeString, Optional: true, @@ -37,12 +25,6 @@ func Provider(version string) *schema.Provider { Description: "Materialize host. Can also come from the `MZ_PASSWORD` environment variable.", DefaultFunc: schema.EnvDefaultFunc("MZ_PASSWORD", nil), }, - "port": { - Type: schema.TypeInt, - Optional: true, - Description: "The Materialize port number to connect to at the server host. Can also come from the `MZ_PORT` environment variable. Defaults to 6875.", - DefaultFunc: schema.EnvDefaultFunc("MZ_PORT", 6875), - }, "database": { Type: schema.TypeString, Optional: true, @@ -55,8 +37,28 @@ func Provider(version string) *schema.Provider { DefaultFunc: schema.EnvDefaultFunc("MZ_SSLMODE", "require"), Description: "For testing purposes, the SSL mode to use.", }, + "endpoint": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("MZ_ENDPOINT", "https://admin.cloud.materialize.com"), + Description: "The endpoint for the Materialize API.", + }, + "cloud_endpoint": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("MZ_CLOUD_ENDPOINT", "https://api.cloud.materialize.com"), + Description: "The endpoint for the Materialize Cloud API.", + }, + "default_region": { + Type: schema.TypeString, + Optional: true, + Description: "The default region if not specified in the resource", + DefaultFunc: schema.EnvDefaultFunc("MZ_DEFAULT_REGION", "aws/us-east-1"), + }, }, ResourcesMap: map[string]*schema.Resource{ + "materialize_app_password": resources.AppPassword(), + "materialize_user": resources.User(), "materialize_cluster": resources.Cluster(), "materialize_cluster_grant": resources.GrantCluster(), "materialize_cluster_grant_default_privilege": resources.GrantClusterDefaultPrivilege(), @@ -108,6 +110,7 @@ func Provider(version string) *schema.Provider { "materialize_egress_ips": datasources.EgressIps(), "materialize_index": datasources.Index(), "materialize_materialized_view": datasources.MaterializedView(), + "materialize_region": datasources.Region(), "materialize_role": datasources.Role(), "materialize_schema": datasources.Schema(), "materialize_secret": datasources.Secret(), @@ -124,41 +127,89 @@ func Provider(version string) *schema.Provider { } func providerConfigure(ctx context.Context, d *schema.ResourceData, version string) (interface{}, diag.Diagnostics) { - host := d.Get("host").(string) - user := d.Get("user").(string) password := d.Get("password").(string) - port := d.Get("port").(int) database := d.Get("database").(string) sslmode := d.Get("sslmode").(string) + endpoint := d.Get("endpoint").(string) + cloudEndpoint := d.Get("cloud_endpoint").(string) + defaultRegion := clients.Region(d.Get("default_region").(string)) application_name := fmt.Sprintf("terraform-provider-materialize v%s", version) - // Set the host in the utils package so that the region can be extracted from it - err := utils.SetRegionFromHostname(host) + err := utils.SetDefaultRegion(string(defaultRegion)) if err != nil { return nil, diag.FromErr(err) } - url := &url.URL{ - Scheme: "postgres", - User: url.UserPassword(user, password), - Host: fmt.Sprintf("%s:%d", host, port), - Path: database, - RawQuery: url.Values{ - "application_name": {application_name}, - "sslmode": {sslmode}, - }.Encode(), + // Initialize the Frontegg client. + fronteggClient, err := clients.NewFronteggClient(ctx, password, endpoint) + if err != nil { + return nil, diag.Errorf("Unable to create Frontegg client: %s", err) } - var diags diag.Diagnostics - db, err := sqlx.Open("pgx", url.String()) + // Initialize the Cloud API client using the Frontegg client and endpoint + cloudAPIClient := clients.NewCloudAPIClient(fronteggClient, cloudEndpoint) + regionsEnabled := make(map[clients.Region]bool) + + // Get the list of cloud providers + providers, err := cloudAPIClient.ListCloudProviders(ctx) if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Unable to create Materialize client", - Detail: "Unable to authenticate user for authenticated Materialize client", - }) - return nil, diags + return nil, diag.Errorf("Unable to list cloud providers: %s", err) + } + + // Store the DB clients for all regions. + dbClients := make(map[clients.Region]*clients.DBClient) + + for _, provider := range providers { + regionDetails, err := cloudAPIClient.GetRegionDetails(ctx, provider) + + log.Printf("[DEBUG] Region details for provider %s: %v\n", provider.ID, regionDetails) + + if err != nil { + log.Printf("[ERROR] Error getting region details for provider %s: %v\n", provider.ID, err) + continue + } + + // Check if regionDetails or RegionInfo is nil before proceeding + if regionDetails == nil || regionDetails.RegionInfo == nil { + continue + } + + regionsEnabled[clients.Region(provider.ID)] = regionDetails.RegionInfo != nil && regionDetails.RegionInfo.Resolvable + + // Get the database connection details for the region + host, port, err := clients.SplitHostPort(regionDetails.RegionInfo.SqlAddress) + if err != nil { + log.Printf("[ERROR] Error splitting host and port for region %s: %v\n", provider.ID, err) + continue + } + + user := fronteggClient.Email + + // Instantiate a new DB client for the region + dbClient, diags := clients.NewDBClient(host, user, password, port, database, application_name, version, sslmode) + if diags.HasError() { + log.Printf("[ERROR] Error initializing DB client for region %s: %v\n", provider.ID, diags) + continue + } + + dbClients[clients.Region(provider.ID)] = dbClient + } + + // Check if at least one region has been initialized successfully + if len(dbClients) == 0 { + return nil, diag.Errorf("No database regions were initialized. Please check your configuration.") + } + + log.Printf("[DEBUG] Initialized DB clients for regions: %v\n", dbClients) + + // Construct and return the provider meta with all clients initialized. + providerMeta := &utils.ProviderMeta{ + DB: dbClients, + Frontegg: fronteggClient, + CloudAPI: cloudAPIClient, + DefaultRegion: clients.Region(defaultRegion), + RegionsEnabled: regionsEnabled, } - return db, diags + return providerMeta, nil } diff --git a/pkg/provider/provider_test.go b/pkg/provider/provider_test.go index beb00ab1..0d2c6fa1 100644 --- a/pkg/provider/provider_test.go +++ b/pkg/provider/provider_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/materialize" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/jmoiron/sqlx" "golang.org/x/exp/slices" ) @@ -42,8 +42,12 @@ func testAccPreCheck(t *testing.T) { func testAccAddColumnComment(object materialize.MaterializeObject, column, comment string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`COMMENT ON COLUMN %[1]s.%[2]s IS %[3]s;`, + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`COMMENT ON COLUMN %[1]s.%[2]s IS %[3]s;`, object.QualifiedName(), column, materialize.QuoteString(comment), @@ -54,16 +58,24 @@ func testAccAddColumnComment(object materialize.MaterializeObject, column, comme func testAccCheckObjectDisappears(object materialize.MaterializeObject) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`DROP %[1]s %[2]s;`, object.ObjectType, object.QualifiedName())) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`DROP %[1]s %[2]s;`, object.ObjectType, object.QualifiedName())) return err } } func testAccCheckGrantRevoked(object materialize.MaterializeObject, roleName, privilege string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf( + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf( `REVOKE %[1]s ON %[2]s %[3]s FROM "%[4]s";`, privilege, object.ObjectType, object.QualifiedName(), roleName, )) @@ -73,7 +85,11 @@ func testAccCheckGrantRevoked(object materialize.MaterializeObject, roleName, pr func testAccCheckGrantExists(object materialize.MaterializeObject, grantName, roleName, privilege string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } _, ok := s.RootModule().Resources[grantName] if !ok { return fmt.Errorf("grant not found") @@ -100,15 +116,23 @@ func testAccCheckGrantExists(object materialize.MaterializeObject, grantName, ro func testAccCheckGrantDefaultPrivilegeRevoked(objectType, granteeName, targetName, privilege string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) - _, err := db.Exec(fmt.Sprintf(`ALTER DEFAULT PRIVILEGES FOR ROLE %[1]s REVOKE %[2]s ON %[3]sS FROM %[4]s;`, targetName, privilege, objectType, granteeName)) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } + _, err = db.Exec(fmt.Sprintf(`ALTER DEFAULT PRIVILEGES FOR ROLE %[1]s REVOKE %[2]s ON %[3]sS FROM %[4]s;`, targetName, privilege, objectType, granteeName)) return err } } func testAccCheckGrantDefaultPrivilegeExists(objectType, grantName, granteeName, targetName, privilege string) resource.TestCheckFunc { return func(s *terraform.State) error { - db := testAccProvider.Meta().(*sqlx.DB) + meta := testAccProvider.Meta() + db, _, err := utils.GetDBClientFromMeta(meta, nil) + if err != nil { + return fmt.Errorf("error getting DB client: %s", err) + } _, ok := s.RootModule().Resources[grantName] if !ok { return fmt.Errorf("default grant not found") diff --git a/pkg/resources/resource_app_password.go b/pkg/resources/resource_app_password.go new file mode 100644 index 00000000..23d1e849 --- /dev/null +++ b/pkg/resources/resource_app_password.go @@ -0,0 +1,280 @@ +package resources + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func AppPassword() *schema.Resource { + return &schema.Resource{ + CreateContext: appPasswordCreate, + ReadContext: appPasswordRead, + DeleteContext: appPasswordDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "owner": { + Type: schema.TypeString, + Computed: true, + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + }, + "secret": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + "password": { + Type: schema.TypeString, + Computed: true, + Sensitive: true, + }, + }, + } +} + +type appPasswordCreateRequest struct { + Description string `json:"description"` +} + +type appPasswordResponse struct { + ClientID string `json:"clientId"` + Description string `json:"description"` + Owner string `json:"owner"` + CreatedAt time.Time `json:"created_at"` + Secret string `json:"secret"` +} + +const ( + apiTokenPath = "/identity/resources/users/api-tokens/v1" +) + +func appPasswordCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + + // Create the app password using the helper function. + response, err := createAppPassword(ctx, d, providerMeta.Frontegg) + if err != nil { + return diag.FromErr(err) + } + + clientId := strings.ReplaceAll(response.ClientID, "-", "") + secret := strings.ReplaceAll(response.Secret, "-", "") + appPassword := fmt.Sprintf("mzp_%s%s", clientId, secret) + + // Set the Terraform resource ID and state. + d.SetId(response.ClientID) + if err := d.Set("name", response.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("created_at", response.CreatedAt.Format(time.RFC3339)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("secret", response.Secret); err != nil { + return diag.FromErr(err) + } + if err := d.Set("password", appPassword); err != nil { + return diag.FromErr(err) + } + // TODO: Get the owner from the API as it's not returned in the response. + // For now, we can either leave this unset or set a default value. + // d.Set("owner", "some-default-or-fetched-value") + + return nil +} + +// appPasswordRead reads the app password resource from the API. +func appPasswordRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + + client := providerMeta.Frontegg + resourceID := d.Id() + + passwords, err := listAppPasswords(ctx, client) + if err != nil { + return diag.FromErr(err) + } + + foundPassword := findAppPasswordById(passwords, resourceID) + if foundPassword == nil { + d.SetId("") + return nil + } + + appPassword := clients.ConstructAppPassword(foundPassword.ClientID, foundPassword.Secret) + + // Update the Terraform state with the retrieved values. + d.Set("name", foundPassword.Description) + d.Set("created_at", foundPassword.CreatedAt.Format(time.RFC3339)) + d.Set("secret", foundPassword.Secret) + d.Set("password", appPassword) + // TODO: Get the owner from the API as it's not returned in the response. + // d.Set("owner", foundPassword.Owner) + + return nil +} + +func appPasswordDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + + client := providerMeta.Frontegg + resourceID := d.Id() + + err = deleteAppPassword(ctx, client, resourceID) + if err != nil { + return diag.FromErr(err) + } + + d.SetId("") + return nil +} + +// Helper function to create or make app password +func createAppPassword(ctx context.Context, d *schema.ResourceData, client *clients.FronteggClient) (appPasswordResponse, error) { + var response appPasswordResponse + + createRequest := appPasswordCreateRequest{ + Description: d.Get("name").(string), + } + requestBody, err := json.Marshal(createRequest) + if err != nil { + return response, err + } + + resp, err := doRequest(ctx, client, "POST", getApiEndpoint(client, apiTokenPath), requestBody) + if err != nil { + return response, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + return response, handleApiError(resp) + } + + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return response, err + } + + return response, nil +} + +// Helper function to construct the full API endpoint for app passwords +func getApiEndpoint(client *clients.FronteggClient, resourcePath string, resourceID ...string) string { + if len(resourceID) > 0 { + return fmt.Sprintf("%s%s/%s", client.Endpoint, resourcePath, resourceID[0]) + } + return fmt.Sprintf("%s%s", client.Endpoint, resourcePath) +} + +// Helper function to perform HTTP requests +func doRequest(ctx context.Context, client *clients.FronteggClient, method, url string, body []byte) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + req.Header.Add("Content-Type", "application/json") + + return client.HTTPClient.Do(req) +} + +// Helper function to handle API errors +func handleApiError(resp *http.Response) error { + responseBody, _ := io.ReadAll(resp.Body) + if resp.StatusCode == http.StatusNotFound { + return nil // Resource not found, equivalent to a successful delete + } + return fmt.Errorf("API error: %s - %s", resp.Status, string(responseBody)) +} + +// Helper function to delete an app password. +func deleteAppPassword(ctx context.Context, client *clients.FronteggClient, resourceID string) error { + resp, err := doRequest(ctx, client, "DELETE", getApiEndpoint(client, apiTokenPath, resourceID), nil) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return handleApiError(resp) + } + return nil +} + +// Helper function to find an app password by ID. +func findAppPasswordById(passwords []appPasswordResponse, id string) *appPasswordResponse { + for _, password := range passwords { + if password.ClientID == id { + return &password + } + } + return nil +} + +// listAppPasswords fetches a list of app passwords from the API. +func listAppPasswords(ctx context.Context, client *clients.FronteggClient) ([]appPasswordResponse, error) { + var passwords []appPasswordResponse + + // Construct the request URL + url := getApiEndpoint(client, apiTokenPath) + + // Create and send the HTTP request + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("creating request failed: %w", err) + } + req.Header.Add("Authorization", "Bearer "+client.Token) + + // Execute the request + resp, err := client.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("executing request failed: %w", err) + } + defer resp.Body.Close() + + // Check the response code + if resp.StatusCode != http.StatusOK { + return nil, handleApiError(resp) + } + + // Decode the response body + responseBody, readErr := io.ReadAll(resp.Body) + if readErr != nil { + return nil, fmt.Errorf("reading response body failed: %w", readErr) + } + + if err := json.Unmarshal(responseBody, &passwords); err != nil { + return nil, fmt.Errorf("decoding response failed: %w", err) + } + + return passwords, nil +} diff --git a/pkg/resources/resource_app_password_test.go b/pkg/resources/resource_app_password_test.go new file mode 100644 index 00000000..b65024fe --- /dev/null +++ b/pkg/resources/resource_app_password_test.go @@ -0,0 +1,95 @@ +package resources + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/require" +) + +func TestAppPasswordResourceCreate(t *testing.T) { + r := require.New(t) + + in := map[string]interface{}{ + "name": "test-app-password", + } + d := schema.TestResourceDataRaw(t, AppPassword().Schema, in) + r.NotNil(d) + + testhelpers.WithMockFronteggServer(t, func(serverURL string) { + client := &clients.FronteggClient{ + Endpoint: serverURL, + HTTPClient: &http.Client{}, + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } + + providerMeta := &utils.ProviderMeta{ + Frontegg: client, + } + + if err := appPasswordCreate(context.TODO(), d, providerMeta); err != nil { + t.Fatal(err) + } + + r.Equal("test-app-password", d.Get("name")) + r.Equal("mock-secret", d.Get("secret")) + }) +} + +func TestAppPasswordResourceRead(t *testing.T) { + r := require.New(t) + + testhelpers.WithMockFronteggServer(t, func(serverURL string) { + client := &clients.FronteggClient{ + Endpoint: serverURL, + HTTPClient: &http.Client{}, + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } + + providerMeta := &utils.ProviderMeta{ + Frontegg: client, + } + + d := schema.TestResourceDataRaw(t, AppPassword().Schema, nil) + d.SetId("mock-client-id") + + if err := appPasswordRead(context.TODO(), d, providerMeta); err != nil { + t.Fatal(err) + } + + // Add assertions to check the state after read + r.Equal("mock-client-id", d.Id()) + r.Equal("test-app-password", d.Get("name")) + }) +} + +func TestAppPasswordResourceDelete(t *testing.T) { + r := require.New(t) + + testhelpers.WithMockFronteggServer(t, func(serverURL string) { + client := &clients.FronteggClient{ + Endpoint: serverURL, + HTTPClient: &http.Client{}, + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } + + providerMeta := &utils.ProviderMeta{ + Frontegg: client, + } + + d := schema.TestResourceDataRaw(t, AppPassword().Schema, nil) + d.SetId("mock-client-id") + + if err := appPasswordDelete(context.TODO(), d, providerMeta); err != nil { + t.Fatal(err) + } + + r.Empty(d.Id()) + }) +} diff --git a/pkg/resources/resource_cluster.go b/pkg/resources/resource_cluster.go index 7166b006..4e68d44e 100644 --- a/pkg/resources/resource_cluster.go +++ b/pkg/resources/resource_cluster.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var clusterSchema = map[string]*schema.Schema{ @@ -36,6 +35,7 @@ var clusterSchema = map[string]*schema.Schema{ "introspection_interval": IntrospectionIntervalSchema(false, []string{"size"}), "introspection_debugging": IntrospectionDebuggingSchema(false, []string{"size"}), "idle_arrangement_merge_effort": IdleArrangementMergeEffortSchema(false, []string{"size"}), + "region": RegionSchema(), } func Cluster() *schema.Resource { @@ -57,7 +57,12 @@ func Cluster() *schema.Resource { func clusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanCluster(meta.(*sqlx.DB), utils.ExtractId(i)) + + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanCluster(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -65,7 +70,7 @@ func clusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.ClusterName.String); err != nil { return diag.FromErr(err) @@ -97,8 +102,12 @@ func clusterRead(ctx context.Context, d *schema.ResourceData, meta interface{}) func clusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { clusterName := d.Get("name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CLUSTER", Name: clusterName} - b := materialize.NewClusterBuilder(meta.(*sqlx.DB), o) + b := materialize.NewClusterBuilder(metaDb, o) // managed cluster options if size, ok := d.GetOk("size"); ok { @@ -139,7 +148,7 @@ func clusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{} // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -150,7 +159,7 @@ func clusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{} // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -160,11 +169,11 @@ func clusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{} } // set id - i, err := materialize.ClusterId(meta.(*sqlx.DB), o) + i, err := materialize.ClusterId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return clusterRead(ctx, d, meta) } @@ -172,17 +181,21 @@ func clusterCreate(ctx context.Context, d *schema.ResourceData, meta interface{} func clusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { clusterName := d.Get("name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CLUSTER", Name: clusterName} if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) } } - b := materialize.NewClusterBuilder(meta.(*sqlx.DB), o) + b := materialize.NewClusterBuilder(metaDb, o) if _, ok := d.GetOk("size"); ok { if d.HasChange("size") { _, newSize := d.GetChange("size") @@ -209,7 +222,7 @@ func clusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{} // if d.HasChange("availability_zones") { // _, n := d.GetChange("availability_zones") // azs := materialize.GetSliceValueString(n.([]interface{})) - // b := materialize.NewClusterBuilder(meta.(*sqlx.DB), o) + // b := materialize.NewClusterBuilder(metaDb, o) // if err := b.SetAvailabilityZones(azs); err != nil { // return diag.FromErr(err) // } @@ -239,7 +252,7 @@ func clusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{} if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -252,8 +265,12 @@ func clusterUpdate(ctx context.Context, d *schema.ResourceData, meta interface{} func clusterDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { clusterName := d.Get("name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: clusterName} - b := materialize.NewClusterBuilder(meta.(*sqlx.DB), o) + b := materialize.NewClusterBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_cluster_replica.go b/pkg/resources/resource_cluster_replica.go index 227e3026..05baa746 100644 --- a/pkg/resources/resource_cluster_replica.go +++ b/pkg/resources/resource_cluster_replica.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var clusterReplicaSchema = map[string]*schema.Schema{ @@ -29,6 +28,7 @@ var clusterReplicaSchema = map[string]*schema.Schema{ "introspection_interval": IntrospectionIntervalSchema(true, []string{}), "introspection_debugging": IntrospectionDebuggingSchema(true, []string{}), "idle_arrangement_merge_effort": IdleArrangementMergeEffortSchema(true, []string{}), + "region": RegionSchema(), } func ClusterReplica() *schema.Resource { @@ -53,7 +53,11 @@ func ClusterReplica() *schema.Resource { func clusterReplicaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanClusterReplica(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanClusterReplica(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -61,7 +65,7 @@ func clusterReplicaRead(ctx context.Context, d *schema.ResourceData, meta interf return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.ReplicaName.String); err != nil { return diag.FromErr(err) @@ -94,12 +98,17 @@ func clusterReplicaCreate(ctx context.Context, d *schema.ResourceData, meta inte replicaName := d.Get("name").(string) clusterName := d.Get("cluster_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ ObjectType: "CLUSTER REPLICA", Name: replicaName, ClusterName: clusterName, } - b := materialize.NewClusterReplicaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewClusterReplicaBuilder(metaDb, o) if v, ok := d.GetOk("size"); ok { b.Size(v.(string)) @@ -132,7 +141,7 @@ func clusterReplicaCreate(ctx context.Context, d *schema.ResourceData, meta inte // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -142,11 +151,11 @@ func clusterReplicaCreate(ctx context.Context, d *schema.ResourceData, meta inte } // set id - i, err := materialize.ClusterReplicaId(meta.(*sqlx.DB), o) + i, err := materialize.ClusterReplicaId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return clusterReplicaRead(ctx, d, meta) } @@ -155,6 +164,10 @@ func clusterReplicaUpdate(ctx context.Context, d *schema.ResourceData, meta inte replicaName := d.Get("name").(string) clusterName := d.Get("cluster_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ ObjectType: "CLUSTER REPLICA", Name: replicaName, @@ -163,7 +176,7 @@ func clusterReplicaUpdate(ctx context.Context, d *schema.ResourceData, meta inte if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -177,8 +190,12 @@ func clusterReplicaDelete(ctx context.Context, d *schema.ResourceData, meta inte replicaName := d.Get("name").(string) clusterName := d.Get("cluster_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: replicaName, ClusterName: clusterName} - b := materialize.NewClusterReplicaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewClusterReplicaBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_cluster_replica_test.go b/pkg/resources/resource_cluster_replica_test.go index ba42d50e..bf7bba8a 100644 --- a/pkg/resources/resource_cluster_replica_test.go +++ b/pkg/resources/resource_cluster_replica_test.go @@ -9,7 +9,6 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -29,7 +28,7 @@ func TestResourceClusterReplicaCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, ClusterReplica().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec(` CREATE CLUSTER REPLICA "cluster"."replica" @@ -59,7 +58,7 @@ func TestResourceClusterReplicaCreate(t *testing.T) { // Confirm id is updated with region for 0.4.0 func TestResourceClusterReplicaReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -71,7 +70,7 @@ func TestResourceClusterReplicaReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_cluster_replicas.id = 'u1'` testhelpers.MockClusterReplicaScan(mock, pp) @@ -96,7 +95,7 @@ func TestResourceClusterReplicaDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, ClusterReplica().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP CLUSTER REPLICA "cluster"."replica";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := clusterReplicaDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_cluster_test.go b/pkg/resources/resource_cluster_test.go index 6c9e469b..c9519321 100644 --- a/pkg/resources/resource_cluster_test.go +++ b/pkg/resources/resource_cluster_test.go @@ -9,7 +9,6 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -30,7 +29,7 @@ func TestResourceClusterCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Cluster().Schema, inCluster) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec(` CREATE CLUSTER "cluster" @@ -60,7 +59,7 @@ func TestResourceClusterCreate(t *testing.T) { // Confirm id is updated with region for 0.4.0 func TestResourceClusterReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -72,7 +71,7 @@ func TestResourceClusterReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_clusters.id = 'u1'` testhelpers.MockClusterScan(mock, pp) @@ -98,7 +97,7 @@ func TestResourceClusterZeroReplicationCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Cluster().Schema, inClusterZeroReplication) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec(` CREATE CLUSTER "cluster" @@ -127,7 +126,7 @@ func TestResourceClusterDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Cluster().Schema, inCluster) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP CLUSTER "cluster";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := clusterDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_connection.go b/pkg/resources/resource_connection.go index 99e631ed..2deacd68 100644 --- a/pkg/resources/resource_connection.go +++ b/pkg/resources/resource_connection.go @@ -9,13 +9,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func connectionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanConnection(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanConnection(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -23,7 +26,7 @@ func connectionRead(ctx context.Context, d *schema.ResourceData, meta interface{ return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.ConnectionName.String); err != nil { return diag.FromErr(err) @@ -58,12 +61,16 @@ func connectionUpdate(ctx context.Context, d *schema.ResourceData, meta interfac schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnection(meta.(*sqlx.DB), o) + b := materialize.NewConnection(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -71,7 +78,7 @@ func connectionUpdate(ctx context.Context, d *schema.ResourceData, meta interfac if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -80,7 +87,7 @@ func connectionUpdate(ctx context.Context, d *schema.ResourceData, meta interfac if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -95,8 +102,12 @@ func connectionDelete(ctx context.Context, d *schema.ResourceData, meta interfac schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnection(meta.(*sqlx.DB), o) + b := materialize.NewConnection(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_connection_aws_privatelink.go b/pkg/resources/resource_connection_aws_privatelink.go index 48ce18d8..6b5e5d3c 100644 --- a/pkg/resources/resource_connection_aws_privatelink.go +++ b/pkg/resources/resource_connection_aws_privatelink.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var connectionAwsPrivatelinkSchema = map[string]*schema.Schema{ @@ -39,6 +38,7 @@ var connectionAwsPrivatelinkSchema = map[string]*schema.Schema{ Sensitive: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func ConnectionAwsPrivatelink() *schema.Resource { @@ -68,7 +68,11 @@ type ConnectionAwsPrivatelinkParams struct { func connectionAwsPrivatelinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanConnectionAwsPrivatelink(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanConnectionAwsPrivatelink(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -76,7 +80,7 @@ func connectionAwsPrivatelinkRead(ctx context.Context, d *schema.ResourceData, m return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.ConnectionName.String); err != nil { return diag.FromErr(err) @@ -115,8 +119,12 @@ func connectionAwsPrivatelinkCreate(ctx context.Context, d *schema.ResourceData, schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionAwsPrivatelinkBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionAwsPrivatelinkBuilder(metaDb, o) if v, ok := d.GetOk("service_name"); ok { b.PrivateLinkServiceName(v.(string)) @@ -134,7 +142,7 @@ func connectionAwsPrivatelinkCreate(ctx context.Context, d *schema.ResourceData, // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -145,7 +153,7 @@ func connectionAwsPrivatelinkCreate(ctx context.Context, d *schema.ResourceData, // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -155,11 +163,11 @@ func connectionAwsPrivatelinkCreate(ctx context.Context, d *schema.ResourceData, } // set id - i, err := materialize.ConnectionId(meta.(*sqlx.DB), o) + i, err := materialize.ConnectionId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return connectionAwsPrivatelinkRead(ctx, d, meta) } @@ -169,12 +177,16 @@ func connectionAwsPrivatelinkUpdate(ctx context.Context, d *schema.ResourceData, schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionAwsPrivatelinkBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionAwsPrivatelinkBuilder(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -182,7 +194,7 @@ func connectionAwsPrivatelinkUpdate(ctx context.Context, d *schema.ResourceData, if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) } @@ -190,7 +202,7 @@ func connectionAwsPrivatelinkUpdate(ctx context.Context, d *schema.ResourceData, if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_connection_aws_privatelink_test.go b/pkg/resources/resource_connection_aws_privatelink_test.go index 723b575c..160fd645 100644 --- a/pkg/resources/resource_connection_aws_privatelink_test.go +++ b/pkg/resources/resource_connection_aws_privatelink_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -25,7 +25,7 @@ func TestResourceConnectionAwsPrivatelinkCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, ConnectionAwsPrivatelink().Schema, inAwsPrivatelink) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE CONNECTION "database"."schema"."conn" @@ -56,7 +56,7 @@ func TestResourceConnectionAwsPrivatelinkReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_connections.id = 'u1'` testhelpers.MockConnectionAwsPrivatelinkScan(mock, pp) @@ -81,7 +81,7 @@ func TestResourceConnectionAwsPrivatelinkUpdate(t *testing.T) { d.Set("name", "old_conn") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER CONNECTION "database"."schema"."" RENAME TO "conn";`).WillReturnResult(sqlmock.NewResult(1, 1)) // Query Params diff --git a/pkg/resources/resource_connection_confluent_schema_registry.go b/pkg/resources/resource_connection_confluent_schema_registry.go index 9c9039f0..706a8a88 100644 --- a/pkg/resources/resource_connection_confluent_schema_registry.go +++ b/pkg/resources/resource_connection_confluent_schema_registry.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var connectionConfluentSchemaRegistrySchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var connectionConfluentSchemaRegistrySchema = map[string]*schema.Schema{ "aws_privatelink": IdentifierSchema("aws_privatelink", "The AWS PrivateLink configuration for the Confluent Schema Registry.", false), "validate": ValidateConnectionSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func ConnectionConfluentSchemaRegistry() *schema.Resource { @@ -56,8 +56,12 @@ func connectionConfluentSchemaRegistryCreate(ctx context.Context, d *schema.Reso schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionConfluentSchemaRegistryBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionConfluentSchemaRegistryBuilder(metaDb, o) if v, ok := d.GetOk("url"); ok { b.ConfluentSchemaRegistryUrl(v.(string)) @@ -109,7 +113,7 @@ func connectionConfluentSchemaRegistryCreate(ctx context.Context, d *schema.Reso // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -120,7 +124,7 @@ func connectionConfluentSchemaRegistryCreate(ctx context.Context, d *schema.Reso // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -130,11 +134,11 @@ func connectionConfluentSchemaRegistryCreate(ctx context.Context, d *schema.Reso } // set id - i, err := materialize.ConnectionId(meta.(*sqlx.DB), o) + i, err := materialize.ConnectionId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return connectionRead(ctx, d, meta) } diff --git a/pkg/resources/resource_connection_confluent_schema_registry_test.go b/pkg/resources/resource_connection_confluent_schema_registry_test.go index 0aa85c52..b5b12077 100644 --- a/pkg/resources/resource_connection_confluent_schema_registry_test.go +++ b/pkg/resources/resource_connection_confluent_schema_registry_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -43,7 +43,7 @@ func TestResourceConnectionConfluentSchemaRegistryCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, ConnectionConfluentSchemaRegistry().Schema, inConfluentSchemaRegistry) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE CONNECTION "database"."schema"."conn" TO CONFLUENT SCHEMA REGISTRY \(URL 'http://localhost:8081', USERNAME = 'user', PASSWORD = SECRET "materialize"."public"."password", SSL CERTIFICATE AUTHORITY = SECRET "materialize"."public"."ssl", SSL CERTIFICATE = SECRET "materialize"."public"."ssl", SSL KEY = SECRET "ssl_key"."public"."ssl", AWS PRIVATELINK "materialize"."public"."privatelink", SSH TUNNEL "materialize"."tunnel_schema"."tunnel"\)`, diff --git a/pkg/resources/resource_connection_kafka.go b/pkg/resources/resource_connection_kafka.go index ea33e9da..e6f9aada 100644 --- a/pkg/resources/resource_connection_kafka.go +++ b/pkg/resources/resource_connection_kafka.go @@ -11,7 +11,6 @@ import ( "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" - "github.com/jmoiron/sqlx" ) var connectionKafkaSchema = map[string]*schema.Schema{ @@ -83,6 +82,7 @@ var connectionKafkaSchema = map[string]*schema.Schema{ "ssh_tunnel": IdentifierSchema("ssh_tunnel", "The default SSH tunnel configuration for the Kafka brokers.", false), "validate": ValidateConnectionSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func ConnectionKafka() *schema.Resource { @@ -107,8 +107,12 @@ func connectionKafkaCreate(ctx context.Context, d *schema.ResourceData, meta int schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionKafkaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionKafkaBuilder(metaDb, o) if v, ok := d.GetOk("kafka_broker"); ok { brokers := materialize.GetKafkaBrokersStruct(v) @@ -168,7 +172,7 @@ func connectionKafkaCreate(ctx context.Context, d *schema.ResourceData, meta int // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -179,7 +183,7 @@ func connectionKafkaCreate(ctx context.Context, d *schema.ResourceData, meta int // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -189,11 +193,11 @@ func connectionKafkaCreate(ctx context.Context, d *schema.ResourceData, meta int } // set id - i, err := materialize.ConnectionId(meta.(*sqlx.DB), o) + i, err := materialize.ConnectionId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return connectionRead(ctx, d, meta) } diff --git a/pkg/resources/resource_connection_kafka_test.go b/pkg/resources/resource_connection_kafka_test.go index 151871d0..0402393b 100644 --- a/pkg/resources/resource_connection_kafka_test.go +++ b/pkg/resources/resource_connection_kafka_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -42,7 +42,7 @@ func TestResourceConnectionKafkaCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, ConnectionKafka().Schema, inKafka) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE CONNECTION "database"."schema"."conn" diff --git a/pkg/resources/resource_connection_postgres.go b/pkg/resources/resource_connection_postgres.go index 44acbd79..acbaa6da 100644 --- a/pkg/resources/resource_connection_postgres.go +++ b/pkg/resources/resource_connection_postgres.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var connectionPostgresSchema = map[string]*schema.Schema{ @@ -52,6 +51,7 @@ var connectionPostgresSchema = map[string]*schema.Schema{ "aws_privatelink": IdentifierSchema("aws_privatelink", "The AWS PrivateLink configuration for the Postgres database.", false), "validate": ValidateConnectionSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func ConnectionPostgres() *schema.Resource { @@ -76,8 +76,12 @@ func connectionPostgresCreate(ctx context.Context, d *schema.ResourceData, meta schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionPostgresBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionPostgresBuilder(metaDb, o) if v, ok := d.GetOk("connection_type"); ok { b.ConnectionType(v.(string)) @@ -145,7 +149,7 @@ func connectionPostgresCreate(ctx context.Context, d *schema.ResourceData, meta // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -156,7 +160,7 @@ func connectionPostgresCreate(ctx context.Context, d *schema.ResourceData, meta // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -166,11 +170,11 @@ func connectionPostgresCreate(ctx context.Context, d *schema.ResourceData, meta } // set id - i, err := materialize.ConnectionId(meta.(*sqlx.DB), o) + i, err := materialize.ConnectionId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return connectionRead(ctx, d, meta) } diff --git a/pkg/resources/resource_connection_postgres_test.go b/pkg/resources/resource_connection_postgres_test.go index e90bac1c..3036b18e 100644 --- a/pkg/resources/resource_connection_postgres_test.go +++ b/pkg/resources/resource_connection_postgres_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -48,7 +48,7 @@ func TestResourceConnectionPostgresCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, ConnectionPostgres().Schema, inPostgres) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE CONNECTION "database"."schema"."conn" TO POSTGRES \(HOST 'postgres_host', PORT 5432, USER SECRET "materialize"."public"."user", PASSWORD SECRET "materialize"."public"."password", SSL MODE 'verify-full', SSH TUNNEL "tunnel_database"."tunnel_schema"."ssh_conn", SSL CERTIFICATE AUTHORITY SECRET "ssl_database"."public"."root", SSL CERTIFICATE SECRET "materialize"."public"."cert", SSL KEY SECRET "materialize"."public"."key", AWS PRIVATELINK "materialize"."public"."link", DATABASE 'default'\);`, diff --git a/pkg/resources/resource_connection_ssh_tunnel.go b/pkg/resources/resource_connection_ssh_tunnel.go index 93ec6e54..f8b9578e 100644 --- a/pkg/resources/resource_connection_ssh_tunnel.go +++ b/pkg/resources/resource_connection_ssh_tunnel.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var connectionSshTunnelSchema = map[string]*schema.Schema{ @@ -48,6 +47,7 @@ var connectionSshTunnelSchema = map[string]*schema.Schema{ Computed: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func ConnectionSshTunnel() *schema.Resource { @@ -70,7 +70,11 @@ func ConnectionSshTunnel() *schema.Resource { func connectionSshTunnelRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanConnectionSshTunnel(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanConnectionSshTunnel(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -78,7 +82,7 @@ func connectionSshTunnelRead(ctx context.Context, d *schema.ResourceData, meta i return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.ConnectionName.String); err != nil { return diag.FromErr(err) @@ -121,8 +125,12 @@ func connectionSshTunnelCreate(ctx context.Context, d *schema.ResourceData, meta schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionSshTunnelBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionSshTunnelBuilder(metaDb, o) b.SSHHost(d.Get("host").(string)) b.SSHUser(d.Get("user").(string)) @@ -135,7 +143,7 @@ func connectionSshTunnelCreate(ctx context.Context, d *schema.ResourceData, meta // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -146,7 +154,7 @@ func connectionSshTunnelCreate(ctx context.Context, d *schema.ResourceData, meta // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -156,11 +164,11 @@ func connectionSshTunnelCreate(ctx context.Context, d *schema.ResourceData, meta } // set id - i, err := materialize.ConnectionId(meta.(*sqlx.DB), o) + i, err := materialize.ConnectionId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return connectionSshTunnelRead(ctx, d, meta) } @@ -170,12 +178,16 @@ func connectionSshTunnelUpdate(ctx context.Context, d *schema.ResourceData, meta schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: connectionName, SchemaName: schemaName, DatabaseName: databaseName} if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "CONNECTION", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewConnectionSshTunnelBuilder(meta.(*sqlx.DB), o) + b := materialize.NewConnectionSshTunnelBuilder(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -183,7 +195,7 @@ func connectionSshTunnelUpdate(ctx context.Context, d *schema.ResourceData, meta if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) } @@ -191,7 +203,7 @@ func connectionSshTunnelUpdate(ctx context.Context, d *schema.ResourceData, meta if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_connection_ssh_tunnel_test.go b/pkg/resources/resource_connection_ssh_tunnel_test.go index 06b00e2b..233eff79 100644 --- a/pkg/resources/resource_connection_ssh_tunnel_test.go +++ b/pkg/resources/resource_connection_ssh_tunnel_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -29,7 +29,7 @@ func TestResourceConnectionSshTunnelCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, ConnectionSshTunnel().Schema, inSshTunnel) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE CONNECTION "database"."schema"."conn" TO SSH TUNNEL \(HOST 'localhost', USER 'user', PORT 123\);`, @@ -62,7 +62,7 @@ func TestResourceConnectionSshTunnelReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_connections.id = 'u1'` testhelpers.MockConnectionSshTunnelScan(mock, pp) @@ -86,7 +86,7 @@ func TestResourceConnectionSshTunnelUpdate(t *testing.T) { d.Set("name", "old_conn") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER CONNECTION "database"."schema"."" RENAME TO "conn";`).WillReturnResult(sqlmock.NewResult(1, 1)) // Comment diff --git a/pkg/resources/resource_connection_test.go b/pkg/resources/resource_connection_test.go index 33f744ba..7d7eef71 100644 --- a/pkg/resources/resource_connection_test.go +++ b/pkg/resources/resource_connection_test.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -29,7 +28,7 @@ func TestResourceConnectionUpdate(t *testing.T) { d.Set("name", "old_conn") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER CONNECTION "database"."schema"."" RENAME TO "conn";`).WillReturnResult(sqlmock.NewResult(1, 1)) // Query Params @@ -46,7 +45,7 @@ func TestResourceConnectionUpdate(t *testing.T) { // All connections (other than AWS Privatelink and SSH Tunnel) // share the same read function func TestResourceConnectionReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) d := schema.TestResourceDataRaw(t, ConnectionKafka().Schema, inConnection) r.NotNil(d) @@ -54,7 +53,7 @@ func TestResourceConnectionReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params p := `WHERE mz_connections.id = 'u1'` testhelpers.MockConnectionScan(mock, p) @@ -76,7 +75,7 @@ func TestResourceConnectionDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, ConnectionKafka().Schema, inConnection) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP CONNECTION "database"."schema"."conn";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := connectionDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_database.go b/pkg/resources/resource_database.go index 872d60c0..31870d07 100644 --- a/pkg/resources/resource_database.go +++ b/pkg/resources/resource_database.go @@ -10,13 +10,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var databaseSchema = map[string]*schema.Schema{ "name": ObjectNameSchema("database", true, true), "comment": CommentSchema(false), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func Database() *schema.Resource { @@ -39,7 +39,12 @@ func Database() *schema.Resource { func databaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanDatabase(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + s, err := materialize.ScanDatabase(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -47,7 +52,7 @@ func databaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.DatabaseName.String); err != nil { return diag.FromErr(err) @@ -67,8 +72,13 @@ func databaseRead(ctx context.Context, d *schema.ResourceData, meta interface{}) func databaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { databaseName := d.Get("name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ObjectType: "DATABASE", Name: databaseName} - b := materialize.NewDatabaseBuilder(meta.(*sqlx.DB), o) + b := materialize.NewDatabaseBuilder(metaDb, o) // create resource if err := b.Create(); err != nil { @@ -77,7 +87,7 @@ func databaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{ // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -88,7 +98,7 @@ func databaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{ // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -98,11 +108,11 @@ func databaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{ } // set id - i, err := materialize.DatabaseId(meta.(*sqlx.DB), o) + i, err := materialize.DatabaseId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return databaseRead(ctx, d, meta) } @@ -110,8 +120,13 @@ func databaseCreate(ctx context.Context, d *schema.ResourceData, meta interface{ func databaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { databaseName := d.Get("name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ObjectType: "DATABASE", Name: databaseName} - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") @@ -122,7 +137,7 @@ func databaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{ if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -135,8 +150,13 @@ func databaseUpdate(ctx context.Context, d *schema.ResourceData, meta interface{ func databaseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { databaseName := d.Get("name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{Name: databaseName} - b := materialize.NewDatabaseBuilder(meta.(*sqlx.DB), o) + b := materialize.NewDatabaseBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_database_test.go b/pkg/resources/resource_database_test.go index 785c277b..15a49cc2 100644 --- a/pkg/resources/resource_database_test.go +++ b/pkg/resources/resource_database_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -21,7 +21,7 @@ func TestResourceDatabaseCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Database().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE DATABASE "database";`, @@ -53,7 +53,7 @@ func TestResourceDatabaseReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_databases.id = 'u1'` testhelpers.MockDatabaseScan(mock, pp) @@ -77,7 +77,7 @@ func TestResourceDatabaseDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Database().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP DATABASE "database";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := databaseDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant.go b/pkg/resources/resource_grant.go index 8c35df1f..2760df5d 100644 --- a/pkg/resources/resource_grant.go +++ b/pkg/resources/resource_grant.go @@ -11,7 +11,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "golang.org/x/exp/slices" ) @@ -38,6 +37,11 @@ func parsePrivilegeKey(id string) (GrantPrivilegeKey, error) { func grantRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + key, err := parsePrivilegeKey(i) if err != nil { log.Printf("[WARN] malformed privilege (%s), removing from state file", d.Id()) @@ -45,7 +49,7 @@ func grantRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di return nil } - p, err := materialize.ScanPrivileges(meta.(*sqlx.DB), key.objectType, key.objectId) + p, err := materialize.ScanPrivileges(metaDb, key.objectType, key.objectId) if err == sql.ErrNoRows { log.Printf("[WARN] grant (%s) not found, removing from state file", d.Id()) d.SetId("") @@ -55,6 +59,9 @@ func grantRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di } privilegeMap, err := materialize.MapGrantPrivileges(p) + if err != nil { + return diag.FromErr(err) + } privilege := d.Get("privilege").(string) if !slices.Contains(privilegeMap[key.roleId], privilege) { log.Printf("[DEBUG] %s object does not contain privilege %s", i, privilege) @@ -62,6 +69,6 @@ func grantRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di d.SetId("") } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return nil } diff --git a/pkg/resources/resource_grant_cluster.go b/pkg/resources/resource_grant_cluster.go index c747cd86..f1587d8e 100644 --- a/pkg/resources/resource_grant_cluster.go +++ b/pkg/resources/resource_grant_cluster.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantClusterSchema = map[string]*schema.Schema{ @@ -20,6 +19,7 @@ var grantClusterSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantCluster() *schema.Resource { @@ -48,7 +48,12 @@ func grantClusterCreate(ctx context.Context, d *schema.ResourceData, meta interf Name: clusterName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -56,17 +61,17 @@ func grantClusterCreate(ctx context.Context, d *schema.ResourceData, meta interf } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -77,8 +82,13 @@ func grantClusterDelete(ctx context.Context, d *schema.ResourceData, meta interf privilege := d.Get("privilege").(string) clusterName := d.Get("cluster_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_cluster_default_privilege.go b/pkg/resources/resource_grant_cluster_default_privilege.go index 3c8ef4c2..0bf97104 100644 --- a/pkg/resources/resource_grant_cluster_default_privilege.go +++ b/pkg/resources/resource_grant_cluster_default_privilege.go @@ -8,13 +8,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantClusterDefaultPrivilegeSchema = map[string]*schema.Schema{ "grantee_name": GranteeNameSchema(), "target_role_name": TargetRoleNameSchema(), "privilege": PrivilegeSchema("CLUSTER"), + "region": RegionSchema(), } func GrantClusterDefaultPrivilege() *schema.Resource { @@ -38,7 +38,12 @@ func grantClusterDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceD targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "CLUSTER", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "CLUSTER", granteeName, targetName, privilege) // create resource if err := b.Grant(); err != nil { @@ -46,17 +51,17 @@ func grantClusterDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceD } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, "CLUSTER", gId, tId, "", "", privilege) + key := b.GrantKey(string(region), "CLUSTER", gId, tId, "", "", privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -67,7 +72,12 @@ func grantClusterDefaultPrivilegeDelete(ctx context.Context, d *schema.ResourceD targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "CLUSTER", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "CLUSTER", granteenName, targetName, privilege) if err := b.Revoke(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_grant_cluster_default_privilege_test.go b/pkg/resources/resource_grant_cluster_default_privilege_test.go index 42239e50..79623d5d 100644 --- a/pkg/resources/resource_grant_cluster_default_privilege_test.go +++ b/pkg/resources/resource_grant_cluster_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantClusterDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantClusterDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantClusterDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON CLUSTERS TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantClusterDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantClusterDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON CLUSTERS FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantClusterDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_cluster_test.go b/pkg/resources/resource_grant_cluster_test.go index 98bf8540..1cce534c 100644 --- a/pkg/resources/resource_grant_cluster_test.go +++ b/pkg/resources/resource_grant_cluster_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantClusterCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantClusterCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantCluster().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT CREATE ON CLUSTER "materialize" TO "joe";`, @@ -63,7 +62,7 @@ func TestResourceGrantClusterDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantCluster().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE CREATE ON CLUSTER "materialize" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantClusterDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_connection.go b/pkg/resources/resource_grant_connection.go index 33c8ec1c..d90ee449 100644 --- a/pkg/resources/resource_grant_connection.go +++ b/pkg/resources/resource_grant_connection.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantConnectionSchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var grantConnectionSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantConnection() *schema.Resource { @@ -64,7 +64,12 @@ func grantConnectionCreate(ctx context.Context, d *schema.ResourceData, meta int DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -72,17 +77,17 @@ func grantConnectionCreate(ctx context.Context, d *schema.ResourceData, meta int } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -95,8 +100,13 @@ func grantConnectionDelete(ctx context.Context, d *schema.ResourceData, meta int schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_connection_default_privilege.go b/pkg/resources/resource_grant_connection_default_privilege.go index 751bbb3a..f29fc880 100644 --- a/pkg/resources/resource_grant_connection_default_privilege.go +++ b/pkg/resources/resource_grant_connection_default_privilege.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantConnectionDefaultPrivilegeSchema = map[string]*schema.Schema{ @@ -17,6 +16,7 @@ var grantConnectionDefaultPrivilegeSchema = map[string]*schema.Schema{ "database_name": GrantDefaultDatabaseNameSchema(), "schema_name": GrantDefaultSchemaNameSchema(), "privilege": PrivilegeSchema("CONNECTION"), + "region": RegionSchema(), } func GrantConnectionDefaultPrivilege() *schema.Resource { @@ -40,7 +40,12 @@ func grantConnectionDefaultPrivilegeCreate(ctx context.Context, d *schema.Resour targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "CONNECTION", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "CONNECTION", granteeName, targetName, privilege) var database, schema string if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { @@ -59,32 +64,32 @@ func grantConnectionDefaultPrivilegeCreate(ctx context.Context, d *schema.Resour } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } var dId, sId string if database != "" { - dId, err = materialize.DatabaseId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: database}) + dId, err = materialize.DatabaseId(metaDb, materialize.MaterializeObject{Name: database}) if err != nil { return diag.FromErr(err) } } if schema != "" { - sId, err = materialize.SchemaId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: schema, DatabaseName: database}) + sId, err = materialize.SchemaId(metaDb, materialize.MaterializeObject{Name: schema, DatabaseName: database}) if err != nil { return diag.FromErr(err) } } - key := b.GrantKey(utils.Region, "CONNECTION", gId, tId, dId, sId, privilege) + key := b.GrantKey(string(region), "CONNECTION", gId, tId, dId, sId, privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -95,7 +100,12 @@ func grantConnectionDefaultPrivilegeDelete(ctx context.Context, d *schema.Resour targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "CONNECTION", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "CONNECTION", granteenName, targetName, privilege) if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { b.DatabaseName(v.(string)) diff --git a/pkg/resources/resource_grant_connection_default_privilege_test.go b/pkg/resources/resource_grant_connection_default_privilege_test.go index 63bd3086..feb27d6d 100644 --- a/pkg/resources/resource_grant_connection_default_privilege_test.go +++ b/pkg/resources/resource_grant_connection_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantConnectionDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantConnectionDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantConnectionDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON CONNECTIONS TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantConnectionDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantConnectionDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON CONNECTIONS FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantConnectionDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_connection_test.go b/pkg/resources/resource_grant_connection_test.go index 2399cc37..5cb4b219 100644 --- a/pkg/resources/resource_grant_connection_test.go +++ b/pkg/resources/resource_grant_connection_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantConnectionCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,7 @@ func TestResourceGrantConnectionCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantConnection().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT USAGE ON CONNECTION "database"."schema"."conn" TO "joe";`, @@ -67,7 +66,7 @@ func TestResourceGrantConnectionDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantConnection().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON CONNECTION "database"."schema"."conn" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantConnectionDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_database.go b/pkg/resources/resource_grant_database.go index 2912d0c4..64b8b580 100644 --- a/pkg/resources/resource_grant_database.go +++ b/pkg/resources/resource_grant_database.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantDatabaseSchema = map[string]*schema.Schema{ @@ -20,6 +19,7 @@ var grantDatabaseSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantDatabase() *schema.Resource { @@ -48,7 +48,12 @@ func grantDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta inter Name: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -56,17 +61,17 @@ func grantDatabaseCreate(ctx context.Context, d *schema.ResourceData, meta inter } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -77,8 +82,13 @@ func grantDatabaseDelete(ctx context.Context, d *schema.ResourceData, meta inter privilege := d.Get("privilege").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_database_default_privilege.go b/pkg/resources/resource_grant_database_default_privilege.go index 093b4a6d..c92a51aa 100644 --- a/pkg/resources/resource_grant_database_default_privilege.go +++ b/pkg/resources/resource_grant_database_default_privilege.go @@ -8,13 +8,13 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantDatabaseDefaultPrivilegeSchema = map[string]*schema.Schema{ "grantee_name": GranteeNameSchema(), "target_role_name": TargetRoleNameSchema(), "privilege": PrivilegeSchema("DATABASE"), + "region": RegionSchema(), } func GrantDatabaseDefaultPrivilege() *schema.Resource { @@ -38,7 +38,12 @@ func grantDatabaseDefaultPrivilegeCreate(ctx context.Context, d *schema.Resource targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "DATABASE", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "DATABASE", granteeName, targetName, privilege) // create resource if err := b.Grant(); err != nil { @@ -46,17 +51,17 @@ func grantDatabaseDefaultPrivilegeCreate(ctx context.Context, d *schema.Resource } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, "DATABASE", gId, tId, "", "", privilege) + key := b.GrantKey(string(region), "DATABASE", gId, tId, "", "", privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -67,7 +72,12 @@ func grantDatabaseDefaultPrivilegeDelete(ctx context.Context, d *schema.Resource targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "DATABASE", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "DATABASE", granteenName, targetName, privilege) if err := b.Revoke(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_grant_database_default_privilege_test.go b/pkg/resources/resource_grant_database_default_privilege_test.go index d510a52b..4795e68d 100644 --- a/pkg/resources/resource_grant_database_default_privilege_test.go +++ b/pkg/resources/resource_grant_database_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantDatabaseDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantDatabaseDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantDatabaseDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON DATABASES TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantDatabaseDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantDatabaseDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON DATABASES FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantDatabaseDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_database_test.go b/pkg/resources/resource_grant_database_test.go index fce9e5b0..301876fb 100644 --- a/pkg/resources/resource_grant_database_test.go +++ b/pkg/resources/resource_grant_database_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantDatabaseCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantDatabaseCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantDatabase().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT CREATE ON DATABASE "materialize" TO "joe";`, @@ -63,7 +62,7 @@ func TestResourceGrantDatabaseCreateEmail(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantDatabase().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT CREATE ON DATABASE "materialize" TO "joe@materialize.com";`, @@ -82,7 +81,7 @@ func TestResourceGrantDatabaseDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantDatabase().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE CREATE ON DATABASE "materialize" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantDatabaseDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_default_privilege.go b/pkg/resources/resource_grant_default_privilege.go index 51aa4dda..ceea38b4 100644 --- a/pkg/resources/resource_grant_default_privilege.go +++ b/pkg/resources/resource_grant_default_privilege.go @@ -11,7 +11,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "golang.org/x/exp/slices" ) @@ -51,7 +50,12 @@ func grantDefaultPrivilegeRead(ctx context.Context, d *schema.ResourceData, meta return nil } - privileges, err := materialize.ScanDefaultPrivilege(meta.(*sqlx.DB), key.objectType, key.granteeId, key.targetRoleId, key.databaseId, key.schemaId) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + privileges, err := materialize.ScanDefaultPrivilege(metaDb, key.objectType, key.granteeId, key.targetRoleId, key.databaseId, key.schemaId) if err == sql.ErrNoRows { log.Printf("[WARN] grant (%s) not found, removing from state file", d.Id()) d.SetId("") @@ -70,6 +74,6 @@ func grantDefaultPrivilegeRead(ctx context.Context, d *schema.ResourceData, meta d.SetId("") } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return nil } diff --git a/pkg/resources/resource_grant_default_privilege_test.go b/pkg/resources/resource_grant_default_privilege_test.go index 1e694b60..69f82024 100644 --- a/pkg/resources/resource_grant_default_privilege_test.go +++ b/pkg/resources/resource_grant_default_privilege_test.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -73,7 +72,7 @@ func TestParseDefaultPrivilegeIdErrorEmpty(t *testing.T) { // Confirm id is updated with region for 0.4.0 // All resources share the same read function func TestResourceGrantDefaultPrivilegeReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -87,7 +86,7 @@ func TestResourceGrantDefaultPrivilegeReadIdMigration(t *testing.T) { // Set id before migration d.SetId("GRANT DEFAULT|CLUSTER|u1|u1|||USAGE") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params qp := ` WHERE mz_default_privileges.grantee = 'u1' diff --git a/pkg/resources/resource_grant_materialized_view.go b/pkg/resources/resource_grant_materialized_view.go index e61dce77..626c7155 100644 --- a/pkg/resources/resource_grant_materialized_view.go +++ b/pkg/resources/resource_grant_materialized_view.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantMaterializedViewSchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var grantMaterializedViewSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantMaterializedView() *schema.Resource { @@ -64,7 +64,12 @@ func grantMaterializedViewCreate(ctx context.Context, d *schema.ResourceData, me DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -72,17 +77,17 @@ func grantMaterializedViewCreate(ctx context.Context, d *schema.ResourceData, me } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -95,8 +100,13 @@ func grantMaterializedViewDelete(ctx context.Context, d *schema.ResourceData, me schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_materialized_view_test.go b/pkg/resources/resource_grant_materialized_view_test.go index 864f08ef..7a42b55b 100644 --- a/pkg/resources/resource_grant_materialized_view_test.go +++ b/pkg/resources/resource_grant_materialized_view_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantMaterializedViewCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,7 @@ func TestResourceGrantMaterializedViewCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantMaterializedView().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT USAGE ON TABLE "database"."schema"."mview" TO "joe";`, @@ -67,7 +66,7 @@ func TestResourceGrantMaterializedViewDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantMaterializedView().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON TABLE "database"."schema"."mview" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantMaterializedViewDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_role.go b/pkg/resources/resource_grant_role.go index c43a5955..466fff5b 100644 --- a/pkg/resources/resource_grant_role.go +++ b/pkg/resources/resource_grant_role.go @@ -10,7 +10,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "golang.org/x/exp/slices" ) @@ -27,6 +26,7 @@ var grantRoleSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantRole() *schema.Resource { @@ -68,8 +68,13 @@ func grantRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{} return diag.FromErr(err) } + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + // Scan role members - roles, err := materialize.ScanRolePrivilege(meta.(*sqlx.DB), key.roleId, key.memberId) + roles, err := materialize.ScanRolePrivilege(metaDb, key.roleId, key.memberId) if err == sql.ErrNoRows { d.SetId("") return nil @@ -85,7 +90,7 @@ func grantRoleRead(ctx context.Context, d *schema.ResourceData, meta interface{} return diag.Errorf("role does contain member %s", key.memberId) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return nil } @@ -93,23 +98,28 @@ func grantRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface roleName := d.Get("role_name").(string) memberName := d.Get("member_name").(string) - b := materialize.NewRolePrivilegeBuilder(meta.(*sqlx.DB), roleName, memberName) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewRolePrivilegeBuilder(metaDb, roleName, memberName) if err := b.Grant(); err != nil { return diag.FromErr(err) } - rId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + rId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - mId, err := materialize.RoleId(meta.(*sqlx.DB), memberName) + mId, err := materialize.RoleId(metaDb, memberName) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, rId, mId) + key := b.GrantKey(string(region), rId, mId) d.SetId(key) return grantRoleRead(ctx, d, meta) @@ -119,7 +129,12 @@ func grantRoleDelete(ctx context.Context, d *schema.ResourceData, meta interface roleName := d.Get("role_name").(string) memberName := d.Get("member_name").(string) - b := materialize.NewRolePrivilegeBuilder(meta.(*sqlx.DB), roleName, memberName) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewRolePrivilegeBuilder(metaDb, roleName, memberName) if err := b.Revoke(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_grant_role_test.go b/pkg/resources/resource_grant_role_test.go index 0c967c0b..53cc17ab 100644 --- a/pkg/resources/resource_grant_role_test.go +++ b/pkg/resources/resource_grant_role_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantRolePrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -23,7 +22,7 @@ func TestResourceGrantRolePrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantRole().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT "role" TO "member";`, @@ -52,7 +51,7 @@ func TestResourceGrantRolePrivilegeCreate(t *testing.T) { // Confirm id is updated with region for 0.4.0 func TestResourceGrantRolePrivilegeReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -65,7 +64,7 @@ func TestResourceGrantRolePrivilegeReadIdMigration(t *testing.T) { // Set id before migration d.SetId("ROLE MEMBER|u1|u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params testhelpers.MockRoleGrantScan(mock) @@ -89,7 +88,7 @@ func TestResourceGrantRolePrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantRole().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE "role" FROM "member";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantRoleDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_schema.go b/pkg/resources/resource_grant_schema.go index 14d3bd79..a586ba1e 100644 --- a/pkg/resources/resource_grant_schema.go +++ b/pkg/resources/resource_grant_schema.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantSchemaSchema = map[string]*schema.Schema{ @@ -26,6 +25,7 @@ var grantSchemaSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantSchema() *schema.Resource { @@ -56,7 +56,12 @@ func grantSchemaCreate(ctx context.Context, d *schema.ResourceData, meta interfa DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -64,17 +69,17 @@ func grantSchemaCreate(ctx context.Context, d *schema.ResourceData, meta interfa } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -86,8 +91,13 @@ func grantSchemaDelete(ctx context.Context, d *schema.ResourceData, meta interfa schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_schema_default_privilege.go b/pkg/resources/resource_grant_schema_default_privilege.go index 7218e69d..b76c2a6b 100644 --- a/pkg/resources/resource_grant_schema_default_privilege.go +++ b/pkg/resources/resource_grant_schema_default_privilege.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantSchemaDefaultPrivilegeSchema = map[string]*schema.Schema{ @@ -16,6 +15,7 @@ var grantSchemaDefaultPrivilegeSchema = map[string]*schema.Schema{ "target_role_name": TargetRoleNameSchema(), "database_name": GrantDefaultDatabaseNameSchema(), "privilege": PrivilegeSchema("SCHEMA"), + "region": RegionSchema(), } func GrantSchemaDefaultPrivilege() *schema.Resource { @@ -39,7 +39,12 @@ func grantSchemaDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceDa targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "SCHEMA", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "SCHEMA", granteeName, targetName, privilege) var database string if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { @@ -53,25 +58,25 @@ func grantSchemaDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceDa } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } var dId string if database != "" { - dId, err = materialize.DatabaseId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: database}) + dId, err = materialize.DatabaseId(metaDb, materialize.MaterializeObject{Name: database}) if err != nil { return diag.FromErr(err) } } - key := b.GrantKey(utils.Region, "SCHEMA", gId, tId, dId, "", privilege) + key := b.GrantKey(string(region), "SCHEMA", gId, tId, dId, "", privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -82,7 +87,12 @@ func grantSchemaDefaultPrivilegeDelete(ctx context.Context, d *schema.ResourceDa targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "SCHEMA", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "SCHEMA", granteenName, targetName, privilege) if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { b.DatabaseName(v.(string)) diff --git a/pkg/resources/resource_grant_schema_default_privilege_test.go b/pkg/resources/resource_grant_schema_default_privilege_test.go index 4541aab1..08324671 100644 --- a/pkg/resources/resource_grant_schema_default_privilege_test.go +++ b/pkg/resources/resource_grant_schema_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantSchemaDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantSchemaDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSchemaDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON SCHEMAS TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantSchemaDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSchemaDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON SCHEMAS FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantSchemaDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_schema_test.go b/pkg/resources/resource_grant_schema_test.go index 2360757e..36677b0d 100644 --- a/pkg/resources/resource_grant_schema_test.go +++ b/pkg/resources/resource_grant_schema_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantSchemaCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -25,7 +24,7 @@ func TestResourceGrantSchemaCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSchema().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT CREATE ON SCHEMA "database"."schema" TO "joe";`, @@ -65,7 +64,7 @@ func TestResourceGrantSchemaDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSchema().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE CREATE ON SCHEMA "database"."schema" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantSchemaDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_secret.go b/pkg/resources/resource_grant_secret.go index 787cef09..11a5bce5 100644 --- a/pkg/resources/resource_grant_secret.go +++ b/pkg/resources/resource_grant_secret.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantSecretSchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var grantSecretSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantSecret() *schema.Resource { @@ -64,7 +64,12 @@ func grantSecretCreate(ctx context.Context, d *schema.ResourceData, meta interfa DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -72,17 +77,17 @@ func grantSecretCreate(ctx context.Context, d *schema.ResourceData, meta interfa } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -95,8 +100,13 @@ func grantSecretDelete(ctx context.Context, d *schema.ResourceData, meta interfa schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_secret_default_privilege.go b/pkg/resources/resource_grant_secret_default_privilege.go index 7d958e31..487f872e 100644 --- a/pkg/resources/resource_grant_secret_default_privilege.go +++ b/pkg/resources/resource_grant_secret_default_privilege.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantSecretDefaultPrivilegeSchema = map[string]*schema.Schema{ @@ -17,6 +16,7 @@ var grantSecretDefaultPrivilegeSchema = map[string]*schema.Schema{ "database_name": GrantDefaultDatabaseNameSchema(), "schema_name": GrantDefaultSchemaNameSchema(), "privilege": PrivilegeSchema("SECRET"), + "region": RegionSchema(), } func GrantSecretDefaultPrivilege() *schema.Resource { @@ -40,7 +40,12 @@ func grantSecretDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceDa targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "SECRET", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "SECRET", granteeName, targetName, privilege) var database, schema string if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { @@ -59,32 +64,32 @@ func grantSecretDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceDa } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } var dId, sId string if database != "" { - dId, err = materialize.DatabaseId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: database}) + dId, err = materialize.DatabaseId(metaDb, materialize.MaterializeObject{Name: database}) if err != nil { return diag.FromErr(err) } } if schema != "" { - sId, err = materialize.SchemaId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: schema, DatabaseName: database}) + sId, err = materialize.SchemaId(metaDb, materialize.MaterializeObject{Name: schema, DatabaseName: database}) if err != nil { return diag.FromErr(err) } } - key := b.GrantKey(utils.Region, "SECRET", gId, tId, dId, sId, privilege) + key := b.GrantKey(string(region), "SECRET", gId, tId, dId, sId, privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -95,7 +100,12 @@ func grantSecretDefaultPrivilegeDelete(ctx context.Context, d *schema.ResourceDa targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "SECRET", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "SECRET", granteenName, targetName, privilege) if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { b.DatabaseName(v.(string)) diff --git a/pkg/resources/resource_grant_secret_default_privilege_test.go b/pkg/resources/resource_grant_secret_default_privilege_test.go index 7bf159a8..d80650ac 100644 --- a/pkg/resources/resource_grant_secret_default_privilege_test.go +++ b/pkg/resources/resource_grant_secret_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantSecretDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantSecretDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSecretDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON SECRETS TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantSecretDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSecretDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON SECRETS FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantSecretDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_secret_test.go b/pkg/resources/resource_grant_secret_test.go index 54ea97e0..8d75040d 100644 --- a/pkg/resources/resource_grant_secret_test.go +++ b/pkg/resources/resource_grant_secret_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantSecretCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,7 @@ func TestResourceGrantSecretCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSecret().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT USAGE ON SECRET "database"."schema"."secret" TO "joe";`, @@ -67,7 +66,7 @@ func TestResourceGrantSecretDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSecret().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON SECRET "database"."schema"."secret" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantSecretDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_source.go b/pkg/resources/resource_grant_source.go index 06181dae..f9781ca7 100644 --- a/pkg/resources/resource_grant_source.go +++ b/pkg/resources/resource_grant_source.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantSourceSchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var grantSourceSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantSource() *schema.Resource { @@ -64,7 +64,12 @@ func grantSourceCreate(ctx context.Context, d *schema.ResourceData, meta interfa DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -72,17 +77,17 @@ func grantSourceCreate(ctx context.Context, d *schema.ResourceData, meta interfa } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -95,8 +100,13 @@ func grantSourceDelete(ctx context.Context, d *schema.ResourceData, meta interfa schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_source_test.go b/pkg/resources/resource_grant_source_test.go index 8ddf865d..c69bb95f 100644 --- a/pkg/resources/resource_grant_source_test.go +++ b/pkg/resources/resource_grant_source_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantSourceCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,7 @@ func TestResourceGrantSourceCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSource().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT USAGE ON TABLE "database"."schema"."source" TO "joe";`, @@ -67,7 +66,7 @@ func TestResourceGrantSourceDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSource().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON TABLE "database"."schema"."source" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantSourceDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_system_privilege.go b/pkg/resources/resource_grant_system_privilege.go index a1450e02..04840e61 100644 --- a/pkg/resources/resource_grant_system_privilege.go +++ b/pkg/resources/resource_grant_system_privilege.go @@ -11,7 +11,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "golang.org/x/exp/slices" ) @@ -24,6 +23,7 @@ var grantSystemPrivilegeSchema = map[string]*schema.Schema{ ForceNew: true, ValidateFunc: validPrivileges("SYSTEM"), }, + "region": RegionSchema(), } func GrantSystemPrivilege() *schema.Resource { @@ -60,6 +60,11 @@ func parseSystemPrivilegeKey(id string) (SystemPrivilegeKey, error) { func grantSystemPrivilegeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + key, err := parseSystemPrivilegeKey(i) if err != nil { log.Printf("[WARN] malformed privilege (%s), removing from state file", d.Id()) @@ -67,7 +72,7 @@ func grantSystemPrivilegeRead(ctx context.Context, d *schema.ResourceData, meta return nil } - p, err := materialize.ScanSystemPrivileges(meta.(*sqlx.DB)) + p, err := materialize.ScanSystemPrivileges(metaDb) if err == sql.ErrNoRows { log.Printf("[WARN] grant (%s) not found, removing from state file", d.Id()) d.SetId("") @@ -82,13 +87,17 @@ func grantSystemPrivilegeRead(ctx context.Context, d *schema.ResourceData, meta privileges = append(privileges, pr.Privileges) } privilegeMap, err := materialize.MapGrantPrivileges(privileges) + if err != nil { + return diag.FromErr(err) + } + if !slices.Contains(privilegeMap[key.roleId], key.privilege) { log.Printf("[DEBUG] %s object does not contain privilege %s", i, key.privilege) // Remove id from state d.SetId("") } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return nil } @@ -96,18 +105,23 @@ func grantSystemPrivilegeCreate(ctx context.Context, d *schema.ResourceData, met roleName := d.Get("role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewSystemPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewSystemPrivilegeBuilder(metaDb, roleName, privilege) if err := b.Grant(); err != nil { return diag.FromErr(err) } - rId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + rId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, rId, privilege) + key := b.GrantKey(string(region), rId, privilege) d.SetId(key) return grantSystemPrivilegeRead(ctx, d, meta) @@ -117,7 +131,12 @@ func grantSystemPrivilegeDelete(ctx context.Context, d *schema.ResourceData, met roleName := d.Get("role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewSystemPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewSystemPrivilegeBuilder(metaDb, roleName, privilege) if err := b.Revoke(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_grant_system_privilege_test.go b/pkg/resources/resource_grant_system_privilege_test.go index 47e749c8..e46657d8 100644 --- a/pkg/resources/resource_grant_system_privilege_test.go +++ b/pkg/resources/resource_grant_system_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantSystemPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -23,7 +22,7 @@ func TestResourceGrantSystemPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSystemPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT CREATEDB ON SYSTEM TO "role";`, @@ -48,7 +47,7 @@ func TestResourceGrantSystemPrivilegeCreate(t *testing.T) { // Confirm id is updated with region for 0.4.0 func TestResourceGrantSystemPrivilegeReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -61,7 +60,7 @@ func TestResourceGrantSystemPrivilegeReadIdMigration(t *testing.T) { // Set id before migration d.SetId("GRANT SYSTEM|u1|CREATEDB") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params testhelpers.MockSystemGrantScan(mock) @@ -85,7 +84,7 @@ func TestResourceGrantSystemPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantSystemPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE CREATEDB ON SYSTEM FROM "role";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantSystemPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_table.go b/pkg/resources/resource_grant_table.go index 2feab1db..1feb1676 100644 --- a/pkg/resources/resource_grant_table.go +++ b/pkg/resources/resource_grant_table.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantTableSchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var grantTableSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantTable() *schema.Resource { @@ -64,7 +64,12 @@ func grantTableCreate(ctx context.Context, d *schema.ResourceData, meta interfac DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -72,17 +77,17 @@ func grantTableCreate(ctx context.Context, d *schema.ResourceData, meta interfac } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -95,8 +100,13 @@ func grantTableDelete(ctx context.Context, d *schema.ResourceData, meta interfac schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_table_default_privilege.go b/pkg/resources/resource_grant_table_default_privilege.go index 108a436f..f61aba9e 100644 --- a/pkg/resources/resource_grant_table_default_privilege.go +++ b/pkg/resources/resource_grant_table_default_privilege.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantTableDefaultPrivilegeSchema = map[string]*schema.Schema{ @@ -17,6 +16,7 @@ var grantTableDefaultPrivilegeSchema = map[string]*schema.Schema{ "database_name": GrantDefaultDatabaseNameSchema(), "schema_name": GrantDefaultSchemaNameSchema(), "privilege": PrivilegeSchema("TABLE"), + "region": RegionSchema(), } func GrantTableDefaultPrivilege() *schema.Resource { @@ -40,7 +40,12 @@ func grantTableDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceDat targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "TABLE", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "TABLE", granteeName, targetName, privilege) var database, schema string if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { @@ -59,32 +64,32 @@ func grantTableDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceDat } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } var dId, sId string if database != "" { - dId, err = materialize.DatabaseId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: database}) + dId, err = materialize.DatabaseId(metaDb, materialize.MaterializeObject{Name: database}) if err != nil { return diag.FromErr(err) } } if schema != "" { - sId, err = materialize.SchemaId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: schema, DatabaseName: database}) + sId, err = materialize.SchemaId(metaDb, materialize.MaterializeObject{Name: schema, DatabaseName: database}) if err != nil { return diag.FromErr(err) } } - key := b.GrantKey(utils.Region, "TABLE", gId, tId, dId, sId, privilege) + key := b.GrantKey(string(region), "TABLE", gId, tId, dId, sId, privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -95,7 +100,12 @@ func grantTableDefaultPrivilegeDelete(ctx context.Context, d *schema.ResourceDat targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "TABLE", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "TABLE", granteenName, targetName, privilege) if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { b.DatabaseName(v.(string)) diff --git a/pkg/resources/resource_grant_table_default_privilege_test.go b/pkg/resources/resource_grant_table_default_privilege_test.go index caff35b9..e9f6d393 100644 --- a/pkg/resources/resource_grant_table_default_privilege_test.go +++ b/pkg/resources/resource_grant_table_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantTableDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantTableDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantTableDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON TABLES TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantTableDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantTableDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON TABLES FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantTableDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_table_test.go b/pkg/resources/resource_grant_table_test.go index 86e014ee..ce00537a 100644 --- a/pkg/resources/resource_grant_table_test.go +++ b/pkg/resources/resource_grant_table_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantTableCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,7 @@ func TestResourceGrantTableCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantTable().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT USAGE ON TABLE "database"."schema"."table" TO "joe";`, @@ -67,7 +66,7 @@ func TestResourceGrantTableDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantTable().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON TABLE "database"."schema"."table" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantTableDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_test.go b/pkg/resources/resource_grant_test.go index 222155a6..7e487cba 100644 --- a/pkg/resources/resource_grant_test.go +++ b/pkg/resources/resource_grant_test.go @@ -8,14 +8,13 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) // Confirm id is updated with region for 0.4.0 // All resources share the same read function func TestResourceGrantPrivilegeReadIdMigration(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -29,7 +28,7 @@ func TestResourceGrantPrivilegeReadIdMigration(t *testing.T) { // Set id before migration d.SetId("GRANT|CLUSTER|u1|u1|CREATE") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_clusters.id = 'u1'` testhelpers.MockClusterScan(mock, pp) diff --git a/pkg/resources/resource_grant_type.go b/pkg/resources/resource_grant_type.go index 60299631..eb085dc4 100644 --- a/pkg/resources/resource_grant_type.go +++ b/pkg/resources/resource_grant_type.go @@ -8,7 +8,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantTypeSchema = map[string]*schema.Schema{ @@ -32,6 +31,7 @@ var grantTypeSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantType() *schema.Resource { @@ -64,7 +64,12 @@ func grantTypeCreate(ctx context.Context, d *schema.ResourceData, meta interface DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -72,17 +77,17 @@ func grantTypeCreate(ctx context.Context, d *schema.ResourceData, meta interface } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -95,8 +100,13 @@ func grantTypeDelete(ctx context.Context, d *schema.ResourceData, meta interface schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_type_default_privilege.go b/pkg/resources/resource_grant_type_default_privilege.go index 59ab06d9..27d5d789 100644 --- a/pkg/resources/resource_grant_type_default_privilege.go +++ b/pkg/resources/resource_grant_type_default_privilege.go @@ -8,7 +8,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantTypeDefaultPrivilegeSchema = map[string]*schema.Schema{ @@ -17,6 +16,7 @@ var grantTypeDefaultPrivilegeSchema = map[string]*schema.Schema{ "database_name": GrantDefaultDatabaseNameSchema(), "schema_name": GrantDefaultSchemaNameSchema(), "privilege": PrivilegeSchema("TYPE"), + "region": RegionSchema(), } func GrantTypeDefaultPrivilege() *schema.Resource { @@ -40,7 +40,12 @@ func grantTypeDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceData targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "TYPE", granteeName, targetName, privilege) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "TYPE", granteeName, targetName, privilege) var database, schema string if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { @@ -59,32 +64,32 @@ func grantTypeDefaultPrivilegeCreate(ctx context.Context, d *schema.ResourceData } // Query ids - gId, err := materialize.RoleId(meta.(*sqlx.DB), granteeName) + gId, err := materialize.RoleId(metaDb, granteeName) if err != nil { return diag.FromErr(err) } - tId, err := materialize.RoleId(meta.(*sqlx.DB), targetName) + tId, err := materialize.RoleId(metaDb, targetName) if err != nil { return diag.FromErr(err) } var dId, sId string if database != "" { - dId, err = materialize.DatabaseId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: database}) + dId, err = materialize.DatabaseId(metaDb, materialize.MaterializeObject{Name: database}) if err != nil { return diag.FromErr(err) } } if schema != "" { - sId, err = materialize.SchemaId(meta.(*sqlx.DB), materialize.MaterializeObject{Name: schema, DatabaseName: database}) + sId, err = materialize.SchemaId(metaDb, materialize.MaterializeObject{Name: schema, DatabaseName: database}) if err != nil { return diag.FromErr(err) } } - key := b.GrantKey(utils.Region, "TYPE", gId, tId, dId, sId, privilege) + key := b.GrantKey(string(region), "TYPE", gId, tId, dId, sId, privilege) d.SetId(key) return grantDefaultPrivilegeRead(ctx, d, meta) @@ -95,7 +100,12 @@ func grantTypeDefaultPrivilegeDelete(ctx context.Context, d *schema.ResourceData targetName := d.Get("target_role_name").(string) privilege := d.Get("privilege").(string) - b := materialize.NewDefaultPrivilegeBuilder(meta.(*sqlx.DB), "TYPE", granteenName, targetName, privilege) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewDefaultPrivilegeBuilder(metaDb, "TYPE", granteenName, targetName, privilege) if v, ok := d.GetOk("database_name"); ok && v.(string) != "" { b.DatabaseName(v.(string)) diff --git a/pkg/resources/resource_grant_type_default_privilege_test.go b/pkg/resources/resource_grant_type_default_privilege_test.go index 3d9a19b8..2c7e2839 100644 --- a/pkg/resources/resource_grant_type_default_privilege_test.go +++ b/pkg/resources/resource_grant_type_default_privilege_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantTypeDefaultPrivilegeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -24,7 +23,7 @@ func TestResourceGrantTypeDefaultPrivilegeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantTypeDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `ALTER DEFAULT PRIVILEGES FOR ROLE "developers" GRANT USAGE ON TYPES TO "project_managers";`, @@ -66,7 +65,7 @@ func TestResourceGrantTypeDefaultPrivilegeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantTypeDefaultPrivilege().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER DEFAULT PRIVILEGES FOR ROLE "developers" REVOKE USAGE ON TYPES FROM "project_managers";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantTypeDefaultPrivilegeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_type_test.go b/pkg/resources/resource_grant_type_test.go index 1e74af3a..c888a65e 100644 --- a/pkg/resources/resource_grant_type_test.go +++ b/pkg/resources/resource_grant_type_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantTypeCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,7 @@ func TestResourceGrantTypeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantType().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `GRANT USAGE ON TYPE "database"."schema"."type" TO "joe";`, @@ -67,7 +66,7 @@ func TestResourceGrantTypeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantType().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON TYPE "database"."schema"."type" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantTypeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_grant_view.go b/pkg/resources/resource_grant_view.go index d83316c5..dbb6bf2c 100644 --- a/pkg/resources/resource_grant_view.go +++ b/pkg/resources/resource_grant_view.go @@ -7,7 +7,6 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var grantViewSchema = map[string]*schema.Schema{ @@ -31,6 +30,7 @@ var grantViewSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func GrantView() *schema.Resource { @@ -63,7 +63,12 @@ func grantViewCreate(ctx context.Context, d *schema.ResourceData, meta interface DatabaseName: databaseName, } - b := materialize.NewPrivilegeBuilder(meta.(*sqlx.DB), roleName, privilege, obj) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + + b := materialize.NewPrivilegeBuilder(metaDb, roleName, privilege, obj) // grant resource if err := b.Grant(); err != nil { @@ -71,17 +76,17 @@ func grantViewCreate(ctx context.Context, d *schema.ResourceData, meta interface } // set grant id - roleId, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + roleId, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - i, err := materialize.ObjectId(meta.(*sqlx.DB), obj) + i, err := materialize.ObjectId(metaDb, obj) if err != nil { return diag.FromErr(err) } - key := b.GrantKey(utils.Region, i, roleId, privilege) + key := b.GrantKey(string(region), i, roleId, privilege) d.SetId(key) return grantRead(ctx, d, meta) @@ -94,8 +99,13 @@ func grantViewDelete(ctx context.Context, d *schema.ResourceData, meta interface schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + b := materialize.NewPrivilegeBuilder( - meta.(*sqlx.DB), + metaDb, roleName, privilege, materialize.MaterializeObject{ diff --git a/pkg/resources/resource_grant_view_test.go b/pkg/resources/resource_grant_view_test.go index 9c04662a..93a4d08a 100644 --- a/pkg/resources/resource_grant_view_test.go +++ b/pkg/resources/resource_grant_view_test.go @@ -8,12 +8,11 @@ import ( "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) func TestResourceGrantViewCreate(t *testing.T) { - utils.SetRegionFromHostname("localhost") + utils.SetDefaultRegion("aws/us-east-1") r := require.New(t) in := map[string]interface{}{ @@ -26,7 +25,8 @@ func TestResourceGrantViewCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantView().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { + // Create mock.ExpectExec( `GRANT USAGE ON TABLE "database"."schema"."view" TO "joe";`, @@ -67,7 +67,7 @@ func TestResourceGrantViewDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, GrantView().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`REVOKE USAGE ON TABLE "database"."schema"."view" FROM "joe";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := grantViewDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_index.go b/pkg/resources/resource_index.go index 93352478..e509984c 100644 --- a/pkg/resources/resource_index.go +++ b/pkg/resources/resource_index.go @@ -11,7 +11,6 @@ import ( "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" - "github.com/jmoiron/sqlx" ) var indexSchema = map[string]*schema.Schema{ @@ -71,6 +70,7 @@ var indexSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, }, + "region": RegionSchema(), } func Index() *schema.Resource { @@ -92,7 +92,12 @@ func Index() *schema.Resource { func indexRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanIndex(meta.(*sqlx.DB), utils.ExtractId(i)) + + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanIndex(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -100,7 +105,7 @@ func indexRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.IndexName.String); err != nil { return diag.FromErr(err) @@ -124,7 +129,7 @@ func indexRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di } // Index columns - indexColumns, err := materialize.ListIndexColumns(meta.(*sqlx.DB), utils.ExtractId(i)) + indexColumns, err := materialize.ListIndexColumns(metaDb, utils.ExtractId(i)) if err != nil { return diag.FromErr(err) } @@ -149,9 +154,14 @@ func indexCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) obj := d.Get("obj_name").([]interface{})[0].(map[string]interface{}) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ObjectType: "INDEX", Name: indexName} b := materialize.NewIndexBuilder( - meta.(*sqlx.DB), + metaDb, o, indexDefault, materialize.IdentifierSchemaStruct{ @@ -189,11 +199,11 @@ func indexCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) } // set id - i, err := materialize.IndexId(meta.(*sqlx.DB), indexName) + i, err := materialize.IndexId(metaDb, indexName) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return indexRead(ctx, d, meta) } @@ -202,9 +212,14 @@ func indexUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) indexName := d.Get("name").(string) o := materialize.MaterializeObject{ObjectType: "INDEX", Name: indexName} + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -218,9 +233,14 @@ func indexDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) obj := d.Get("obj_name").([]interface{})[0].(map[string]interface{}) name := d.Get("name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ObjectType: "INDEX", Name: name} b := materialize.NewIndexBuilder( - meta.(*sqlx.DB), + metaDb, o, d.Get("default").(bool), materialize.IdentifierSchemaStruct{ diff --git a/pkg/resources/resource_index_test.go b/pkg/resources/resource_index_test.go index 96ca44b5..43fb851e 100644 --- a/pkg/resources/resource_index_test.go +++ b/pkg/resources/resource_index_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -24,7 +24,7 @@ func TestResourceIndexCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Index().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE INDEX index IN CLUSTER cluster ON "database"."schema"."source" USING ARRANGEMENT \(\);`, @@ -61,7 +61,7 @@ func TestResourceIndexReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_indexes.id = 'u1' AND mz_objects.type IN \('source', 'view', 'materialized-view'\)` testhelpers.MockIndexScan(mock, pp) @@ -91,7 +91,7 @@ func TestResourceIndexDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Index().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP INDEX "database"."schema"."index" RESTRICT;`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := indexDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_materialized_view.go b/pkg/resources/resource_materialized_view.go index 314d5763..43b6f5d0 100644 --- a/pkg/resources/resource_materialized_view.go +++ b/pkg/resources/resource_materialized_view.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var GrantDefinition = "Manages the privileges on a Materailize %[1]s for roles." @@ -42,6 +41,7 @@ var materializedViewSchema = map[string]*schema.Schema{ ForceNew: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func MaterializedView() *schema.Resource { @@ -64,7 +64,11 @@ func MaterializedView() *schema.Resource { func materializedViewRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanMaterializedView(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanMaterializedView(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -72,7 +76,7 @@ func materializedViewRead(ctx context.Context, d *schema.ResourceData, meta inte return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.MaterializedViewName.String); err != nil { return diag.FromErr(err) @@ -111,8 +115,12 @@ func materializedViewCreate(ctx context.Context, d *schema.ResourceData, meta in schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "MATERIALIZED VIEW", Name: materializedViewName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewMaterializedViewBuilder(meta.(*sqlx.DB), o) + b := materialize.NewMaterializedViewBuilder(metaDb, o) if v, ok := d.GetOk("cluster_name"); ok && v.(string) != "" { b.ClusterName(v.(string)) @@ -134,7 +142,7 @@ func materializedViewCreate(ctx context.Context, d *schema.ResourceData, meta in // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -145,7 +153,7 @@ func materializedViewCreate(ctx context.Context, d *schema.ResourceData, meta in // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -155,11 +163,11 @@ func materializedViewCreate(ctx context.Context, d *schema.ResourceData, meta in } // set id - i, err := materialize.MaterializedViewId(meta.(*sqlx.DB), o) + i, err := materialize.MaterializedViewId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return materializedViewRead(ctx, d, meta) } @@ -169,12 +177,16 @@ func materializedViewUpdate(ctx context.Context, d *schema.ResourceData, meta in schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "MATERIALIZED VIEW", Name: materializedViewName, SchemaName: schemaName, DatabaseName: databaseName} if d.HasChange("name") { oldName, newMaterializedViewName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "MATERIALIZED VIEW", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewMaterializedViewBuilder(meta.(*sqlx.DB), o) + b := materialize.NewMaterializedViewBuilder(metaDb, o) if err := b.Rename(newMaterializedViewName.(string)); err != nil { return diag.FromErr(err) } @@ -182,7 +194,7 @@ func materializedViewUpdate(ctx context.Context, d *schema.ResourceData, meta in if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -191,7 +203,7 @@ func materializedViewUpdate(ctx context.Context, d *schema.ResourceData, meta in if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -206,8 +218,12 @@ func materializedViewDelete(ctx context.Context, d *schema.ResourceData, meta an schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: materializedViewName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewMaterializedViewBuilder(meta.(*sqlx.DB), o) + b := materialize.NewMaterializedViewBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_materialized_view_test.go b/pkg/resources/resource_materialized_view_test.go index a130c0af..e0e92711 100644 --- a/pkg/resources/resource_materialized_view_test.go +++ b/pkg/resources/resource_materialized_view_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -26,7 +26,7 @@ func TestResourceMaterializedViewCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, MaterializedView().Schema, inMaterializedView) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE MATERIALIZED VIEW "database"."schema"."materialized_view" IN CLUSTER "cluster" WITH \(ASSERT NOT NULL "column_1", ASSERT NOT NULL "column_2"\) AS SELECT 1 FROM 1;`, @@ -55,7 +55,7 @@ func TestResourceMaterializedViewReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_materialized_views.id = 'u1'` testhelpers.MockMaterializeViewScan(mock, pp) @@ -79,7 +79,7 @@ func TestResourceMaterializedViewUpdate(t *testing.T) { d.Set("name", "old_materialized_view") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER MATERIALIZED VIEW "database"."schema"."" RENAME TO "materialized_view";`).WillReturnResult(sqlmock.NewResult(1, 1)) // Query Params @@ -103,7 +103,7 @@ func TestResourceMaterializedViewDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, MaterializedView().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP MATERIALIZED VIEW "database"."schema"."materialized_view";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := materializedViewDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_role.go b/pkg/resources/resource_role.go index b1b0ee96..5c7a2a7b 100644 --- a/pkg/resources/resource_role.go +++ b/pkg/resources/resource_role.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var roleSchema = map[string]*schema.Schema{ @@ -22,6 +21,7 @@ var roleSchema = map[string]*schema.Schema{ Type: schema.TypeBool, Computed: true, }, + "region": RegionSchema(), } func Role() *schema.Resource { @@ -43,7 +43,12 @@ func Role() *schema.Resource { func roleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanRole(meta.(*sqlx.DB), utils.ExtractId(i)) + + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanRole(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -51,7 +56,7 @@ func roleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.RoleName.String); err != nil { return diag.FromErr(err) @@ -76,8 +81,13 @@ func roleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia func roleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { roleName := d.Get("name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ObjectType: "ROLE", Name: roleName} - b := materialize.NewRoleBuilder(meta.(*sqlx.DB), o) + b := materialize.NewRoleBuilder(metaDb, o) if v, ok := d.GetOk("inherit"); ok && v.(bool) { b.Inherit() @@ -90,7 +100,7 @@ func roleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -100,11 +110,11 @@ func roleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d } // set id - i, err := materialize.RoleId(meta.(*sqlx.DB), roleName) + i, err := materialize.RoleId(metaDb, roleName) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return roleRead(ctx, d, meta) } @@ -114,9 +124,14 @@ func roleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d o := materialize.MaterializeObject{ObjectType: "ROLE", Name: roleName} + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -129,8 +144,13 @@ func roleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d func roleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { roleName := d.Get("name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + o := materialize.MaterializeObject{ObjectType: "ROLE", Name: roleName} - b := materialize.NewRoleBuilder(meta.(*sqlx.DB), o) + b := materialize.NewRoleBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_role_test.go b/pkg/resources/resource_role_test.go index dfade28b..90ccf0c3 100644 --- a/pkg/resources/resource_role_test.go +++ b/pkg/resources/resource_role_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestResourceRoleCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Role().Schema, inRole) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE ROLE "role" INHERIT;`, @@ -51,7 +51,7 @@ func TestResourceRoleReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_roles.id = 'u1'` testhelpers.MockRoleScan(mock, pp) @@ -72,7 +72,7 @@ func TestResourceRoleDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Role().Schema, inRole) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP ROLE "role";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := roleDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_schema.go b/pkg/resources/resource_schema.go index 46bd936a..3edc7442 100644 --- a/pkg/resources/resource_schema.go +++ b/pkg/resources/resource_schema.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var schemaSchema = map[string]*schema.Schema{ @@ -19,6 +18,7 @@ var schemaSchema = map[string]*schema.Schema{ "qualified_sql_name": QualifiedNameSchema("schema"), "comment": CommentSchema(false), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func Schema() *schema.Resource { @@ -40,7 +40,11 @@ func Schema() *schema.Resource { func schemaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanSchema(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanSchema(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -48,7 +52,7 @@ func schemaRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.SchemaName.String); err != nil { return diag.FromErr(err) @@ -78,8 +82,12 @@ func schemaCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SCHEMA", Name: schemaName, DatabaseName: databaseName} - b := materialize.NewSchemaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSchemaBuilder(metaDb, o) // create resource if err := b.Create(); err != nil { @@ -88,7 +96,7 @@ func schemaCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -99,7 +107,7 @@ func schemaCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -109,11 +117,11 @@ func schemaCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) } // set id - i, err := materialize.SchemaId(meta.(*sqlx.DB), o) + i, err := materialize.SchemaId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return schemaRead(ctx, d, meta) } @@ -122,8 +130,12 @@ func schemaUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SCHEMA", Name: schemaName, DatabaseName: databaseName} - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") @@ -134,7 +146,7 @@ func schemaUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -148,8 +160,12 @@ func schemaDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: schemaName, DatabaseName: databaseName} - b := materialize.NewSchemaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSchemaBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_schema_test.go b/pkg/resources/resource_schema_test.go index 53441dac..0bfd292c 100644 --- a/pkg/resources/resource_schema_test.go +++ b/pkg/resources/resource_schema_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestResourceSchemaCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Schema().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SCHEMA "database"."schema";`, @@ -55,7 +55,7 @@ func TestResourceSchemaReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_schemas.id = 'u1'` testhelpers.MockSchemaScan(mock, pp) @@ -80,7 +80,7 @@ func TestResourceSchemaDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Schema().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP SCHEMA "database"."schema";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := schemaDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_secret.go b/pkg/resources/resource_secret.go index aafa3d0c..f1cad480 100644 --- a/pkg/resources/resource_secret.go +++ b/pkg/resources/resource_secret.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var secretSchema = map[string]*schema.Schema{ @@ -26,6 +25,7 @@ var secretSchema = map[string]*schema.Schema{ Sensitive: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func Secret() *schema.Resource { @@ -48,7 +48,11 @@ func Secret() *schema.Resource { func secretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanSecret(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanSecret(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -56,7 +60,7 @@ func secretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.SecretName.String); err != nil { return diag.FromErr(err) @@ -91,8 +95,12 @@ func secretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SECRET", Name: secretName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSecretBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSecretBuilder(metaDb, o) if v, ok := d.GetOk("value"); ok { b.Value(v.(string)) @@ -105,7 +113,7 @@ func secretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -116,7 +124,7 @@ func secretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -126,11 +134,11 @@ func secretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) } // set id - i, err := materialize.SecretId(meta.(*sqlx.DB), o) + i, err := materialize.SecretId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return secretRead(ctx, d, meta) } @@ -140,13 +148,17 @@ func secretUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SECRET", Name: secretName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSecretBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSecretBuilder(metaDb, o) if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "SECRET", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSecretBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSecretBuilder(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -161,7 +173,7 @@ func secretUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -170,7 +182,7 @@ func secretUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -185,8 +197,12 @@ func secretDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: secretName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSecretBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSecretBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_secret_test.go b/pkg/resources/resource_secret_test.go index db99653e..4e504403 100644 --- a/pkg/resources/resource_secret_test.go +++ b/pkg/resources/resource_secret_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -24,7 +24,8 @@ func TestResourceSecretCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Secret().Schema, inSecret) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { + // Create mock.ExpectExec( `CREATE SECRET "database"."schema"."secret" AS 'value';`, @@ -53,7 +54,7 @@ func TestResourceSecretReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_secrets.id = 'u1'` testhelpers.MockSecretScan(mock, pp) @@ -78,7 +79,7 @@ func TestResourceSecretUpdate(t *testing.T) { d.Set("value", "old_value") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER SECRET "database"."schema"."" RENAME TO "secret";`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`ALTER SECRET "database"."schema"."old_secret" AS 'value';`).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -104,7 +105,7 @@ func TestResourceSecretDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Secret().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP SECRET "database"."schema"."secret";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := secretDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_sink.go b/pkg/resources/resource_sink.go index 29716992..d27454ba 100644 --- a/pkg/resources/resource_sink.go +++ b/pkg/resources/resource_sink.go @@ -9,13 +9,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func sinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanSink(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanSink(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -23,7 +26,7 @@ func sinkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.SinkName.String); err != nil { return diag.FromErr(err) @@ -66,13 +69,17 @@ func sinkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SINK", Name: sinkName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSink(meta.(*sqlx.DB), o) + b := materialize.NewSink(metaDb, o) if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "SINK", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSink(meta.(*sqlx.DB), o) + b := materialize.NewSink(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -87,7 +94,7 @@ func sinkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -96,7 +103,7 @@ func sinkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -111,8 +118,12 @@ func sinkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) d schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: sinkName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSink(meta.(*sqlx.DB), o) + b := materialize.NewSink(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_sink_kafka.go b/pkg/resources/resource_sink_kafka.go index 97a27200..05808177 100644 --- a/pkg/resources/resource_sink_kafka.go +++ b/pkg/resources/resource_sink_kafka.go @@ -10,7 +10,6 @@ import ( "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" - "github.com/jmoiron/sqlx" ) var sinkKafkaSchema = map[string]*schema.Schema{ @@ -84,6 +83,7 @@ var sinkKafkaSchema = map[string]*schema.Schema{ ForceNew: true, Default: false, }, + "region": RegionSchema(), } func SinkKafka() *schema.Resource { @@ -108,8 +108,12 @@ func sinkKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) diag schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SINK", Name: sinkName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSinkKafkaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSinkKafkaBuilder(metaDb, o) if v, ok := d.GetOk("cluster_name"); ok { b.ClusterName(v.(string)) @@ -167,7 +171,7 @@ func sinkKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) diag // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -178,7 +182,7 @@ func sinkKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) diag // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -188,11 +192,11 @@ func sinkKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) diag } // set id - i, err := materialize.SinkId(meta.(*sqlx.DB), o) + i, err := materialize.SinkId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return sinkRead(ctx, d, meta) } diff --git a/pkg/resources/resource_sink_kafka_test.go b/pkg/resources/resource_sink_kafka_test.go index 05e9faeb..abd1ac13 100644 --- a/pkg/resources/resource_sink_kafka_test.go +++ b/pkg/resources/resource_sink_kafka_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -94,7 +94,7 @@ func TestResourceSinkKafkaCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, SinkKafka().Schema, inSinkKafka) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SINK "database"."schema"."sink" diff --git a/pkg/resources/resource_sink_test.go b/pkg/resources/resource_sink_test.go index d20775ca..e91fc7a5 100644 --- a/pkg/resources/resource_sink_test.go +++ b/pkg/resources/resource_sink_test.go @@ -6,8 +6,8 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -20,7 +20,7 @@ func TestResourceSinkReadIdMigration(t *testing.T) { // Set current state d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_sinks.id = 'u1'` testhelpers.MockSinkScan(mock, pp) @@ -45,7 +45,7 @@ func TestResourceSinkUpdate(t *testing.T) { d.Set("size", "medium") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER SINK "database"."schema"."" RENAME TO "sink";`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`ALTER SINK "database"."schema"."old_sink" SET \(SIZE = 'small'\);`).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -70,7 +70,7 @@ func TestResourceSinkDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, SinkKafka().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP SINK "database"."schema"."sink";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := sinkDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_source.go b/pkg/resources/resource_source.go index 12a115ad..c00fd705 100644 --- a/pkg/resources/resource_source.go +++ b/pkg/resources/resource_source.go @@ -9,13 +9,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) func sourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanSource(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanSource(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -23,7 +26,7 @@ func sourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.SourceName.String); err != nil { return diag.FromErr(err) @@ -59,7 +62,7 @@ func sourceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) d } // Subsources - deps, err := materialize.ListDependencies(meta.(*sqlx.DB), utils.ExtractId(i), "source") + deps, err := materialize.ListDependencies(metaDb, utils.ExtractId(i), "source") if err != nil { return diag.FromErr(err) } @@ -87,13 +90,17 @@ func sourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Di schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSource(meta.(*sqlx.DB), o) + b := materialize.NewSource(metaDb, o) if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSource(meta.(*sqlx.DB), o) + b := materialize.NewSource(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -108,7 +115,7 @@ func sourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Di if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -117,7 +124,7 @@ func sourceUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Di if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -132,8 +139,12 @@ func sourceDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Di schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSource(meta.(*sqlx.DB), o) + b := materialize.NewSource(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_source_kafka.go b/pkg/resources/resource_source_kafka.go index ebf77526..a42c56d7 100644 --- a/pkg/resources/resource_source_kafka.go +++ b/pkg/resources/resource_source_kafka.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var sourceKafkaSchema = map[string]*schema.Schema{ @@ -141,6 +140,7 @@ var sourceKafkaSchema = map[string]*schema.Schema{ "expose_progress": IdentifierSchema("expose_progress", "The name of the progress subsource for the source. If this is not specified, the subsource will be named `_progress`.", false), "subsource": SubsourceSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func SourceKafka() *schema.Resource { @@ -165,8 +165,12 @@ func sourceKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) di schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSourceKafkaBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSourceKafkaBuilder(metaDb, o) if v, ok := d.GetOk("cluster_name"); ok { b.ClusterName(v.(string)) @@ -266,7 +270,7 @@ func sourceKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) di // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -277,7 +281,7 @@ func sourceKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) di // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -287,11 +291,11 @@ func sourceKafkaCreate(ctx context.Context, d *schema.ResourceData, meta any) di } // set id - i, err := materialize.SourceId(meta.(*sqlx.DB), o) + i, err := materialize.SourceId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return sourceRead(ctx, d, meta) } diff --git a/pkg/resources/resource_source_kafka_test.go b/pkg/resources/resource_source_kafka_test.go index 0e008b16..68f4f3bc 100644 --- a/pkg/resources/resource_source_kafka_test.go +++ b/pkg/resources/resource_source_kafka_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -57,7 +57,7 @@ func TestResourceSourceKafkaCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, SourceKafka().Schema, inSourceKafka) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SOURCE "database"."schema"."source" diff --git a/pkg/resources/resource_source_load_generator.go b/pkg/resources/resource_source_load_generator.go index 216661bf..37ec4328 100644 --- a/pkg/resources/resource_source_load_generator.go +++ b/pkg/resources/resource_source_load_generator.go @@ -11,7 +11,6 @@ import ( "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" - "github.com/jmoiron/sqlx" ) var tick_interval = &schema.Schema{ @@ -113,6 +112,7 @@ var sourceLoadgenSchema = map[string]*schema.Schema{ "expose_progress": IdentifierSchema("expose_progress", "The name of the progress subsource for the source. If this is not specified, the subsource will be named `_progress`.", false), "subsource": SubsourceSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func SourceLoadgen() *schema.Resource { @@ -137,8 +137,12 @@ func sourceLoadgenCreate(ctx context.Context, d *schema.ResourceData, meta any) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSourceLoadgenBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSourceLoadgenBuilder(metaDb, o) if v, ok := d.GetOk("cluster_name"); ok { b.ClusterName(v.(string)) @@ -184,7 +188,7 @@ func sourceLoadgenCreate(ctx context.Context, d *schema.ResourceData, meta any) // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -195,7 +199,7 @@ func sourceLoadgenCreate(ctx context.Context, d *schema.ResourceData, meta any) // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -205,11 +209,11 @@ func sourceLoadgenCreate(ctx context.Context, d *schema.ResourceData, meta any) } // set id - i, err := materialize.SourceId(meta.(*sqlx.DB), o) + i, err := materialize.SourceId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return sourceRead(ctx, d, meta) } diff --git a/pkg/resources/resource_source_load_generator_test.go b/pkg/resources/resource_source_load_generator_test.go index 7e9bb4ed..4f6a85b4 100644 --- a/pkg/resources/resource_source_load_generator_test.go +++ b/pkg/resources/resource_source_load_generator_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -31,7 +31,7 @@ func TestResourceSourceLoadgenCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, SourceLoadgen().Schema, inSourceLoadgen) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SOURCE "database"."schema"."source" diff --git a/pkg/resources/resource_source_postgres.go b/pkg/resources/resource_source_postgres.go index 6fba1493..e7a10953 100644 --- a/pkg/resources/resource_source_postgres.go +++ b/pkg/resources/resource_source_postgres.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var sourcePostgresSchema = map[string]*schema.Schema{ @@ -66,6 +65,7 @@ var sourcePostgresSchema = map[string]*schema.Schema{ "expose_progress": IdentifierSchema("expose_progress", "The name of the progress subsource for the source. If this is not specified, the subsource will be named `_progress`.", false), "subsource": SubsourceSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func SourcePostgres() *schema.Resource { @@ -90,8 +90,12 @@ func sourcePostgresCreate(ctx context.Context, d *schema.ResourceData, meta any) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSourcePostgresBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSourcePostgresBuilder(metaDb, o) if v, ok := d.GetOk("cluster_name"); ok { b.ClusterName(v.(string)) @@ -137,7 +141,7 @@ func sourcePostgresCreate(ctx context.Context, d *schema.ResourceData, meta any) // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -148,7 +152,7 @@ func sourcePostgresCreate(ctx context.Context, d *schema.ResourceData, meta any) // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -158,11 +162,11 @@ func sourcePostgresCreate(ctx context.Context, d *schema.ResourceData, meta any) } // set id - i, err := materialize.SourceId(meta.(*sqlx.DB), o) + i, err := materialize.SourceId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return sourceRead(ctx, d, meta) } @@ -172,13 +176,17 @@ func sourcePostgresUpdate(ctx context.Context, d *schema.ResourceData, meta any) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSource(meta.(*sqlx.DB), o) + b := materialize.NewSource(metaDb, o) if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSource(meta.(*sqlx.DB), o) + b := materialize.NewSource(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) } @@ -193,7 +201,7 @@ func sourcePostgresUpdate(ctx context.Context, d *schema.ResourceData, meta any) if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -225,7 +233,7 @@ func sourcePostgresUpdate(ctx context.Context, d *schema.ResourceData, meta any) if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_source_postgres_test.go b/pkg/resources/resource_source_postgres_test.go index 246caa59..1aa9f9f3 100644 --- a/pkg/resources/resource_source_postgres_test.go +++ b/pkg/resources/resource_source_postgres_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -38,7 +38,7 @@ func TestResourceSourcePostgresCreateTable(t *testing.T) { d := schema.TestResourceDataRaw(t, SourcePostgres().Schema, inSourcePostgresTable) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SOURCE "database"."schema"."source" IN CLUSTER "cluster" FROM POSTGRES CONNECTION "materialize"."public"."pg_connection" \(PUBLICATION 'mz_source', TEXT COLUMNS \(table.unsupported_type_1\)\) FOR TABLES \(name1 AS alias, name2 AS name2\) WITH \(SIZE = 'small'\);`, @@ -83,7 +83,7 @@ func TestResourceSourcePostgresCreateSchema(t *testing.T) { d := schema.TestResourceDataRaw(t, SourcePostgres().Schema, inSourcePostgresSchema) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SOURCE "database"."schema"."source" IN CLUSTER "cluster" FROM POSTGRES CONNECTION "materialize"."public"."pg_connection" \(PUBLICATION 'mz_source', TEXT COLUMNS \(table.unsupported_type_1\)\) FOR SCHEMAS \(schema1, schema2\) WITH \(SIZE = 'small'\);`, @@ -116,7 +116,7 @@ func TestResourceSourcePostgresUpdate(t *testing.T) { d.Set("size", "large") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER SOURCE "database"."schema"."" RENAME TO "source"`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`ALTER SOURCE "database"."schema"."old_source" SET \(SIZE = 'small'\)`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`ALTER SOURCE "database"."schema"."old_source" ADD SUBSOURCE "name1" AS "alias", "name2"`).WillReturnResult(sqlmock.NewResult(1, 1)) diff --git a/pkg/resources/resource_source_test.go b/pkg/resources/resource_source_test.go index f4aee7e8..e4670905 100644 --- a/pkg/resources/resource_source_test.go +++ b/pkg/resources/resource_source_test.go @@ -6,8 +6,8 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -20,7 +20,7 @@ func TestResourceSourceReadIdMigration(t *testing.T) { // Set current state d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_sources.id = 'u1'` testhelpers.MockSourceScan(mock, pp) @@ -49,7 +49,7 @@ func TestResourceSourceUpdate(t *testing.T) { d.Set("size", "medium") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER SOURCE "database"."schema"."" RENAME TO "source";`).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec(`ALTER SOURCE "database"."schema"."old_source" SET \(SIZE = 'small'\);`).WillReturnResult(sqlmock.NewResult(1, 1)) // Query Params @@ -77,7 +77,7 @@ func TestResourceSourceDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, SourcePostgres().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP SOURCE "database"."schema"."source"`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := sourceDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_source_webhook.go b/pkg/resources/resource_source_webhook.go index d3c7a0e2..3faddc53 100644 --- a/pkg/resources/resource_source_webhook.go +++ b/pkg/resources/resource_source_webhook.go @@ -9,7 +9,6 @@ import ( "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" - "github.com/jmoiron/sqlx" ) var sourceWebhookSchema = map[string]*schema.Schema{ @@ -150,6 +149,7 @@ var sourceWebhookSchema = map[string]*schema.Schema{ }, "subsource": SubsourceSchema(), "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func SourceWebhook() *schema.Resource { @@ -177,8 +177,12 @@ func sourceWebhookCreate(ctx context.Context, d *schema.ResourceData, meta inter clusterName := d.Get("cluster_name").(string) bodyFormat := d.Get("body_format").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "SOURCE", Name: sourceName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewSourceWebhookBuilder(meta.(*sqlx.DB), o) + b := materialize.NewSourceWebhookBuilder(metaDb, o) b.ClusterName(clusterName). BodyFormat(bodyFormat). @@ -248,7 +252,7 @@ func sourceWebhookCreate(ctx context.Context, d *schema.ResourceData, meta inter // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -259,7 +263,7 @@ func sourceWebhookCreate(ctx context.Context, d *schema.ResourceData, meta inter // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -269,11 +273,11 @@ func sourceWebhookCreate(ctx context.Context, d *schema.ResourceData, meta inter } // Set id - i, err := materialize.SourceId(meta.(*sqlx.DB), o) + i, err := materialize.SourceId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return sourceRead(ctx, d, meta) } diff --git a/pkg/resources/resource_source_webhook_test.go b/pkg/resources/resource_source_webhook_test.go index adf81376..f7a2130c 100644 --- a/pkg/resources/resource_source_webhook_test.go +++ b/pkg/resources/resource_source_webhook_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -45,7 +45,7 @@ func TestResourceSourceWebhookCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, SourceWebhook().Schema, inSourceWebhook) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec( `CREATE SOURCE "database"."schema"."webhook_source" IN CLUSTER "cluster" FROM WEBHOOK BODY FORMAT JSON INCLUDE HEADERS CHECK \( WITH \(BODY AS bytes\, HEADERS AS headers\) check_expression\);`, diff --git a/pkg/resources/resource_table.go b/pkg/resources/resource_table.go index 4f51df72..24d08da6 100644 --- a/pkg/resources/resource_table.go +++ b/pkg/resources/resource_table.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var tableSchema = map[string]*schema.Schema{ @@ -65,6 +64,7 @@ var tableSchema = map[string]*schema.Schema{ ForceNew: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func Table() *schema.Resource { @@ -87,7 +87,11 @@ func Table() *schema.Resource { func tableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanTable(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanTable(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -95,7 +99,7 @@ func tableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.TableName.String); err != nil { return diag.FromErr(err) @@ -123,7 +127,7 @@ func tableRead(ctx context.Context, d *schema.ResourceData, meta interface{}) di } // Table columns - tableColumns, err := materialize.ListTableColumns(meta.(*sqlx.DB), utils.ExtractId(i)) + tableColumns, err := materialize.ListTableColumns(metaDb, utils.ExtractId(i)) if err != nil { log.Print("[DEBUG] cannot query list tables") return diag.FromErr(err) @@ -151,8 +155,12 @@ func tableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "TABLE", Name: tableName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewTableBuilder(meta.(*sqlx.DB), o) + b := materialize.NewTableBuilder(metaDb, o) if v, ok := d.GetOk("column"); ok { columns := materialize.GetTableColumnStruct(v.([]interface{})) @@ -166,7 +174,7 @@ func tableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -177,7 +185,7 @@ func tableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -189,7 +197,7 @@ func tableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) // column comment if v, ok := d.GetOk("column"); ok { columns := materialize.GetTableColumnStruct(v.([]interface{})) - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) for _, c := range columns { if c.Comment != "" { @@ -203,13 +211,13 @@ func tableCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) } // set id - i, err := materialize.TableId(meta.(*sqlx.DB), o) + i, err := materialize.TableId(metaDb, o) if err != nil { log.Printf("[DEBUG] cannot query table: %s", o.QualifiedName()) return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return tableRead(ctx, d, meta) } @@ -219,12 +227,16 @@ func tableUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "TABLE", Name: tableName, SchemaName: schemaName, DatabaseName: databaseName} if d.HasChange("name") { oldName, newName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "TABLE", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewTableBuilder(meta.(*sqlx.DB), o) + b := materialize.NewTableBuilder(metaDb, o) if err := b.Rename(newName.(string)); err != nil { return diag.FromErr(err) @@ -233,7 +245,7 @@ func tableUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -242,7 +254,7 @@ func tableUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -261,7 +273,7 @@ func tableUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) // Check specifically if the column comment has changed. if newCol["comment"] != oldCol["comment"] { // Apply the comment change - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) colName := newCol["name"].(string) colComment := newCol["comment"].(string) if err := comment.Column(colName, colComment); err != nil { @@ -279,8 +291,12 @@ func tableDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Dia schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: tableName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewTableBuilder(meta.(*sqlx.DB), o) + b := materialize.NewTableBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_table_test.go b/pkg/resources/resource_table_test.go index f9098216..c99462f6 100644 --- a/pkg/resources/resource_table_test.go +++ b/pkg/resources/resource_table_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -32,7 +32,7 @@ func TestResourceTableCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Table().Schema, inTable) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec(` CREATE TABLE "database"."schema"."table" \(column text NOT NULL DEFAULT NULL\); @@ -72,7 +72,7 @@ func TestResourceTableReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_tables.id = 'u1'` testhelpers.MockTableScan(mock, pp) @@ -102,7 +102,8 @@ func TestResourceTableDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Table().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { + mock.ExpectExec(`DROP TABLE "database"."schema"."table";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := tableDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_type.go b/pkg/resources/resource_type.go index a7f1173d..293c6f87 100644 --- a/pkg/resources/resource_type.go +++ b/pkg/resources/resource_type.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var typeSchema = map[string]*schema.Schema{ @@ -88,6 +87,7 @@ var typeSchema = map[string]*schema.Schema{ Computed: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func Type() *schema.Resource { @@ -110,7 +110,11 @@ func Type() *schema.Resource { func typeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanType(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanType(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -118,7 +122,7 @@ func typeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.TypeName.String); err != nil { return diag.FromErr(err) @@ -157,8 +161,12 @@ func typeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "TYPE", Name: typeName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewTypeBuilder(meta.(*sqlx.DB), o) + b := materialize.NewTypeBuilder(metaDb, o) if v, ok := d.GetOk("row_properties"); ok { p := materialize.GetRowProperties(v) @@ -182,7 +190,7 @@ func typeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -193,7 +201,7 @@ func typeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -203,11 +211,11 @@ func typeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d } // set id - i, err := materialize.TypeId(meta.(*sqlx.DB), o) + i, err := materialize.TypeId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return typeRead(ctx, d, meta) } @@ -217,8 +225,12 @@ func typeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "TYPE", Name: typeName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") @@ -230,7 +242,7 @@ func typeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -245,8 +257,12 @@ func typeDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diag schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: typeName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewTypeBuilder(meta.(*sqlx.DB), o) + b := materialize.NewTypeBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_type_test.go b/pkg/resources/resource_type_test.go index 57a5466b..36932e7b 100644 --- a/pkg/resources/resource_type_test.go +++ b/pkg/resources/resource_type_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -24,7 +24,7 @@ func TestResourceTypeCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, Type().Schema, inType) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec(`CREATE TYPE "database"."schema"."type" AS LIST \(ELEMENT TYPE = int4\);`).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -51,7 +51,7 @@ func TestResourceTypeReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_types.id = 'u1'` testhelpers.MockTypeScan(mock, pp) @@ -72,7 +72,7 @@ func TestResourceTypeDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, Type().Schema, inType) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP TYPE "database"."schema"."type";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := typeDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/resource_user.go b/pkg/resources/resource_user.go new file mode 100644 index 00000000..35e04d7e --- /dev/null +++ b/pkg/resources/resource_user.go @@ -0,0 +1,300 @@ +package resources + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func User() *schema.Resource { + return &schema.Resource{ + CreateContext: userCreate, + ReadContext: userRead, + // UpdateContext: userUpdate, + DeleteContext: userDelete, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "email": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The email address of the user. This must be unique across all users in the organization.", + }, + "auth_provider": { + Type: schema.TypeString, + Computed: true, + Description: "The authentication provider for the user.", + }, + "roles": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeString}, + Required: true, + ForceNew: true, + Description: "The roles to assign to the user. Allowed values are 'Member' and 'Admin'.", + }, + "verified": { + Type: schema.TypeBool, + Computed: true, + }, + "metadata": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// CreateUserRequest is used to serialize the request body for creating a new user. +type CreateUserRequest struct { + Email string `json:"email"` + RoleIDs []string `json:"roleIds"` +} + +// CreatedUser represents the expected structure of a user creation response. +type CreatedUser struct { + ID string `json:"id"` + Email string `json:"email"` + ProfilePictureURL string `json:"profilePictureUrl"` + Verified bool `json:"verified"` + Metadata string `json:"metadata"` + Provider string `json:"provider"` +} + +type FronteggRolesResponse struct { + Items []FronteggRole `json:"items"` + Metadata struct { + TotalItems int `json:"totalItems"` + TotalPages int `json:"totalPages"` + } `json:"_metadata"` +} + +type FronteggRole struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// userCreate is the Terraform resource create function for a Frontegg user. +func userCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + + client := providerMeta.Frontegg + email := d.Get("email").(string) + roleNames := convertToStringSlice(d.Get("roles").([]interface{})) + + for _, roleName := range roleNames { + if roleName != "Member" && roleName != "Admin" { + return diag.Errorf("invalid role: %s. Roles must be either 'Member' or 'Admin'", roleName) + } + } + + // Fetch role IDs based on role names. + roleMap, err := listRoles(ctx, client) + if err != nil { + return diag.FromErr(fmt.Errorf("error fetching roles: %s", err)) + } + + var roleIDs []string + for _, roleName := range roleNames { + if roleID, ok := roleMap[roleName]; ok { + roleIDs = append(roleIDs, roleID) + } else { + // Consider failing the process if the role is not found + return diag.Errorf("role not found: %s", roleName) + } + } + + createUserRequest := CreateUserRequest{ + Email: email, + RoleIDs: roleIDs, + } + + requestBody, err := json.Marshal(createUserRequest) + if err != nil { + return diag.FromErr(fmt.Errorf("error marshaling create user request: %s", err)) + } + + req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("%s/identity/resources/users/v2", client.Endpoint), bytes.NewBuffer(requestBody)) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating request: %s", err)) + } + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", "Bearer "+client.Token) + + resp, err := client.HTTPClient.Do(req) + if err != nil { + return diag.FromErr(fmt.Errorf("error sending request: %s", err)) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + return diag.FromErr(fmt.Errorf("error creating user: status %d", resp.StatusCode)) + } + + var createdUser CreatedUser + if err := json.NewDecoder(resp.Body).Decode(&createdUser); err != nil { + return diag.FromErr(fmt.Errorf("error decoding response: %s", err)) + } + + d.Set("verified", createdUser.Verified) + d.Set("metadata", createdUser.Metadata) + d.Set("auth_provider", createdUser.Provider) + d.SetId(createdUser.ID) + return nil +} + +func userRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + + client := providerMeta.Frontegg + userID := d.Id() + + // Construct the API request + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/identity/resources/users/v1/%s", client.Endpoint, userID), nil) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating request: %s", err)) + } + req.Header.Add("Authorization", "Bearer "+client.Token) + + // Send the request to the Frontegg API + resp, err := client.HTTPClient.Do(req) + if err != nil { + return diag.FromErr(fmt.Errorf("error reading user: %s", err)) + } + defer resp.Body.Close() + + // Check for a successful response + if resp.StatusCode != http.StatusOK { + // If the user is not found, remove it from the Terraform state + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + return diag.Errorf("API error: %s", resp.Status) + } + + // Parse the response body + var user CreatedUser + if err := json.NewDecoder(resp.Body).Decode(&user); err != nil { + return diag.FromErr(fmt.Errorf("error decoding user response: %s", err)) + } + + // Update the Terraform state with the fetched user data + d.Set("email", user.Email) + d.Set("verified", user.Verified) + d.Set("metadata", user.Metadata) + d.Set("auth_provider", user.Provider) + + return nil +} + +// TODO: Add userUpdate function to change user roles + +// userDelete is the Terraform resource delete function for a Frontegg user. +func userDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + providerMeta, err := utils.GetProviderMeta(meta) + if err != nil { + return diag.FromErr(err) + } + + client := providerMeta.Frontegg + userID := d.Id() + + // Send the request to the Frontegg API to delete the user. + req, err := http.NewRequestWithContext(ctx, "DELETE", fmt.Sprintf("%s/identity/resources/users/v1/%s", client.Endpoint, userID), nil) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating request to delete user: %s", err)) + } + req.Header.Add("Authorization", "Bearer "+client.Token) + + // Perform the request + resp, err := client.HTTPClient.Do(req) + if err != nil { + return diag.FromErr(fmt.Errorf("error sending request to delete user: %s", err)) + } + defer resp.Body.Close() + + // Check for a successful response + if resp.StatusCode != http.StatusOK { + return diag.FromErr(fmt.Errorf("error deleting user: status %d", resp.StatusCode)) + } + + // Remove the user from the Terraform state + d.SetId("") + return nil +} + +// convertToStringSlice is a helper function to convert an interface slice to a string slice. +func convertToStringSlice(input []interface{}) []string { + result := make([]string, len(input)) + for i, v := range input { + result[i] = v.(string) + } + return result +} + +// listRoles fetches roles from the Frontegg API and returns a map of role names to their IDs. +func listRoles(ctx context.Context, client *clients.FronteggClient) (map[string]string, error) { + rolesURL := fmt.Sprintf("%s/identity/resources/roles/v2", client.Endpoint) + + req, err := http.NewRequestWithContext(ctx, "GET", rolesURL, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + req.Header.Add("Authorization", "Bearer "+client.Token) + + resp, err := client.HTTPClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error executing request: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("error fetching roles, status code: %d", resp.StatusCode) + } + + // Read and reset the response body + responseBody, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %v", err) + } + resp.Body = io.NopCloser(bytes.NewBuffer(responseBody)) + + // Decode the JSON response + var rolesResponse FronteggRolesResponse + if err := json.NewDecoder(resp.Body).Decode(&rolesResponse); err != nil { + return nil, fmt.Errorf("error decoding response: %v", err) + } + + // Create a map of role names to their IDs + roleMap := make(map[string]string) + for _, role := range rolesResponse.Items { + log.Printf("[DEBUG] Role found: %s - %s\n", role.Name, role.ID) + if role.Name == "Organization Admin" { + roleMap["Admin"] = role.ID + } else if role.Name == "Organization Member" { + roleMap["Member"] = role.ID + } + } + + return roleMap, nil +} diff --git a/pkg/resources/resource_user_test.go b/pkg/resources/resource_user_test.go new file mode 100644 index 00000000..325bed0f --- /dev/null +++ b/pkg/resources/resource_user_test.go @@ -0,0 +1,65 @@ +package resources + +import ( + "context" + "net/http" + "testing" + "time" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/require" +) + +func TestUserResourceRead(t *testing.T) { + r := require.New(t) + + testhelpers.WithMockFronteggServer(t, func(serverURL string) { + client := &clients.FronteggClient{ + Endpoint: serverURL, + HTTPClient: &http.Client{}, + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } + + providerMeta := &utils.ProviderMeta{ + Frontegg: client, + } + + d := schema.TestResourceDataRaw(t, User().Schema, nil) + d.SetId("mock-user-id") + + if err := userRead(context.TODO(), d, providerMeta); err != nil { + t.Fatal(err) + } + + r.Equal("test@example.com", d.Get("email")) + }) +} + +func TestUserResourceDelete(t *testing.T) { + r := require.New(t) + + testhelpers.WithMockFronteggServer(t, func(serverURL string) { + client := &clients.FronteggClient{ + Endpoint: serverURL, + HTTPClient: &http.Client{}, + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + } + + providerMeta := &utils.ProviderMeta{ + Frontegg: client, + } + + d := schema.TestResourceDataRaw(t, User().Schema, nil) + d.SetId("mock-user-id") + + if err := userDelete(context.TODO(), d, providerMeta); err != nil { + t.Fatal(err) + } + + // Assert the user is removed from the state + r.Empty(d.Id()) + }) +} diff --git a/pkg/resources/resource_view.go b/pkg/resources/resource_view.go index 30e442fb..8a091add 100644 --- a/pkg/resources/resource_view.go +++ b/pkg/resources/resource_view.go @@ -10,7 +10,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" ) var viewSchema = map[string]*schema.Schema{ @@ -26,6 +25,7 @@ var viewSchema = map[string]*schema.Schema{ ForceNew: true, }, "ownership_role": OwnershipRoleSchema(), + "region": RegionSchema(), } func View() *schema.Resource { @@ -48,7 +48,11 @@ func View() *schema.Resource { func viewRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { i := d.Id() - s, err := materialize.ScanView(meta.(*sqlx.DB), utils.ExtractId(i)) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } + s, err := materialize.ScanView(metaDb, utils.ExtractId(i)) if err == sql.ErrNoRows { d.SetId("") return nil @@ -56,7 +60,7 @@ func viewRead(ctx context.Context, d *schema.ResourceData, meta interface{}) dia return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) if err := d.Set("name", s.ViewName.String); err != nil { return diag.FromErr(err) @@ -91,8 +95,12 @@ func viewCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, region, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "VIEW", Name: viewName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewViewBuilder(meta.(*sqlx.DB), o) + b := materialize.NewViewBuilder(metaDb, o) if v, ok := d.GetOk("statement"); ok && v.(string) != "" { b.SelectStmt(v.(string)) @@ -105,7 +113,7 @@ func viewCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d // ownership if v, ok := d.GetOk("ownership_role"); ok { - ownership := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + ownership := materialize.NewOwnershipBuilder(metaDb, o) if err := ownership.Alter(v.(string)); err != nil { log.Printf("[DEBUG] resource failed ownership, dropping object: %s", o.Name) @@ -116,7 +124,7 @@ func viewCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d // object comment if v, ok := d.GetOk("comment"); ok { - comment := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + comment := materialize.NewCommentBuilder(metaDb, o) if err := comment.Object(v.(string)); err != nil { log.Printf("[DEBUG] resource failed comment, dropping object: %s", o.Name) @@ -126,11 +134,11 @@ func viewCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) d } // set id - i, err := materialize.ViewId(meta.(*sqlx.DB), o) + i, err := materialize.ViewId(metaDb, o) if err != nil { return diag.FromErr(err) } - d.SetId(utils.TransformIdWithRegion(i)) + d.SetId(utils.TransformIdWithRegion(string(region), i)) return viewRead(ctx, d, meta) } @@ -140,12 +148,16 @@ func viewUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{ObjectType: "VIEW", Name: viewName, SchemaName: schemaName, DatabaseName: databaseName} if d.HasChange("name") { oldName, newViewName := d.GetChange("name") o := materialize.MaterializeObject{ObjectType: "VIEW", Name: oldName.(string), SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewViewBuilder(meta.(*sqlx.DB), o) + b := materialize.NewViewBuilder(metaDb, o) if err := b.Rename(newViewName.(string)); err != nil { return diag.FromErr(err) @@ -154,7 +166,7 @@ func viewUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d if d.HasChange("ownership_role") { _, newRole := d.GetChange("ownership_role") - b := materialize.NewOwnershipBuilder(meta.(*sqlx.DB), o) + b := materialize.NewOwnershipBuilder(metaDb, o) if err := b.Alter(newRole.(string)); err != nil { return diag.FromErr(err) @@ -163,7 +175,7 @@ func viewUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) d if d.HasChange("comment") { _, newComment := d.GetChange("comment") - b := materialize.NewCommentBuilder(meta.(*sqlx.DB), o) + b := materialize.NewCommentBuilder(metaDb, o) if err := b.Object(newComment.(string)); err != nil { return diag.FromErr(err) @@ -178,8 +190,12 @@ func viewDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diag schemaName := d.Get("schema_name").(string) databaseName := d.Get("database_name").(string) + metaDb, _, err := utils.GetDBClientFromMeta(meta, d) + if err != nil { + return diag.FromErr(err) + } o := materialize.MaterializeObject{Name: viewName, SchemaName: schemaName, DatabaseName: databaseName} - b := materialize.NewViewBuilder(meta.(*sqlx.DB), o) + b := materialize.NewViewBuilder(metaDb, o) if err := b.Drop(); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/resource_view_test.go b/pkg/resources/resource_view_test.go index 06226206..7d641754 100644 --- a/pkg/resources/resource_view_test.go +++ b/pkg/resources/resource_view_test.go @@ -5,10 +5,10 @@ import ( "testing" "github.com/MaterializeInc/terraform-provider-materialize/pkg/testhelpers" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" sqlmock "github.com/DATA-DOG/go-sqlmock" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) @@ -24,7 +24,7 @@ func TestResourceViewCreate(t *testing.T) { d := schema.TestResourceDataRaw(t, View().Schema, inView) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Create mock.ExpectExec(`CREATE VIEW "database"."schema"."view" AS SELECT 1 FROM 1;`).WillReturnResult(sqlmock.NewResult(1, 1)) @@ -51,7 +51,7 @@ func TestResourceViewReadIdMigration(t *testing.T) { // Set id before migration d.SetId("u1") - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { // Query Params pp := `WHERE mz_views.id = 'u1'` testhelpers.MockViewScan(mock, pp) @@ -75,7 +75,7 @@ func TestResourceViewUpdate(t *testing.T) { d.Set("name", "old_view") r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`ALTER VIEW "database"."schema"."" RENAME TO "view";`).WillReturnResult(sqlmock.NewResult(1, 1)) // Query Params @@ -99,7 +99,7 @@ func TestResourceViewDelete(t *testing.T) { d := schema.TestResourceDataRaw(t, View().Schema, in) r.NotNil(d) - testhelpers.WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + testhelpers.WithMockProviderMeta(t, func(db *utils.ProviderMeta, mock sqlmock.Sqlmock) { mock.ExpectExec(`DROP VIEW "database"."schema"."view";`).WillReturnResult(sqlmock.NewResult(1, 1)) if err := viewDelete(context.TODO(), d, db); err != nil { diff --git a/pkg/resources/schema.go b/pkg/resources/schema.go index 2e219445..34b6d46e 100644 --- a/pkg/resources/schema.go +++ b/pkg/resources/schema.go @@ -512,3 +512,12 @@ func CommentSchema(forceNew bool) *schema.Schema { ForceNew: forceNew, } } + +func RegionSchema() *schema.Schema { + return &schema.Schema{ + Description: "The region to use for the resource connection. If not set, the default region is used.", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + } +} diff --git a/pkg/testhelpers/helpers.go b/pkg/testhelpers/helpers.go index 885bd405..9a4c5ccb 100644 --- a/pkg/testhelpers/helpers.go +++ b/pkg/testhelpers/helpers.go @@ -1,17 +1,34 @@ package testhelpers import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "strings" "testing" + "time" sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/require" ) +type MockAppPassword struct { + ClientID string `json:"clientId"` + Description string `json:"description"` + Owner string `json:"owner"` + CreatedAt time.Time `json:"created_at"` + Secret string `json:"secret"` +} + func WithMockDb(t *testing.T, f func(*sqlx.DB, sqlmock.Sqlmock)) { // Set the region for testing - utils.Region = "aws/us-east-1" + utils.DefaultRegion = "aws/us-east-1" t.Helper() r := require.New(t) @@ -24,3 +41,186 @@ func WithMockDb(t *testing.T, f func(*sqlx.DB, sqlmock.Sqlmock)) { f(dbx, mock) } + +func WithMockProviderMeta(t *testing.T, f func(*utils.ProviderMeta, sqlmock.Sqlmock)) { + t.Helper() + r := require.New(t) + db, mock, err := sqlmock.New() + r.NoError(err) + defer db.Close() + + dbx := sqlx.NewDb(db, "sqlmock") + dbClients := make(map[clients.Region]*clients.DBClient) + dbClients[clients.AwsUsEast1] = &clients.DBClient{DB: dbx} + regionsEnabled := make(map[clients.Region]bool) + regionsEnabled[clients.AwsUsEast1] = true + + providerMeta := &utils.ProviderMeta{ + DB: dbClients, + RegionsEnabled: regionsEnabled, + DefaultRegion: clients.AwsUsEast1, + Frontegg: &clients.FronteggClient{ + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + }, + CloudAPI: nil, + } + + mock.MatchExpectationsInOrder(true) + + f(providerMeta, mock) +} + +func WithMockFronteggServer(t *testing.T, f func(url string)) { + t.Helper() + r := require.New(t) + + // Create a mock HTTP server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + switch req.URL.Path { + case "/identity/resources/users/api-tokens/v1": + switch req.Method { + case http.MethodPost: + var createReq struct { + Description string `json:"description"` + } + err := json.NewDecoder(req.Body).Decode(&createReq) + r.NoError(err) + + appPassword := MockAppPassword{ + ClientID: "mock-client-id", + Description: createReq.Description, + Owner: "mockOwner", + CreatedAt: time.Now(), + Secret: "mock-secret", + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(appPassword) + case http.MethodGet: + mockAppPassword := MockAppPassword{ + ClientID: "mock-client-id", + Description: "test-app-password", + Owner: "mockOwner", + CreatedAt: time.Now(), + Secret: "mock-secret", + } + json.NewEncoder(w).Encode([]MockAppPassword{mockAppPassword}) + case http.MethodDelete: + w.WriteHeader(http.StatusNoContent) + default: + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + } + case "/identity/resources/users/v1/mock-user-id": + switch req.Method { + case http.MethodGet: + if strings.HasSuffix(req.URL.Path, "/mock-user-id") { + mockUser := struct { + ID string `json:"id"` + Email string `json:"email"` + ProfilePictureURL string `json:"profilePictureUrl"` + Verified bool `json:"verified"` + Metadata string `json:"metadata"` + }{ + ID: "mock-user-id", + Email: "test@example.com", + ProfilePictureURL: "http://example.com/picture.jpg", + Verified: true, + Metadata: "{}", + } + json.NewEncoder(w).Encode(mockUser) + } else { + w.WriteHeader(http.StatusNotFound) + } + case http.MethodDelete: + if strings.HasSuffix(req.URL.Path, "/mock-user-id") { + w.WriteHeader(http.StatusOK) + } else { + w.WriteHeader(http.StatusNotFound) + } + } + + default: + http.Error(w, "Not Found", http.StatusNotFound) + } + })) + defer server.Close() + + f(server.URL) +} + +// MockCloudService is a mock implementation of the http.RoundTripper interface for cloud-related requests +type MockCloudService struct{} + +func (m *MockCloudService) RoundTrip(req *http.Request) (*http.Response, error) { + // Check the requested URL and return a response accordingly + if strings.HasSuffix(req.URL.Path, "/api/cloud-regions") { + // Mock response data + data := clients.CloudProviderResponse{ + Data: []clients.CloudProvider{ + {ID: "aws/us-east-1", Name: "us-east-1", Url: "http://mockendpoint", CloudProvider: "aws"}, + {ID: "aws/eu-west-1", Name: "eu-west-1", Url: "http://mockendpoint", CloudProvider: "aws"}, + }, + } + + // Convert response data to JSON + respData, _ := json.Marshal(data) + + // Create a new HTTP response with the JSON data + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(respData)), + Header: make(http.Header), + }, nil + } else if strings.HasSuffix(req.URL.Path, "/api/region") { + // Return mock response for GetRegionDetails + details := clients.CloudRegion{ + RegionInfo: &clients.RegionInfo{ + SqlAddress: "sql.materialize.com", + HttpAddress: "http.materialize.com", + Resolvable: true, + EnabledAt: "2021-01-01T00:00:00Z", + }, + } + respData, _ := json.Marshal(details) + return &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewReader(respData)), + Header: make(http.Header), + }, nil + } + return nil, fmt.Errorf("no mock available for the requested endpoint") +} + +// WithMockCloudServer sets up a mock HTTP server for cloud-related requests and calls the provided function with the server URL. +func WithMockCloudServer(t *testing.T, f func(url string)) { + t.Helper() + + // Create a mock HTTP server + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + // Use the MockCloudService for handling requests + m := &MockCloudService{} + resp, err := m.RoundTrip(req) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + // Copy the response to the server's response writer + copyHeaders(w.Header(), resp.Header) + w.WriteHeader(resp.StatusCode) + _, _ = io.Copy(w, resp.Body) + })) + + defer server.Close() + + f(server.URL) +} + +// Helper function to copy headers from the response to the writer +func copyHeaders(dst, src http.Header) { + for key, values := range src { + for _, value := range values { + dst.Add(key, value) + } + } +} diff --git a/pkg/testhelpers/helpers_test.go b/pkg/testhelpers/helpers_test.go new file mode 100644 index 00000000..d0113e64 --- /dev/null +++ b/pkg/testhelpers/helpers_test.go @@ -0,0 +1,129 @@ +package testhelpers + +import ( + "encoding/json" + "net/http" + "strings" + "testing" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/utils" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWithMockDb(t *testing.T) { + WithMockDb(t, func(db *sqlx.DB, mock sqlmock.Sqlmock) { + rows := sqlmock.NewRows([]string{"number"}).AddRow(1) + mock.ExpectQuery("^SELECT 1$").WillReturnRows(rows) + var result int + err := db.Get(&result, "SELECT 1") + assert.NoError(t, err) + assert.Equal(t, 1, result) + err = mock.ExpectationsWereMet() + assert.NoError(t, err) + }) +} + +func TestWithMockProviderMeta(t *testing.T) { + WithMockProviderMeta(t, func(providerMeta *utils.ProviderMeta, mock sqlmock.Sqlmock) { + // Assert that the providerMeta is not nil + assert.NotNil(t, providerMeta) + + // Assert that the providerMeta has the expected default region + assert.Equal(t, clients.AwsUsEast1, providerMeta.DefaultRegion) + + // Assert that the providerMeta has the expected regions enabled + assert.True(t, providerMeta.RegionsEnabled[clients.AwsUsEast1]) + + // Optionally, set up mock expectations if you are going to perform any database operations + mock.ExpectQuery("SELECT VERSION()").WillReturnRows(sqlmock.NewRows([]string{"version"}).AddRow("test-version")) + + // Perform a database operation using providerMeta.DB[clients.AwsUsEast1] + var version string + err := providerMeta.DB[clients.AwsUsEast1].DB.Get(&version, "SELECT VERSION()") + require.NoError(t, err) + assert.Equal(t, "test-version", version) + + err = mock.ExpectationsWereMet() + assert.NoError(t, err) + }) +} + +func TestWithMockFronteggServer(t *testing.T) { + t.Run("TestWithMockFronteggServer_PostRequest", func(t *testing.T) { + WithMockFronteggServer(t, func(url string) { + // Perform HTTP POST request to the mock server and assert the response + req, err := http.NewRequest(http.MethodPost, url+"/identity/resources/users/api-tokens/v1", strings.NewReader(`{"description":"test-description"}`)) + require.NoError(t, err) + + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, http.StatusCreated, resp.StatusCode) + + var appPassword MockAppPassword + err = json.NewDecoder(resp.Body).Decode(&appPassword) + require.NoError(t, err) + + assert.Equal(t, "mock-client-id", appPassword.ClientID) + assert.Equal(t, "test-description", appPassword.Description) + assert.Equal(t, "mockOwner", appPassword.Owner) + assert.NotNil(t, appPassword.CreatedAt) + assert.Equal(t, "mock-secret", appPassword.Secret) + }) + }) + + t.Run("TestWithMockFronteggServer_GetRequest", func(t *testing.T) { + WithMockFronteggServer(t, func(url string) { + resp, err := http.Get(url + "/identity/resources/users/api-tokens/v1") + require.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + + var appPasswords []MockAppPassword + err = json.NewDecoder(resp.Body).Decode(&appPasswords) + require.NoError(t, err) + + require.Len(t, appPasswords, 1) + appPassword := appPasswords[0] + assert.Equal(t, "mock-client-id", appPassword.ClientID) + assert.Equal(t, "test-app-password", appPassword.Description) + assert.Equal(t, "mockOwner", appPassword.Owner) + assert.NotNil(t, appPassword.CreatedAt) + assert.Equal(t, "mock-secret", appPassword.Secret) + }) + }) +} + +func TestMockCloudService_RoundTrip(t *testing.T) { + t.Run("TestMockCloudService_RoundTrip_ValidURL", func(t *testing.T) { + mockService := &MockCloudService{} + req, err := http.NewRequest(http.MethodGet, "http://example.com/api/cloud-regions", nil) + assert.NoError(t, err) + + resp, err := mockService.RoundTrip(req) + assert.NoError(t, err) + defer resp.Body.Close() + + assert.Equal(t, http.StatusOK, resp.StatusCode) + }) +} + +func TestCopyHeaders(t *testing.T) { + t.Run("TestCopyHeaders", func(t *testing.T) { + dstHeader := make(http.Header) + srcHeader := http.Header{ + "Content-Type": []string{"application/json"}, + "Authorization": []string{"Bearer token"}, + } + + copyHeaders(dstHeader, srcHeader) + assert.Equal(t, "application/json", dstHeader.Get("Content-Type")) + assert.Equal(t, "Bearer token", dstHeader.Get("Authorization")) + }) +} diff --git a/pkg/utils/ids_migration_helper.go b/pkg/utils/ids_migration_helper.go deleted file mode 100644 index 50f77abb..00000000 --- a/pkg/utils/ids_migration_helper.go +++ /dev/null @@ -1,57 +0,0 @@ -package utils - -import ( - "context" - "fmt" - "strings" -) - -var Region string - -func SetRegionFromHostname(host string) error { - defaultRegion := "aws/us-east-1" - if host == "localhost" || host == "materialize" || host == "materialized" || host == "127.0.0.1" { - Region = defaultRegion - return nil - } - - parts := strings.Split(host, ".") - if len(parts) < 3 { - Region = defaultRegion - return nil - } - - Region = fmt.Sprintf("aws/%s", parts[1]) - return nil -} - -// Helper function to prepend region to the ID -func TransformIdWithRegion(oldID string) string { - // If the ID already has a region, return the original ID - if strings.Contains(oldID, ":") { - return oldID - } - return fmt.Sprintf("%s:%s", Region, oldID) -} - -// Function to get the ID from the region + ID string -func ExtractId(oldID string) string { - parts := strings.Split(oldID, ":") - if len(parts) < 2 { - // Return the original ID if it doesn't have a region - return oldID - } - return parts[1] -} - -func IdStateUpgradeV0(ctx context.Context, rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { - oldID, ok := rawState["id"].(string) - if !ok { - return nil, fmt.Errorf("unexpected type for ID") - } - - newID := TransformIdWithRegion(oldID) - rawState["id"] = newID - - return rawState, nil -} diff --git a/pkg/utils/ids_migration_helper_test.go b/pkg/utils/ids_migration_helper_test.go deleted file mode 100644 index bffced87..00000000 --- a/pkg/utils/ids_migration_helper_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package utils - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestTransformIdWithRegion(t *testing.T) { - SetRegionFromHostname("localhost") - testCases := []map[string]interface{}{ - { - "input": "aws/us-east-1:GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", - "expected": "aws/us-east-1:GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", - }, - { - "input": "GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", - "expected": "aws/us-east-1:GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", - }, - { - "input": "aws/us-east-1:u1", - "expected": "aws/us-east-1:u1", - }, - { - "input": "u1", - "expected": "aws/us-east-1:u1", - }, - } - for tc := range testCases { - c := testCases[tc] - o := TransformIdWithRegion(c["input"].(string)) - assert.Equal(t, o, c["expected"].(string)) - } -} diff --git a/pkg/utils/provider_meta.go b/pkg/utils/provider_meta.go new file mode 100644 index 00000000..32674877 --- /dev/null +++ b/pkg/utils/provider_meta.go @@ -0,0 +1,107 @@ +package utils + +import ( + "fmt" + "strings" + + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/jmoiron/sqlx" +) + +// ProviderMeta holds essential configuration and client information +// required across various parts of the provider. It acts as a central +// repository of shared data, particularly for database connections, API clients, +// and regional settings. +type ProviderMeta struct { + // DB is a map that associates each supported region with its corresponding + // database client. This allows for region-specific database operations. + DB map[clients.Region]*clients.DBClient + + // Frontegg represents the client used to interact with the Frontegg API, + // which may involve authentication, token management, etc. + Frontegg *clients.FronteggClient + + // CloudAPI is the client used for interactions with the cloud API + CloudAPI *clients.CloudAPIClient + + // DefaultRegion specifies the default region to be used when no specific + // region is provided in the resources and data sources. + DefaultRegion clients.Region + + // RegionsEnabled is a map indicating which regions are currently enabled + // for use. This can be used to quickly check the availability in different regions. + RegionsEnabled map[clients.Region]bool +} + +var DefaultRegion string + +func GetProviderMeta(meta interface{}) (*ProviderMeta, error) { + providerMeta := meta.(*ProviderMeta) + + if err := providerMeta.Frontegg.NeedsTokenRefresh(); err != nil { + err := providerMeta.Frontegg.RefreshToken() + if err != nil { + return nil, fmt.Errorf("failed to refresh token: %v", err) + } + } + + return providerMeta, nil +} + +func GetDBClientFromMeta(meta interface{}, d *schema.ResourceData) (*sqlx.DB, clients.Region, error) { + providerMeta, err := GetProviderMeta(meta) + if err != nil { + return nil, "", err + } + + // Determine the region to use, if one is not specified, use the default region + var region clients.Region + if d != nil && d.Get("region") != "" { + region = clients.Region(d.Get("region").(string)) + } else { + region = providerMeta.DefaultRegion + } + + // Check if the region is enabled using the stored information + enabled, exists := providerMeta.RegionsEnabled[region] + if !exists { + return nil, region, fmt.Errorf("no information available for region: %s", region) + } + + if !enabled { + return nil, region, fmt.Errorf("region '%s' is not enabled", region) + } + + // Retrieve the appropriate DBClient for the region from the map + dbClient, exists := providerMeta.DB[region] + if !exists { + return nil, region, fmt.Errorf("no database client for region: %s", region) + } + + return dbClient.SQLX(), region, nil +} + +func SetDefaultRegion(region string) error { + DefaultRegion = region + return nil +} + +// Helper function to prepend region to the ID +func TransformIdWithRegion(region string, oldID string) string { + // If the ID already has a region, return the original ID + if strings.Contains(oldID, ":") { + return oldID + } + return fmt.Sprintf("%s:%s", region, oldID) +} + +// Function to get the ID from the region + ID string +func ExtractId(oldID string) string { + parts := strings.Split(oldID, ":") + if len(parts) < 2 { + // Return the original ID if it doesn't have a region + return oldID + } + return parts[1] +} diff --git a/pkg/utils/provider_meta_test.go b/pkg/utils/provider_meta_test.go new file mode 100644 index 00000000..0ead0c83 --- /dev/null +++ b/pkg/utils/provider_meta_test.go @@ -0,0 +1,85 @@ +package utils + +import ( + "testing" + "time" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/MaterializeInc/terraform-provider-materialize/pkg/clients" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetDBClientFromMeta(t *testing.T) { + // Set up the SQL mock database + db, mock, err := sqlmock.New() + require.NoError(t, err) + defer db.Close() + + // Wrap the sql.DB with sqlx + dbx := sqlx.NewDb(db, "sqlmock") + + // Set up the mock ProviderMeta + providerMeta := &ProviderMeta{ + DB: map[clients.Region]*clients.DBClient{ + clients.AwsUsEast1: {DB: dbx}, + }, + RegionsEnabled: map[clients.Region]bool{ + clients.AwsUsEast1: true, + }, + DefaultRegion: clients.AwsUsEast1, + Frontegg: &clients.FronteggClient{ + TokenExpiry: time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC), + }, + } + + // Create a ResourceData schema with the "region" key + resourceDataSchema := map[string]*schema.Schema{ + "region": { + Type: schema.TypeString, + Optional: true, + }, + } + + // Create a ResourceData object with a valid region + resourceData := schema.TestResourceDataRaw(t, resourceDataSchema, nil) + err = resourceData.Set("region", "aws/us-east-1") + require.NoError(t, err) + + // Call the GetDBClientFromMeta function + dbClient, _, err := GetDBClientFromMeta(providerMeta, resourceData) + require.NoError(t, err) + assert.NotNil(t, dbClient) + + // Check that the mock expectations are met + err = mock.ExpectationsWereMet() + assert.NoError(t, err) +} + +func TestTransformIdWithRegion(t *testing.T) { + testCases := []map[string]interface{}{ + { + "input": "aws/us-east-1:GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", + "expected": "aws/us-east-1:GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", + }, + { + "input": "GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", + "expected": "aws/us-east-1:GRANT DEFAULT|SCHEMA|u1|u1|||USAGE", + }, + { + "input": "aws/us-east-1:u1", + "expected": "aws/us-east-1:u1", + }, + { + "input": "u1", + "expected": "aws/us-east-1:u1", + }, + } + for tc, _ := range testCases { + c := testCases[tc] + o := TransformIdWithRegion("aws/us-east-1", c["input"].(string)) + assert.Equal(t, o, c["expected"].(string)) + } +} diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index a84bbbc6..9b986033 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -15,11 +15,9 @@ Configure the provider by adding the following block to your Terraform project: ## Schema -* `host` (String) Materialize host. Can also come from the `MZ_HOST` environment variable. -* `user` (String) Materialize user. Can also come from the `MZ_USER` environment variable. -* `password` (String, Sensitive) Materialize host. Can also come from the `MZ_PASSWORD` environment variable. -* `port` (Number) The Materialize port number to connect to at the server host. Can also come from the `MZ_PORT` environment variable. Defaults to 6875. -* `database` (String) The Materialize database. Can also come from the `MZ_DATABASE` environment variable. Defaults to `materialize`. +* `password` (String, Sensitive) Materialize App Password. Can also come from the `MZ_PASSWORD` environment variable. +* `database` (String, Optional) The Materialize database. Can also come from the `MZ_DATABASE` environment variable. Defaults to `materialize`. +* `default_region` (String, Optional) The Materialize AWS region. Can also come from the `MZ_DEFAULT_REGION` environment variable. Defaults to `aws/us-east-1`. ## Order precedence