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