From a12da693a3892b1ea88615bd47582f30fbe7e7df Mon Sep 17 00:00:00 2001 From: Somnath Shukla Date: Thu, 13 Oct 2022 11:39:00 +0530 Subject: [PATCH] added support 2nd generation cloud function (#872) * added support 2nd generation cloud function * changed function_version to a simple boolean v2 removed memory_2ndGen * will use the var.v2 to add the invoker role * removed the list uisng compact and formated the code * formated the code and added conditional feature * formated the code * added formating * resolved the merge conflict * Update readme * Create local function object * added secret_volumes and secret_environment_variables for CF V2 Co-authored-by: Julio Castillo --- .../network-dashboard/cloud-function/main.py | 50 +++++++------ .../cloud-function/requirements.txt | 3 +- .../network-dashboard/main.tf | 34 +++++++-- .../network-dashboard/variables.tf | 5 ++ modules/cloud-function/README.md | 12 ++-- modules/cloud-function/main.tf | 71 ++++++++++++++++++- modules/cloud-function/outputs.tf | 9 ++- modules/cloud-function/variables.tf | 8 ++- 8 files changed, 157 insertions(+), 35 deletions(-) diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/main.py b/blueprints/cloud-operations/network-dashboard/cloud-function/main.py index 7b36c79ab..ae380c209 100644 --- a/blueprints/cloud-operations/network-dashboard/cloud-function/main.py +++ b/blueprints/cloud-operations/network-dashboard/cloud-function/main.py @@ -12,7 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -# +# CFv2 define whether to use Cloud function 2nd generation or 1st generation from distutils.command.config import config import os @@ -22,16 +22,20 @@ from google.protobuf import field_mask_pb2 from googleapiclient import discovery from metrics import ilb_fwrules, instances, networks, metrics, limits, peerings, routes, subnets, vpc_firewalls +CFv2 = False +if CFv2: + import functions_framework + def get_monitored_projects_list(config): ''' - Gets the projects to be monitored from the MONITORED_FOLDERS_LIST environment variable. + Gets the projects to be monitored from the MONITORED_FOLDERS_LIST environment variable. - Parameters: - config (dict): The dict containing config like clients and limits - Returns: - monitored_projects (List of strings): Full list of projects to be monitored - ''' + Parameters: + config (dict): The dict containing config like clients and limits + Returns: + monitored_projects (List of strings): Full list of projects to be monitored + ''' monitored_projects = config["monitored_projects"] monitored_folders = [] #os.environ.get("MONITORED_FOLDERS_LIST").split(",") @@ -68,10 +72,10 @@ def get_monitored_projects_list(config): def monitoring_interval(): ''' - Creates the monitoring interval of 24 hours - Returns: - monitoring_v3.TimeInterval: Monitoring time interval of 24h - ''' + Creates the monitoring interval of 24 hours + Returns: + monitoring_v3.TimeInterval: Monitoring time interval of 24h + ''' now = time.time() seconds = int(now) nanos = int((now - seconds) * 10**9) @@ -124,13 +128,13 @@ config = { def main(event, context): ''' - Cloud Function Entry point, called by the scheduler. - Parameters: - event: Not used for now (Pubsub trigger) - context: Not used for now (Pubsub trigger) - Returns: - 'Function executed successfully' - ''' + Cloud Function Entry point, called by the scheduler. + Parameters: + event: Not used for now (Pubsub trigger) + context: Not used for now (Pubsub trigger) + Returns: + 'Function executed successfully' + ''' # Handling empty monitored projects list if config["monitored_projects"] == ['']: config["monitored_projects"] = [] @@ -203,5 +207,11 @@ def main(event, context): return 'Function executed successfully' -if __name__ == "__main__": - main(None, None) +if CFv2: + + @functions_framework.http + def main_http(request): + main(None, None) +else: + if __name__ == "__main__": + main(None, None) \ No newline at end of file diff --git a/blueprints/cloud-operations/network-dashboard/cloud-function/requirements.txt b/blueprints/cloud-operations/network-dashboard/cloud-function/requirements.txt index 8a6a5960b..d56134822 100644 --- a/blueprints/cloud-operations/network-dashboard/cloud-function/requirements.txt +++ b/blueprints/cloud-operations/network-dashboard/cloud-function/requirements.txt @@ -7,4 +7,5 @@ google-cloud-monitoring==2.9.1 oauth2client==4.1.3 google-api-core==2.7.0 PyYAML==6.0 -google-cloud-asset==3.8.1 \ No newline at end of file +google-cloud-asset==3.8.1 +functions-framework==3.* \ No newline at end of file diff --git a/blueprints/cloud-operations/network-dashboard/main.tf b/blueprints/cloud-operations/network-dashboard/main.tf index a381d21d5..932adeaea 100644 --- a/blueprints/cloud-operations/network-dashboard/main.tf +++ b/blueprints/cloud-operations/network-dashboard/main.tf @@ -50,6 +50,8 @@ module "service-account-function" { # Required IAM permissions for this service account are: # 1) compute.networkViewer on projects to be monitored (I gave it at organization level for now for simplicity) # 2) monitoring viewer on the projects to be monitored (I gave it at organization level for now for simplicity) + # 3) if you dont have permission to create service account and assign permission at organization Level, move these 3 roles to project level. + iam_organization_roles = { "${var.organization_id}" = [ "roles/compute.networkViewer", @@ -59,17 +61,20 @@ module "service-account-function" { } iam_project_roles = { - "${local.monitoring_project}" = [ - "roles/monitoring.metricWriter" - ] + "${local.monitoring_project}" = compact([ + "roles/monitoring.metricWriter", + var.v2 ? "roles/run.invoker" : "" + ]) } } ################################################ # Cloud Function configuration (& Scheduler) # +# you can comment out the pub/sub call in case of 2nd generation function ################################################ module "pubsub" { + source = "../../../modules/pubsub" project_id = local.monitoring_project name = "network-dashboard-pubsub" @@ -81,6 +86,7 @@ module "pubsub" { } resource "google_cloud_scheduler_job" "job" { + count = var.v2 ? 0 : 1 project = local.monitoring_project region = var.region name = "network-dashboard-scheduler" @@ -92,9 +98,28 @@ resource "google_cloud_scheduler_job" "job" { data = base64encode("test") } } +#http trigger for 2nd generation function +resource "google_cloud_scheduler_job" "job_httptrigger" { + count = var.v2 ? 1 : 0 + project = local.monitoring_project + region = var.region + name = "network-dashboard-scheduler" + schedule = var.schedule_cron + time_zone = "UTC" + + http_target { + http_method = "POST" + uri = module.cloud-function.uri + + oidc_token { + service_account_email = module.service-account-function.email + } + } +} module "cloud-function" { + v2 = var.v2 source = "../../../modules/cloud-function" project_id = local.monitoring_project name = "network-dashboard-cloud-function" @@ -115,7 +140,8 @@ module "cloud-function" { entry_point = "main" runtime = "python39" instances = 1 - memory = 256 + memory = 256 # Memory in MB + } environment_variables = { diff --git a/blueprints/cloud-operations/network-dashboard/variables.tf b/blueprints/cloud-operations/network-dashboard/variables.tf index 63a61ee99..dbb2921f4 100644 --- a/blueprints/cloud-operations/network-dashboard/variables.tf +++ b/blueprints/cloud-operations/network-dashboard/variables.tf @@ -69,4 +69,9 @@ variable "project_monitoring_services" { variable "region" { description = "Region used to deploy the cloud functions and scheduler" default = "europe-west1" +} +variable "v2" { + description = "Whether to use Cloud Function version 2nd Gen or 1st Gen." + type = bool + default = false } \ No newline at end of file diff --git a/modules/cloud-function/README.md b/modules/cloud-function/README.md index ffad5d4cc..b51285b3c 100644 --- a/modules/cloud-function/README.md +++ b/modules/cloud-function/README.md @@ -167,7 +167,7 @@ module "cf-http" { | [bucket_config](variables.tf#L17) | Enable and configure auto-created bucket. Set fields to null to use defaults. | object({…}) | | null | | [description](variables.tf#L40) | Optional description. | string | | "Terraform managed." | | [environment_variables](variables.tf#L46) | Cloud function environment variables. | map(string) | | {} | -| [function_config](variables.tf#L52) | Cloud function configuration. | object({…}) | | {…} | +| [function_config](variables.tf#L52) | Cloud function configuration. | object({…}) | | {…} | | [iam](variables.tf#L70) | IAM bindings for topic in {ROLE => [MEMBERS]} format. | map(list(string)) | | {} | | [ingress_settings](variables.tf#L76) | Control traffic that reaches the cloud function. Allowed values are ALLOW_ALL, ALLOW_INTERNAL_AND_GCLB and ALLOW_INTERNAL_ONLY . | string | | null | | [labels](variables.tf#L82) | Resource labels. | map(string) | | {} | @@ -177,6 +177,7 @@ module "cf-http" { | [service_account](variables.tf#L122) | Service account email. Unused if service account is auto-created. | string | | null | | [service_account_create](variables.tf#L128) | Auto-create service account. | bool | | false | | [trigger_config](variables.tf#L134) | Function trigger configuration. Leave null for HTTP trigger. | object({…}) | | null | +| [v2](variables.tf#L163) | Whether to use Cloud Function version 2nd Gen or 1st Gen. | bool | | false | | [vpc_connector](variables.tf#L144) | VPC connector configuration. Set create to 'true' if a new connector needs to be created. | object({…}) | | null | | [vpc_connector_config](variables.tf#L154) | VPC connector network configuration. Must be provided if new VPC connector is being created. | object({…}) | | null | @@ -188,9 +189,10 @@ module "cf-http" { | [bucket_name](outputs.tf#L24) | Bucket name. | | | [function](outputs.tf#L29) | Cloud function resources. | | | [function_name](outputs.tf#L34) | Cloud function name. | | -| [service_account](outputs.tf#L39) | Service account resource. | | -| [service_account_email](outputs.tf#L44) | Service account email. | | -| [service_account_iam_email](outputs.tf#L49) | Service account email. | | -| [vpc_connector](outputs.tf#L57) | VPC connector resource if created. | | +| [service_account](outputs.tf#L42) | Service account resource. | | +| [service_account_email](outputs.tf#L47) | Service account email. | | +| [service_account_iam_email](outputs.tf#L52) | Service account email. | | +| [uri](outputs.tf#L38) | Cloud function service uri. | | +| [vpc_connector](outputs.tf#L60) | VPC connector resource if created. | | diff --git a/modules/cloud-function/main.tf b/modules/cloud-function/main.tf index 0a26c1205..1ad64f46e 100644 --- a/modules/cloud-function/main.tf +++ b/modules/cloud-function/main.tf @@ -24,6 +24,11 @@ locals { : null ) ) + function = ( + var.v2 + ? google_cloudfunctions2_function.function[0] + : google_cloudfunctions_function.function[0] + ) prefix = var.prefix == null ? "" : "${var.prefix}-" service_account_email = ( var.service_account_create @@ -55,6 +60,7 @@ resource "google_vpc_access_connector" "connector" { } resource "google_cloudfunctions_function" "function" { + count = var.v2 ? 0 : 1 project = var.project_id region = var.region name = "${local.prefix}${var.name}" @@ -122,11 +128,74 @@ resource "google_cloudfunctions_function" "function" { } +resource "google_cloudfunctions2_function" "function" { + count = var.v2 ? 1 : 0 + provider = google-beta + project = var.project_id + location = var.region + name = "${local.prefix}${var.name}" + description = var.description + build_config { + runtime = var.function_config.runtime + entry_point = "${var.function_config.entry_point}_http" # Set the entry point + environment_variables = var.environment_variables + source { + storage_source { + bucket = google_storage_bucket.bucket[0].name + object = google_storage_bucket_object.bundle.name + } + } + } + service_config { + max_instance_count = var.function_config.instances + min_instance_count = 0 + available_memory = "${var.function_config.memory}M" + timeout_seconds = var.function_config.timeout + environment_variables = var.environment_variables + ingress_settings = var.ingress_settings + all_traffic_on_latest_revision = true + service_account_email = local.service_account_email + vpc_connector = local.vpc_connector + vpc_connector_egress_settings = try( + var.vpc_connector.egress_settings, null) + + dynamic "secret_environment_variables" { + for_each = { for k, v in var.secrets : k => v if !v.is_volume } + iterator = secret + content { + key = secret.key + project_id = secret.value.project_id + secret = secret.value.secret + version = try(secret.value.versions.0, "latest") + } + } + + dynamic "secret_volumes" { + for_each = { for k, v in var.secrets : k => v if v.is_volume } + iterator = secret + content { + mount_path = secret.key + project_id = secret.value.project_id + secret = secret.value.secret + dynamic "versions" { + for_each = secret.value.versions + iterator = version + content { + path = split(":", version)[1] + version = split(":", version)[0] + } + } + } + } + } + labels = var.labels +} + resource "google_cloudfunctions_function_iam_binding" "default" { for_each = var.iam project = var.project_id region = var.region - cloud_function = google_cloudfunctions_function.function.name + cloud_function = local.function.name role = each.key members = each.value } diff --git a/modules/cloud-function/outputs.tf b/modules/cloud-function/outputs.tf index 1071270e1..a4bbcebb4 100644 --- a/modules/cloud-function/outputs.tf +++ b/modules/cloud-function/outputs.tf @@ -28,14 +28,17 @@ output "bucket_name" { output "function" { description = "Cloud function resources." - value = google_cloudfunctions_function.function + value = local.function } output "function_name" { description = "Cloud function name." - value = google_cloudfunctions_function.function.name + value = local.function.name +} +output "uri" { + description = "Cloud function service uri." + value = var.v2 ? google_cloudfunctions2_function.function[0].service_config[0].uri : null } - output "service_account" { description = "Service account resource." value = try(google_service_account.service_account[0], null) diff --git a/modules/cloud-function/variables.tf b/modules/cloud-function/variables.tf index ce8633c8f..8458bff6a 100644 --- a/modules/cloud-function/variables.tf +++ b/modules/cloud-function/variables.tf @@ -54,7 +54,7 @@ variable "function_config" { type = object({ entry_point = string instances = number - memory = number + memory = number # Memory in MB runtime = string timeout = number }) @@ -160,3 +160,9 @@ variable "vpc_connector_config" { default = null } +variable "v2" { + description = "Whether to use Cloud Function version 2nd Gen or 1st Gen." + type = bool + default = false +} +