diff --git a/gcp/cloud-function-gen2/README.md b/gcp/cloud-function-gen2/README.md new file mode 100644 index 0000000..0f4c5e8 --- /dev/null +++ b/gcp/cloud-function-gen2/README.md @@ -0,0 +1,57 @@ +# Terraform Google Cloud Function gen2 + +## Overview + +This updated Terraform module provides a comprehensive set of reusable configurations for deploying services to Google Cloud Function 2nd generation, including its deployment, scheduling, IAM permissions, Cloud Build triggers, and alerting. + +## Branching Strategy + +[Details about the branching strategy](https://chat.openai.com/cloud-cloudbuild-trigger/README.md) + +## Features + +### Internal modules + +- **google_storage_bucket_object Resource:** This resource defines a Cloud Storage bucket object. It specifies the name, bucket, and source of the object. + +- **google_cloudfunctions2_function Resource:** This resource defines a Google Cloud Functions 2.0 function. It includes configurations for function behavior, environment variables, event triggers, and build settings. Conditional blocks are used to handle different event types and configurations. + +- **google_cloud_scheduler_job Resource:** This resource defines a Google Cloud Scheduler job. It schedules the execution of a Cloud Function using HTTP requests, specifying details like schedule, time zone, and retry settings. + +- **google_cloudfunctions_function_iam_member Resource:** This resource grants IAM permissions to invoke the Cloud Function. It is conditional based on the `var.public` flag and grants the `cloudfunctions.invoker` role to `allUsers` if `var.public` is true. + +- **module "trigger_provision":** This module configures a Cloud Build trigger for provisioning the Cloud Function. It includes settings for the trigger's name, description, source, and environment variables. + +- **module "cloud_function_alerts":** This module sets up alerts and notifications for the Cloud Function. It includes configurations for alert thresholds, duration, and notification channels. + +## Specific Variables + +- `var.function_name`: This variable holds the name of the Google Cloud Function. +- `var.function_path`: It defines the path to the function's source code within the repository. +- `var.bucket_functions`: This variable specifies the name of the Google Cloud Storage bucket where function source code is stored. +- `var.function_source_archive_object`: It represents the name of the Cloud Storage object containing the function's source code archive. +- `var.max_instance_count`: This variable defines the maximum number of instances for the Google Cloud Function. +- `var.min_instance_count`: It sets the minimum number of instances for the Google Cloud Function. +- `var.available_memory_mb`: This variable specifies the amount of memory allocated to each function instance. +- `var.timeout_seconds`: It determines the maximum execution time in seconds for the function. +- `var.language_config`: This variable is a map that defines language-specific configuration for the function, including source code archives, entry points, and runtimes. +- `var.function_type`: It specifies the programming language used for the function (e.g., "node," "go"). +- `var.event_type`: This variable defines the type of event trigger for the function (e.g., "PUBSUB," "STORAGE," "SCHEDULER"). +- `var.schedule`: This variable holds scheduling information for the function, including cron schedules and time zones. +- `var.public`: It determines whether the Cloud Function is publicly accessible, and if true, grants permissions to "allUsers" to invoke the function. + +## Usage + +Refer to the example Terraform script in the example folder for a demonstration on how to use this updated module. + +Example of use: + +[test/gcp/cloud-function-gen2.tf](https://chat.openai.com/test/gcp/cloud-function-gen2.tf) + +## Contribution + +Contributions are welcome! Please open an issue or submit a pull request. + +## License + +This module is released under the MIT License. Check the LICENSE file for more details.--- diff --git a/gcp/cloud-function-gen2/main.tf b/gcp/cloud-function-gen2/main.tf new file mode 100644 index 0000000..ad57bec --- /dev/null +++ b/gcp/cloud-function-gen2/main.tf @@ -0,0 +1,183 @@ +data "google_project" "current" { + project_id = var.project_id +} + +locals { + default_substitution_vars = { + _STAGE = "provision", + _BUILD_ENV = var.environment, + _FUNCTION_NAME = var.function_name, + _FUNCTION_PATH = var.function_path == "" ? "services/${var.service_name}/functions/${var.function_name}" : var.function_path + _LOCATION = var.region + } + default_environment_variables = {} + service_account = var.service_account_email != "" ? var.service_account_email : "${data.google_project.current.project_id}@appspot.gserviceaccount.com" + + // Default values for each cloud function language. + // These are chosen by the 'function_type' variable. + language_config = { + node = { + source_archive_object_name = "node-default.zip" + source_archive_object_source = "../../utils/default-node-function/default.zip" + default_entry_point = "helloWorld" + default_runtime = "nodejs16" + } + go = { + source_archive_object_name = "go-default.zip" + source_archive_object_source = "../../utils/default-go-function/default.zip" + default_entry_point = "Entrypoint" + default_runtime = "go116" + } + }[var.function_type] +} + +/****************************************** + Cloud Storage Object + *****************************************/ + +resource "google_storage_bucket_object" "cloud_functions_bucket_archive" { + name = var.function_source_archive_object != "" ? var.function_source_archive_object : local.language_config.source_archive_object_name + bucket = var.bucket_functions + source = local.language_config.source_archive_object_source +} + +/****************************************** + Default Function + Will be replaced by the one deployed via cloudbuild.yaml (ignore_changes = all) + *****************************************/ + +resource "google_cloudfunctions2_function" "function" { + name = var.function_name + description = var.function_description + location = var.region + + service_config { + max_instance_count = var.max_instance_count + min_instance_count = var.min_instance_count + available_memory = var.available_memory_mb + timeout_seconds = var.timeout_seconds + service_account_email = local.service_account + environment_variables = merge(local.default_environment_variables, var.environment_variables) + dynamic "secret_environment_variables" { + for_each = var.secret_keys + content { + key = secret_environment_variables.value + project_id = var.project_id + + secret = secret_environment_variables.value + version = "latest" + } + } + } + + dynamic "event_trigger" { + for_each = var.event_type == "PUBSUB" ? [var.event_trigger] : [] + content { + trigger_region = var.region + event_type = event_trigger.value["event_type"] + pubsub_topic = event_trigger.value["pubsub_topic"] + retry_policy = event_trigger.value["retry_policy"] + service_account_email = event_trigger.value["service_account_email"] + } + } + + dynamic "event_trigger" { + for_each = var.event_type == "STORAGE" ? [var.event_trigger] : [] + content { + trigger_region = var.region + event_type = event_trigger.value["event_type"] + retry_policy = event_trigger.value["retry_policy"] + service_account_email = event_trigger.value["service_account_email"] + event_filters { + attribute = "bucket" + value = event_trigger.value["bucket_name"] + } + } + } + + build_config { + runtime = var.function_runtime != "" ? var.function_runtime : local.language_config.default_runtime + entry_point = var.function_entry_point != "" ? var.function_entry_point : local.language_config.default_entry_point + source { + storage_source { + bucket = var.bucket_functions + object = google_storage_bucket_object.cloud_functions_bucket_archive.name + } + } + } + + lifecycle { + ignore_changes = [build_config] + } +} + + +resource "google_cloud_scheduler_job" "job" { + count = var.event_type == "SCHEDULER" ? 1 : 0 + name = var.function_name + paused = var.schedule.paused + description = "Schedule ${var.function_name}" + schedule = var.schedule.cron + time_zone = var.schedule.timezone + attempt_deadline = var.schedule.attempt_deadline + + http_target { + http_method = var.schedule.http_method + uri = google_cloudfunctions2_function.function.service_config[0].uri + + oidc_token { + service_account_email = local.service_account + } + body = var.schedule.http_method == "POST" || var.schedule.http_method == "PUT" ? base64encode(var.schedule.http_body) : null + headers = var.schedule.http_headers + } + retry_config { + retry_count = 1 + } + lifecycle { + ignore_changes = [paused, http_target["body"], schedule] + } +} + + +# IAM entry for all users to invoke the function +resource "google_cloudfunctions_function_iam_member" "invoker" { + count = var.public ? 1 : 0 + project = google_cloudfunctions2_function.function.project + cloud_function = google_cloudfunctions2_function.function.name + + role = "roles/cloudfunctions.invoker" + member = "allUsers" +} + + +/****************************************** + Triggers + *****************************************/ +module "trigger_provision" { + name = "function-${var.function_name}-provision" + description = "Provision ${var.function_name} Service (CI/CD)" + source = "../cloud-cloudbuild-trigger" + filename = var.function_path == "" ? "services/${var.service_name}/functions/${var.function_name}/cloudbuild.yaml" : "${var.function_path}/cloudbuild.yaml" + include = var.function_path == "" ? ["services/${var.service_name}/functions/${var.function_name}/**"] : ["${var.function_path}/**"] + tags = ["function"] + substitutions = merge(local.default_substitution_vars, var.trigger_substitutions) + environment = var.environment + repository_name = var.repository_name +} + +/****************************************** + Alerts definition + *****************************************/ + +module "cloud_function_alerts" { + source = "../cloud-alerts" + project_id = var.project_id + service_name = var.function_name + enabled = var.alert_config.enabled + threshold_value = var.alert_config.threshold_value + duration = var.alert_config.duration + alignment_period = var.alert_config.alignment_period + auto_close = var.alert_config.auto_close + notification_channels = var.alert_config.notification_channels +} diff --git a/gcp/cloud-function-gen2/outputs.tf b/gcp/cloud-function-gen2/outputs.tf new file mode 100644 index 0000000..c84c4bd --- /dev/null +++ b/gcp/cloud-function-gen2/outputs.tf @@ -0,0 +1,3 @@ +output "https_trigger_url" { + value = google_cloudfunctions2_function.function.service_config[0].uri +} diff --git a/gcp/cloud-function-gen2/variables.tf b/gcp/cloud-function-gen2/variables.tf new file mode 100644 index 0000000..f79aa40 --- /dev/null +++ b/gcp/cloud-function-gen2/variables.tf @@ -0,0 +1,171 @@ +variable "bucket_functions" {} +variable "function_name" {} +variable "function_description" {} + +// Overrides the default path of services/{service name}/functions/{function name} +variable "function_path" { + description = "Path to the function, if not provided, it will be generated based on the function name" + default = "" +} +variable "service_name" { + description = "Name of the service wrapping this function (you must have functions folder in it)" +} + +variable "branching_strategy" {} + +variable "trigger_substitutions" { + description = "Substitution variable for the trigger, think about Buckets names, pubsub names, service accounts, etc. Anything dynamic you will need to deploy this function (via Yaml file)" + +} +variable "environment_variables" { + description = "Environment variables that shall be available during function execution." + default = {} +} + +variable "region" { + default = "europe-west2" +} + +variable "environment" {} + +variable "public" { + type = bool + default = false +} + +variable "timeout_seconds" { + type = number + default = 60 +} + +variable "max_instance_count" { + type = number + default = 1 +} + +variable "min_instance_count" { + type = number + default = 1 +} + +variable "service_account_email" { + default = "" +} + +variable "available_memory_mb" { + type = string + default = "256M" +} + +variable "notification_channels" {} + +variable "function_runtime" { + default = "" +} + +variable "function_entry_point" { + default = "" +} + +variable "function_source_archive_object" { + default = "" +} + +variable "function_type" {} + +variable "threshold_value" { + default = 60 +} +variable "event_trigger" { + description = "event trigger, per resource docs" + default = null +} + +variable "event_type" { + default = null +} + +variable "schedule" { + type = object({ + cron = string + timezone = string + attempt_deadline = string + paused = bool + http_method = string + http_body = any + http_headers = map(string) + }) + default = { + cron = "0 0 5 31 2 ?" + timezone = "Europe/London" + attempt_deadline = "320s" + paused = true + http_method = "GET" + http_body = null + http_headers = {} + } +} + +variable "secret_keys" { + default = [] +} + +variable "http_headers" { + type = map(string) + default = {} +} + +variable "repository_name" { + description = "Repo name where the service is located (in GitHub)" + type = string +} + +variable "project_id" { + description = "The ID of the project in which the resource belongs." + type = string +} + +variable "trigger_config" { + description = "Configuration for the Cloud Build Trigger" + type = object({ + name = string + repository_name = string + description = string + filename = string + include = list(string) + exclude = list(string) + environment = string + substitutions = map(string) + create_trigger = bool + }) + default = { + name = "default-trigger-name" + repository_name = "default-repo-name" + description = "default-description" + filename = "cloudbuild.yaml" + include = [] + exclude = [] + environment = null + substitutions = {} + create_trigger = true + } +} +variable "alert_config" { + description = "Configuration for alerts" + type = object({ + enabled = bool + threshold_value = number + duration = number + alignment_period = number + auto_close = number + notification_channels = list(string) + }) + default = { + enabled = true + threshold_value = 10.0 + duration = 300 + alignment_period = 60 + auto_close = 86400 + notification_channels = [] + } +} diff --git a/test/gcp/cloud-function-gen2.tf b/test/gcp/cloud-function-gen2.tf new file mode 100644 index 0000000..0448f12 --- /dev/null +++ b/test/gcp/cloud-function-gen2.tf @@ -0,0 +1,33 @@ +module "cloud-function-my-awesome-cf" { + source = "../../gcp/cloud-function-gen2" + function_name = "my-awesome-cf" + function_type = "node" + function_description = "My awesome cloud function" + environment = "preview" + region = "europe-west2" + notification_channels = [] + service_name = "my-awesome-cf" + bucket_functions = "test-bucket-functions" + service_account_email = "test-service-account-email" + function_path = "services/my-awesome-cf/functions/my-awesome-cf" + max_instance_count = 3 + min_instance_count = 1 + branching_strategy = "master" + repository_name = "my-awesome-cf" + project_id = "my-awesome-project" + timeout_seconds = 60 + trigger_substitutions = { + _RUNTIME = "nodejs16" + _ENTRYPOINT = "helloWorld" + _FUNCTION_SA = "test-service-account-email" + _FUNCTION_PATH = "services/my-awesome-cf/functions/my-awesome-cf" + _FUNCTION_TYPE_TRIGGER = "trigger-http" + + } + environment_variables = { + LOCATION = "europe-west2" + } + secret_keys = [] + threshold_value = 0 + function_entry_point = "helloWorld" +} diff --git a/test/gcp/cloud-run-v2.tf b/test/gcp/cloud-run-v2.tf index 5dfadd8..427ef64 100644 --- a/test/gcp/cloud-run-v2.tf +++ b/test/gcp/cloud-run-v2.tf @@ -1,6 +1,6 @@ module "cloud-run-api-my-awesome-api" { source = "../../gcp/cloud-run-v2" - project_id = "test-project-id" + project_id = "my-awesome-project" name = "my-awesome-api" project_region = "europe-west2" allow_public_access = true @@ -10,7 +10,7 @@ module "cloud-run-api-my-awesome-api" { service_path = "/services/my-awesome-api" dependencies = ["services/shared/**", "services/types/**"] - startup_probe_initial_delay = 5 + startup_probe_initial_delay = 5 liveness_probe_initial_delay = 10 env_vars = {