diff --git a/modules/analytics-hub/README.md b/modules/analytics-hub/README.md new file mode 100644 index 0000000000..3482d797c4 --- /dev/null +++ b/modules/analytics-hub/README.md @@ -0,0 +1,216 @@ +# BigQuery Analytics Hub + +This module allows managing [Analytics Hub](https://cloud.google.com/bigquery/docs/analytics-hub-introduction) Exchange and Listing resources. + +## Examples + +### Exchange + +Exchange argument references can be found in: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_analytics_hub_data_exchange + +```hcl +module "analytics-hub" { + source = "./fabric/modules/analytics-hub" + project_id = "project-id" + region = "us-central1" + prefix = "test" + name = "exchange" + primary_contact = "exchange-owner-group@domain.com" + documentation = "documentation" +} +# tftest modules=1 resources=1 +``` + +### Listings + +Listing definitions can be provided in the form {LISTING_ID => LISTING_CONFIGS}. Listing argument references can be found in: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_analytics_hub_listing + +```hcl +module "analytics-hub" { + source = "./fabric/modules/analytics-hub" + project_id = "project-id" + region = "us-central1" + name = "exchange" + listings = { + "listing_id" = { + bigquery_dataset = "projects/{project}/datasets/{dataset}" + }, + "listing_id_2" = { + bigquery_dataset = "projects/{project}/datasets/{dataset}" + description = "(Optional) Short description of the listing." + documentation = "(Optional) Documentation describing the listing." + categories = [] + primary_contact = "(Optional) Email or URL of the primary point of contact of the listing." + icon = "(Optional) Base64 encoded image representing the listing." + request_access = "(Optional) Email or URL of the request access of the listing. Subscribers can use this reference to request access." + data_provider = { + name = "(Required) Name of the data provider." + primary_contact = "(Optional) Email or URL of the data provider." + } + publisher = { + name = "(Required) Name of the listing publisher." + primary_contact = "(Optional) Email or URL of the listing publisher." + } + restricted_export_config = { + enabled = true + restrict_query_result = true + } + } + } +} +# tftest modules=1 resources=3 +``` + +### IAM + +This module supports setting IAM permissions on both the exchange and listing resources. IAM permissions on the exchange is inherited on the listings. + +See [this page](https://cloud.google.com/bigquery/docs/analytics-hub-grant-roles) to see IAM roles that can be granted on exchange and listings. + +#### Exchange +Input to variables `iam`, `iam_bindings`, and `iam_by_principals` will be merged, and are [authoritative for the given role](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_analytics_hub_data_exchange_iam#google_bigquery_analytics_hub_data_exchange_iam_binding). Inputs to variable `iam_bindings_additive` are [additive](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_analytics_hub_data_exchange_iam#google_bigquery_analytics_hub_data_exchange_iam_member). + +In practice, you should only need to use either `iam` or `iam_bindings`. + +```hcl +module "analytics-hub" { + source = "./fabric/modules/analytics-hub" + project_id = "project-id" + region = "us-central1" + name = "exchange" + iam = { + "roles/analyticshub.viewer" = [ + "group:viewer@domain.com" + ], + } + iam_bindings = { + "viewers" = { + role = "roles/analyticshub.viewer" + members = ["user:user@domain.com"] + } + } + iam_by_principals = { + "user:user@domain.com" = [ + "roles/analyticshub.viewer" + ] + } + iam_bindings_additive = { + "subscribers" = { + role = "roles/analyticshub.subscriber" + member = "user:user@domain.com" + } + } +} +# tftest modules=1 resources=3 inventory=iam_exchange.yaml +``` + +#### Listings +The listings variable block support the `iam` input which are [authoritative for the given role](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/bigquery_analytics_hub_listing_iam#google_bigquery_analytics_hub_listing_iam_binding). + +```hcl +module "analytics-hub" { + source = "./fabric/modules/analytics-hub" + project_id = "project-id" + region = "us-central1" + name = "exchange" + iam = { + "roles/analyticshub.viewer" = [ + "group:viewer@domain.com" + ], + } + listings = { + "listing_id" = { + bigquery_dataset = "projects/{project}/datasets/{dataset}" + iam = { + "roles/analyticshub.subscriber" = [ + "group:subscriber@domain.com" + ], + "roles/analyticshub.subscriptionOwner" = [ + "group:subscription-owner@domain.com" + ], + } + } + } +} +# tftest modules=1 resources=5 inventory=iam_listing.yaml +``` + +### Factory + +Similarly to other modules, a rules factory (see [Resource Factories](../../blueprints/factories/)) is also included here to allow managing listings inside the same exchange via descriptive configuration files. + +Factory configuration is via one optional attributes in the `factory_config_path` variable specifying the path where tags files are stored. + +Factory tags are merged with rules declared in code, with the latter taking precedence where both use the same key. + +This is an example of a simple factory: + +```hcl +module "analytics-hub" { + source = "./fabric/modules/analytics-hub" + project_id = "project-id" + region = "us-central1" + name = "exchange" + listings = { + "listing_id" = { + bigquery_dataset = "projects/{project}/datasets/{dataset}" + }, + } + factories_config = { + listings = "listings" + } +} +# tftest modules=1 resources=5 files=yaml +``` + +```yaml +# tftest-file id=yaml path=listings/listing_1.yaml +bigquery_dataset: projects/{project}/datasets/{dataset} +description: "(Optional) Short description of the listing." +documentation: "(Optional) Documentation describing the listing." +categories: [] +icon: "(Optional) Base64 encoded image representing the listing." +primary_contact: "(Optional) Email or URL of the primary point of contact of the listing." +request_access: "(Optional) Email or URL of the request access of the listing. Subscribers can use this reference to request access." +data_provider: + name: "(Required) Name of the data provider." + primary_contact: "(Optional) Email or URL of the data provider." +iam: + roles/analyticshub.subscriber: + - group:subscriber@domain.com + roles/analyticshub.subscriptionOwner: + - group:subscription-owner@domain.com +publisher: + name: "(Required) Name of the listing publisher." + primary_contact: "(Optional) Email or URL of the listing publisher." +restricted_export_config: + enabled: true + restrict_query_result: true +``` + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [name](variables.tf#L71) | The ID of the data exchange. Must contain only Unicode letters, numbers (0-9), underscores (_). Should not use characters that require URL-escaping or characters outside of ASCII spaces. | string | ✓ | | +| [project_id](variables.tf#L88) | The ID of the project where the data exchange will be created. | string | ✓ | | +| [region](variables.tf#L93) | Region for the data exchange. | string | ✓ | | +| [description](variables.tf#L17) | Resource description for data exchange. | string | | null | +| [documentation](variables.tf#L23) | Documentation describing the data exchange. | string | | null | +| [factories_config](variables.tf#L29) | Paths to data files and folders that enable factory functionality. | object({…}) | | {} | +| [iam](variables-iam.tf#L17) | Authoritative IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_bindings](variables-iam.tf#L24) | Authoritative IAM bindings in {KEY => {role = ROLE, members = []}}. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_bindings_additive](variables-iam.tf#L34) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_by_principals](variables-iam.tf#L44) | Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable. | map(list(string)) | | {} | +| [icon](variables.tf#L38) | Base64 encoded image representing the data exchange. | string | | null | +| [listings](variables.tf#L44) | Listings definitions in the form {LISTING_ID => LISTING_CONFIGS}. LISTING_ID must contain only Unicode letters, numbers (0-9), underscores (_). Should not use characters that require URL-escaping or characters outside of ASCII spaces. | map(object({…})) | | {} | +| [prefix](variables.tf#L76) | Optional prefix for data exchange ID. | string | | null | +| [primary_contact](variables.tf#L82) | Email or URL of the primary point of contact of the data exchange. | string | | null | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [data_exchange_id](outputs.tf#L17) | Data exchange id. | | +| [data_listings](outputs.tf#L27) | Data listings and corresponding configs. | | + diff --git a/modules/analytics-hub/iam.tf b/modules/analytics-hub/iam.tf new file mode 100644 index 0000000000..ed8b98fe19 --- /dev/null +++ b/modules/analytics-hub/iam.tf @@ -0,0 +1,78 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + _exchange_iam_principal_roles = distinct(flatten(values(var.iam_by_principals))) + _exchange_iam_principals = { + for r in local._exchange_iam_principal_roles : r => [ + for k, v in var.iam_by_principals : + k if try(index(v, r), null) != null + ] + } + _exchange_iam_bindings = { + for key, iam_bindings in var.iam_bindings : + iam_bindings.role => iam_bindings.members + } + exchange_iam = { + for role in distinct(concat(keys(var.iam), keys(local._exchange_iam_principals), keys(local._exchange_iam_bindings))) : + role => concat( + try(var.iam[role], []), + try(local._exchange_iam_principals[role], []), + try(local._exchange_iam_bindings[role], []), + ) + } + listing_iam = flatten([ + for listing_id, listing_configs in local.factory_listings : [ + for role, members in coalesce(listing_configs.iam, tomap({})) : { + listing_id = listing_id + role = role + members = members + } + ] + ]) +} + + +resource "google_bigquery_analytics_hub_data_exchange_iam_binding" "exchange_iam_bindings" { + for_each = local.exchange_iam + project = google_bigquery_analytics_hub_data_exchange.data_exchange.project + location = google_bigquery_analytics_hub_data_exchange.data_exchange.location + data_exchange_id = google_bigquery_analytics_hub_data_exchange.data_exchange.data_exchange_id + role = each.key + members = each.value +} + +resource "google_bigquery_analytics_hub_data_exchange_iam_member" "exchange_iam_members" { + for_each = var.iam_bindings_additive + project = google_bigquery_analytics_hub_data_exchange.data_exchange.project + location = google_bigquery_analytics_hub_data_exchange.data_exchange.location + data_exchange_id = google_bigquery_analytics_hub_data_exchange.data_exchange.data_exchange_id + role = each.value.role + member = each.value.member +} + +resource "google_bigquery_analytics_hub_listing_iam_binding" "listing_iam_bindings" { + for_each = { + for index, listing_iam in local.listing_iam : + "${listing_iam.listing_id}-${listing_iam.role}" => listing_iam + } + project = google_bigquery_analytics_hub_data_exchange.data_exchange.project + location = google_bigquery_analytics_hub_data_exchange.data_exchange.location + data_exchange_id = google_bigquery_analytics_hub_data_exchange.data_exchange.data_exchange_id + listing_id = google_bigquery_analytics_hub_listing.listing[each.value.listing_id].id + role = each.value.role + members = each.value.members +} diff --git a/modules/analytics-hub/main.tf b/modules/analytics-hub/main.tf new file mode 100644 index 0000000000..e82798c175 --- /dev/null +++ b/modules/analytics-hub/main.tf @@ -0,0 +1,64 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + prefix = var.prefix == null || var.prefix == "" ? "" : "${var.prefix}_" + _factory_listings = { + for f in try(fileset(var.factories_config.listings, "*.yaml"), []) : + trimsuffix(f, ".yaml") => yamldecode(file("${var.factories_config.listings}/${f}")) + } + + factory_listings = merge(local._factory_listings, var.listings) +} + +resource "google_bigquery_analytics_hub_data_exchange" "data_exchange" { + project = var.project_id + location = var.region + data_exchange_id = "${local.prefix}${var.name}" + display_name = "${local.prefix}${var.name}" + description = var.description + primary_contact = var.primary_contact + documentation = var.documentation + icon = var.icon +} + +resource "google_bigquery_analytics_hub_listing" "listing" { + for_each = local.factory_listings + project = var.project_id + location = var.region + data_exchange_id = google_bigquery_analytics_hub_data_exchange.data_exchange.data_exchange_id + listing_id = each.key + display_name = each.key + description = try(each.value.description, null) + primary_contact = try(each.value.primary_contact, null) + documentation = try(each.value.documentation, null) + icon = try(each.value.icon, null) + request_access = try(each.value.request_access, null) + categories = try(each.value.categories, null) + + bigquery_dataset { + dataset = each.value.bigquery_dataset + } + + dynamic "restricted_export_config" { + for_each = each.value.restricted_export_config != null ? [""] : [] + content { + enabled = try(each.value.restricted_export_config.enabled, null) + restrict_query_result = try(each.value.restricted_export_config.restrict_query_result, null) + } + } + +} diff --git a/modules/analytics-hub/outputs.tf b/modules/analytics-hub/outputs.tf new file mode 100644 index 0000000000..5cbf45d8a4 --- /dev/null +++ b/modules/analytics-hub/outputs.tf @@ -0,0 +1,34 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "data_exchange_id" { + description = "Data exchange id." + value = google_bigquery_analytics_hub_data_exchange.data_exchange.data_exchange_id + depends_on = [ + google_bigquery_analytics_hub_data_exchange.data_exchange, + google_bigquery_analytics_hub_data_exchange_iam_binding.exchange_iam_bindings, + google_bigquery_analytics_hub_data_exchange_iam_member.exchange_iam_members + ] +} + +output "data_listings" { + description = "Data listings and corresponding configs." + value = { for k, v in google_bigquery_analytics_hub_listing.listing : k => v.bigquery_dataset } + depends_on = [ + google_bigquery_analytics_hub_listing.listing, + google_bigquery_analytics_hub_listing_iam_binding.listing_iam_bindings + ] +} diff --git a/modules/analytics-hub/variables-iam.tf b/modules/analytics-hub/variables-iam.tf new file mode 100644 index 0000000000..5ad9a377d5 --- /dev/null +++ b/modules/analytics-hub/variables-iam.tf @@ -0,0 +1,49 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "iam" { + description = "Authoritative IAM bindings in {ROLE => [MEMBERS]} format." + type = map(list(string)) + default = {} + nullable = false +} + +variable "iam_bindings" { + description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = []}}. Keys are arbitrary." + type = map(object({ + members = list(string) + role = string + })) + nullable = false + default = {} +} + +variable "iam_bindings_additive" { + description = "Individual additive IAM bindings. Keys are arbitrary." + type = map(object({ + member = string + role = string + })) + nullable = false + default = {} +} + +variable "iam_by_principals" { + description = "Authoritative IAM binding in {PRINCIPAL => [ROLES]} format. Principals need to be statically defined to avoid cycle errors. Merged internally with the `iam` variable." + type = map(list(string)) + default = {} + nullable = false +} diff --git a/modules/analytics-hub/variables.tf b/modules/analytics-hub/variables.tf new file mode 100644 index 0000000000..6f6136389f --- /dev/null +++ b/modules/analytics-hub/variables.tf @@ -0,0 +1,96 @@ +/** + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "description" { + description = "Resource description for data exchange." + default = null + type = string +} + +variable "documentation" { + description = "Documentation describing the data exchange." + default = null + type = string +} + +variable "factories_config" { + description = "Paths to data files and folders that enable factory functionality." + type = object({ + listings = optional(string) + }) + nullable = false + default = {} +} + +variable "icon" { + description = "Base64 encoded image representing the data exchange." + default = null + type = string +} + +variable "listings" { + description = "Listings definitions in the form {LISTING_ID => LISTING_CONFIGS}. LISTING_ID must contain only Unicode letters, numbers (0-9), underscores (_). Should not use characters that require URL-escaping or characters outside of ASCII spaces." + type = map(object({ + bigquery_dataset = string + description = optional(string) + documentation = optional(string) + categories = optional(list(string)) + icon = optional(string) + primary_contact = optional(string) + request_access = optional(string) + data_provider = optional(object({ + name = string + primary_contact = optional(string) + })) + iam = optional(map(list(string))) + publisher = optional(object({ + name = string + primary_contact = optional(string) + })) + restricted_export_config = optional(object({ + enabled = optional(bool) + restrict_query_result = optional(bool) + })) + })) + default = {} +} + +variable "name" { + description = "The ID of the data exchange. Must contain only Unicode letters, numbers (0-9), underscores (_). Should not use characters that require URL-escaping or characters outside of ASCII spaces." + type = string +} + +variable "prefix" { + description = "Optional prefix for data exchange ID." + type = string + default = null +} + +variable "primary_contact" { + description = "Email or URL of the primary point of contact of the data exchange." + type = string + default = null +} + +variable "project_id" { + description = "The ID of the project where the data exchange will be created." + type = string +} + +variable "region" { + description = "Region for the data exchange." + type = string +} diff --git a/modules/analytics-hub/versions.tf b/modules/analytics-hub/versions.tf new file mode 100644 index 0000000000..3db0e2076e --- /dev/null +++ b/modules/analytics-hub/versions.tf @@ -0,0 +1,27 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.7.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 5.11.0, < 6.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 5.11.0, < 6.0.0" # tftest + } + } +} diff --git a/tests/modules/analytics_hub/examples/iam_exchange.yaml b/tests/modules/analytics_hub/examples/iam_exchange.yaml new file mode 100644 index 0000000000..4ba6912004 --- /dev/null +++ b/tests/modules/analytics_hub/examples/iam_exchange.yaml @@ -0,0 +1,50 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.analytics-hub.google_bigquery_analytics_hub_data_exchange.data_exchange: + data_exchange_id: exchange + description: null + display_name: exchange + documentation: null + icon: null + location: us-central1 + primary_contact: null + project: project-id + timeouts: null + ? module.analytics-hub.google_bigquery_analytics_hub_data_exchange_iam_binding.exchange_iam_bindings["roles/analyticshub.viewer"] + : condition: [] + data_exchange_id: exchange + location: us-central1 + members: + - group:viewer@domain.com + - user:user@domain.com + project: project-id + role: roles/analyticshub.viewer + module.analytics-hub.google_bigquery_analytics_hub_data_exchange_iam_member.exchange_iam_members["subscribers"]: + condition: [] + data_exchange_id: exchange + location: us-central1 + member: user:user@domain.com + project: project-id + role: roles/analyticshub.subscriber + +counts: + google_bigquery_analytics_hub_data_exchange: 1 + google_bigquery_analytics_hub_data_exchange_iam_binding: 1 + google_bigquery_analytics_hub_data_exchange_iam_member: 1 + modules: 1 + resources: 3 + +outputs: {} diff --git a/tests/modules/analytics_hub/examples/iam_listing.yaml b/tests/modules/analytics_hub/examples/iam_listing.yaml new file mode 100644 index 0000000000..604cb9bb02 --- /dev/null +++ b/tests/modules/analytics_hub/examples/iam_listing.yaml @@ -0,0 +1,77 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +values: + module.analytics-hub.google_bigquery_analytics_hub_data_exchange.data_exchange: + data_exchange_id: exchange + description: null + display_name: exchange + documentation: null + icon: null + location: us-central1 + primary_contact: null + project: project-id + timeouts: null + ? module.analytics-hub.google_bigquery_analytics_hub_data_exchange_iam_binding.exchange_iam_bindings["roles/analyticshub.viewer"] + : condition: [] + data_exchange_id: exchange + location: us-central1 + members: + - group:viewer@domain.com + project: project-id + role: roles/analyticshub.viewer + module.analytics-hub.google_bigquery_analytics_hub_listing.listing["listing_id"]: + bigquery_dataset: + - dataset: projects/{project}/datasets/{dataset} + categories: null + data_exchange_id: exchange + data_provider: [] + description: null + display_name: listing_id + documentation: null + icon: null + listing_id: listing_id + location: us-central1 + primary_contact: null + project: project-id + publisher: [] + request_access: null + restricted_export_config: [] + timeouts: null + ? module.analytics-hub.google_bigquery_analytics_hub_listing_iam_binding.listing_iam_bindings["listing_id-roles/analyticshub.subscriber"] + : condition: [] + data_exchange_id: exchange + location: us-central1 + members: + - group:subscriber@domain.com + project: project-id + role: roles/analyticshub.subscriber + ? module.analytics-hub.google_bigquery_analytics_hub_listing_iam_binding.listing_iam_bindings["listing_id-roles/analyticshub.subscriptionOwner"] + : condition: [] + data_exchange_id: exchange + location: us-central1 + members: + - group:subscription-owner@domain.com + project: project-id + role: roles/analyticshub.subscriptionOwner + +counts: + google_bigquery_analytics_hub_data_exchange: 1 + google_bigquery_analytics_hub_data_exchange_iam_binding: 1 + google_bigquery_analytics_hub_listing: 1 + google_bigquery_analytics_hub_listing_iam_binding: 2 + modules: 1 + resources: 5 + +outputs: {} \ No newline at end of file