Deploys a single 2nd-generation, Cloud-Run-backed Cloud Function (
google_cloudfunctions2_function) β build configuration, service (runtime) configuration, and an optional Eventarc-backed event trigger. Targetshashicorp/google ~> 7.0, Terraform>= 1.12.0.
- π Creates one
google_cloudfunctions2_functionβ a Cloud-Run-backed, event- or HTTP-invoked serverless function (GCP console: "Cloud Function," 2nd gen). - ποΈ Renders all three of the resource's nested configuration groups:
build_config(how the container is built from caller/CI-staged source),service_config(the deployed Cloud Run runtime β memory, CPU, concurrency, VPC egress, secrets), and an optionalevent_trigger(Eventarc β Pub/Sub, GCS, Audit Log, or custom event sources). - π Locks down network exposure by default β
service_config.ingress_settingsdefaults toALLOW_INTERNAL_ONLY(a secure-by-default extension to this module suite's house table of secure defaults; this resource's own provider default, if left unmanaged, isALLOW_ALL). - π Wires cleanly to sibling modules as plain string inputs: a service account across three
distinct arguments with two distinct string shapes, an Artifact Registry repository
id, a Pub/Sub topicid, a Secret Managersecret_id, and a VPC subnetwork name (not self_link β a confirmed deviation fromtf-mod-gcp-cloud-run-service's precedent). - π§± Standalone by design β
build_config/service_config/event_triggerare nested configuration blocks on one resource, not independent Terraform resources, so this stays a single-keystone module with no composite shape. - βοΈ Does not manage the source archive itself β
build_config.sourceaccepts plain bucket/object (or repo) coordinates pointing at source the caller/CI pipeline already staged.
π‘ Why it matters: Cloud Functions (2nd gen) is our default serverless compute target for lightweight event-driven and HTTP workloads that don't warrant a full Cloud Run deployment pipeline. Getting the ingress default locked down, the three separate identity arguments wired to their correct string shapes, and secrets sourced from Secret Manager rather than literal values are the most common Cloud Functions misconfiguration findings this module forecloses by default.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
graph LR
PS["tf-mod-gcp-project-services"]:::external
SA["tf-mod-gcp-service-account"]:::keystoneSibling
AR["tf-mod-gcp-artifact-registry-repository"]:::sibling
PST["tf-mod-gcp-pubsub-topic"]:::sibling
SM["tf-mod-gcp-secret-manager-secret"]:::sibling
VPCN["tf-mod-gcp-vpc-network"]:::sibling
KMS["tf-mod-gcp-kms-keyring"]:::sibling
CF["tf-mod-gcp-cloud-function"]:::thisModule
SCHED["tf-mod-gcp-cloud-scheduler-job"]:::sibling
PS -- "enables cloudfunctions/run/cloudbuild/artifactregistry.googleapis.com" --> CF
SA -- "id consumed as build_config.service_account (fully-qualified)" --> CF
SA -- "email consumed as service_config.service_account_email (bare)" --> CF
SA -- "email consumed as event_trigger.service_account_email (bare)" --> CF
AR -- "id consumed as build_config.docker_repository" --> CF
PST -- "id consumed as event_trigger.pubsub_topic" --> CF
SM -- "secret_id consumed as secret_environment_variables / secret_volumes" --> CF
VPCN -- "subnetwork name (not self_link) consumed as direct_vpc_network_interface" --> CF
KMS -- "crypto key name consumed as kms_key_name (CMEK)" --> CF
CF -. "informational β plain url string, not a Terraform reference".-> SCHED
classDef thisModule fill:#4285F4,color:#ffffff,stroke:#174EA6,stroke-width:1px;
classDef keystoneSibling fill:#174EA6,color:#ffffff,stroke:#174EA6,stroke-width:1px;
classDef sibling fill:#E8EAED,color:#202124,stroke:#9AA0A6,stroke-width:1px;
classDef external fill:#E8EAED,color:#202124,stroke:#9AA0A6,stroke-width:1px,stroke-dasharray: 3 3;
This module has a real sibling family, so the family DAG is not skipped.
tf-mod-gcp-service-account is marked as the keystone sibling β every real deployment needs at
least one identity, and this resource is unusual in consuming three distinct arguments from the
same sibling module, with two different string shapes (see Architecture Notes). Validated via the
Mermaid Chart MCP before embedding. tf-mod-gcp-project-services (external, dashed) must have
already enabled the unconditional APIs before this module applies.
tf-mod-gcp-artifact-registry-repository's and tf-mod-gcp-kms-keyring's relationships are both
genuinely optional β GCF creates its own default Artifact Registry repository and Google-managed
encryption if neither is supplied. The edge toward tf-mod-gcp-cloud-scheduler-job is informational
only (dashed) β the deployed url crosses as a plain string, not a Terraform id/self_link
reference.
graph LR
subgraph Inputs
A["var.name / var.location / var.description"]
B["var.kms_key_name"]
C["var.deletion_policy"]
D["var.labels / var.timeouts"]
E["var.build_config
(runtime, entry_point, source,
docker_repository, service_account,...)"]
F["var.service_config
(ingress_settings, secrets, VPC egress,
service_account_email,...)"]
G["var.event_trigger
(event_type, pubsub_topic,
event_filters,...)"]
end
R["google_cloudfunctions2_function.this"]:::thisModule
A --> R
B --> R
C --> R
D --> R
E -- "nested block: build_config" --> R
F -- "nested block: service_config" --> R
G -- "nested block: event_trigger (optional)" --> R
R --> O1["output: id"]
R --> O2["output: url"]
R --> O3["output: name"]
R --> O4["output: state"]
R --> O5["output: environment"]
R --> O6["output: service_uri / service_gcf_uri"]
classDef thisModule fill:#4285F4,color:#ffffff,stroke:#174EA6,stroke-width:1px;
Resource inventory: exactly one resource, google_cloudfunctions2_function.this.
build_config, service_config, and event_trigger are internal nested configuration groups on
that one resource, not separate managed resources β validated via the Mermaid Chart MCP before
embedding.
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/google provider |
~> 7.0 |
| Provider block | None β the caller configures google (project, region/zone, auth) |
Schema notes that bite (verified against hashicorp/google v7.39.0 via the live provider schema,
provider_doc_id 12683364):
- No
deletion_protection. Confirmed absent from the live schema β a gap versustf-mod-gcp-cloud-run-service.deletion_policy(Terraform-only, defaultDELETE) is the only deletion-related guard this resource exposes. - Three distinct service-account-consuming arguments, two distinct string shapes.
build_config.service_accountwants the FULLY-QUALIFIEDprojects/{project}/serviceAccounts/{email}form;service_config.service_account_emailandevent_trigger.service_account_emailboth want the BARE email string. Confirmed via the provider's own "Basic Builder" (fully-qualified) and "Full"/"Basic Auditlogs" (bare email) examples. direct_vpc_network_interface.network/.subnetworktake bare NAME strings, not self_link/id β a deviation fromtf-mod-gcp-cloud-run-service'svpc_accessprecedent. Confirmed directly in the argument reference text: "The name of the VPC network..."/"The name of the VPC subnetwork...".- No
self_link. Confirmed absent from the live Attributes Reference βidand top-levelurlare this module's primary outputs instead. url,service_config[0].uri, andservice_config[0].gcf_uriare three distinct attribute paths all expected to carry substantially the same deployed URL β a confirmed, documented duplication (see the provider's own "Cloudfunctions2 Scheduler Auth" example, which readsservice_config[0].urispecifically), not a bug.ingress_settingspossible values areALLOW_ALL/ALLOW_INTERNAL_ONLY/ALLOW_INTERNAL_AND_GCLBβ NOT the same enum strings as Cloud Run v2'sINGRESS_TRAFFIC_*values. Provider default if unmanaged isALLOW_ALL; this module overrides that toALLOW_INTERNAL_ONLY(secure-by-default extension β see Design Principles).kms_key_name(CMEK) is a plain GA argument, resolved during this authoring session β the only official worked example demonstrating it end-to-end setsprovider = google-betaon every resource, but that example also provisions a genuinely beta-onlygoogle_project_service_identitylookup;kms_key_nameitself carries no beta annotation in the Argument Reference. Seevariables.tf's header comment for the full cross-check. Still recommend a liveplan/applycheck before relying on CMEK in production (this library's plan-only posture cannot catch apply-time KMS IAM-permission rejections either way).timeoutssupports the full create/update/delete triad, each defaulting to 60 minutes.- This module does not manage the source archive.
build_config.source.storage_source/.repo_sourcereference already-staged source (a caller/CI responsibility) β see Architecture Notes.
roles/cloudfunctions.adminon the target project β least-privilege role for the applying principal to create, update, and delete Cloud Functions (2nd gen).- Separately (required for the function to build and run, not for this module's own apply): the
build service account needs
roles/logging.logWriter,roles/artifactregistry.writer, androles/storage.objectAdmin(or narrower equivalents); the runtime service account needsroles/secretmanager.secretAccessoron any secret it reads; an event-triggered function's trigger service account needsroles/run.invokerandroles/eventarc.eventReceiver. Grant these viatf-mod-gcp-project-iam-bindingsor a resource-scoped IAM binding, not inside this module.
cloudfunctions.googleapis.com,run.googleapis.com,cloudbuild.googleapis.com, andartifactregistry.googleapis.comenabled on the target project (viatf-mod-gcp-project-services, applied before this module) β all four UNCONDITIONALLY required.eventarc.googleapis.comβ CONDITIONALLY required, only whenevent_triggeris populated.secretmanager.googleapis.comenabled, the referenced secret(s) already exist, and the runtime service account already holdsroles/secretmanager.secretAccessoron them β if anysecret_environment_variables/secret_volumesentry is set (allow for IAM propagation lag).vpcaccess.googleapis.comenabled, or an existing Serverless VPC Access connector β ifservice_config.direct_vpc_network_interfaceor.vpc_connectoris set.cloudkms.googleapis.comenabled, the key ring/crypto key already exists, and the relevant service agents already holdroles/cloudkms.cryptoKeyEncrypterDecrypteron it β ifkms_key_nameis set.
tf-mod-gcp-cloud-function/
βββ providers.tf # required_providers (hashicorp/google ~> 7.0) + required_version β no provider {} block
βββ variables.tf # name, location, description, kms_key_name, deletion_policy, labels, timeouts, build_config, service_config, event_trigger
βββ main.tf # google_cloudfunctions2_function.this β the sole resource
βββ outputs.tf # id, url, name, state, environment, update_time, terraform_labels, effective_labels, service_uri, service_gcf_uri
βββ README.md # this file
βββ SCOPE.md # lightweight cross-module contract
βββ examples/ # runnable example matching the Quick Start below
module "hello_http" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "hello-http"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "helloHttp"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "hello-http-source.zip"
}
}
}
}The caller's root module configures the google provider (project, region/zone, and
authentication via ADC, Workload Identity Federation, or a service account key supplied
out-of-band) β this module accepts none of those as variables. The staged source archive
(my-project-gcf-source/hello-http-source.zip) is a caller/CI responsibility this module does
not manage. The empty-beyond-required call above produces
service_config.ingress_settings = ALLOW_INTERNAL_ONLY β the function is not reachable from the
public internet by default.
Consumes
| Input | Type | Source module |
|---|---|---|
build_config.service_account |
string (plain, FULLY-QUALIFIED projects/{project}/serviceAccounts/{email}) |
tf-mod-gcp-service-account .id output |
service_config.service_account_email |
string (plain, BARE email) |
tf-mod-gcp-service-account .email output |
event_trigger.service_account_email |
string (plain, BARE email) |
tf-mod-gcp-service-account .email output |
build_config.docker_repository |
string (plain) |
tf-mod-gcp-artifact-registry-repository .id output (NOT .registry_uri) |
event_trigger.pubsub_topic |
string (plain) |
tf-mod-gcp-pubsub-topic .id output |
service_config.secret_environment_variables[*].secret / .secret_volumes[*].secret |
string (plain, bare secret_id) |
tf-mod-gcp-secret-manager-secret (secret's own secret_id, not a module output field name β see example 4) |
service_config.direct_vpc_network_interface.network / .subnetwork |
string (plain, bare NAME β not self_link) |
tf-mod-gcp-vpc-network network name output / subnetwork map key |
kms_key_name |
string (plain) |
tf-mod-gcp-kms-keyring crypto key resource name |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
projects/{{project}}/locations/{{location}}/functions/{{name}} |
Any module/resource needing the Terraform-internal reference (e.g. a Cloud Functions IAM binding) |
url |
The deployed url for the function β this module's second primary output, in place of self_link (this resource has none) |
Callers of an HTTP-invoked function; informationally, tf-mod-gcp-cloud-scheduler-job's http_target.uri |
name |
The function's name | Cloud Functions IAM binding resources' cloud_function argument |
state |
Current state of the function | Diagnostic tooling |
environment |
Always GEN_2 for this resource type |
Diagnostic/informational |
update_time |
Last update timestamp | Diagnostic/audit tooling |
terraform_labels / effective_labels |
Resolved label sets | Diagnostic/audit tooling |
service_uri / service_gcf_uri |
service_config[0].uri / .gcf_uri β confirmed duplicates of url |
Consumers reading the nested path directly (e.g. Cloud Scheduler compositions) |
This resource has no self_link β none is emitted.
1 Β· Minimal HTTP-invoked function
module "hello_http" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "hello-http"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "helloHttp"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "hello-http-source.zip"
}
}
}
}π‘ No
event_triggerblock means this function is invoked purely over HTTP via itsurloutput.service_config.ingress_settingsdefaults toALLOW_INTERNAL_ONLY.
2 Β· Publicly invocable function
module "public_webhook" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "public-webhook"
location = "us-central1"
build_config = {
runtime = "python312"
entry_point = "handle_webhook"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "webhook-source.zip"
}
}
}
service_config = {
ingress_settings = "ALLOW_ALL"
}
}
β οΈ ingress_settings = "ALLOW_ALL"is the explicit opt-out this module requires for public internet exposure β never the default.
3 Β· Runtime identity from tf-mod-gcp-service-account
module "webhook_sa" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-service-account.git?ref=v1.0.0"
account_id = "webhook-runtime"
display_name = "Webhook function runtime identity"
}
module "public_webhook" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "public-webhook"
location = "us-central1"
build_config = {
runtime = "python312"
entry_point = "handle_webhook"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "webhook-source.zip"
}
}
}
service_config = {
service_account_email = module.webhook_sa.email # BARE email β service_config wants this shape
}
}
β οΈ service_config.service_account_emailwants the BAREbuild_config.service_account(example 7) wants the FULLY-QUALIFIED.idoutput instead β these are two different arguments consuming two different string shapes from the same sibling module. Do not swap them.
4 Β· Pub/Sub-triggered function
module "orders_topic" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-pubsub-topic.git?ref=v1.0.0"
name = "orders-events"
}
module "order_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "order-processor"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "processOrder"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "order-processor-source.zip"
}
}
}
service_config = {
max_instance_count = 5
min_instance_count = 0
}
event_trigger = {
event_type = "google.cloud.pubsub.topic.v1.messagePublished"
pubsub_topic = module.orders_topic.id
retry_policy = "RETRY_POLICY_RETRY"
}
}βΉοΈ
event_trigger.pubsub_topicconsumestf-mod-gcp-pubsub-topic'sidoutput directly β the provider's own docs confirm this must be in PubSub'sPublishRequest.nameformat, which is exactly this module'sidoutput shape.
5 Β· GCS/Eventarc-triggered function with event_filters
module "uploads_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "uploads-processor"
location = "us-central1"
build_config = {
runtime = "python312"
entry_point = "process_upload"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "uploads-processor-source.zip"
}
}
}
service_config = {
service_account_email = "gcf-events-sa@my-project.iam.gserviceaccount.com"
}
event_trigger = {
event_type = "google.cloud.storage.object.v1.finalized"
retry_policy = "RETRY_POLICY_RETRY"
service_account_email = "gcf-events-sa@my-project.iam.gserviceaccount.com"
event_filters = {
"bucket" = { value = "my-uploads-trigger-bucket" }
}
}
}π The GCS service account requires
roles/pubsub.publisherand the trigger service account requiresroles/eventarc.eventReceiver+roles/run.invokerβ grant these viatf-mod-gcp-project-iam-bindingsbefore this function's first successful deploy (see Troubleshooting for the doc-confirmed IAM propagation pattern).
6 Β· Audit-log-triggered function with a path-pattern filter
module "audit_watcher" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "audit-watcher"
location = "us-central1"
build_config = {
runtime = "python312"
entry_point = "watch_audit_log"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "audit-watcher-source.zip"
}
}
}
event_trigger = {
trigger_region = "us-central1" # must match the audited bucket's location
event_type = "google.cloud.audit.log.v1.written"
retry_policy = "RETRY_POLICY_RETRY"
event_filters = {
"serviceName" = { value = "storage.googleapis.com" }
"methodName" = { value = "storage.objects.create" }
"resourceName" = {
value = "/projects/_/buckets/my-audit-log-bucket/objects/*.txt"
operator = "match-path-pattern"
}
}
}
}βΉοΈ
event_filtersis keyed by CloudEvents attribute name β matching the provider's own "Basic Auditlogs" example, which keys three filters byserviceName/methodName/resourceName.operator = "match-path-pattern"is the only accepted value when set (enforced via this module'svalidation {}block).
7 Β· Custom build service account
module "builder_sa" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-service-account.git?ref=v1.0.0"
account_id = "gcf-builder"
display_name = "Cloud Function build identity"
}
module "hello_http" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "hello-http"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "helloHttp"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "hello-http-source.zip"
}
}
service_account = module.builder_sa.id # FULLY-QUALIFIED β build_config wants this shape
}
}
β οΈ build_config.service_accountwants the FULLY-QUALIFIEDprojects/{project}/serviceAccounts/{email}form β confirmed via the provider's own "Basic Builder" example (service_account = google_service_account.account.id). This is the account's.idoutput, notservice_config.service_account_email.
8 Β· Custom docker_repository (Artifact Registry)
module "functions_repo" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-artifact-registry-repository.git?ref=v1.0.0"
repository_id = "gcf-functions"
location = "us-central1"
format = "DOCKER"
}
module "hello_http" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "hello-http"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "helloHttp"
docker_repository = module.functions_repo.id # id, NOT registry_uri
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "hello-http-source.zip"
}
}
}
}βΉοΈ Omitting
docker_repositoryentirely is normal β GCF creates and manages its own default repository (gcf-artifacts) in the function's region.docker_repositoryconsumes.id, not.registry_uri(the latter istf-mod-gcp-artifact-registry-repository's own self_link substitute for CI pipelines building/pushing images directly β this argument wants the Terraform resource reference form instead).
9 Β· Secret-sourced environment variable
module "db_password" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-secret-manager-secret.git?ref=v1.0.0"
secret_id = "order-processor-db-password"
replication = { auto = {} }
}
module "order_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "order-processor"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "processOrder"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "order-processor-source.zip"
}
}
}
service_config = {
secret_environment_variables = {
"DB_PASSWORD" = {
project_id = "my-project"
secret = "order-processor-db-password" # bare secret_id, as supplied above β not a module output
version = "latest"
}
}
}
}π The runtime service account must hold
roles/secretmanager.secretAccessoron this secret β grant it viatf-mod-gcp-project-iam-bindings(see the end-to-end composition, example 15). This module only wires the reference; it does not grant access.
10 Β· Secret mounted as a volume
module "tls_cert" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-secret-manager-secret.git?ref=v1.0.0"
secret_id = "order-processor-tls-cert"
replication = { auto = {} }
}
module "order_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "order-processor"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "processOrder"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "order-processor-source.zip"
}
}
}
service_config = {
secret_volumes = {
"/etc/secrets/tls" = {
project_id = "my-project"
secret = "order-processor-tls-cert"
versions = []
}
}
}
}βΉοΈ
secret_volumesis keyed bymount_pathβ the field that must be unique across volumes. Omittingversions(the default,[]) mounts the latest version under a file named after the secret.
11 Β· Direct VPC egress via a subnetwork name
module "network" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-vpc-network.git?ref=v1.0.0"
network_name = "workloads-network"
subnetworks = {
"functions-subnet-usc1" = {
ip_cidr_range = "10.6.0.0/28"
region = "us-central1"
}
}
}
module "order_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "order-processor"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "processOrder"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "order-processor-source.zip"
}
}
}
service_config = {
direct_vpc_network_interface = {
network = module.network.name # bare NAME, not self_link
subnetwork = "functions-subnet-usc1" # the map key used above β also a bare NAME
}
direct_vpc_egress = "VPC_EGRESS_PRIVATE_RANGES_ONLY"
}
}
β οΈ CONFIRMED DEVIATION fromtf-mod-gcp-cloud-run-service:direct_vpc_network_interface.network/.subnetworktake bare NAME strings, not self_link. Do not wiremodule.network.subnetwork_self_links[...]here β use the literal subnetwork name (the same string used as thesubnetworksmap key when callingtf-mod-gcp-vpc-network).
12 Β· Legacy Serverless VPC Access connector egress
module "legacy_integration" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "legacy-integration"
location = "us-central1"
build_config = {
runtime = "python312"
entry_point = "handle_legacy_call"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "legacy-integration-source.zip"
}
}
}
service_config = {
vpc_connector = "projects/my-project/locations/us-central1/connectors/run-vpc"
vpc_connector_egress_settings = "PRIVATE_RANGES_ONLY"
}
}
β οΈ vpc_connector(legacy) anddirect_vpc_network_interface(modern, example 11) are alternative paths on the sameservice_configblock β this module does not validate mutual exclusivity; supplying both is a caller error the GCP API itself will reject. Notf-mod-gcp-vpc-access-connectorsibling module exists in the current catalog.
13 Β· CMEK (customer-managed encryption key)
module "cmek_function" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "cmek-function"
location = "us-central1"
kms_key_name = "projects/my-project/locations/us-central1/keyRings/gcf-keyring/cryptoKeys/gcf-key"
build_config = {
runtime = "nodejs20"
entry_point = "helloHttp"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "hello-http-source.zip"
}
}
}
}
β οΈ Confirmed GA onhashicorp/google ~> 7.0during this module's authoring session β see Architecture Notes for the full google-beta ambiguity resolution. The GCF/Artifact Registry/Cloud Storage/Eventarc service agents must already holdroles/cloudkms.cryptoKeyEncrypterDecrypteron this key before the first apply (IAM propagation lag applies here too) β a liveplan/applycheck against a real project is still recommended before relying on this in production.
14 Β· Custom scaling, memory, and concurrency
module "high_throughput_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "high-throughput-processor"
location = "us-central1"
build_config = {
runtime = "python312"
entry_point = "process_batch"
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "high-throughput-processor-source.zip"
}
}
}
service_config = {
available_memory = "4Gi"
available_cpu = "4"
max_instance_request_concurrency = 80
max_instance_count = 20
min_instance_count = 1
timeout_seconds = 300
}
}βΉοΈ Per the live docs,
available_cpuis computed fromavailable_memorywhen left unset β set it explicitly (as here) when concurrency needs more CPU per instance than the memory-derived default would provide.
15 Β· ποΈ End-to-end composition
module "project_services" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-project-services.git?ref=v1.0.0"
# Enables cloudfunctions.googleapis.com, run.googleapis.com,
# cloudbuild.googleapis.com, artifactregistry.googleapis.com,
# eventarc.googleapis.com, secretmanager.googleapis.com, and any other
# APIs this composition needs.
}
module "gcf_build_sa" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-service-account.git?ref=v1.0.0"
account_id = "gcf-order-builder"
display_name = "Order processor build identity"
depends_on = [module.project_services]
}
module "gcf_runtime_sa" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-service-account.git?ref=v1.0.0"
account_id = "gcf-order-runtime"
display_name = "Order processor runtime + event-trigger identity"
depends_on = [module.project_services]
}
module "functions_repo" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-artifact-registry-repository.git?ref=v1.0.0"
repository_id = "gcf-functions"
location = "us-central1"
format = "DOCKER"
depends_on = [module.project_services]
}
module "orders_topic" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-pubsub-topic.git?ref=v1.0.0"
name = "orders-events"
depends_on = [module.project_services]
}
module "network" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-vpc-network.git?ref=v1.0.0"
network_name = "workloads-network"
subnetworks = {
"functions-subnet-usc1" = {
ip_cidr_range = "10.6.0.0/28"
region = "us-central1"
}
}
depends_on = [module.project_services]
}
module "db_password" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-secret-manager-secret.git?ref=v1.0.0"
secret_id = "order-processor-db-password"
replication = { auto = {} }
depends_on = [module.project_services]
}
module "order_processor_iam" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-project-iam-bindings.git?ref=v1.0.0"
bindings = {
"gcf-builder-log-writer" = {
role = "roles/logging.logWriter"
member = module.gcf_build_sa.member
}
"gcf-builder-ar-writer" = {
role = "roles/artifactregistry.writer"
member = module.gcf_build_sa.member
}
"gcf-runtime-secret-accessor" = {
role = "roles/secretmanager.secretAccessor"
member = module.gcf_runtime_sa.member
}
"gcf-runtime-eventarc-receiver" = {
role = "roles/eventarc.eventReceiver"
member = module.gcf_runtime_sa.member
}
"gcf-runtime-run-invoker" = {
role = "roles/run.invoker"
member = module.gcf_runtime_sa.member
}
}
}
module "order_processor" {
source = "git::https://github.com/microsoftexpert/tf-mod-gcp-cloud-function.git?ref=v1.0.0"
name = "order-processor"
location = "us-central1"
build_config = {
runtime = "nodejs20"
entry_point = "processOrder"
docker_repository = module.functions_repo.id # id, NOT registry_uri
service_account = module.gcf_build_sa.id # FULLY-QUALIFIED β build_config's shape
source = {
storage_source = {
bucket = "my-project-gcf-source"
object = "order-processor-source.zip"
}
}
}
service_config = {
service_account_email = module.gcf_runtime_sa.email # BARE email β service_config's shape
max_instance_count = 10
min_instance_count = 0
direct_vpc_network_interface = {
network = module.network.name
subnetwork = "functions-subnet-usc1" # bare NAME β the subnetworks map key above, not self_link
}
secret_environment_variables = {
"DB_PASSWORD" = {
project_id = "my-project"
secret = "order-processor-db-password" # module.db_password's secret_id, as supplied
version = "latest"
}
}
}
event_trigger = {
event_type = "google.cloud.pubsub.topic.v1.messagePublished"
pubsub_topic = module.orders_topic.id
service_account_email = module.gcf_runtime_sa.email # BARE email β event_trigger's shape too
retry_policy = "RETRY_POLICY_RETRY"
}
depends_on = [
module.gcf_build_sa,
module.gcf_runtime_sa,
module.functions_repo,
module.orders_topic,
module.network,
module.db_password,
module.order_processor_iam,
]
}π‘ This wires the full dependency chain in order: APIs enabled, then identities/repository/ topic/network/secret created in parallel, then IAM grants, then the function itself β with an explicit
depends_onon the IAM bindings so the build/runtime service accounts' roles have a chance to propagate before the function's firstapplybuilds and deploys.
β οΈ IAM propagation can still take up to ~60 seconds even with correct graph ordering β the provider's own "Basic Builder" example inserts an explicit 60-secondtime_sleepfor exactly this reason. A first-timeapplyof this composition may need a secondapplyif the build fails to pull the source or the runtime fails to read the secret. See Troubleshooting.
| Variable | Type | Required | Default | Notes |
|---|---|---|---|---|
name |
string |
Yes | β | Embedded in the resource id path; treat as effectively force-new |
location |
string |
Yes | β | Resource-level required argument, not a provider override |
description |
string |
No | null |
No stated character limit in the live docs |
kms_key_name |
string |
No | null |
CMEK; confirmed GA β see Architecture Notes; format-validated |
deletion_policy |
string |
No | "DELETE" |
DELETE | ABANDON | PREVENT; only deletion guard this resource has (no deletion_protection) |
labels |
map(string) |
No | {} |
GCP label-format validated; non-authoritative per the live docs |
timeouts |
object({create,update,delete}) |
No | null |
All three supported; each defaults to 60m |
build_config |
object({...}) |
No | null |
Build step; omitting it entirely produces a function with no deployable code |
service_config |
object({...}) |
No | {} |
Runtime configuration; every field defaulted, so {} is a complete, secure-by-default object |
event_trigger |
object({...}) |
No | null |
Eventarc trigger; absence = pure HTTP-invoked function |
Full variable schemas
variable "name" {
type = string
}
variable "location" {
type = string
}
variable "description" {
type = string
default = null
}
variable "kms_key_name" {
type = string
default = null
# must match projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{crypto_key}
# β enforced via validation {}
}
variable "deletion_policy" {
type = string
default = "DELETE" # DELETE | ABANDON | PREVENT
}
variable "labels" {
type = map(string)
default = {}
# GCP label key/value format enforced via validation {}
}
variable "timeouts" {
type = object({
create = optional(string)
update = optional(string)
delete = optional(string)
})
default = null
}
variable "build_config" {
type = object({
runtime = optional(string)
entry_point = optional(string)
worker_pool = optional(string)
environment_variables = optional(map(string), {})
docker_repository = optional(string)
service_account = optional(string) # FULLY-QUALIFIED projects/{project}/serviceAccounts/{email}
automatic_update_policy = optional(bool, false) # mutually exclusive with on_deploy_update_policy
on_deploy_update_policy = optional(bool, false)
source = optional(object({
storage_source = optional(object({
bucket = optional(string)
object = optional(string)
generation = optional(number)
}))
repo_source = optional(object({
project_id = optional(string)
repo_name = optional(string)
branch_name = optional(string)
tag_name = optional(string)
commit_sha = optional(string)
dir = optional(string)
invert_regex = optional(bool)
}))
}))
})
default = null
}
variable "service_config" {
type = object({
timeout_seconds = optional(number, 60)
available_memory = optional(string, "256M")
available_cpu = optional(string)
max_instance_request_concurrency = optional(number, 1)
environment_variables = optional(map(string), {})
max_instance_count = optional(number)
min_instance_count = optional(number)
vpc_connector = optional(string)
vpc_connector_egress_settings = optional(string) # VPC_CONNECTOR_EGRESS_SETTINGS_UNSPECIFIED | PRIVATE_RANGES_ONLY | ALL_TRAFFIC
direct_vpc_network_interface = optional(object({
network = optional(string) # bare NAME, not self_link
subnetwork = optional(string) # bare NAME, not self_link
tags = optional(list(string), [])
}))
direct_vpc_egress = optional(string) # VPC_EGRESS_ALL_TRAFFIC | VPC_EGRESS_PRIVATE_RANGES_ONLY
ingress_settings = optional(string, "ALLOW_INTERNAL_ONLY") # secure-by-default extension
service_account_email = optional(string) # BARE email
all_traffic_on_latest_revision = optional(bool, true)
secret_environment_variables = optional(map(object({ # keyed by env var name
project_id = string
secret = string
version = string
})), {})
secret_volumes = optional(map(object({ # keyed by mount_path
project_id = string
secret = string
versions = optional(list(object({
version = string
path = string
})), [])
})), {})
binary_authorization_policy = optional(string)
})
default = {}
}
variable "event_trigger" {
type = object({
trigger_region = optional(string)
event_type = string # Required if event_trigger is present at all
pubsub_topic = optional(string)
service_account_email = optional(string) # BARE email
retry_policy = optional(string) # RETRY_POLICY_UNSPECIFIED | RETRY_POLICY_DO_NOT_RETRY | RETRY_POLICY_RETRY
event_filters = optional(map(object({ # keyed by CloudEvents attribute name
value = string
operator = optional(string) # only "match-path-pattern" accepted when set
})), {})
})
default = null
}| Output | Description |
|---|---|
id |
projects/{{project}}/locations/{{location}}/functions/{{name}} |
url |
The deployed url for the function β second primary output, in place of self_link |
name |
The function's name |
state |
Current state of the function |
environment |
Always GEN_2 for this resource type |
update_time |
Last update timestamp |
terraform_labels / effective_labels |
Resolved label sets |
service_uri / service_gcf_uri |
service_config[0].uri / .gcf_uri β confirmed duplicates of url |
βΉοΈ Deviation from the house
id/self_linkconvention, confirmed during authoring: the live Attributes Reference forgoogle_cloudfunctions2_functionhas noself_linkattribute. This module's primary_output isidfirst, thenurlin its place. Separately,service_config[0].uriandservice_config[0].gcf_uriare two additional, distinct attribute paths expected to carry the same underlying value asurlβ a confirmed, documented duplication, exposed here asservice_uri/service_gcf_uriso a future maintainer does not "fix" it as a bug.
None of these outputs are secret-bearing; no sensitive = true is applied to any of them. Secret
Manager references (secret_environment_variables/secret_volumes) are write-only inputs β never
echoed back as outputs.
idthenurl, notidthenself_link. Confirmed via the live schema: this resource has noself_linkattribute.url("The deployed url for the function") is the closest analog and is this module's second primary output β a deliberate, documented deviation from the house convention, not an oversight.url,service_config[0].uri, andservice_config[0].gcf_uriare three distinct attribute paths confirmed to carry substantially the same deployed URL. The provider's own "Cloudfunctions2 Scheduler Auth" example specifically readsservice_config[0].urirather than the top-levelurlβ both are populated with the same URL, but a future maintainer should not "simplify" this module by removing the apparent redundancy; all three are exposed as distinct outputs precisely so consumers can pick whichever path a given sibling composition's own examples reference.- Three distinct service-account-consuming arguments, two distinct string shapes.
build_config.service_accountwants the FULLY-QUALIFIEDprojects/{project}/serviceAccounts/{email}form (tf-mod-gcp-service-account's.idoutput);service_config.service_account_emailandevent_trigger.service_account_emailboth want the BARE email string (the same module's.emailoutput). This is the single most common cross-module wiring mistake this README's examples exist to forestall. direct_vpc_network_interface.network/.subnetworktake bare NAME strings, not self_link/id. A confirmed deviation fromtf-mod-gcp-cloud-run-service'svpc_access.network_interfacesprecedent, which takes a subnetwork self_link. Wire the literal name β the same string used as thesubnetworksmap key when callingtf-mod-gcp-vpc-networkβ not aself_link/idoutput.- This module does not manage the source archive.
build_config.source.storage_source/.repo_sourcereference already-staged source coordinates only β the caller/CI pipeline owns the archive's contents and its upload to GCS or a Cloud Source Repository. This module never creates agoogle_storage_bucket_object. - No
deletion_protectionexists on this resource. Confirmed absent from the live schema β a gap versustf-mod-gcp-cloud-run-service.deletion_policy(Terraform-only, defaultDELETE) is the only guard available; set it to"PREVENT"explicitly for a stronger destroy guard, understanding that it is not an API-enforced guard the waydeletion_protectionis on sibling modules. - IAM propagation delay is doc-confirmed for this specific resource. The provider's own "Basic
Builder" example inserts a 60-second
time_sleepbefore the function can build ("builder permissions need to stabilize before it can pull the source zip"); the "Basic Gcs"/"Basic Auditlogs" examples chain IAM grants viadepends_onbefore the function/trigger resource. Cite this as resource-specific, doc-confirmed evidence for this module suite's general ~60-second IAM propagation note β a firstapplyfailing on a fresh IAM grant is expected behavior, not a module bug. kms_key_name(CMEK) google-beta ambiguity β resolved. The only official worked example end-to-end setsprovider = google-betaon every resource, includinggoogle_cloudfunctions2_functionitself β but that example also provisions agoogle_project_service_identityresource, and the live provider schema for that resource (provider_doc_id12684073) confirms an explicit warning: "These resources are in beta, and should be used with the terraform-provider-google-beta provider."google_cloudfunctions2_function's own doc page carries no such warning, andkms_key_nameis listed as a plain Optional argument with no beta annotation. Conclusion: thegoogle-betaprovider tag in that example is attributable to the co-located service-identity lookup, not to any beta requirement onkms_key_nameitself. Treated as GA here β a liveplan/applycheck is still recommended before production use, per this library's general plan-only posture (which cannot catch apply-time KMS/IAM rejections regardless of GA-vs-beta status).secret_environment_variables/secret_volumesmodeled asmap(object(...)), not the provider's own repeatable-block-with-a-key/mount_pathshape.secret_environment_variablesis keyed by environment variable name;secret_volumesis keyed bymount_path(the field that must be unique across volumes) β the house for_each-over-a-keyed-map pattern applied to these two nested collections.automatic_update_policy/on_deploy_update_policyare two mutually-exclusive, zero-argument nested blocks in the live schema, represented here as two boolean flags rather than two empty object types β this module's ownvalidation {}block rejects setting bothtrue.
| Concern | Secure default | Opt-out (explicit) |
|---|---|---|
| Ingress / public exposure | service_config.ingress_settings = "ALLOW_INTERNAL_ONLY" (extension to this module suite's house table of secure defaults β this resource has no pre-existing row there; provider's own default if unmanaged is ALLOW_ALL) |
Caller sets ingress_settings = "ALLOW_ALL" explicitly |
| Deletion guard | N/A β this resource exposes no deletion_protection boolean (confirmed absent from the live schema); deletion_policy (Terraform-only, default DELETE) is the only guard available |
Caller sets deletion_policy = "PREVENT" explicitly for a stronger (Terraform-only) destroy guard |
| CMEK for function resources | kms_key_name accepted as an optional variable, never defaulted to a specific key |
Caller supplies a tf-mod-gcp-kms-keyring crypto key resource name |
| Service account key material | This module never creates a service account or key β all three identity arguments are plain strings consumed from tf-mod-gcp-service-account |
N/A β use Workload Identity Federation or a dedicated service account module |
| Secrets in configuration | Env/volume secret references are Secret Manager secret/project_id/version string pointers only β this module never accepts or stores a literal secret value |
N/A β literal secret values are out of scope by design |
| VPC egress identity | direct_vpc_network_interface/vpc_connector both accepted as plain strings; neither defaults to a specific network |
Caller supplies the subnetwork/connector explicitly to opt into VPC egress at all |
| Source deployment | This module never manages the source archive's contents or upload β a caller/CI pipeline responsibility by design | N/A β out of scope; see Architecture Notes |
cd tf-mod-gcp-cloud-function
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module source to ?ref=v1.0.0 β never a branch. This library is plan-only from an
authoring session; a human applies from CI with valid Workload Identity Federation or ADC
credentials.
terraform validate confirms internal type and reference consistency β every validation {} block
(deletion_policy/ingress_settings/vpc_connector_egress_settings/direct_vpc_egress/
retry_policy/event_filters[*].operator enums, kms_key_name format, label format, the
automatic_update_policy/on_deploy_update_policy mutual-exclusivity check) runs at plan time,
before any API call. terraform fmt -check confirms canonical formatting. Neither can catch GCP
API-level rejections this module cannot see in advance: a malformed event_trigger/event_filters
combination the Eventarc API itself rejects, an invalid or inaccessible docker_repository
reference, quota limits, IAM propagation lag on the build/runtime/trigger service accounts, or the
kms_key_name google-beta ambiguity this session resolved via documentation cross-checking rather
than a live apply β only a real terraform plan/apply against a live project, with valid
credentials, exercises those paths. That step belongs to the consuming CI pipeline, not this
authoring session.
$ terraform output
effective_labels = {
"goog-terraform-provisioned" = "true"
}
environment = "GEN_2"
id = "projects/casey-prod-workloads/locations/us-central1/functions/order-processor"
name = "order-processor"
service_gcf_uri = "https://order-processor-abcdefghij-uc.a.run.app"
service_uri = "https://order-processor-abcdefghij-uc.a.run.app"
state = "ACTIVE"
terraform_labels = {
"goog-terraform-provisioned" = "true"
}
update_time = "2026-07-11T18:42:03Z"
url = "https://order-processor-abcdefghij-uc.a.run.app"
| Symptom | Cause | Fix |
|---|---|---|
apply fails immediately after granting a build/runtime IAM role, with a permission-denied error building or invoking the function |
IAM propagation lag (up to ~60 seconds) β doc-confirmed for this resource via the provider's own "Basic Builder" time_sleep pattern |
Re-run apply; consider an explicit depends_on plus a short delay (or a time_sleep resource) for first-time bring-up |
plan/apply rejects a service-account-shaped string with an invalid-format error |
Wrong string shape supplied β build_config.service_account wants the FULLY-QUALIFIED projects/{project}/serviceAccounts/{email} form, while service_config.service_account_email/event_trigger.service_account_email want the BARE email |
Swap to the correct tf-mod-gcp-service-account output (.id for build_config.service_account; .email for the other two) |
| Function builds but the container image pull or push to Artifact Registry fails with a permission error | The build service account (or GCF's own default builder identity) lacks roles/artifactregistry.writer on the target docker_repository, or roles/artifactregistry.reader at runtime |
Grant the missing role via tf-mod-gcp-project-iam-bindings or a resource-scoped Artifact Registry IAM binding |
| Function is reachable from the public internet unexpectedly | ingress_settings left at its secure default is fine β this symptom means a caller explicitly set ingress_settings = "ALLOW_ALL" without intending public exposure |
Review the composition for an explicit override; revert to "ALLOW_INTERNAL_ONLY" (or "ALLOW_INTERNAL_AND_GCLB") if public exposure was not intended |
| Event-triggered function never fires | Missing roles/eventarc.eventReceiver/roles/run.invoker on the trigger service account, or (for GCS triggers) missing roles/pubsub.publisher on the GCS service agent |
Grant the missing roles; confirm via gcloud eventarc triggers describe that the underlying Eventarc trigger (this resource's trigger output-only attribute) exists and is healthy |
apply with kms_key_name set fails with a KMS permission error |
The GCF/Artifact Registry/Cloud Storage/Eventarc service agents have not been granted roles/cloudkms.cryptoKeyEncrypterDecrypter on the referenced key, or IAM propagation has not yet completed |
Grant the role to all relevant service agents before the first apply; allow for propagation lag; if the error persists, treat it as evidence for re-checking the google-beta resolution documented in Architecture Notes against a live plan |
terraform destroy fails even after setting deletion_policy = "ABANDON"/"PREVENT" reviewed |
This resource has no deletion_protection β a "PREVENT" deletion_policy blocks Terraform itself, but check for an out-of-band GCP-side lock (e.g. an org policy) if the failure is unexpected |
Set deletion_policy = "DELETE" explicitly if destruction is intended; investigate org-policy constraints if not |
google_cloudfunctions2_functionprovider resource referencetf-mod-gcp-service-account(build/runtime/event-trigger identity source β three arguments, two string shapes)tf-mod-gcp-artifact-registry-repository(build output destination forbuild_config.docker_repository)tf-mod-gcp-pubsub-topic(event trigger source forevent_trigger.pubsub_topic)tf-mod-gcp-secret-manager-secret(secret source for env/volume secret references)tf-mod-gcp-vpc-network(subnetwork NAME source for direct VPC egress)tf-mod-gcp-kms-keyring(CMEK crypto key source forkms_key_name)tf-mod-gcp-project-iam-bindings(grants the build/runtime/trigger service accounts' roles)tf-mod-gcp-project-services(must enable the unconditional + conditional APIs before this module applies)tf-mod-gcp-cloud-scheduler-job(informational consumer of this module'surloutput)tf-mod-gcp-cloud-run-service(sibling Cloud-Run-backed compute module; compare itsself_link-based VPC egress precedent against this module's name-based one)- This module's
SCOPE.md