qwiklabs/terraform-modules/colab-deployment/terraform-module/tf-deploy-notebooks.tf (246 lines of code) (raw):

#################################################################################### # Copyright 2024 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # 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. #################################################################################### terraform { required_providers { google = { source = "hashicorp/google-beta" version = "5.35.0" } } } #################################################################################### # Variables #################################################################################### variable "project_id" {} variable "vertex_ai_region" {} variable "bigquery_data_beans_curated_dataset" {} variable "data_beans_curated_bucket" {} variable "data_beans_code_bucket" {} variable "dataform_region" {} variable "cloud_function_region" {} variable "workflow_region" {} variable "random_extension" {} variable "gcp_account_name" {} data "google_client_config" "current" { } # Define the list of notebook files to be created locals { notebook_names = [ for s in fileset("./colab-enterprise/gen-ai-demo/", "*.ipynb") : trimsuffix(s, ".ipynb") ] } # Create the notebook files to be uploaded resource "local_file" "notebooks" { count = length(local.notebook_names) filename = "./terraform-modules/colab-deployment/cloud-function/notebooks/${local.notebook_names[count.index]}.ipynb" content = templatefile("./colab-enterprise/gen-ai-demo/${local.notebook_names[count.index]}.ipynb", { project_id = var.project_id vertex_ai_region = var.vertex_ai_region bigquery_data_beans_curated_dataset = var.bigquery_data_beans_curated_dataset data_beans_curated_bucket = var.data_beans_curated_bucket data_beans_code_bucket = var.data_beans_code_bucket } ) } # Upload the Cloud Function source code to a GCS bucket ## Define/create zip file for the Cloud Function source. This includes notebooks that will be uploaded data "archive_file" "create_notebook_function_zip" { type = "zip" output_path = "./tmp/notebooks_function_source.zip" source_dir = "./terraform-modules/colab-deployment/cloud-function/" depends_on = [local_file.notebooks] } ## Upload the zip file of the source code to GCS resource "google_storage_bucket_object" "function_source_upload" { name = "cloud-functions/notebooks_function_source.zip" bucket = var.data_beans_code_bucket source = data.archive_file.create_notebook_function_zip.output_path } # Manage Cloud Function permissions and access # Create a service account to manage the function resource "google_service_account" "cloud_function_manage_sa" { project = var.project_id account_id = "notebook-deployment" display_name = "Cloud Functions Service Account" description = "Service account used to manage Cloud Function" } ## Define the IAM roles that are granted to the Cloud Function service account locals { cloud_function_roles = [ "roles/aiplatform.user", // Needs to predict from endpoints "roles/aiplatform.serviceAgent", // Service account role "roles/cloudfunctions.admin", // Service account role to manage access to the remote function "roles/dataform.codeEditor", // Edit access code resources "roles/iam.serviceAccountUser", "roles/iam.serviceAccountTokenCreator", "roles/run.invoker", // Service account role to invoke the remote function "roles/storage.objectViewer" // Read GCS files ] } ## Assign required permissions to the function service account resource "google_project_iam_member" "function_manage_roles" { project = var.project_id count = length(local.cloud_function_roles) role = local.cloud_function_roles[count.index] member = "serviceAccount:${google_service_account.cloud_function_manage_sa.email}" depends_on = [google_service_account.cloud_function_manage_sa] } ## Grant the Cloud Workflows service account access to act as the Cloud Function service account resource "google_service_account_iam_member" "workflow_auth_function" { service_account_id = google_service_account.cloud_function_manage_sa.name role = "roles/iam.serviceAccountUser" member = "serviceAccount:${google_service_account.workflow_manage_sa.email}" depends_on = [ google_service_account.workflow_manage_sa, ] } # Setup Dataform repositories to host notebooks ## Create the Dataform repos resource "google_dataform_repository" "notebook_repo" { count = length(local.notebook_names) provider = google-beta project = var.project_id region = var.dataform_region name = local.notebook_names[count.index] display_name = local.notebook_names[count.index] labels = { "single-file-asset-type" = "notebook" } } ## Grant Cloud Function service account access to write to the repo resource "google_dataform_repository_iam_member" "function_manage_repo" { provider = google-beta project = var.project_id region = var.dataform_region role = "roles/dataform.admin" member = "serviceAccount:${google_service_account.cloud_function_manage_sa.email}" count = length(local.notebook_names) repository = local.notebook_names[count.index] depends_on = [ google_service_account.cloud_function_manage_sa, google_dataform_repository.notebook_repo ] } ## Grant Cloud Workflows service account access to write to the repo resource "google_dataform_repository_iam_member" "workflow_manage_repo" { provider = google-beta project = var.project_id region = var.dataform_region role = "roles/dataform.admin" member = "serviceAccount:${google_service_account.workflow_manage_sa.email}" count = length(local.notebook_names) repository = local.notebook_names[count.index] depends_on = [ google_service_account.workflow_manage_sa, google_dataform_repository.notebook_repo ] } # Create and deploy a Cloud Function to deploy notebooks ## Create the Cloud Function resource "google_cloudfunctions2_function" "notebook_deploy_function" { name = "deploy-notebooks" project = var.project_id location = var.cloud_function_region description = "A Cloud Function that deploys sample notebooks." build_config { runtime = "python310" entry_point = "run_it" docker_repository = "projects/${var.project_id}/locations/${var.cloud_function_region}/repositories/gcf-artifacts" source { storage_source { bucket = var.data_beans_code_bucket object = google_storage_bucket_object.function_source_upload.name } } } service_config { max_instance_count = 1 # min_instance_count can be set to 1 to improve performance and responsiveness min_instance_count = 0 available_memory = "512Mi" timeout_seconds = 300 max_instance_request_concurrency = 1 available_cpu = "2" ingress_settings = "ALLOW_ALL" all_traffic_on_latest_revision = true service_account_email = google_service_account.cloud_function_manage_sa.email environment_variables = { "PROJECT_ID" : var.project_id, "DATAFORM_REGION" : var.dataform_region, "GCP_ACCOUNT_NAME" : var.gcp_account_name, } } depends_on = [ google_project_iam_member.function_manage_roles, google_dataform_repository.notebook_repo, google_dataform_repository_iam_member.workflow_manage_repo, google_dataform_repository_iam_member.function_manage_repo ] } ## Wait for Function deployment to complete resource "time_sleep" "wait_after_function" { create_duration = "15s" depends_on = [google_cloudfunctions2_function.notebook_deploy_function] } # Activate the Google Service account resource "google_project_service_identity" "service_identity_workflows" { project = var.project_id service = "workflows.googleapis.com" depends_on = [ ] } # Set up the Workflow ## Create the Workflows service account resource "google_service_account" "workflow_manage_sa" { project = var.project_id account_id = "cloud-workflow-sa-${var.random_extension}" display_name = "Service Account for Cloud Workflows" description = "Service account used to manage Cloud Workflows" depends_on = [ google_project_service_identity.service_identity_workflows ] } ## Define the IAM roles granted to the Workflows service account locals { workflow_roles = [ "roles/bigquery.connectionUser", "roles/bigquery.dataEditor", "roles/bigquery.jobUser", "roles/iam.serviceAccountTokenCreator", "roles/iam.serviceAccountUser", "roles/run.invoker", "roles/storage.objectAdmin", "roles/workflows.admin", ] } ## Grant the Workflow service account access resource "google_project_iam_member" "workflow_manage_sa_roles" { count = length(local.workflow_roles) project = var.project_id member = "serviceAccount:${google_service_account.workflow_manage_sa.email}" role = local.workflow_roles[count.index] depends_on = [google_service_account.workflow_manage_sa] } # This is/was an issue with Qwiklabs resource "google_cloudfunctions2_function_iam_member" "invoker" { project = var.project_id location = var.cloud_function_region cloud_function = google_cloudfunctions2_function.notebook_deploy_function.name role = "roles/cloudfunctions.invoker" member = "serviceAccount:${google_service_account.workflow_manage_sa.email}" depends_on = [google_project_iam_member.workflow_manage_sa_roles] } ## Wait for Function deployment to complete resource "time_sleep" "wait_after_permissions" { create_duration = "15s" depends_on = [google_cloudfunctions2_function_iam_member.invoker] } ## Create the workflow resource "google_workflows_workflow" "workflow" { name = "initial-workflow" project = var.project_id region = var.workflow_region description = "Runs post Terraform setup steps for Solution in Console" service_account = google_service_account.workflow_manage_sa.id source_contents = templatefile("./terraform-modules/colab-deployment/workflow/workflow.yaml", { function_url = google_cloudfunctions2_function.notebook_deploy_function.url function_name = google_cloudfunctions2_function.notebook_deploy_function.name }) depends_on = [ time_sleep.wait_after_permissions, time_sleep.wait_after_function ] } # Wait for workflow to initialize resource "time_sleep" "wait_after_workflow" { create_duration = "15s" depends_on = [ google_workflows_workflow.workflow ] } /* data "google_client_config" "current" { } ## Trigger the execution of the setup workflow with an API call data "http" "call_workflows_setup" { url = "https://workflowexecutions.googleapis.com/v1/projects/${var.project_id}/locations/${var.workflow_region}/workflows/${google_workflows_workflow.workflow.name}/executions" method = "POST" request_headers = { Accept = "application/json" Authorization = "Bearer ${data.google_client_config.current.access_token}" } depends_on = [ time_sleep.wait_after_workflow ] } */ resource "null_resource" "execute_workflow" { provisioner "local-exec" { when = create command = <<EOF curl -X POST \ https://workflowexecutions.googleapis.com/v1/projects/${var.project_id}/locations/${var.workflow_region}/workflows/${google_workflows_workflow.workflow.name}/executions \ --header "Authorization: Bearer ${data.google_client_config.current.access_token}" \ --header "Content-Type: application/json" EOF } depends_on = [ time_sleep.wait_after_workflow ] }