Skip to main content

Tenant Module (terraform-platform-create-tenant)

The tenant module creates all infrastructure required for a new workload tenant within a business unit. It provisions per-environment GCP projects, a GitHub repository, Terraform Cloud workspaces, Workload Identity Federation, and optionally Google Artifact Registry repositories and budget alerts.

What It Creates

  1. GCP projects for each environment (inside the BU's environment folders)
  2. A GitHub repository ({business_unit}-{tenant}-{suffix}) with optional template
  3. TFC project and workspaces (one per environment or single, depending on branching model)
  4. Per-environment WIF with separate service accounts scoped to each project
  5. Optional GAR repositories per environment per artifact type with vulnerability scanning
  6. Optional budget alerts per environment with configurable thresholds

Resource Relationship Diagram

create-tenant module
├── module.gcp_projects (per env)
│ └── GCP project: {tenant}-{env_code}-{suffix}
├── module.repository (integrated_repository/github)
│ ├── GitHub repo: {bu}-{tenant}-{suffix}
│ ├── TFC project + workspaces (per env)
│ └── Per-env WIF + service accounts
├── google_artifact_registry_repository (per env x type)
│ └── GAR repos with vulnerability scanning
└── Budget alerts (per env, via project-factory)

Key Differences from the Business Unit Module

AspectBusiness Unit ModuleTenant Module
GCP resourcesCreates foldersCreates projects
WIF patternCross-environment (single SA)Per-environment (separate SAs)
WIF host projectCreates its ownUses BU's project via env folder
Branching modelAlways trunk-based, single workspaceTrunk-based or environment-based
Budget supportNoYes (tenant-level + per-env overrides)
Scaffold filesYes (full Terraform scaffold)No (uses template repo)
GitHub AppRequired (creates repos for tenants)Not required
Naming patternbu-{name}-{suffix}{bu}-{tenant}-{suffix}
TFC workspacesSingle workspaceOne per environment

Naming Convention

Resources use a randomly generated 5-character alphanumeric suffix (or a user-provided one):

ResourceNaming PatternExample
GitHub repository{business_unit}-{tenant}-{suffix}data-template-b8cbu
GCP projects{tenant}-{env_code}-{suffix}template-d-b8cbu
TFC workspaces{bu}-{tenant}-{env_code}-{suffix}data-template-d-b8cbu

Environment codes are derived from the first letter of each hyphen-separated segment of the environment name:

EnvironmentCode
developmentd
non-productionnp
productionp

GCP project IDs must be 6-30 characters. The tenant name is validated to be 1-22 lowercase characters starting with a letter, leaving room for the -{env_code}-{suffix} suffix.

Key Variables

VariableTypeRequiredDefaultDescription
namestringYes-Tenant name (1-22 chars, lowercase, starts with letter). Used without the BU prefix.
business_unitstringYes-Parent business unit name
suffixstringNoRandom 5-charOptional fixed suffix for resource naming
gh_orgstringYes-GitHub organization name
environmentsmap(object)Yes-Per-environment GCP config (see Environment Configuration)
branching_modelobjectNo{}Either trunk_based or environment_based (see Branching Models)
tfcobjectYes-TFC config: org_id, gh_app_oauth_id, enable (default true), share_state (default false)
templateobjectNonullTemplate repo: owner (defaults to gh_org), repository, include_all_branches
teamsmap(object)No{}GitHub teams to grant permissions
topicslist(string)No[]Additional GitHub topics (merged with tenant, backstage-catalog)
budgetobjectNonullDefault budget for all environments (see Budget Configuration)
garobjectNo{ enable = false }GAR config: enable, location, artifact_types, upstream_service_agents

Environment Configuration

Each entry in the environments map accepts:

FieldTypeRequiredDefaultDescription
org_idstringYes-GCP Organization ID
billing_account_idstringYes-GCP Billing Account ID
parent_folder_idstringYes-GCP Folder ID (the BU's environment folder)
service_account_roleslist(string)Yes-IAM roles for the environment service account
enable_billing_user_roleboolNofalseGrant roles/billing.user to the SA
additional_apislist(string)No[]Extra GCP APIs to enable
labelsmap(string)No{}Additional project labels
vpc_subnet_project_idstringNo""Shared VPC host project ID
vpc_subnetslist(string)No[]Fully qualified subnet IDs for Shared VPC
vpc_service_control_attach_enabledboolNofalseAttach to VPC SC perimeter (enforced)
vpc_service_control_attach_dry_runboolNofalseAttach to VPC SC perimeter (dry run)
vpc_service_control_perimeter_namestringNo""VPC SC perimeter name
enable_shared_vpc_host_projectboolNofalseCreate a shared VPC host project
budgetobjectNonullPer-env budget override (overrides tenant-level)

Key Outputs

OutputDescription
repositoryGitHub repository details (name, URL, default branch)
terraform_projectTFC project details
terraform_workspacesTFC workspaces keyed by environment name
environmentsEnvironment config including WIF providers and service accounts
gcp_projectsMap of GCP projects per environment (project_id, project_number, name)
budgetsResolved budget configuration per environment (null when no budget)
gar_repositoriesMap of GAR repos by artifact type, then by environment. Empty when GAR disabled.

Branching Models

The tenant module supports two branching strategies. Exactly one must be configured.

Trunk-Based

A single main branch deploys to all environments. Deployments can be triggered by push (default) or tag.

branching_model = {
trunk_based = {
deployment_trigger = "push" # or "tag"
}
}
  • One TFC workspace per environment, all triggered from the same branch
  • Simpler workflow, suitable for most tenants

Environment-Based

Separate branches map to separate environments (e.g., development, non-production, production).

branching_model = {
environment_based = {}
}
  • One TFC workspace per environment, each linked to its corresponding branch
  • Promotes through environments by merging branches
  • Useful when environments need independent change control

Note: Single workspace mode is NOT supported for tenants (only for business units).

Budget Configuration

Budgets support a two-level override pattern:

  1. Tenant-level default: Applied to all environments unless overridden
  2. Per-environment override: Specified in the environment's budget block, takes precedence
# Tenant-level default: $1000 for all environments
budget = {
amount = 1000
alert_spent_percents = [0.5, 0.7, 1.0]
alert_spend_basis = "CURRENT_SPEND"
}

environments = {
development = {
# ... inherits $1000 budget
}
production = {
# ... overrides to $5000 budget
budget = {
amount = 5000
alert_spent_percents = [0.5, 0.8, 0.9, 1.0]
}
}
}

Budget Fields

FieldTypeDefaultDescription
amountnumber-Budget amount in billing account currency
alert_spent_percentslist(number)[0.5, 0.7, 1.0]Thresholds that trigger alerts
alert_spend_basisstringCURRENT_SPENDCURRENT_SPEND or FORECASTED_SPEND
alert_pubsub_topicstringnullPub/Sub topic for programmatic alerts
monitoring_notification_channelslist(string)[]Notification channels (max 5)
calendar_periodstringnullMONTH, QUARTER, YEAR, or CUSTOM
custom_period_start_datestringnullStart date (DD-MM-YYYY) for custom period
custom_period_end_datestringnullEnd date (DD-MM-YYYY) for custom period
labelsmap(string){}Budget labels (max 1)
display_namestringnullDisplay name for the budget

GAR Configuration

When enabled, the tenant module creates standard GAR repositories in each environment's project:

gar = {
enable = true
location = "us-central1"
artifact_types = ["DOCKER", "PYTHON"]
upstream_service_agents = ["sa-shared@project.iam.gserviceaccount.com"]
}

For each environment and artifact type combination, the module creates:

  • A GAR repository in the environment's GCP project
  • Automatic vulnerability scanning (Container Analysis + Container Scanning APIs)
  • IAM bindings granting roles/artifactregistry.reader to upstream service agents

The upstream_service_agents field is used to grant the BU-level shared project's GAR service agent read access, enabling virtual repository aggregation at the BU level.

The Tenant Repository (foundations-template)

Tenant repositories are typically created from the foundations-template via the template variable. This template provides:

  • YAML-driven configuration in terraform/configs/{env}/*.yaml
  • Single module call to tenant-infra-factory that reads YAML and provisions resources
  • Supported resource types: Cloud Run, GCS, BigQuery, Labels
  • Reusable workflow integration for CI/CD
tenant-repo/
├── terraform/
│ ├── configs/
│ │ ├── development/
│ │ │ └── resources.yaml
│ │ ├── non-production/
│ │ │ └── resources.yaml
│ │ └── production/
│ │ └── resources.yaml
│ └── main.tf (calls tenant-infra-factory)
└── catalog-info.yaml

Usage Example

module "tenant" {
source = "app.terraform.io/Badal_devex/create-tenant/platform"
version = "~> 1.0"

name = "backstage"
business_unit = "devex"
gh_org = "badal-io"

template = {
owner = "badal-io"
repository = "foundations-template"
}

branching_model = {
environment_based = {}
}

budget = {
amount = 1000
}

environments = {
non-production = {
org_id = "758951886862"
billing_account_id = "01DEF7-F9833E-AD765A"
parent_folder_id = "folders/123456789"
service_account_roles = ["roles/run.admin", "roles/storage.admin"]
}
production = {
org_id = "758951886862"
billing_account_id = "01DEF7-F9833E-AD765A"
parent_folder_id = "folders/234567890"
service_account_roles = ["roles/run.admin", "roles/storage.admin"]
budget = {
amount = 5000
alert_spent_percents = [0.5, 0.8, 0.9, 1.0]
}
}
}

tfc = {
org_id = "Badal_devex"
gh_app_oauth_id = "ot-xxxxxxxxxxxxx"
share_state = true
}

gar = {
enable = true
location = "us-central1"
artifact_types = ["DOCKER"]
}
}

Default APIs and Roles

Every tenant project has the following APIs enabled by default:

  • iamcredentials.googleapis.com
  • iam.googleapis.com
  • serviceusage.googleapis.com
  • cloudresourcemanager.googleapis.com
  • cloudbilling.googleapis.com

When GAR is enabled, additionally:

  • artifactregistry.googleapis.com
  • containeranalysis.googleapis.com
  • containerscanning.googleapis.com

When budgets are configured:

  • billingbudgets.googleapis.com

Every service account automatically receives roles/serviceusage.serviceUsageViewer in addition to the roles specified in service_account_roles.

Project Labels

Each GCP project is automatically labeled with:

LabelValue
business_unitThe parent BU name
environmentThe environment key
tenantThe tenant name

Additional labels can be added via the per-environment labels field.