Support GCS objects in cloud function modules bundles (#2361)

* cloud function v2

* cloud function v1

* blueprints
This commit is contained in:
Ludovico Magnocavallo 2024-06-14 13:44:01 +02:00 committed by GitHub
parent ea5e6dedc9
commit fa00deb747
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 229 additions and 187 deletions

View File

@ -23,8 +23,7 @@ module "instance_monitor_function" {
bucket_config = { bucket_config = {
} }
bundle_config = { bundle_config = {
path = "${path.module}/functions/instance-monitor" path = "${path.module}/functions/instance-monitor"
output_path = "bundle.zip"
} }
function_config = { function_config = {
entry_point = "writeMetric" entry_point = "writeMetric"

View File

@ -164,9 +164,7 @@ module "function_export" {
lifecycle_delete_age = 1 lifecycle_delete_age = 1
} }
bundle_config = { bundle_config = {
path = "${path.module}/functions/export" path = "${path.module}/functions/export"
output_path = "${path.module}/bundle-export.zip"
excludes = null
} }
function_config = { function_config = {
entry_point = "export" entry_point = "export"
@ -200,9 +198,7 @@ module "function_gcs2bq" {
lifecycle_delete_age = 1 lifecycle_delete_age = 1
} }
bundle_config = { bundle_config = {
path = "${path.module}/functions/gcs2bq" path = "${path.module}/functions/gcs2bq"
output_path = "${path.module}/bundle-gcs2bq.zip"
excludes = null
} }
function_config = { function_config = {
entry_point = "gcs2bq" entry_point = "gcs2bq"

View File

@ -85,8 +85,10 @@ module "cf" {
location = var.region location = var.region
} }
bundle_config = { bundle_config = {
path = "${path.module}/cf" path = "${path.module}/cf"
output_path = var.bundle_path folder_options = {
archive_path = var.bundle_path
}
} }
service_account = module.service-account.email service_account = module.service-account.email
trigger_config = { trigger_config = {

View File

@ -60,8 +60,10 @@ module "cf" {
location = var.region location = var.region
} }
bundle_config = { bundle_config = {
path = "${path.module}/src" path = "${path.module}/src"
output_path = var.bundle_path folder_options = {
archive_path = var.bundle_path
}
} }
service_account_create = true service_account_create = true
trigger_config = { trigger_config = {

View File

@ -73,8 +73,10 @@ module "cloud-function" {
} }
build_worker_pool = var.cloud_function_config.build_worker_pool_id build_worker_pool = var.cloud_function_config.build_worker_pool_id
bundle_config = { bundle_config = {
path = var.cloud_function_config.source_dir path = var.cloud_function_config.source_dir
output_path = var.cloud_function_config.bundle_path folder_options = {
archive_path = var.cloud_function_config.bundle_path
}
} }
environment_variables = ( environment_variables = (
var.cloud_function_config.debug != true ? {} : { DEBUG = "1" } var.cloud_function_config.debug != true ? {} : { DEBUG = "1" }
@ -145,8 +147,10 @@ module "cloud-function-v2" {
} }
build_worker_pool = var.cloud_function_config.build_worker_pool_id build_worker_pool = var.cloud_function_config.build_worker_pool_id
bundle_config = { bundle_config = {
path = var.cloud_function_config.source_dir path = var.cloud_function_config.source_dir
output_path = var.cloud_function_config.bundle_path folder_options = {
archive_path = var.cloud_function_config.bundle_path
}
} }
environment_variables = ( environment_variables = (
var.cloud_function_config.debug != true ? {} : { DEBUG = "1" } var.cloud_function_config.debug != true ? {} : { DEBUG = "1" }

View File

@ -94,8 +94,10 @@ module "cf" {
location = var.region location = var.region
} }
bundle_config = { bundle_config = {
path = "${path.module}/cf" path = "${path.module}/cf"
output_path = var.bundle_path folder_options = {
archive_path = var.bundle_path
}
} }
service_account = module.service-account.email service_account = module.service-account.email
trigger_config = { trigger_config = {
@ -116,9 +118,10 @@ module "cffile" {
lifecycle_delete_age_days = null lifecycle_delete_age_days = null
} }
bundle_config = { bundle_config = {
path = "${path.module}/cffile" path = "${path.module}/cffile"
output_path = var.bundle_path_cffile folder_options = {
excludes = null archive_path = var.bundle_path_cffile
}
} }
service_account = module.service-account.email service_account = module.service-account.email
trigger_config = { trigger_config = {

View File

@ -117,8 +117,7 @@ module "cf-restarter" {
location = var.region location = var.region
} }
bundle_config = { bundle_config = {
path = "${path.module}/function/restarter" path = "${path.module}/function/restarter"
output_path = "restarter.zip"
} }
service_account = module.service-account-restarter.email service_account = module.service-account-restarter.email
@ -145,8 +144,7 @@ module "cf-healthchecker" {
region = var.region region = var.region
bucket_name = module.cf-restarter.bucket_name bucket_name = module.cf-restarter.bucket_name
bundle_config = { bundle_config = {
path = "${path.module}/function/healthchecker" path = "${path.module}/function/healthchecker"
output_path = "healthchecker.zip"
} }
service_account = module.service-account-healthchecker.email service_account = module.service-account-healthchecker.email
function_config = { function_config = {

View File

@ -184,8 +184,7 @@ module "function-hello" {
bucket_name = "${var.name}-tf-cf-deploy" bucket_name = "${var.name}-tf-cf-deploy"
ingress_settings = "ALLOW_INTERNAL_ONLY" ingress_settings = "ALLOW_INTERNAL_ONLY"
bundle_config = { bundle_config = {
path = "${path.module}/assets" path = "${path.module}/assets"
output_path = "bundle.zip"
} }
bucket_config = { bucket_config = {
location = var.region location = var.region

View File

@ -1,8 +1,6 @@
# Cloud Function Module (V1) # Cloud Function Module (V1)
Cloud Function management, with support for IAM roles and optional bucket creation. Cloud Function management, with support for IAM roles, optional bucket creation and bundle via GCS URI, local zip, or local source folder.
The GCS object used for deployment uses a hash of the bundle zip contents in its name, which ensures change tracking and avoids recreating the function if the GCS object is deleted and needs recreating.
<!-- BEGIN TOC --> <!-- BEGIN TOC -->
- [TODO](#todo) - [TODO](#todo)
@ -39,8 +37,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = var.bucket bucket_name = var.bucket
bundle_config = { bundle_config = {
path = "assets/sample-function/" path = "assets/sample-function/"
output_path = "bundle.zip"
} }
} }
# tftest modules=1 resources=2 e2e # tftest modules=1 resources=2 e2e
@ -58,8 +55,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "assets/sample-function/"
output_path = "bundle.zip"
} }
trigger_config = { trigger_config = {
event = "google.pubsub.topic.publish" event = "google.pubsub.topic.publish"
@ -81,8 +77,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "assets/sample-function/"
output_path = "bundle.zip"
} }
iam = { iam = {
"roles/cloudfunctions.invoker" = ["allUsers"] "roles/cloudfunctions.invoker" = ["allUsers"]
@ -107,7 +102,7 @@ module "cf-http" {
lifecycle_delete_age_days = 1 lifecycle_delete_age_days = 1
} }
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "assets/sample-function/"
} }
} }
# tftest modules=1 resources=3 inventory=bucket-creation.yaml # tftest modules=1 resources=3 inventory=bucket-creation.yaml
@ -125,8 +120,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "assets/sample-function/"
output_path = "bundle.zip"
} }
service_account_create = true service_account_create = true
} }
@ -143,8 +137,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "assets/sample-function/"
output_path = "bundle.zip"
} }
service_account = "non-existent@serice.account.email" service_account = "non-existent@serice.account.email"
} }
@ -153,11 +146,13 @@ module "cf-http" {
### Custom bundle config ### Custom bundle config
The Cloud Function bundle can be configured via the `bundle_config` variable, so that either a `zip` archive or a source folder can be used. The Cloud Function bundle can be configured via the `bundle_config` variable. The only mandatory argument is `bundle_config.path` which can point to:
If a `zip` archive is already available, simply set the archive path in `bundle_config.path`. If a dynamically generated archive is needed, set `bundle_config.path` to the source folder path, then optionally configure the path where the archive will be created, and any exclusions needed in the archive. - a GCS URI of a ZIP archive
- a local path to a ZIP archive
- a local path to a source folder
If you use a folder and dynamic archive bundling, be mindful that the MD5 checksum of the generated `zip` file does not change across environments (e.g. Cloud Build vs your local development environment), by ensuring that the files in the folder are always the same. When a GCS URI or a local zip file are used, a change in their names will trigger redeployment. When a local source folder is used a ZIP archive will be automatically generated and its internally derived checksum will drive redeployment. You can optionally control its name and exclusions via the attributes in `bundle_config.folder_options`.
```hcl ```hcl
module "cf-http" { module "cf-http" {
@ -167,9 +162,11 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip" folder_options = {
excludes = ["__pycache__"] archive_path = "bundle.zip"
excludes = ["__pycache__"]
}
} }
} }
# tftest modules=1 resources=2 # tftest modules=1 resources=2
@ -188,8 +185,7 @@ module "cf-http" {
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
build_worker_pool = "projects/my-project/locations/europe-west1/workerPools/my_build_worker_pool" build_worker_pool = "projects/my-project/locations/europe-west1/workerPools/my_build_worker_pool"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
} }
# tftest modules=1 resources=2 # tftest modules=1 resources=2
@ -197,7 +193,7 @@ module "cf-http" {
### Multiple Cloud Functions within project ### Multiple Cloud Functions within project
When deploying multiple functions do not reuse `bundle_config.output_path` between instances as the result is undefined. Default `output_path` creates file in `/tmp` folder using project Id and function name to avoid name conflicts. When deploying multiple functions do not reuse `bundle_config.archive_path` between instances as the result is undefined. Default `archive_path` creates file in `/tmp` folder using project Id and function name to avoid name conflicts.
```hcl ```hcl
module "cf-http-one" { module "cf-http-one" {
@ -207,7 +203,7 @@ module "cf-http-one" {
name = "test-cf-http-one" name = "test-cf-http-one"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets" path = "fabric/assets/"
} }
} }
@ -218,7 +214,7 @@ module "cf-http-two" {
name = "test-cf-http-two" name = "test-cf-http-two"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets" path = "fabric/assets/"
} }
} }
# tftest modules=2 resources=4 inventory=multiple_functions.yaml # tftest modules=2 resources=4 inventory=multiple_functions.yaml
@ -240,8 +236,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
secrets = { secrets = {
VARIABLE_SECRET = { VARIABLE_SECRET = {
@ -279,8 +274,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
kms_key = "projects/my-project/locations/europe-west1/keyRings/mykeyring/cryptoKeys/mykey" kms_key = "projects/my-project/locations/europe-west1/keyRings/mykeyring/cryptoKeys/mykey"
repository_settings = { repository_settings = {
@ -295,29 +289,29 @@ module "cf-http" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | <code>string</code> | ✓ | | | [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | <code>string</code> | ✓ | |
| [bundle_config](variables.tf#L44) | Cloud function source. If path points to a .zip archive it is uploaded as-is, otherwise an archive is created on the fly. A null output path will use a unique name for the bundle in /tmp. | <code title="object&#40;&#123;&#10; path &#61; string&#10; excludes &#61; optional&#40;list&#40;string&#41;&#41;&#10; output_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [bundle_config](variables.tf#L44) | Cloud function source. Path can point to a GCS object URI, or a local path. A local path to a zip archive will generate a GCS object using its basename, a folder will be zipped and the GCS object name inferred when not specified. | <code title="object&#40;&#123;&#10; path &#61; string&#10; folder_options &#61; optional&#40;object&#40;&#123;&#10; archive_path &#61; optional&#40;string&#41;&#10; excludes &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [name](variables.tf#L127) | Name used for cloud function and associated resources. | <code>string</code> | ✓ | | | [name](variables.tf#L139) | Name used for cloud function and associated resources. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L142) | Project id used for all resources. | <code>string</code> | ✓ | | | [project_id](variables.tf#L154) | Project id used for all resources. | <code>string</code> | ✓ | |
| [region](variables.tf#L147) | Region used for all resources. | <code>string</code> | ✓ | | | [region](variables.tf#L159) | Region used for all resources. | <code>string</code> | ✓ | |
| [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | <code title="object&#40;&#123;&#10; location &#61; optional&#40;string&#41;&#10; lifecycle_delete_age_days &#61; optional&#40;number&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | <code title="object&#40;&#123;&#10; location &#61; optional&#40;string&#41;&#10; lifecycle_delete_age_days &#61; optional&#40;number&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [build_environment_variables](variables.tf#L32) | A set of key/value environment variable pairs available during build time. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [build_environment_variables](variables.tf#L32) | A set of key/value environment variable pairs available during build time. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [build_worker_pool](variables.tf#L38) | Build worker pool, in projects/<PROJECT-ID>/locations/<REGION>/workerPools/<POOL_NAME> format. | <code>string</code> | | <code>null</code> | | [build_worker_pool](variables.tf#L38) | Build worker pool, in projects/<PROJECT-ID>/locations/<REGION>/workerPools/<POOL_NAME> format. | <code>string</code> | | <code>null</code> |
| [description](variables.tf#L65) | Optional description. | <code>string</code> | | <code>&#34;Terraform managed.&#34;</code> | | [description](variables.tf#L77) | Optional description. | <code>string</code> | | <code>&#34;Terraform managed.&#34;</code> |
| [environment_variables](variables.tf#L71) | Cloud function environment variables. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [environment_variables](variables.tf#L83) | Cloud function environment variables. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [function_config](variables.tf#L77) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | <code title="object&#40;&#123;&#10; entry_point &#61; optional&#40;string, &#34;main&#34;&#41;&#10; instance_count &#61; optional&#40;number, 1&#41;&#10; memory_mb &#61; optional&#40;number, 256&#41; &#35; Memory in MB&#10; cpu &#61; optional&#40;string, &#34;0.166&#34;&#41;&#10; runtime &#61; optional&#40;string, &#34;python310&#34;&#41;&#10; timeout_seconds &#61; optional&#40;number, 180&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; entry_point &#61; &#34;main&#34;&#10; instance_count &#61; 1&#10; memory_mb &#61; 256&#10; cpu &#61; &#34;0.166&#34;&#10; runtime &#61; &#34;python310&#34;&#10; timeout_seconds &#61; 180&#10;&#125;">&#123;&#8230;&#125;</code> | | [function_config](variables.tf#L89) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | <code title="object&#40;&#123;&#10; entry_point &#61; optional&#40;string, &#34;main&#34;&#41;&#10; instance_count &#61; optional&#40;number, 1&#41;&#10; memory_mb &#61; optional&#40;number, 256&#41; &#35; Memory in MB&#10; cpu &#61; optional&#40;string, &#34;0.166&#34;&#41;&#10; runtime &#61; optional&#40;string, &#34;python310&#34;&#41;&#10; timeout_seconds &#61; optional&#40;number, 180&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; entry_point &#61; &#34;main&#34;&#10; instance_count &#61; 1&#10; memory_mb &#61; 256&#10; cpu &#61; &#34;0.166&#34;&#10; runtime &#61; &#34;python310&#34;&#10; timeout_seconds &#61; 180&#10;&#125;">&#123;&#8230;&#125;</code> |
| [https_security_level](variables.tf#L97) | The security level for the function: Allowed values are SECURE_ALWAYS, SECURE_OPTIONAL. | <code>string</code> | | <code>null</code> | | [https_security_level](variables.tf#L109) | The security level for the function: Allowed values are SECURE_ALWAYS, SECURE_OPTIONAL. | <code>string</code> | | <code>null</code> |
| [iam](variables.tf#L103) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L115) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_settings](variables.tf#L109) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | <code>string</code> | | <code>null</code> | | [ingress_settings](variables.tf#L121) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | <code>string</code> | | <code>null</code> |
| [kms_key](variables.tf#L115) | Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository field that was created with the same KMS crypto key. | <code>string</code> | | <code>null</code> | | [kms_key](variables.tf#L127) | Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository field that was created with the same KMS crypto key. | <code>string</code> | | <code>null</code> |
| [labels](variables.tf#L121) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [labels](variables.tf#L133) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L132) | Optional prefix used for resource names. | <code>string</code> | | <code>null</code> | | [prefix](variables.tf#L144) | Optional prefix used for resource names. | <code>string</code> | | <code>null</code> |
| [repository_settings](variables.tf#L152) | Docker Registry to use for storing the function's Docker images and specific repository. If kms_key is provided, the repository must have already been encrypted with the key. | <code title="object&#40;&#123;&#10; registry &#61; optional&#40;string&#41;&#10; repository &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; registry &#61; &#34;ARTIFACT_REGISTRY&#34;&#10;&#125;">&#123;&#8230;&#125;</code> | | [repository_settings](variables.tf#L164) | Docker Registry to use for storing the function's Docker images and specific repository. If kms_key is provided, the repository must have already been encrypted with the key. | <code title="object&#40;&#123;&#10; registry &#61; optional&#40;string&#41;&#10; repository &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; registry &#61; &#34;ARTIFACT_REGISTRY&#34;&#10;&#125;">&#123;&#8230;&#125;</code> |
| [secrets](variables.tf#L163) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | <code title="map&#40;object&#40;&#123;&#10; is_volume &#61; bool&#10; project_id &#61; number&#10; secret &#61; string&#10; versions &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [secrets](variables.tf#L175) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | <code title="map&#40;object&#40;&#123;&#10; is_volume &#61; bool&#10; project_id &#61; number&#10; secret &#61; string&#10; versions &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_account](variables.tf#L175) | Service account email. Unused if service account is auto-created. | <code>string</code> | | <code>null</code> | | [service_account](variables.tf#L187) | Service account email. Unused if service account is auto-created. | <code>string</code> | | <code>null</code> |
| [service_account_create](variables.tf#L181) | Auto-create service account. | <code>bool</code> | | <code>false</code> | | [service_account_create](variables.tf#L193) | Auto-create service account. | <code>bool</code> | | <code>false</code> |
| [trigger_config](variables.tf#L187) | Function trigger configuration. Leave null for HTTP trigger. | <code title="object&#40;&#123;&#10; event &#61; string&#10; resource &#61; string&#10; retry &#61; optional&#40;bool&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [trigger_config](variables.tf#L199) | Function trigger configuration. Leave null for HTTP trigger. | <code title="object&#40;&#123;&#10; event &#61; string&#10; resource &#61; string&#10; retry &#61; optional&#40;bool&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [vpc_connector](variables.tf#L197) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | <code title="object&#40;&#123;&#10; create &#61; bool&#10; name &#61; string&#10; egress_settings &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [vpc_connector](variables.tf#L209) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | <code title="object&#40;&#123;&#10; create &#61; bool&#10; name &#61; string&#10; egress_settings &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [vpc_connector_config](variables.tf#L207) | VPC connector network configuration. Must be provided if new VPC connector is being created. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; string&#10; network &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [vpc_connector_config](variables.tf#L219) | VPC connector network configuration. Must be provided if new VPC connector is being created. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; string&#10; network &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -15,16 +15,16 @@
*/ */
locals { locals {
bundle = { bundle_type = (
name = try( startswith(var.bundle_config.path, "gs://")
"bundle-${data.archive_file.bundle[0].output_md5}.zip", ? "gcs"
basename(var.bundle_config.path) : (
try(fileexists(pathexpand(var.bundle_config.path)), null) != null &&
endswith(var.bundle_config.path, ".zip")
? "local-file"
: "local-folder"
) )
path = try( )
data.archive_file.bundle[0].output_path,
pathexpand(var.bundle_config.path)
)
}
} }
resource "google_storage_bucket" "bucket" { resource "google_storage_bucket" "bucket" {
@ -59,20 +59,29 @@ resource "google_storage_bucket" "bucket" {
# compress bundle in a zip archive if it's a folder # compress bundle in a zip archive if it's a folder
data "archive_file" "bundle" { data "archive_file" "bundle" {
count = ( count = local.bundle_type == "local-folder" ? 1 : 0
try(fileexists(pathexpand(var.bundle_config.path)), null) == null ? 1 : 0 type = "zip"
source_dir = pathexpand(var.bundle_config.path)
output_path = (
var.bundle_config.folder_options.archive_path != null
? pathexpand(var.bundle_config.folder_options.archive_path)
: "/tmp/bundle-${var.project_id}-${var.name}.zip"
) )
type = "zip"
source_dir = pathexpand(var.bundle_config.path)
output_path = coalesce(var.bundle_config.output_path, "/tmp/bundle-${var.project_id}-${var.name}.zip")
output_file_mode = "0644" output_file_mode = "0644"
excludes = var.bundle_config.excludes excludes = var.bundle_config.folder_options.excludes
} }
# upload to GCS # upload to GCS
resource "google_storage_bucket_object" "bundle" { resource "google_storage_bucket_object" "bundle" {
name = local.bundle.name count = local.bundle_type != "gcs" ? 1 : 0
name = try(
"bundle-${data.archive_file.bundle[0].output_md5}.zip",
basename(var.bundle_config.path)
)
bucket = local.bucket bucket = local.bucket
source = local.bundle.path source = try(
data.archive_file.bundle[0].output_path,
pathexpand(var.bundle_config.path)
)
} }

View File

@ -51,19 +51,23 @@ resource "google_vpc_access_connector" "connector" {
} }
resource "google_cloudfunctions_function" "function" { resource "google_cloudfunctions_function" "function" {
project = var.project_id project = var.project_id
region = var.region region = var.region
name = "${local.prefix}${var.name}" name = "${local.prefix}${var.name}"
description = var.description description = var.description
runtime = var.function_config.runtime runtime = var.function_config.runtime
available_memory_mb = var.function_config.memory_mb available_memory_mb = var.function_config.memory_mb
max_instances = var.function_config.instance_count max_instances = var.function_config.instance_count
timeout = var.function_config.timeout_seconds timeout = var.function_config.timeout_seconds
entry_point = var.function_config.entry_point entry_point = var.function_config.entry_point
environment_variables = var.environment_variables environment_variables = var.environment_variables
service_account_email = local.service_account_email service_account_email = local.service_account_email
source_archive_bucket = local.bucket source_archive_bucket = local.bucket
source_archive_object = google_storage_bucket_object.bundle.name source_archive_object = (
local.bundle_type == "gcs"
? var.bundle_config.path
: google_storage_bucket_object.bundle[0].name
)
labels = var.labels labels = var.labels
trigger_http = var.trigger_config == null ? true : null trigger_http = var.trigger_config == null ? true : null
https_trigger_security_level = var.https_security_level == null ? "SECURE_ALWAYS" : var.https_security_level https_trigger_security_level = var.https_security_level == null ? "SECURE_ALWAYS" : var.https_security_level

View File

@ -42,23 +42,35 @@ variable "build_worker_pool" {
} }
variable "bundle_config" { variable "bundle_config" {
description = "Cloud function source. If path points to a .zip archive it is uploaded as-is, otherwise an archive is created on the fly. A null output path will use a unique name for the bundle in /tmp." description = "Cloud function source. Path can point to a GCS object URI, or a local path. A local path to a zip archive will generate a GCS object using its basename, a folder will be zipped and the GCS object name inferred when not specified."
type = object({ type = object({
path = string path = string
excludes = optional(list(string)) folder_options = optional(object({
output_path = optional(string) archive_path = optional(string)
excludes = optional(list(string))
}), {})
}) })
nullable = false
validation { validation {
condition = ( condition = (
var.bundle_config.path != null && ( var.bundle_config.path != null && (
# GCS object
(
startswith(var.bundle_config.path, "gs://") &&
endswith(var.bundle_config.path, ".zip")
)
||
# local folder
length(fileset(pathexpand(var.bundle_config.path), "**/*")) > 0
||
# local ZIP archive
( (
try(fileexists(pathexpand(var.bundle_config.path)), null) != null && try(fileexists(pathexpand(var.bundle_config.path)), null) != null &&
endswith(var.bundle_config.path, ".zip") endswith(var.bundle_config.path, ".zip")
) || )
length(fileset(pathexpand(var.bundle_config.path), "**/*")) > 0
) )
) )
error_message = "Bundle path must be set to a local folder or zip file." error_message = "Bundle path must be set to a GCS object URI, a local folder or a local zip file."
} }
} }

View File

@ -1,8 +1,6 @@
# Cloud Function Module (v2) # Cloud Function Module (v2)
Cloud Function management, with support for IAM roles and optional bucket creation. Cloud Function management, with support for IAM roles, optional bucket creation and bundle via GCS URI, local zip, or local source folder.
The GCS object used for deployment uses a hash of the bundle zip contents in its name, which ensures change tracking and avoids recreating the function if the GCS object is deleted and needs recreating.
<!-- BEGIN TOC --> <!-- BEGIN TOC -->
- [TODO](#todo) - [TODO](#todo)
@ -38,8 +36,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
} }
# tftest modules=1 resources=2 # tftest modules=1 resources=2
@ -68,8 +65,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
trigger_config = { trigger_config = {
event_type = "google.cloud.pubsub.topic.v1.messagePublished" event_type = "google.cloud.pubsub.topic.v1.messagePublished"
@ -95,8 +91,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
iam = { iam = {
"roles/run.invoker" = ["allUsers"] "roles/run.invoker" = ["allUsers"]
@ -139,8 +134,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
service_account_create = true service_account_create = true
} }
@ -157,8 +151,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
service_account = "non-existent@serice.account.email" service_account = "non-existent@serice.account.email"
} }
@ -167,11 +160,13 @@ module "cf-http" {
### Custom bundle config ### Custom bundle config
The Cloud Function bundle can be configured via the `bundle_config` variable, so that either a `zip` archive or a source folder can be used. The Cloud Function bundle can be configured via the `bundle_config` variable. The only mandatory argument is `bundle_config.path` which can point to:
If a `zip` archive is already available, simply set the archive path in `bundle_config.path`. If a dynamically generated archive is needed, set `bundle_config.path` to the source folder path, then optionally configure the path where the archive will be created, and any exclusions needed in the archive. - a GCS URI of a ZIP archive
- a local path to a ZIP archive
- a local path to a source folder
If you use a folder and dynamic archive bundling, be mindful that the MD5 checksum of the generated `zip` file does not change across environments (e.g. Cloud Build vs your local development environment), by ensuring that the files in the folder are always the same. When a GCS URI or a local zip file are used, a change in their names will trigger redeployment. When a local source folder is used a ZIP archive will be automatically generated and its internally derived checksum will drive redeployment. You can optionally control its name and exclusions via the attributes in `bundle_config.folder_options`.
```hcl ```hcl
module "cf-http" { module "cf-http" {
@ -181,9 +176,11 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip" folder_options = {
excludes = ["__pycache__"] archive_path = "bundle.zip"
excludes = ["__pycache__"]
}
} }
} }
# tftest modules=1 resources=2 # tftest modules=1 resources=2
@ -202,8 +199,7 @@ module "cf-http" {
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
build_worker_pool = "projects/my-project/locations/europe-west1/workerPools/my_build_worker_pool" build_worker_pool = "projects/my-project/locations/europe-west1/workerPools/my_build_worker_pool"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
} }
# tftest modules=1 resources=2 # tftest modules=1 resources=2
@ -211,7 +207,7 @@ module "cf-http" {
### Multiple Cloud Functions within project ### Multiple Cloud Functions within project
When deploying multiple functions do not reuse `bundle_config.output_path` between instances as the result is undefined. Default `output_path` creates file in `/tmp` folder using project Id and function name to avoid name conflicts. When deploying multiple functions via local folders do not reuse `bundle_config.archive_path` between instances as the result is undefined. Default `archive_path` creates file in `/tmp` folder using project Id and function name to avoid name conflicts.
```hcl ```hcl
module "cf-http-one" { module "cf-http-one" {
@ -254,8 +250,7 @@ module "cf-http" {
name = "test-cf-http" name = "test-cf-http"
bucket_name = "test-cf-bundles" bucket_name = "test-cf-bundles"
bundle_config = { bundle_config = {
path = "fabric/assets/" path = "fabric/assets/"
output_path = "bundle.zip"
} }
secrets = { secrets = {
VARIABLE_SECRET = { VARIABLE_SECRET = {
@ -287,27 +282,27 @@ module "cf-http" {
| name | description | type | required | default | | name | description | type | required | default |
|---|---|:---:|:---:|:---:| |---|---|:---:|:---:|:---:|
| [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | <code>string</code> | ✓ | | | [bucket_name](variables.tf#L26) | Name of the bucket that will be used for the function code. It will be created with prefix prepended if bucket_config is not null. | <code>string</code> | ✓ | |
| [bundle_config](variables.tf#L38) | Cloud function source. If path points to a .zip archive it is uploaded as-is, otherwise an archive is created on the fly. A null output path will use a unique name for the bundle in /tmp. | <code title="object&#40;&#123;&#10; path &#61; string&#10; excludes &#61; optional&#40;list&#40;string&#41;&#41;&#10; output_path &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | | | [bundle_config](variables.tf#L38) | Cloud function source. Path can point to a GCS object URI, or a local path. A local path to a zip archive will generate a GCS object using its basename, a folder will be zipped and the GCS object name inferred when not specified. | <code title="object&#40;&#123;&#10; path &#61; string&#10; folder_options &#61; optional&#40;object&#40;&#123;&#10; archive_path &#61; optional&#40;string&#41;&#10; excludes &#61; optional&#40;list&#40;string&#41;&#41;&#10; &#125;&#41;, &#123;&#125;&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | ✓ | |
| [name](variables.tf#L121) | Name used for cloud function and associated resources. | <code>string</code> | ✓ | | | [name](variables.tf#L133) | Name used for cloud function and associated resources. | <code>string</code> | ✓ | |
| [project_id](variables.tf#L136) | Project id used for all resources. | <code>string</code> | ✓ | | | [project_id](variables.tf#L148) | Project id used for all resources. | <code>string</code> | ✓ | |
| [region](variables.tf#L141) | Region used for all resources. | <code>string</code> | ✓ | | | [region](variables.tf#L153) | Region used for all resources. | <code>string</code> | ✓ | |
| [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | <code title="object&#40;&#123;&#10; location &#61; optional&#40;string&#41;&#10; lifecycle_delete_age_days &#61; optional&#40;number&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | <code title="object&#40;&#123;&#10; location &#61; optional&#40;string&#41;&#10; lifecycle_delete_age_days &#61; optional&#40;number&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [build_worker_pool](variables.tf#L32) | Build worker pool, in projects/<PROJECT-ID>/locations/<REGION>/workerPools/<POOL_NAME> format. | <code>string</code> | | <code>null</code> | | [build_worker_pool](variables.tf#L32) | Build worker pool, in projects/<PROJECT-ID>/locations/<REGION>/workerPools/<POOL_NAME> format. | <code>string</code> | | <code>null</code> |
| [description](variables.tf#L59) | Optional description. | <code>string</code> | | <code>&#34;Terraform managed.&#34;</code> | | [description](variables.tf#L71) | Optional description. | <code>string</code> | | <code>&#34;Terraform managed.&#34;</code> |
| [docker_repository_id](variables.tf#L65) | User managed repository created in Artifact Registry. | <code>string</code> | | <code>null</code> | | [docker_repository_id](variables.tf#L77) | User managed repository created in Artifact Registry. | <code>string</code> | | <code>null</code> |
| [environment_variables](variables.tf#L71) | Cloud function environment variables. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [environment_variables](variables.tf#L83) | Cloud function environment variables. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [function_config](variables.tf#L77) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | <code title="object&#40;&#123;&#10; entry_point &#61; optional&#40;string, &#34;main&#34;&#41;&#10; instance_count &#61; optional&#40;number, 1&#41;&#10; memory_mb &#61; optional&#40;number, 256&#41; &#35; Memory in MB&#10; cpu &#61; optional&#40;string, &#34;0.166&#34;&#41;&#10; runtime &#61; optional&#40;string, &#34;python310&#34;&#41;&#10; timeout_seconds &#61; optional&#40;number, 180&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; entry_point &#61; &#34;main&#34;&#10; instance_count &#61; 1&#10; memory_mb &#61; 256&#10; cpu &#61; &#34;0.166&#34;&#10; runtime &#61; &#34;python310&#34;&#10; timeout_seconds &#61; 180&#10;&#125;">&#123;&#8230;&#125;</code> | | [function_config](variables.tf#L89) | Cloud function configuration. Defaults to using main as entrypoint, 1 instance with 256MiB of memory, and 180 second timeout. | <code title="object&#40;&#123;&#10; entry_point &#61; optional&#40;string, &#34;main&#34;&#41;&#10; instance_count &#61; optional&#40;number, 1&#41;&#10; memory_mb &#61; optional&#40;number, 256&#41; &#35; Memory in MB&#10; cpu &#61; optional&#40;string, &#34;0.166&#34;&#41;&#10; runtime &#61; optional&#40;string, &#34;python310&#34;&#41;&#10; timeout_seconds &#61; optional&#40;number, 180&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code title="&#123;&#10; entry_point &#61; &#34;main&#34;&#10; instance_count &#61; 1&#10; memory_mb &#61; 256&#10; cpu &#61; &#34;0.166&#34;&#10; runtime &#61; &#34;python310&#34;&#10; timeout_seconds &#61; 180&#10;&#125;">&#123;&#8230;&#125;</code> |
| [iam](variables.tf#L97) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [iam](variables.tf#L109) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | <code>map&#40;list&#40;string&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [ingress_settings](variables.tf#L103) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | <code>string</code> | | <code>null</code> | | [ingress_settings](variables.tf#L115) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | <code>string</code> | | <code>null</code> |
| [kms_key](variables.tf#L109) | Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository_id field that was created with the same KMS crypto key. | <code>string</code> | | <code>null</code> | | [kms_key](variables.tf#L121) | Resource name of a KMS crypto key (managed by the user) used to encrypt/decrypt function resources in key id format. If specified, you must also provide an artifact registry repository using the docker_repository_id field that was created with the same KMS crypto key. | <code>string</code> | | <code>null</code> |
| [labels](variables.tf#L115) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> | | [labels](variables.tf#L127) | Resource labels. | <code>map&#40;string&#41;</code> | | <code>&#123;&#125;</code> |
| [prefix](variables.tf#L126) | Optional prefix used for resource names. | <code>string</code> | | <code>null</code> | | [prefix](variables.tf#L138) | Optional prefix used for resource names. | <code>string</code> | | <code>null</code> |
| [secrets](variables.tf#L146) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | <code title="map&#40;object&#40;&#123;&#10; is_volume &#61; bool&#10; project_id &#61; number&#10; secret &#61; string&#10; versions &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> | | [secrets](variables.tf#L158) | Secret Manager secrets. Key is the variable name or mountpoint, volume versions are in version:path format. | <code title="map&#40;object&#40;&#123;&#10; is_volume &#61; bool&#10; project_id &#61; number&#10; secret &#61; string&#10; versions &#61; list&#40;string&#41;&#10;&#125;&#41;&#41;">map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> | | <code>&#123;&#125;</code> |
| [service_account](variables.tf#L158) | Service account email. Unused if service account is auto-created. | <code>string</code> | | <code>null</code> | | [service_account](variables.tf#L170) | Service account email. Unused if service account is auto-created. | <code>string</code> | | <code>null</code> |
| [service_account_create](variables.tf#L164) | Auto-create service account. | <code>bool</code> | | <code>false</code> | | [service_account_create](variables.tf#L176) | Auto-create service account. | <code>bool</code> | | <code>false</code> |
| [trigger_config](variables.tf#L170) | Function trigger configuration. Leave null for HTTP trigger. | <code title="object&#40;&#123;&#10; event_type &#61; string&#10; pubsub_topic &#61; optional&#40;string&#41;&#10; region &#61; optional&#40;string&#41;&#10; event_filters &#61; optional&#40;list&#40;object&#40;&#123;&#10; attribute &#61; string&#10; value &#61; string&#10; operator &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; service_account_email &#61; optional&#40;string&#41;&#10; service_account_create &#61; optional&#40;bool, false&#41;&#10; retry_policy &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [trigger_config](variables.tf#L182) | Function trigger configuration. Leave null for HTTP trigger. | <code title="object&#40;&#123;&#10; event_type &#61; string&#10; pubsub_topic &#61; optional&#40;string&#41;&#10; region &#61; optional&#40;string&#41;&#10; event_filters &#61; optional&#40;list&#40;object&#40;&#123;&#10; attribute &#61; string&#10; value &#61; string&#10; operator &#61; optional&#40;string&#41;&#10; &#125;&#41;&#41;, &#91;&#93;&#41;&#10; service_account_email &#61; optional&#40;string&#41;&#10; service_account_create &#61; optional&#40;bool, false&#41;&#10; retry_policy &#61; optional&#40;string&#41;&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [vpc_connector](variables.tf#L188) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | <code title="object&#40;&#123;&#10; create &#61; bool&#10; name &#61; string&#10; egress_settings &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [vpc_connector](variables.tf#L200) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | <code title="object&#40;&#123;&#10; create &#61; bool&#10; name &#61; string&#10; egress_settings &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
| [vpc_connector_config](variables.tf#L198) | VPC connector network configuration. Must be provided if new VPC connector is being created. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; string&#10; network &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> | | [vpc_connector_config](variables.tf#L210) | VPC connector network configuration. Must be provided if new VPC connector is being created. | <code title="object&#40;&#123;&#10; ip_cidr_range &#61; string&#10; network &#61; string&#10;&#125;&#41;">object&#40;&#123;&#8230;&#125;&#41;</code> | | <code>null</code> |
## Outputs ## Outputs

View File

@ -15,16 +15,16 @@
*/ */
locals { locals {
bundle = { bundle_type = (
name = try( startswith(var.bundle_config.path, "gs://")
"bundle-${data.archive_file.bundle[0].output_md5}.zip", ? "gcs"
basename(var.bundle_config.path) : (
try(fileexists(pathexpand(var.bundle_config.path)), null) != null &&
endswith(var.bundle_config.path, ".zip")
? "local-file"
: "local-folder"
) )
path = try( )
data.archive_file.bundle[0].output_path,
pathexpand(var.bundle_config.path)
)
}
} }
resource "google_storage_bucket" "bucket" { resource "google_storage_bucket" "bucket" {
@ -59,20 +59,29 @@ resource "google_storage_bucket" "bucket" {
# compress bundle in a zip archive if it's a folder # compress bundle in a zip archive if it's a folder
data "archive_file" "bundle" { data "archive_file" "bundle" {
count = ( count = local.bundle_type == "local-folder" ? 1 : 0
try(fileexists(pathexpand(var.bundle_config.path)), null) == null ? 1 : 0 type = "zip"
source_dir = pathexpand(var.bundle_config.path)
output_path = (
var.bundle_config.folder_options.archive_path != null
? pathexpand(var.bundle_config.folder_options.archive_path)
: "/tmp/bundle-${var.project_id}-${var.name}.zip"
) )
type = "zip"
source_dir = pathexpand(var.bundle_config.path)
output_path = coalesce(var.bundle_config.output_path, "/tmp/bundle-${var.project_id}-${var.name}.zip")
output_file_mode = "0644" output_file_mode = "0644"
excludes = var.bundle_config.excludes excludes = var.bundle_config.folder_options.excludes
} }
# upload to GCS # upload to GCS
resource "google_storage_bucket_object" "bundle" { resource "google_storage_bucket_object" "bundle" {
name = local.bundle.name count = local.bundle_type != "gcs" ? 1 : 0
name = try(
"bundle-${data.archive_file.bundle[0].output_md5}.zip",
basename(var.bundle_config.path)
)
bucket = local.bucket bucket = local.bucket
source = local.bundle.path source = try(
data.archive_file.bundle[0].output_path,
pathexpand(var.bundle_config.path)
)
} }

View File

@ -74,7 +74,11 @@ resource "google_cloudfunctions2_function" "function" {
source { source {
storage_source { storage_source {
bucket = local.bucket bucket = local.bucket
object = google_storage_bucket_object.bundle.name object = (
local.bundle_type == "gcs"
? var.bundle_config.path
: google_storage_bucket_object.bundle[0].name
)
} }
} }
} }

View File

@ -36,23 +36,35 @@ variable "build_worker_pool" {
} }
variable "bundle_config" { variable "bundle_config" {
description = "Cloud function source. If path points to a .zip archive it is uploaded as-is, otherwise an archive is created on the fly. A null output path will use a unique name for the bundle in /tmp." description = "Cloud function source. Path can point to a GCS object URI, or a local path. A local path to a zip archive will generate a GCS object using its basename, a folder will be zipped and the GCS object name inferred when not specified."
type = object({ type = object({
path = string path = string
excludes = optional(list(string)) folder_options = optional(object({
output_path = optional(string) archive_path = optional(string)
excludes = optional(list(string))
}), {})
}) })
nullable = false
validation { validation {
condition = ( condition = (
var.bundle_config.path != null && ( var.bundle_config.path != null && (
# GCS object
(
startswith(var.bundle_config.path, "gs://") &&
endswith(var.bundle_config.path, ".zip")
)
||
# local folder
length(fileset(pathexpand(var.bundle_config.path), "**/*")) > 0
||
# local ZIP archive
( (
try(fileexists(pathexpand(var.bundle_config.path)), null) != null && try(fileexists(pathexpand(var.bundle_config.path)), null) != null &&
endswith(var.bundle_config.path, ".zip") endswith(var.bundle_config.path, ".zip")
) || )
length(fileset(pathexpand(var.bundle_config.path), "**/*")) > 0
) )
) )
error_message = "Bundle path must be set to a local folder or zip file." error_message = "Bundle path must be set to a GCS object URI, a local folder or a local zip file."
} }
} }

View File

@ -13,9 +13,9 @@
# limitations under the License. # limitations under the License.
values: values:
module.cf-http-one.google_storage_bucket_object.bundle: module.cf-http-one.google_storage_bucket_object.bundle[0]:
source: /tmp/bundle-my-project-test-cf-http-one.zip source: /tmp/bundle-my-project-test-cf-http-one.zip
module.cf-http-two.google_storage_bucket_object.bundle: module.cf-http-two.google_storage_bucket_object.bundle[0]:
source: /tmp/bundle-my-project-test-cf-http-two.zip source: /tmp/bundle-my-project-test-cf-http-two.zip
counts: counts:

View File

@ -22,12 +22,12 @@ values:
role: roles/run.invoker role: roles/run.invoker
service: test-cf-http service: test-cf-http
module.cf-http.google_cloudfunctions2_function.function: {} module.cf-http.google_cloudfunctions2_function.function: {}
module.cf-http.google_storage_bucket_object.bundle: module.cf-http.google_storage_bucket_object.bundle[0]:
bucket: test-cf-bundles bucket: test-cf-bundles
customer_encryption: [] customer_encryption: []
detect_md5hash: different hash detect_md5hash: different hash
name: bundle-6f1ece136848fee658e335b05fe2d79d.zip name: bundle-6f1ece136848fee658e335b05fe2d79d.zip
source: bundle.zip source: /tmp/bundle-my-project-test-cf-http.zip
counts: counts:
google_cloud_run_service_iam_binding: 1 google_cloud_run_service_iam_binding: 1

View File

@ -13,9 +13,9 @@
# limitations under the License. # limitations under the License.
values: values:
module.cf-http-one.google_storage_bucket_object.bundle: module.cf-http-one.google_storage_bucket_object.bundle[0]:
source: /tmp/bundle-my-project-test-cf-http-one.zip source: /tmp/bundle-my-project-test-cf-http-one.zip
module.cf-http-two.google_storage_bucket_object.bundle: module.cf-http-two.google_storage_bucket_object.bundle[0]:
source: /tmp/bundle-my-project-test-cf-http-two.zip source: /tmp/bundle-my-project-test-cf-http-two.zip
counts: counts: