diff --git a/docs/images/V7_Billing_Budget.png b/docs/images/V7_Billing_Budget.png new file mode 100644 index 00000000..754e3606 Binary files /dev/null and b/docs/images/V7_Billing_Budget.png differ diff --git a/modules/billing_budget/README.md b/modules/billing_budget/README.md new file mode 100644 index 00000000..9f2a06b8 --- /dev/null +++ b/modules/billing_budget/README.md @@ -0,0 +1,69 @@ +# RAD Lab Billing Budget Module + +This module creates a simple GCP project, enables APIs, adds users to the project and also creates a Billing Budget for the Project. + +## GCP Products/Services + +* GCP Project +* APIs & Services +* Billing Budget + +## Reference Architecture Diagram + +Below Architechture Diagram is the base representation of what will be created as a part of [RAD Lab Launcher](../../radlab-launcher/radlab.py). + +![](../../docs/images/V7_Billing_Budget.png) + +## API Prerequisites + +In the RAD Lab Management Project make sure that _Cloud Billing Budget API (`billingbudgets.googleapis.com`)_ is enabled. + +## IAM Permissions Prerequisites + +Create a Terraform Service Account in RAD Lab Management Project to execute / deploy the RAD Lab module. Ensure that the Service Account has the following IAM permissions, **when creating the project** (`create_project` = true): + +- Parent: `roles/resourcemanager.projectCreator` +- Parent: `roles/billing.user` +- Parent: `roles/billing.costsManager` + +The User, Group, or Service Account who will be deploying the module should have access to impersonate and grant it the roles, `roles/iam.serviceAccountTokenCreator` on the **Terraform Service Account’s IAM Policy**. + +Note: This is not a Project IAM Binding; this is a **Service Account** IAM Binding. + +NOTE: Additional [permissions](../../radlab-launcher/README.md#iam-permissions-prerequisites) are required when deploying the RAD Lab modules via [RAD Lab Launcher](../../radlab-launcher). Use `--disable-perm-check` or `-dc` arguments when using RAD lab Launcher for the module deployment. + +_Usage:_ + +```python3 radlab.py --disable-perm-check``` + + + +## Variables + +| name | description | type | required | default | +|---|---|:---: |:---:|:---:| +| billing_account_id | Billing Account associated to the GCP Resources | string | ✓ | | +| *apis* | The list of GCP apis to enable. | set(string) | | ["compute.googleapis.com","bigquery.googleapis.com","bigquerystorage.googleapis.com"] | +| *billing_budget_alert_spend_basis* | The type of basis used to determine if spend has passed the threshold | string | | CURRENT_SPEND | +| *billing_budget_alert_spent_percents* | A list of percentages of the budget to alert on when threshold is exceeded | list(number) | | [0.5,0.7,1] | +| *billing_budget_amount* | The amount to use as the budget in USD | number | | 1000 | +| *billing_budget_credit_types_treatment* | Specifies how credits should be treated when determining spend for threshold calculations | string | | INCLUDE_ALL_CREDITS | +| *billing_budget_labels* | A single label and value pair specifying that usage from only this set of labeled resources should be included in the budget. | map(string) | | ... | +| *billing_budget_services* | A list of services ids to be included in the budget. If omitted, all services will be included in the budget. Service ids can be found at https://cloud.google.com/skus/ | list(string) | | null | +| *create_project* | Set to true if the module has to create a project. If you want to deploy in an existing project, set this variable to false. | bool | | true | +| *enable_services* | Enable the necessary APIs on the project. When using an existing project, this can be set to false. | bool | | true | +| *folder_id* | Folder ID where the project should be created. It can be skipped if already setting organization_id. Leave blank if the project should be created directly underneath the Organization node. | string | | | +| *organization_id* | Organization ID where GCP Resources need to get spin up. It can be skipped if already setting folder_id | string | | | +| *project_name* | Project name or ID, if it's an existing project. | string | | radlab-billing-budget | +| *random_id* | Adds a suffix of 4 random characters to the `project_id` | string | | null | +| *resource_creator_identity* | Terraform Service Account which will be creating the GCP resources | string | | | +| *trusted_users* | The list of trusted users who will be assigned Editor role on the project | set(string) | | [] | + +## Outputs + +| name | description | sensitive | +|---|---|:---:| +| billing_budget_budgetId | Resource name of the budget. Values are of the form `billingAccounts/{billingAccountId}/budgets/{budgetId}` | | +| deployment_id | RADLab Module Deployment ID | | +| project-radlab-billing-budget-id | GCP Project ID | | + diff --git a/modules/billing_budget/main.tf b/modules/billing_budget/main.tf new file mode 100644 index 00000000..8b630eed --- /dev/null +++ b/modules/billing_budget/main.tf @@ -0,0 +1,120 @@ +/** + * Copyright 2022 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. + */ + + +provider "google" { + alias = "impersonated" + scopes = [ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/userinfo.email" + ] +} + +data "google_service_account_access_token" "default" { + provider = google.impersonated + scopes = ["userinfo-email", "cloud-platform"] + target_service_account = var.resource_creator_identity + lifetime = "1800s" +} + +provider "google" { + access_token = data.google_service_account_access_token.default.access_token +} + +provider "google-beta" { + access_token = data.google_service_account_access_token.default.access_token +} + + +locals { + random_id = var.random_id != null ? var.random_id : random_id.default.hex + project = (var.create_project + ? try(module.project_radlab_billing_budget.0, null) + : try(data.google_project.existing_project.0, null) + ) + + project_services = var.enable_services ? var.apis : [] +} + +resource "random_id" "default" { + byte_length = 2 +} + +############### +# GCP PROJECT # +############### + +data "google_project" "existing_project" { + count = var.create_project ? 0 : 1 + project_id = var.project_name +} + +module "project_radlab_billing_budget" { + count = var.create_project ? 1 : 0 + source = "terraform-google-modules/project-factory/google" + version = "~> 11.0" + + name = format("%s-%s", var.project_name, local.random_id) + random_project_id = false + folder_id = var.folder_id + billing_account = var.billing_account_id + org_id = var.organization_id + + activate_apis = [] +} + + +resource "google_project_service" "enabled_services" { + for_each = toset(local.project_services) + project = local.project.project_id + service = each.value + disable_dependent_services = true + disable_on_destroy = true +} + +resource "time_sleep" "wait_120_seconds" { + count = var.enable_services ? 1 : 0 + create_duration = "120s" + + depends_on = [ + google_project_service.enabled_services + ] +} + +module "billing_budget" { + source = "terraform-google-modules/project-factory/google//modules/budget" + display_name = format("RADLab Billing Budget - %s", local.project.project_id) + billing_account = var.billing_account_id + projects = ["${local.project.project_id}"] + amount = var.billing_budget_amount + alert_spend_basis = var.billing_budget_alert_spend_basis + alert_spent_percents = var.billing_budget_alert_spent_percents + credit_types_treatment = var.billing_budget_credit_types_treatment + labels = var.billing_budget_labels + services = var.billing_budget_services + + depends_on = [ + time_sleep.wait_120_seconds + ] + +} + +resource "google_project_iam_member" "user_role_assignment" { + for_each = var.trusted_users + project = local.project.project_id + member = each.value + role = "roles/editor" +} \ No newline at end of file diff --git a/modules/billing_budget/outputs.tf b/modules/billing_budget/outputs.tf new file mode 100644 index 00000000..d002994d --- /dev/null +++ b/modules/billing_budget/outputs.tf @@ -0,0 +1,30 @@ +/** + * Copyright 2022 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 "deployment_id" { + description = "RADLab Module Deployment ID" + value = local.random_id +} + +output "project-radlab-billing-budget-id" { + description = "GCP Project ID" + value = local.project.project_id +} + +output "billing_budget_budgetId" { + description = "Resource name of the budget. Values are of the form `billingAccounts/{billingAccountId}/budgets/{budgetId}`" + value = module.billing_budget.name +} \ No newline at end of file diff --git a/modules/billing_budget/variables.tf b/modules/billing_budget/variables.tf new file mode 100644 index 00000000..5b7ee1a0 --- /dev/null +++ b/modules/billing_budget/variables.tf @@ -0,0 +1,114 @@ +/** + * Copyright 2022 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 "apis" { + description = "The list of GCP apis to enable." + type = set(string) + default = ["compute.googleapis.com","bigquery.googleapis.com","bigquerystorage.googleapis.com"] +} + +variable "billing_account_id" { + description = "Billing Account associated to the GCP Resources" + type = string +} + +variable "billing_budget_alert_spend_basis" { + description = "The type of basis used to determine if spend has passed the threshold" + type = string + default = "CURRENT_SPEND" +} + +variable "billing_budget_alert_spent_percents" { + description = "A list of percentages of the budget to alert on when threshold is exceeded" + type = list(number) + default = [0.5,0.7,1] +} + +variable "billing_budget_amount" { + description = "The amount to use as the budget in USD" + type = number + default = 1000 +} + +variable "billing_budget_credit_types_treatment" { + description = "Specifies how credits should be treated when determining spend for threshold calculations" + type = string + default = "INCLUDE_ALL_CREDITS" +} + +variable "billing_budget_labels" { + description = "A single label and value pair specifying that usage from only this set of labeled resources should be included in the budget." + type = map(string) + default = {} + validation { + condition = length(var.billing_budget_labels) <= 1 + error_message = "Only 0 or 1 labels may be supplied for the budget filter." + } +} + +variable "billing_budget_services" { + description = "A list of services ids to be included in the budget. If omitted, all services will be included in the budget. Service ids can be found at https://cloud.google.com/skus/" + type = list(string) + default = null +} + +variable "create_project" { + description = "Set to true if the module has to create a project. If you want to deploy in an existing project, set this variable to false." + type = bool + default = true +} + +variable "enable_services" { + description = "Enable the necessary APIs on the project. When using an existing project, this can be set to false." + type = bool + default = true +} + +variable "folder_id" { + description = "Folder ID where the project should be created. It can be skipped if already setting organization_id. Leave blank if the project should be created directly underneath the Organization node. " + type = string + default = "" +} + +variable "organization_id" { + description = "Organization ID where GCP Resources need to get spin up. It can be skipped if already setting folder_id" + type = string + default = "" +} + +variable "project_name" { + description = "Project name or ID, if it's an existing project." + type = string + default = "radlab-billing-budget" +} + +variable "random_id" { + description = "Adds a suffix of 4 random characters to the `project_id`" + type = string + default = null +} + +variable "resource_creator_identity" { + description = "Terraform Service Account which will be creating the GCP resources" + type = string + default = "" +} + +variable "trusted_users" { + description = "The list of trusted users who will be assigned Editor role on the project" + type = set(string) + default = [] +} \ No newline at end of file diff --git a/modules/billing_budget/versions.tf b/modules/billing_budget/versions.tf new file mode 100644 index 00000000..fd73aca5 --- /dev/null +++ b/modules/billing_budget/versions.tf @@ -0,0 +1,24 @@ +/** + * Copyright 2022 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. + */ + +terraform { + required_version = "~> 1.0" + + required_providers { + google = ">= 3.87.0" + google-beta = ">= 3.87.0" + } +} \ No newline at end of file