diff --git a/README.md b/README.md index 45bdebe6ad..add1dee1a3 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Currently available modules: - **compute** - [VM/VM group](./modules/compute-vm), [MIG](./modules/compute-mig), [COS container](./modules/cloud-config-container/cos-generic-metadata/) (coredns, mysql, onprem, squid), [GKE cluster](./modules/gke-cluster-standard), [GKE hub](./modules/gke-hub), [GKE nodepool](./modules/gke-nodepool), [GCVE private cloud](./modules/gcve-private-cloud) - **data** - [AlloyDB instance](./modules/alloydb), [Analytics Hub](./modules/analytics-hub), [BigQuery dataset](./modules/bigquery-dataset), [Biglake Catalog](./modules/biglake-catalog), [Bigtable instance](./modules/bigtable-instance), [Dataplex](./modules/dataplex), [Dataplex Aspect Types](./modules/dataplex-aspect-types/), [Dataplex DataScan](./modules/dataplex-datascan), [Cloud SQL instance](./modules/cloudsql-instance), [Spanner instance](./modules/spanner-instance), [Firestore](./modules/firestore), [Data Catalog Policy Tag](./modules/data-catalog-policy-tag), [Data Catalog Tag](./modules/data-catalog-tag), [Data Catalog Tag Template](./modules/data-catalog-tag-template), [Datafusion](./modules/datafusion), [Dataproc](./modules/dataproc), [GCS](./modules/gcs), [Pub/Sub](./modules/pubsub), [Dataform Repository](./modules/dataform-repository/), [Looker Core](./modules/looker-core) - **AI** - [AI Applications](./modules/ai-applications/README.md) -- **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository), [Cloud Deploy](./modules/cloud-deploy), [Secure Source Manager instance](./modules/secure-source-manager-instance), [Workstation cluster](./modules/workstation-cluster) +- **development** - [API Gateway](./modules/api-gateway), [Apigee](./modules/apigee), [Artifact Registry](./modules/artifact-registry), [Cloud Build V2 Connection](./modules/cloud-build-v2-connection), [Container Registry](./modules/container-registry), [Cloud Source Repository](./modules/source-repository), [Cloud Deploy](./modules/cloud-deploy), [Secure Source Manager instance](./modules/secure-source-manager-instance), [Workstation cluster](./modules/workstation-cluster) - **security** - [Binauthz](./modules/binauthz/), [Certificate Authority Service (CAS)](./modules/certificate-authority-service), [KMS](./modules/kms), [SecretManager](./modules/secret-manager), [VPC Service Control](./modules/vpc-sc), [Certificate Manager](./modules/certificate-manager/) - **serverless** - [Cloud Function v1](./modules/cloud-function-v1), [Cloud Function v2](./modules/cloud-function-v2), [Cloud Run](./modules/cloud-run), [Cloud Run v2](./modules/cloud-run-v2) diff --git a/modules/README.md b/modules/README.md index 7529d614d8..ab43a73157 100644 --- a/modules/README.md +++ b/modules/README.md @@ -110,6 +110,7 @@ These modules are used in the examples included in this repository. If you are u - [API Gateway](./api-gateway) - [Apigee](./apigee) - [Artifact Registry](./artifact-registry) +- [Cloud Build V2 Connection](./cloud-build-v2-connection) - [Container Registry](./container-registry) - [Cloud Source Repository](./source-repository) - [Cloud Deploy](./cloud-deploy) diff --git a/modules/cloud-build-v2-connection/README.md b/modules/cloud-build-v2-connection/README.md new file mode 100644 index 0000000000..3007fe1748 --- /dev/null +++ b/modules/cloud-build-v2-connection/README.md @@ -0,0 +1,554 @@ +# Cloud Build Connection (V2) Module + +This module allows to create a Cloud Build v2 connection with associated repositories and triggers linked to each of them. Additionaly it also familitates the creation of IAM bindings for the connection. + + +- [Github](#github) +- [Github Enterprise](#github-enterprise) +- [Bitbucket Cloud](#bitbucket-cloud) +- [Bitbucket Data Center](#bitbucket-data-center) +- [Gitlab](#gitlab) +- [Variables](#variables) +- [Outputs](#outputs) + + +## Github + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + name = "my-project" + parent = var.folder_id + prefix = var.prefix + services = [ + "cloudbuild.googleapis.com", + "secretmanager.googleapis.com" + ] + iam = { + "roles/logging.logWriter" = [ + module.cb_service_account.iam_email + ] + } +} + +module "cb_service_account" { + source = "./fabric/modules/iam-service-account" + project_id = module.project.id + name = "cloudbuild" +} + +module "secret_manager" { + source = "./fabric/modules/secret-manager" + project_id = module.project.id + secrets = { + authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + } +} + +module "cb_connection" { + source = "./fabric/modules/cloud-build-v2-connection" + project_id = module.project.id + name = "my-connection" + location = var.region + context = { + iam_principals = { + mygroup = "group:${var.group_email}" + } + } + connection_config = { + github = { + authorizer_credential_secret_version = module.secret_manager.version_ids["authorizer-credential/v1"] + app_instalation_id = 1234567 + } + } + repositories = { + my-repository = { + remote_uri = "https://github.com/my-user/my-repo.git" + triggers = { + my-trigger = { + push = { + branch = "main" + } + filename = "cloudbuild.yaml" + } + } + } + } + iam = { + "roles/cloudbuild.connectionViewer" = ["$iam_principals:mygroup"] + } +} +# tftest modules=4 resources=15 inventory=github.yaml skip-tofu +``` + +## Github Enterprise + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + name = "my-project" + parent = var.folder_id + prefix = var.prefix + services = [ + "cloudbuild.googleapis.com", + "secretmanager.googleapis.com" + ] + iam = { + "roles/logging.logWriter" = [ + module.cb_service_account.iam_email + ] + } +} + +module "cb_service_account" { + source = "./fabric/modules/iam-service-account" + project_id = module.project.id + name = "cloudbuild" +} + +module "secret_manager" { + source = "./fabric/modules/secret-manager" + project_id = module.project.id + secrets = { + webhook-secret = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + private-key-secret = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + } +} + +module "cb_connection" { + source = "./fabric/modules/cloud-build-v2-connection" + project_id = module.project.id + name = "my-connection" + location = var.region + context = { + iam_principals = { + mygroup = "group:${var.group_email}" + } + } + connection_config = { + github_enterprise = { + host_uri = "https://mmy-ghe-server.net." + app_id = "1234567" + app_installation_id = "123456789" + app_slug = "https://my-ghe-server.net/settings/apps/app-slug" + private_key_secret_version = module.secret_manager.version_ids["private-key-secret/v1"] + webhook_secret_secret_version = module.secret_manager.version_ids["webhook-secret/v1"] + } + } + repositories = { + my-repository = { + remote_uri = "https://github.com/my-user/my-repo.git" + triggers = { + my-trigger = { + push = { + branch = "main" + } + filename = "cloudbuild.yaml" + } + } + } + } + iam = { + "roles/cloudbuild.connectionViewer" = ["$iam_principals:mygroup"] + } +} +# tftest modules=4 resources=18 inventory=github-enterprise.yaml skip-tofu +``` + +## Bitbucket Cloud + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + name = "my-project" + parent = var.folder_id + prefix = var.prefix + services = [ + "cloudbuild.googleapis.com", + "secretmanager.googleapis.com" + ] + iam = { + "roles/logging.logWriter" = [ + module.cb_service_account.iam_email + ] + } +} + +module "cb_service_account" { + source = "./fabric/modules/iam-service-account" + project_id = module.project.id + name = "cloudbuild" +} + +module "secret_manager" { + source = "./fabric/modules/secret-manager" + project_id = module.project.id + secrets = { + webhook-secret = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + read-authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + } +} + +module "cb_connection" { + source = "./fabric/modules/cloud-build-v2-connection" + project_id = module.project.id + name = "my-connection" + location = var.region + context = { + iam_principals = { + mygroup = "group:${var.group_email}" + } + } + connection_config = { + bitbucket_cloud = { + workspace = "my-workspace" + webhook_secret_secret_version = module.secret_manager.version_ids["webhook-secret/v1"] + authorizer_credential_secret_version = module.secret_manager.version_ids["authorizer-credential/v1"] + read_authorizer_credential_secret_version = module.secret_manager.version_ids["read-authorizer-credential/v1"] + app_instalation_id = 1234567 + } + } + repositories = { + my-repository = { + remote_uri = "https://bitbucket.org/my-workspace/my-repository.git" + triggers = { + my-trigger = { + push = { + branch = "main" + } + filename = "cloudbuild.yaml" + } + } + } + } + iam = { + "roles/cloudbuild.connectionViewer" = ["$iam_principals:mygroup"] + } +} +# tftest modules=4 resources=21 inventory=bitbucket-cloud.yaml skip-tofu +``` + +# Bitbucket Data Center + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + name = "my-project" + parent = var.folder_id + prefix = var.prefix + services = [ + "cloudbuild.googleapis.com", + "secretmanager.googleapis.com" + ] + iam = { + "roles/logging.logWriter" = [ + module.cb_service_account.iam_email + ] + } +} + +module "cb_service_account" { + source = "./fabric/modules/iam-service-account" + project_id = module.project.id + name = "cloudbuild" +} + +module "secret_manager" { + source = "./fabric/modules/secret-manager" + project_id = module.project.id + secrets = { + webhook-secret = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + read-authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + } +} + +module "cb_connection" { + source = "./fabric/modules/cloud-build-v2-connection" + project_id = module.project.id + name = "my-connection" + location = var.region + context = { + iam_principals = { + mygroup = "group:${var.group_email}" + } + } + connection_config = { + bitbucket_data_center = { + host_uri = "https://bbdc-host.com" + webhook_secret_secret_version = module.secret_manager.version_ids["webhook-secret/v1"] + authorizer_credential_secret_version = module.secret_manager.version_ids["authorizer-credential/v1"] + read_authorizer_credential_secret_version = module.secret_manager.version_ids["read-authorizer-credential/v1"] + app_instalation_id = 1234567 + } + } + repositories = { + my-repository = { + remote_uri = "https://bbdc-host.com/scm/my-project/my-repository.git." + triggers = { + my-trigger = { + push = { + branch = "main" + } + filename = "cloudbuild.yaml" + } + } + } + } + iam = { + "roles/cloudbuild.connectionViewer" = ["$iam_principals:mygroup"] + } +} +# tftest modules=4 resources=21 inventory=bitbucket-data-center.yaml skip-tofu +``` + +## Gitlab + +```hcl +module "project" { + source = "./fabric/modules/project" + billing_account = var.billing_account_id + name = "my-project" + parent = var.folder_id + prefix = var.prefix + services = [ + "cloudbuild.googleapis.com", + "secretmanager.googleapis.com" + ] + iam = { + "roles/logging.logWriter" = [ + module.cb_service_account.iam_email + ] + } +} + +module "cb_service_account" { + source = "./fabric/modules/iam-service-account" + project_id = module.project.id + name = "cloudbuild" +} + +module "secret_manager" { + source = "./fabric/modules/secret-manager" + project_id = module.project.id + secrets = { + webhook-secret = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + read-authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + authorizer-credential = { + versions = { + v1 = { + data = "ENTER HERE YOUR SECRET VALUE" + data_config = { + write_only_version = 1 + } + } + } + iam = { + "roles/secretmanager.secretAccessor" = [module.project.service_agents.cloudbuild.iam_email] + } + } + } +} + +module "cb_connection" { + source = "./fabric/modules/cloud-build-v2-connection" + project_id = module.project.id + name = "my-connection" + location = var.region + context = { + iam_principals = { + mygroup = "group:${var.group_email}" + } + } + connection_config = { + gitlab = { + webhook_secret_secret_version = module.secret_manager.version_ids["webhook-secret/v1"] + read_authorizer_credential_secret_version = module.secret_manager.version_ids["read-authorizer-credential/v1"] + authorizer_credential_secret_version = module.secret_manager.version_ids["authorizer-credential/v1"] + } + } + repositories = { + my-repository = { + remote_uri = "https://github.com/my-user/my-repo.git" + triggers = { + my-trigger = { + push = { + branch = "main" + } + filename = "cloudbuild.yaml" + } + } + } + } + iam = { + "roles/cloudbuild.connectionViewer" = ["$iam_principals:mygroup"] + } +} +# tftest modules=4 resources=21 inventory=gitlab.yaml skip-tofu +``` + +## Variables + +| name | description | type | required | default | +|---|---|:---:|:---:|:---:| +| [location](variables.tf#L103) | Location. | string | ✓ | | +| [name](variables.tf#L108) | Name. | string | ✓ | | +| [project_id](variables.tf#L113) | Project ID. | string | ✓ | | +| [annotations](variables.tf#L17) | Annotations. | map(string) | | {} | +| [connection_config](variables.tf#L23) | Connection configuration. | object({…}) | | {} | +| [connection_create](variables.tf#L78) | Create connection. | bool | | true | +| [context](variables.tf#L85) | Context-specific interpolations. | object({…}) | | {} | +| [disabled](variables.tf#L97) | Flag indicating whether the connection is disabled or not. | bool | | false | +| [iam](variables-iam.tf#L17) | IAM bindings in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | +| [iam_bindings](variables-iam.tf#L23) | Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_bindings_additive](variables-iam.tf#L38) | Individual additive IAM bindings. Keys are arbitrary. | map(object({…})) | | {} | +| [iam_by_principals](variables-iam.tf#L53) | 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)) | | {} | +| [repositories](variables.tf#L118) | Repositories. | map(object({…})) | | {} | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| [id](outputs.tf#L17) | Connection id. | | +| [repositories](outputs.tf#L24) | Repositories. | | +| [repository_ids](outputs.tf#L29) | Repository ids. | | +| [trigger_ids](outputs.tf#L34) | Trigger ids. | | +| [triggers](outputs.tf#L39) | Triggers. | | + diff --git a/modules/cloud-build-v2-connection/iam.tf b/modules/cloud-build-v2-connection/iam.tf new file mode 100644 index 0000000000..3fe54903da --- /dev/null +++ b/modules/cloud-build-v2-connection/iam.tf @@ -0,0 +1,67 @@ +/** + * Copyright 2025 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. + */ + +# tfdoc:file:description IAM bindings + +locals { + _iam_principal_roles = distinct(flatten(values(var.iam_by_principals))) + _iam_principals = { + for r in local._iam_principal_roles : r => [ + for k, v in var.iam_by_principals : + k if try(index(v, r), null) != null + ] + } + iam = { + for role in distinct(concat(keys(var.iam), keys(local._iam_principals))) : + role => concat( + try(var.iam[role], []), + try(local._iam_principals[role], []) + ) + } +} + +resource "google_cloudbuildv2_connection_iam_binding" "authoritative" { + for_each = local.iam + project = local.project_id + location = var.location + name = local.name + role = lookup(local.ctx.custom_roles, each.key, each.key) + members = [ + for v in each.value : lookup(local.ctx.iam_principals, v, v) + ] +} + +resource "google_cloudbuildv2_connection_iam_binding" "bindings" { + for_each = var.iam_bindings + project = local.project_id + location = var.location + name = local.name + role = lookup(local.ctx.custom_roles, each.value.role, each.value.role) + members = [ + for v in each.value.members : lookup(local.ctx.iam_principals, v, v) + ] +} + +resource "google_cloudbuildv2_connection_iam_member" "bindings" { + for_each = var.iam_bindings_additive + project = local.project_id + location = var.location + name = local.name + role = lookup(local.ctx.custom_roles, each.value.role, each.value.role) + member = lookup( + local.ctx.iam_principals, each.value.member, each.value.member + ) +} \ No newline at end of file diff --git a/modules/cloud-build-v2-connection/main.tf b/modules/cloud-build-v2-connection/main.tf new file mode 100644 index 0000000000..76f162cda7 --- /dev/null +++ b/modules/cloud-build-v2-connection/main.tf @@ -0,0 +1,170 @@ +/** + * Copyright 2025 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 { + ctx = { + for k, v in var.context : k => { + for kk, vv in v : "${local.ctx_p}${k}:${kk}" => vv + } + } + ctx_p = "$" + project_id = lookup(local.ctx.project_ids, var.project_id, var.project_id) + name = var.connection_create ? try(google_cloudbuildv2_connection.connection[0].name, null) : var.name + triggers = merge([for k1, v1 in var.repositories : { for k2, v2 in v1.triggers : "${k1}-${k2}" => merge(v2, { + repository_name = k1 + }) }]...) +} + + +resource "google_cloudbuildv2_connection" "connection" { + count = var.connection_create ? 1 : 0 + location = var.location + project = var.project_id + name = var.name + annotations = var.annotations + disabled = var.disabled + + dynamic "bitbucket_cloud_config" { + for_each = var.connection_config.bitbucket_cloud == null ? [] : [""] + content { + workspace = var.connection_config.bitbucket_cloud.workspace + webhook_secret_secret_version = var.connection_config.bitbucket_cloud.webhook_secret_secret_version + read_authorizer_credential { + user_token_secret_version = var.connection_config.bitbucket_cloud.read_authorizer_credential_secret_version + } + authorizer_credential { + user_token_secret_version = var.connection_config.bitbucket_cloud.authorizer_credential_secret_version + } + } + } + + dynamic "bitbucket_data_center_config" { + for_each = var.connection_config.bitbucket_data_center == null ? [] : [""] + content { + host_uri = var.connection_config.bitbucket_data_center.host_uri + webhook_secret_secret_version = var.connection_config.bitbucket_data_center.webhook_secret_secret_version + read_authorizer_credential { + user_token_secret_version = var.connection_config.bitbucket_data_center.read_authorizer_credential_secret_version + } + authorizer_credential { + user_token_secret_version = var.connection_config.bitbucket_data_center.authorizer_credential_secret_version + } + dynamic "service_directory_config" { + for_each = var.connection_config.bitbucket_data_center.service == null ? [] : [""] + content { + service = var.connection_config.bitbucket_data_center.service + } + } + ssl_ca = var.connection_config.bitbucket_data_center.ssl_ca + } + } + dynamic "github_config" { + for_each = var.connection_config.github == null ? [] : [""] + content { + app_installation_id = var.connection_config.github.app_installation_id + authorizer_credential { + oauth_token_secret_version = var.connection_config.github.authorizer_credential_secret_version + } + } + } + + dynamic "github_enterprise_config" { + for_each = var.connection_config.github_enterprise == null ? [] : [""] + content { + host_uri = var.connection_config.github_enterprise.host_uri + app_id = var.connection_config.github_enterprise.app_id + app_slug = var.connection_config.github_enterprise.app_slug + app_installation_id = var.connection_config.github_enterprise.app_installation_id + private_key_secret_version = var.connection_config.github_enterprise.private_key_secret_version + webhook_secret_secret_version = var.connection_config.github_enterprise.webhook_secret_secret_version + ssl_ca = var.connection_config.github_enterprise.ssl_ca + dynamic "service_directory_config" { + for_each = var.connection_config.github_enterprise.service == null ? [] : [""] + content { + service = var.connection_config.github_enterprise.service + } + } + } + } + + dynamic "gitlab_config" { + for_each = var.connection_config.gitlab == null ? [] : [""] + content { + host_uri = var.connection_config.gitlab.host_uri + webhook_secret_secret_version = var.connection_config.gitlab.webhook_secret_secret_version + ssl_ca = var.connection_config.gitlab.ssl_ca + dynamic "authorizer_credential" { + for_each = var.connection_config.gitlab.authorizer_credential_secret_version == null ? [] : [""] + content { + user_token_secret_version = var.connection_config.gitlab.authorizer_credential_secret_version + } + } + dynamic "read_authorizer_credential" { + for_each = var.connection_config.gitlab.read_authorizer_credential_secret_version == null ? [] : [""] + content { + user_token_secret_version = var.connection_config.gitlab.read_authorizer_credential_secret_version + } + } + dynamic "service_directory_config" { + for_each = var.connection_config.gitlab.service == null ? [] : [""] + content { + service = var.connection_config.gitlab.service + } + } + } + + } +} + +resource "google_cloudbuildv2_repository" "repositories" { + for_each = var.repositories + name = each.key + project = local.project_id + location = var.location + parent_connection = local.name + remote_uri = each.value.remote_uri + annotations = each.value.annotations +} + +resource "google_cloudbuild_trigger" "triggers" { + for_each = local.triggers + location = var.location + name = each.key + project = local.project_id + description = each.value.description + disabled = each.value.disabled + repository_event_config { + repository = google_cloudbuildv2_repository.repositories[each.value.repository_name].id + dynamic "push" { + for_each = try(each.value.push, null) == null ? [] : [""] + content { + branch = each.value.push.branch + invert_regex = each.value.push.invert_regex + tag = each.value.push.tag + } + } + dynamic "pull_request" { + for_each = try(each.value.pull_request, null) == null ? [] : [""] + content { + branch = each.value.pull_request.branch + invert_regex = each.value.pull_request.invert_regex + comment_control = each.value.pull_request.comment_control + } + } + } + + filename = each.value.filename +} \ No newline at end of file diff --git a/modules/cloud-build-v2-connection/outputs.tf b/modules/cloud-build-v2-connection/outputs.tf new file mode 100644 index 0000000000..c37be2d253 --- /dev/null +++ b/modules/cloud-build-v2-connection/outputs.tf @@ -0,0 +1,42 @@ +/** + * Copyright 2025 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 "id" { + description = "Connection id." + value = (var.connection_create ? + google_cloudbuildv2_connection.connection[0].id : + "projects/${local.project_id}/locations/${var.location}/connections/${local.name}") +} + +output "repositories" { + description = "Repositories." + value = google_cloudbuildv2_repository.repositories +} + +output "repository_ids" { + description = "Repository ids." + value = { for k, v in google_cloudbuildv2_repository.repositories : k => v.id } +} + +output "trigger_ids" { + description = "Trigger ids." + value = { for k, v in google_cloudbuild_trigger.triggers : k => v.id } +} + +output "triggers" { + description = "Triggers." + value = google_cloudbuild_trigger.triggers +} diff --git a/modules/cloud-build-v2-connection/variables-iam.tf b/modules/cloud-build-v2-connection/variables-iam.tf new file mode 100644 index 0000000000..433fcf3b73 --- /dev/null +++ b/modules/cloud-build-v2-connection/variables-iam.tf @@ -0,0 +1,58 @@ +/** + * Copyright 2025 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 = "IAM bindings in {ROLE => [MEMBERS]} format." + type = map(list(string)) + default = {} +} + +variable "iam_bindings" { + description = "Authoritative IAM bindings in {KEY => {role = ROLE, members = [], condition = {}}}. Keys are arbitrary." + type = map(object({ + members = list(string) + role = string + condition = optional(object({ + expression = string + title = string + description = optional(string) + })) + })) + nullable = false + default = {} +} + +variable "iam_bindings_additive" { + description = "Individual additive IAM bindings. Keys are arbitrary." + type = map(object({ + member = string + role = string + condition = optional(object({ + expression = string + title = string + description = optional(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 +} \ No newline at end of file diff --git a/modules/cloud-build-v2-connection/variables.tf b/modules/cloud-build-v2-connection/variables.tf new file mode 100644 index 0000000000..93667e8865 --- /dev/null +++ b/modules/cloud-build-v2-connection/variables.tf @@ -0,0 +1,171 @@ +/** + * Copyright 2025 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 "annotations" { + description = "Annotations." + type = map(string) + default = {} +} + +variable "connection_config" { + description = "Connection configuration." + type = object({ + bitbucket_cloud = optional(object({ + app_installation_id = optional(string) + authorizer_credential_secret_version = string + read_authorizer_credential_secret_version = string + webhook_secret_secret_version = string + workspace = string + })) + bitbucket_data_center = optional(object({ + authorizer_credential_secret_version = string + host_uri = string + read_authorizer_credential_secret_version = string + service = optional(string) + ssl_ca = optional(string) + webhook_secret_secret_version = optional(string) + })) + github = optional(object({ + app_installation_id = optional(string) + authorizer_credential_secret_version = optional(string) + })) + github_enterprise = optional(object({ + app_id = optional(string) + app_installation_id = optional(string) + app_slug = optional(string) + host_uri = string + private_key_secret_version = optional(string) + service = optional(string) + ssl_ca = optional(string) + webhook_secret_secret_version = optional(string) + })) + gitlab = optional(object({ + host_uri = optional(string) + webhook_secret_secret_version = string + read_authorizer_credential_secret_version = string + authorizer_credential_secret_version = string + service = optional(string) + ssl_ca = optional(string) + })) + }) + default = {} + nullable = false + validation { + condition = ( + (try(var.connection_config.bitbucket_cloud, null) == null ? 0 : 1) + + (try(var.connection_config.bitbucket_data_center, null) == null ? 0 : 1) + + (try(var.connection_config.github, null) == null ? 0 : 1) + + (try(var.connection_config.github_enterprise, null) == null ? 0 : 1) + + (try(var.connection_config.gitlab, null) == null ? 0 : 1) == 1 + ) + error_message = "One and only one of bitbucket_cloud, bitbucket_data_center, github, github_enterprise, gitlab can be defined." + } +} + +variable "connection_create" { + description = "Create connection." + type = bool + default = true +} + + +variable "context" { + description = "Context-specific interpolations." + type = object({ + custom_roles = optional(map(string), {}) + iam_principals = optional(map(string), {}) + locations = optional(map(string), {}) + project_ids = optional(map(string), {}) + }) + default = {} + nullable = false +} + +variable "disabled" { + description = "Flag indicating whether the connection is disabled or not." + type = bool + default = false +} + +variable "location" { + description = "Location." + type = string +} + +variable "name" { + description = "Name." + type = string +} + +variable "project_id" { + description = "Project ID." + type = string +} + +variable "repositories" { + description = "Repositories." + type = map(object({ + remote_uri = string + annotations = optional(map(string), {}) + triggers = optional(map(object({ + approval_required = optional(bool, false) + description = optional(string) + pull_request = optional(object({ + branch = optional(string) + invert_regex = optional(string) + comment_control = optional(string) + })) + push = optional(object({ + branch = optional(string) + invert_regex = optional(string) + tag = optional(string) + })) + disabled = optional(bool, false) + filename = string + include_build_logs = optional(string) + substitutions = optional(map(string), {}) + service_account = optional(string) + tags = optional(map(string)) + })), {}) + })) + default = {} + nullable = false + + validation { + condition = alltrue([for k1, v1 in var.repositories : + alltrue([for k2, v2 in v1.triggers : + contains(["INCLUDE_BUILD_LOGS_UNSPECIFIED", + "INCLUDE_BUILD_LOGS_WITH_STATUS"], + coalesce(v2.include_build_logs, "INCLUDE_BUILD_LOGS_UNSPECIFIED"))])]) + error_message = "Possible values for include_build_logs are: INCLUDE_BUILD_LOGS_UNSPECIFIED, INCLUDE_BUILD_LOGS_WITH_STATUS." + } + + validation { + condition = alltrue([for k1, v1 in var.repositories : + alltrue([for k2, v2 in v1.triggers : + contains(["COMMENTS_DISABLED", + "COMMENTS_ENABLED", "COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY"], + try(v2.push.comment_control, "COMMENTS_DISABLED"))])]) + error_message = "Possible values for include_build_logs are: COMMENTS_DISABLED, COMMENTS_ENABLED, COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY." + } + + validation { + condition = alltrue([for k1, v1 in var.repositories : + alltrue([for k2, v2 in v1.triggers : (v2.push == null) != (v2.pull_request == null)]) + ]) + error_message = "One of pull or push needs to be populated for a trigger." + } +} diff --git a/modules/cloud-build-v2-connection/versions.tf b/modules/cloud-build-v2-connection/versions.tf new file mode 100644 index 0000000000..d639323400 --- /dev/null +++ b/modules/cloud-build-v2-connection/versions.tf @@ -0,0 +1,35 @@ +# Copyright 2025 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. + +# Fabric release: v45.0.0 + +terraform { + required_version = ">= 1.11.4" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 7.0.1, < 8.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 7.0.1, < 8.0.0" # tftest + } + } + provider_meta "google" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/certificate-manager:v45.0.0-tf" + } + provider_meta "google-beta" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/certificate-manager:v45.0.0-tf" + } +} diff --git a/modules/cloud-build-v2-connection/versions.tofu b/modules/cloud-build-v2-connection/versions.tofu new file mode 100644 index 0000000000..a581ae71e8 --- /dev/null +++ b/modules/cloud-build-v2-connection/versions.tofu @@ -0,0 +1,35 @@ +# Copyright 2025 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. + +# Fabric release: v45.0.0 + +terraform { + required_version = ">= 1.9.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 7.0.1, < 8.0.0" # tftest + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 7.0.1, < 8.0.0" # tftest + } + } + provider_meta "google" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/cloud-deploy:v45.0.0-tofu" + } + provider_meta "google-beta" { + module_name = "google-pso-tool/cloud-foundation-fabric/modules/cloud-deploy:v45.0.0-tofu" + } +} \ No newline at end of file diff --git a/tests/modules/cloud_build_v2_connection/examples/bitbucket-cloud.yaml b/tests/modules/cloud_build_v2_connection/examples/bitbucket-cloud.yaml new file mode 100644 index 0000000000..7d6e3895ce --- /dev/null +++ b/tests/modules/cloud_build_v2_connection/examples/bitbucket-cloud.yaml @@ -0,0 +1,246 @@ +# Copyright 2025 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.cb_connection.google_cloudbuild_trigger.triggers["my-repository-my-trigger"]: + bitbucket_server_trigger_config: [] + build: [] + description: null + developer_connect_event_config: [] + disabled: false + filename: cloudbuild.yaml + filter: null + git_file_source: [] + github: [] + ignored_files: null + include_build_logs: null + included_files: null + location: europe-west8 + name: my-repository-my-trigger + project: test-my-project + pubsub_config: [] + repository_event_config: + - pull_request: [] + push: + - branch: main + invert_regex: null + tag: null + service_account: null + source_to_build: [] + substitutions: null + tags: null + timeouts: null + trigger_template: [] + webhook_config: [] + module.cb_connection.google_cloudbuildv2_connection.connection[0]: + annotations: null + bitbucket_cloud_config: + - authorizer_credential: + - {} + read_authorizer_credential: + - {} + workspace: my-workspace + bitbucket_data_center_config: [] + disabled: false + github_config: [] + github_enterprise_config: [] + gitlab_config: [] + location: europe-west8 + name: my-connection + project: test-my-project + timeouts: null + module.cb_connection.google_cloudbuildv2_connection_iam_binding.authoritative["roles/cloudbuild.connectionViewer"]: + condition: [] + location: europe-west8 + members: + - group:organization-admins@example.org + name: my-connection + project: test-my-project + role: roles/cloudbuild.connectionViewer + module.cb_connection.google_cloudbuildv2_repository.repositories["my-repository"]: + annotations: null + location: europe-west8 + name: my-repository + parent_connection: my-connection + project: test-my-project + remote_uri: https://bitbucket.org/my-workspace/my-repository.git + timeouts: null + module.cb_service_account.google_service_account.service_account[0]: + account_id: cloudbuild + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: cloudbuild@test-my-project.iam.gserviceaccount.com + member: serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + timeouts: null + module.project.google_project.project[0]: + auto_create_network: false + billing_account: 123456-123456-123456 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + folder_id: '1122334455' + labels: null + name: test-my-project + org_id: null + project_id: test-my-project + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.project.google_project_iam_binding.authoritative["roles/logging.logWriter"]: + condition: [] + members: + - serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + role: roles/logging.logWriter + module.project.google_project_iam_member.service_agents["cloudbuild"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.serviceAgent + module.project.google_project_iam_member.service_agents["cloudbuild-sa"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.builds.builder + module.project.google_project_service.project_services["cloudbuild.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: cloudbuild.googleapis.com + timeouts: null + module.project.google_project_service.project_services["secretmanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.project.google_project_service_identity.default["secretmanager.googleapis.com"]: + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.secret_manager.google_secret_manager_secret.default["authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["read-authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: read-authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["webhook-secret"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: webhook-secret + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + ? module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["authorizer-credential.roles/secretmanager.secretAccessor"] + : condition: [] + role: roles/secretmanager.secretAccessor + ? module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["read-authorizer-credential.roles/secretmanager.secretAccessor"] + : condition: [] + role: roles/secretmanager.secretAccessor + ? module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["webhook-secret.roles/secretmanager.secretAccessor"] + : condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_version.default["authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["read-authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["webhook-secret/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + +counts: + google_cloudbuild_trigger: 1 + google_cloudbuildv2_connection: 1 + google_cloudbuildv2_connection_iam_binding: 1 + google_cloudbuildv2_repository: 1 + google_project: 1 + google_project_iam_binding: 1 + google_project_iam_member: 2 + google_project_service: 2 + google_project_service_identity: 1 + google_secret_manager_secret: 3 + google_secret_manager_secret_iam_binding: 3 + google_secret_manager_secret_version: 3 + google_service_account: 1 + modules: 4 + resources: 21 \ No newline at end of file diff --git a/tests/modules/cloud_build_v2_connection/examples/bitbucket-data-center.yaml b/tests/modules/cloud_build_v2_connection/examples/bitbucket-data-center.yaml new file mode 100644 index 0000000000..7d2978cf61 --- /dev/null +++ b/tests/modules/cloud_build_v2_connection/examples/bitbucket-data-center.yaml @@ -0,0 +1,248 @@ +# Copyright 2025 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.cb_connection.google_cloudbuild_trigger.triggers["my-repository-my-trigger"]: + bitbucket_server_trigger_config: [] + build: [] + description: null + developer_connect_event_config: [] + disabled: false + filename: cloudbuild.yaml + filter: null + git_file_source: [] + github: [] + ignored_files: null + include_build_logs: null + included_files: null + location: europe-west8 + name: my-repository-my-trigger + project: test-my-project + pubsub_config: [] + repository_event_config: + - pull_request: [] + push: + - branch: main + invert_regex: null + tag: null + service_account: null + source_to_build: [] + substitutions: null + tags: null + timeouts: null + trigger_template: [] + webhook_config: [] + module.cb_connection.google_cloudbuildv2_connection.connection[0]: + annotations: null + bitbucket_cloud_config: [] + bitbucket_data_center_config: + - authorizer_credential: + - {} + host_uri: https://bbdc-host.com + read_authorizer_credential: + - {} + service_directory_config: [] + ssl_ca: null + disabled: false + github_config: [] + github_enterprise_config: [] + gitlab_config: [] + location: europe-west8 + name: my-connection + project: test-my-project + timeouts: null + module.cb_connection.google_cloudbuildv2_connection_iam_binding.authoritative["roles/cloudbuild.connectionViewer"]: + condition: [] + location: europe-west8 + members: + - group:organization-admins@example.org + name: my-connection + project: test-my-project + role: roles/cloudbuild.connectionViewer + module.cb_connection.google_cloudbuildv2_repository.repositories["my-repository"]: + annotations: null + location: europe-west8 + name: my-repository + parent_connection: my-connection + project: test-my-project + remote_uri: https://bbdc-host.com/scm/my-project/my-repository.git. + timeouts: null + module.cb_service_account.google_service_account.service_account[0]: + account_id: cloudbuild + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: cloudbuild@test-my-project.iam.gserviceaccount.com + member: serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + timeouts: null + module.project.google_project.project[0]: + auto_create_network: false + billing_account: 123456-123456-123456 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: 'true' + folder_id: '1122334455' + labels: null + name: test-my-project + org_id: null + project_id: test-my-project + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + module.project.google_project_iam_binding.authoritative["roles/logging.logWriter"]: + condition: [] + members: + - serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + role: roles/logging.logWriter + module.project.google_project_iam_member.service_agents["cloudbuild"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.serviceAgent + module.project.google_project_iam_member.service_agents["cloudbuild-sa"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.builds.builder + module.project.google_project_service.project_services["cloudbuild.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: cloudbuild.googleapis.com + timeouts: null + module.project.google_project_service.project_services["secretmanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.project.google_project_service_identity.default["secretmanager.googleapis.com"]: + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.secret_manager.google_secret_manager_secret.default["authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["read-authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: read-authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["webhook-secret"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: 'true' + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: webhook-secret + tags: null + terraform_labels: + goog-terraform-provisioned: 'true' + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + ? module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["authorizer-credential.roles/secretmanager.secretAccessor"] + : condition: [] + role: roles/secretmanager.secretAccessor + ? module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["read-authorizer-credential.roles/secretmanager.secretAccessor"] + : condition: [] + role: roles/secretmanager.secretAccessor + ? module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["webhook-secret.roles/secretmanager.secretAccessor"] + : condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_version.default["authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["read-authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["webhook-secret/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + +counts: + google_cloudbuild_trigger: 1 + google_cloudbuildv2_connection: 1 + google_cloudbuildv2_connection_iam_binding: 1 + google_cloudbuildv2_repository: 1 + google_project: 1 + google_project_iam_binding: 1 + google_project_iam_member: 2 + google_project_service: 2 + google_project_service_identity: 1 + google_secret_manager_secret: 3 + google_secret_manager_secret_iam_binding: 3 + google_secret_manager_secret_version: 3 + google_service_account: 1 + modules: 4 + resources: 21 \ No newline at end of file diff --git a/tests/modules/cloud_build_v2_connection/examples/github-enterprise.yaml b/tests/modules/cloud_build_v2_connection/examples/github-enterprise.yaml new file mode 100644 index 0000000000..adee8fbdc2 --- /dev/null +++ b/tests/modules/cloud_build_v2_connection/examples/github-enterprise.yaml @@ -0,0 +1,215 @@ +# Copyright 2025 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.cb_connection.google_cloudbuild_trigger.triggers["my-repository-my-trigger"]: + bitbucket_server_trigger_config: [] + build: [] + description: null + developer_connect_event_config: [] + disabled: false + filename: cloudbuild.yaml + filter: null + git_file_source: [] + github: [] + ignored_files: null + include_build_logs: null + included_files: null + location: europe-west8 + name: my-repository-my-trigger + project: test-my-project + pubsub_config: [] + repository_event_config: + - pull_request: [] + push: + - branch: main + invert_regex: null + tag: null + service_account: null + source_to_build: [] + substitutions: null + tags: null + timeouts: null + trigger_template: [] + webhook_config: [] + module.cb_connection.google_cloudbuildv2_connection.connection[0]: + annotations: null + bitbucket_cloud_config: [] + bitbucket_data_center_config: [] + disabled: false + github_config: [] + github_enterprise_config: + - app_id: 1234567 + app_installation_id: 123456789 + app_slug: https://my-ghe-server.net/settings/apps/app-slug + host_uri: https://mmy-ghe-server.net. + service_directory_config: [] + ssl_ca: null + gitlab_config: [] + location: europe-west8 + name: my-connection + project: test-my-project + timeouts: null + module.cb_connection.google_cloudbuildv2_connection_iam_binding.authoritative["roles/cloudbuild.connectionViewer"]: + condition: [] + location: europe-west8 + members: + - group:organization-admins@example.org + name: my-connection + project: test-my-project + role: roles/cloudbuild.connectionViewer + module.cb_connection.google_cloudbuildv2_repository.repositories["my-repository"]: + annotations: null + location: europe-west8 + name: my-repository + parent_connection: my-connection + project: test-my-project + remote_uri: https://github.com/my-user/my-repo.git + timeouts: null + module.cb_service_account.google_service_account.service_account[0]: + account_id: cloudbuild + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: cloudbuild@test-my-project.iam.gserviceaccount.com + member: serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + timeouts: null + module.project.google_project.project[0]: + auto_create_network: false + billing_account: 123456-123456-123456 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: "true" + folder_id: "1122334455" + labels: null + name: test-my-project + org_id: null + project_id: test-my-project + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + module.project.google_project_iam_binding.authoritative["roles/logging.logWriter"]: + condition: [] + members: + - serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + role: roles/logging.logWriter + module.project.google_project_iam_member.service_agents["cloudbuild"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.serviceAgent + module.project.google_project_iam_member.service_agents["cloudbuild-sa"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.builds.builder + module.project.google_project_service.project_services["cloudbuild.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: cloudbuild.googleapis.com + timeouts: null + module.project.google_project_service.project_services["secretmanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.project.google_project_service_identity.default["secretmanager.googleapis.com"]: + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.secret_manager.google_secret_manager_secret.default["private-key-secret"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: "true" + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: private-key-secret + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["webhook-secret"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: "true" + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: webhook-secret + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["private-key-secret.roles/secretmanager.secretAccessor"]: + condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["webhook-secret.roles/secretmanager.secretAccessor"]: + condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_version.default["private-key-secret/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["webhook-secret/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + +counts: + google_cloudbuild_trigger: 1 + google_cloudbuildv2_connection: 1 + google_cloudbuildv2_connection_iam_binding: 1 + google_cloudbuildv2_repository: 1 + google_project: 1 + google_project_iam_binding: 1 + google_project_iam_member: 2 + google_project_service: 2 + google_project_service_identity: 1 + google_secret_manager_secret: 2 + google_secret_manager_secret_iam_binding: 2 + google_secret_manager_secret_version: 2 + google_service_account: 1 + modules: 4 + resources: 18 diff --git a/tests/modules/cloud_build_v2_connection/examples/github.yaml b/tests/modules/cloud_build_v2_connection/examples/github.yaml new file mode 100644 index 0000000000..e98268f08b --- /dev/null +++ b/tests/modules/cloud_build_v2_connection/examples/github.yaml @@ -0,0 +1,180 @@ +# Copyright 2025 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.cb_connection.google_cloudbuild_trigger.triggers["my-repository-my-trigger"]: + bitbucket_server_trigger_config: [] + build: [] + description: null + developer_connect_event_config: [] + disabled: false + filename: cloudbuild.yaml + filter: null + git_file_source: [] + github: [] + ignored_files: null + include_build_logs: null + included_files: null + location: europe-west8 + name: my-repository-my-trigger + project: test-my-project + pubsub_config: [] + repository_event_config: + - pull_request: [] + push: + - branch: main + invert_regex: null + tag: null + service_account: null + source_to_build: [] + substitutions: null + tags: null + timeouts: null + trigger_template: [] + webhook_config: [] + module.cb_connection.google_cloudbuildv2_connection.connection[0]: + annotations: null + bitbucket_cloud_config: [] + bitbucket_data_center_config: [] + disabled: false + github_config: + - app_installation_id: null + authorizer_credential: + - {} + github_enterprise_config: [] + gitlab_config: [] + location: europe-west8 + name: my-connection + project: test-my-project + timeouts: null + module.cb_connection.google_cloudbuildv2_connection_iam_binding.authoritative["roles/cloudbuild.connectionViewer"]: + condition: [] + location: europe-west8 + members: + - group:organization-admins@example.org + name: my-connection + project: test-my-project + role: roles/cloudbuild.connectionViewer + module.cb_connection.google_cloudbuildv2_repository.repositories["my-repository"]: + annotations: null + location: europe-west8 + name: my-repository + parent_connection: my-connection + project: test-my-project + remote_uri: https://github.com/my-user/my-repo.git + timeouts: null + module.cb_service_account.google_service_account.service_account[0]: + account_id: cloudbuild + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: cloudbuild@test-my-project.iam.gserviceaccount.com + member: serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + timeouts: null + module.project.google_project.project[0]: + auto_create_network: false + billing_account: 123456-123456-123456 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: "true" + folder_id: "1122334455" + labels: null + name: test-my-project + org_id: null + project_id: test-my-project + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + module.project.google_project_iam_binding.authoritative["roles/logging.logWriter"]: + condition: [] + members: + - serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + role: roles/logging.logWriter + module.project.google_project_iam_member.service_agents["cloudbuild"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.serviceAgent + module.project.google_project_iam_member.service_agents["cloudbuild-sa"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.builds.builder + module.project.google_project_service.project_services["cloudbuild.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: cloudbuild.googleapis.com + timeouts: null + module.project.google_project_service.project_services["secretmanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.project.google_project_service_identity.default["secretmanager.googleapis.com"]: + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.secret_manager.google_secret_manager_secret.default["authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: "true" + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["authorizer-credential.roles/secretmanager.secretAccessor"]: + condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_version.default["authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + +counts: + google_cloudbuild_trigger: 1 + google_cloudbuildv2_connection: 1 + google_cloudbuildv2_connection_iam_binding: 1 + google_cloudbuildv2_repository: 1 + google_project: 1 + google_project_iam_binding: 1 + google_project_iam_member: 2 + google_project_service: 2 + google_project_service_identity: 1 + google_secret_manager_secret: 1 + google_secret_manager_secret_iam_binding: 1 + google_secret_manager_secret_version: 1 + google_service_account: 1 + modules: 4 + resources: 15 diff --git a/tests/modules/cloud_build_v2_connection/examples/gitlab.yaml b/tests/modules/cloud_build_v2_connection/examples/gitlab.yaml new file mode 100644 index 0000000000..ed183f8886 --- /dev/null +++ b/tests/modules/cloud_build_v2_connection/examples/gitlab.yaml @@ -0,0 +1,243 @@ +# Copyright 2025 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.cb_connection.google_cloudbuild_trigger.triggers["my-repository-my-trigger"]: + bitbucket_server_trigger_config: [] + build: [] + description: null + developer_connect_event_config: [] + disabled: false + filename: cloudbuild.yaml + filter: null + git_file_source: [] + github: [] + ignored_files: null + include_build_logs: null + included_files: null + location: europe-west8 + name: my-repository-my-trigger + project: test-my-project + pubsub_config: [] + repository_event_config: + - pull_request: [] + push: + - branch: main + invert_regex: null + tag: null + service_account: null + source_to_build: [] + substitutions: null + tags: null + timeouts: null + trigger_template: [] + webhook_config: [] + module.cb_connection.google_cloudbuildv2_connection.connection[0]: + annotations: null + bitbucket_cloud_config: [] + bitbucket_data_center_config: [] + disabled: false + github_config: [] + github_enterprise_config: [] + gitlab_config: + - service_directory_config: [] + ssl_ca: null + location: europe-west8 + name: my-connection + project: test-my-project + timeouts: null + module.cb_connection.google_cloudbuildv2_connection_iam_binding.authoritative["roles/cloudbuild.connectionViewer"]: + condition: [] + location: europe-west8 + members: + - group:organization-admins@example.org + name: my-connection + project: test-my-project + role: roles/cloudbuild.connectionViewer + module.cb_connection.google_cloudbuildv2_repository.repositories["my-repository"]: + annotations: null + location: europe-west8 + name: my-repository + parent_connection: my-connection + project: test-my-project + remote_uri: https://github.com/my-user/my-repo.git + timeouts: null + module.cb_service_account.google_service_account.service_account[0]: + account_id: cloudbuild + create_ignore_already_exists: null + description: null + disabled: false + display_name: Terraform-managed. + email: cloudbuild@test-my-project.iam.gserviceaccount.com + member: serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + timeouts: null + module.project.google_project.project[0]: + auto_create_network: false + billing_account: 123456-123456-123456 + deletion_policy: DELETE + effective_labels: + goog-terraform-provisioned: "true" + folder_id: "1122334455" + labels: null + name: test-my-project + org_id: null + project_id: test-my-project + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + module.project.google_project_iam_binding.authoritative["roles/logging.logWriter"]: + condition: [] + members: + - serviceAccount:cloudbuild@test-my-project.iam.gserviceaccount.com + project: test-my-project + role: roles/logging.logWriter + module.project.google_project_iam_member.service_agents["cloudbuild"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.serviceAgent + module.project.google_project_iam_member.service_agents["cloudbuild-sa"]: + condition: [] + project: test-my-project + role: roles/cloudbuild.builds.builder + module.project.google_project_service.project_services["cloudbuild.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: cloudbuild.googleapis.com + timeouts: null + module.project.google_project_service.project_services["secretmanager.googleapis.com"]: + disable_dependent_services: false + disable_on_destroy: false + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.project.google_project_service_identity.default["secretmanager.googleapis.com"]: + project: test-my-project + service: secretmanager.googleapis.com + timeouts: null + module.secret_manager.google_secret_manager_secret.default["authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: "true" + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["read-authorizer-credential"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: "true" + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: read-authorizer-credential + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret.default["webhook-secret"]: + annotations: null + deletion_protection: false + effective_labels: + goog-terraform-provisioned: "true" + labels: null + project: test-my-project + replication: + - auto: + - customer_managed_encryption: [] + user_managed: [] + rotation: [] + secret_id: webhook-secret + tags: null + terraform_labels: + goog-terraform-provisioned: "true" + timeouts: null + topics: [] + ttl: null + version_aliases: null + version_destroy_ttl: null + module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["authorizer-credential.roles/secretmanager.secretAccessor"]: + condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["read-authorizer-credential.roles/secretmanager.secretAccessor"]: + condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_iam_binding.authoritative["webhook-secret.roles/secretmanager.secretAccessor"]: + condition: [] + role: roles/secretmanager.secretAccessor + module.secret_manager.google_secret_manager_secret_version.default["authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["read-authorizer-credential/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + module.secret_manager.google_secret_manager_secret_version.default["webhook-secret/v1"]: + deletion_policy: DELETE + enabled: true + is_secret_data_base64: false + secret_data: null + secret_data_wo: null + secret_data_wo_version: 1 + timeouts: null + +counts: + google_cloudbuild_trigger: 1 + google_cloudbuildv2_connection: 1 + google_cloudbuildv2_connection_iam_binding: 1 + google_cloudbuildv2_repository: 1 + google_project: 1 + google_project_iam_binding: 1 + google_project_iam_member: 2 + google_project_service: 2 + google_project_service_identity: 1 + google_secret_manager_secret: 3 + google_secret_manager_secret_iam_binding: 3 + google_secret_manager_secret_version: 3 + google_service_account: 1 + modules: 4 + resources: 21