Overview
Cloud Functions can extend your applications and services by integrating with Google Cloud databases namely Firestore, Cloud Spanner, Cloud SQL, Cloud Bigtable and with Memorystore, Google Cloud's in-memory datastore cache service.
In this lab, you create Cloud Functions that integrate with Firestore, Google Cloud's serverless NoSQL document database. You'll use the Cloud Functions Framework and Firestore client library for Node.js to create functions, and set up triggers to execute them when events occur in the database.
A Firestore function's lifecycle typically involves these steps:
Wait for changes to a particular document in the Firestore database.
Trigger when an event occurs.
Perform tasks using a data object that is received with a snapshot of the affected document.
Objectives
In this lab, you will:
Set up a Firestore database.
Develop and deploy an event-driven function to log information when a document is created in Firestore.
Develop and deploy an event-driven function to update the document contents.
Access and use Secrets with Cloud Functions.
Use the Google Cloud console to view logs generated by your function.
Setup
Before you click the Start Lab button
Note: Read these instructions.
Labs are timed and you cannot pause them. The timer, which starts when you click Start Lab, shows how long Google Cloud resources will be made available to you.
This Qwiklabs hands-on lab lets you do the lab activities yourself in a real cloud environment, not in a simulation or demo environment. It does so by giving you new, temporary credentials that you use to sign in and access Google Cloud for the duration of the lab.
What you need
To complete this lab, you need:
Access to a standard internet browser (Chrome browser recommended).
Time to complete the lab.
Note: If you already have your own personal Google Cloud account or project, do not use it for this lab.
Note: If you are using a Pixelbook, open an Incognito window to run this lab.
Activate Google Cloud Shell
Google Cloud Shell is a virtual machine that is loaded with development tools. It offers a persistent 5GB home directory and runs on the Google Cloud.
Google Cloud Shell provides command-line access to your Google Cloud resources.
In Cloud console, on the top right toolbar, click the Open Cloud Shell button.
Click Continue.
It takes a few moments to provision and connect to the environment. When you are connected, you are already authenticated, and the project is set to your PROJECT_ID. For example:
gcloud is the command-line tool for Google Cloud. It comes pre-installed on Cloud Shell and supports tab-completion.
- You can list the active account name with this command:
gcloud auth list
Output:
Credentialed accounts:
- @.com (active)
Example output:
Credentialed accounts:
- google1623327_student@qwiklabs.net
- You can list the project ID with this command:
gcloud config list project
Output:
[core]
project =
Example output:
[core]
project = qwiklabs-gcp-44776a13dea667a6
Note: Full documentation of gcloud is available in the gcloud CLI overview guide .
Task 1. Set up the environment
In this task, you set up environment variables and enable relevant service APIs that are needed to perform this lab.
Set environment variables
Before you create Cloud Functions, you set some environment variables.
Sign in to the Google Cloud console with your lab credentials, and open the Cloud Shell terminal window.
Run the following command in Cloud Shell to set your Project ID and REGION environment variables.
PROJECT_ID=$(gcloud config get-value project) REGION=us-east1
Set an environment variable for the Project Number:
PROJECT_NUMBER=$(gcloud projects list \ --filter="project_id:$PROJECT_ID" \ --format='value(project_number)')
Set the default region for Cloud Functions:
gcloud config set functions/region us-east1
Enable APIs
To enable service APIs that are needed for this lab, run the following command:
gcloud services enable \ artifactregistry.googleapis.com \ cloudfunctions.googleapis.com \ cloudbuild.googleapis.com \ eventarc.googleapis.com \ run.googleapis.com \ logging.googleapis.com \ storage.googleapis.com \ pubsub.googleapis.com
Task 2. Set up Firestore
To perform the tasks in this lab, you need to set up a Firestore database. Firestore stores data in the form of documents and collections. To use Cloud Functions with Firestore, you must first set up Firestore before deploying the functions.
In the Cloud Console, on the Navigation menu (), click Firestore.
Click Create Database.
Click Native mode (recommended), and then click Continue.
In Location type, click Region, and then select the lab region
us-east1
from the list.Note: If the list of regions is not populated, refresh the browser or try the wizard again from the Cloud console menu.
For Secure rules, select Test rules.
Click Create Database.
Click Check my progress to verify the objective.
Set up Firestore.
Check my progress
Task 3. Develop an event-driven function for new Firestore documents
After your Firestore database is created, you can develop your function code. In this task, you write your function's source code that responds to the creation of new documents in the database. The function logs information about the data received in the function invocation.
Set up your working directory
Firestore cloud functions are invoked with a cloudevents
data structure that can be decoded using Protocol Buffers with the protobuf.js
NPM module. For more information see the links that are provided at the end of the lab.
Copy the required
.proto
and dependency files into a directory namedfirestore_functions
:gcloud storage cp -R gs://cloud-training/CBL493/firestore_functions .
Change to the
firestore_functions
directory:cd firestore_functions
The
firestore_functions
directory also contains emptynode.js
andpackage.json
files which you will update in the next subtask.
Write the function code
In the Cloud Shell toolbar, click Open Editor.
You can switch between Cloud Shell and the code editor using Open Editor and Open Terminal, or click Open in new window to leave the editor open in a separate tab.
In the editor navigation menu, click Explorer (), and then click Open Folder.
Select
firestore_functions
under the student home folder, and then click Ok.In the editor navigation menu, select the
index.js
file.In the editor, add the following code to the
firestore-functions/index.js
file:/** * Cloud Event Function triggered by a change to a Firestore document. */ const functions = require('@google-cloud/functions-framework'); const protobuf = require('protobufjs'); functions.cloudEvent('newCustomer', async cloudEvent => { console.log(`Function triggered by event on: ${cloudEvent.source}`); console.log(`Event type: ${cloudEvent.type}`); console.log('Loading protos...'); const root = await protobuf.load('data.proto'); const DocumentEventData = root.lookupType('google.events.cloud.firestore.v1.DocumentEventData'); console.log('Decoding data...'); const firestoreReceived = DocumentEventData.decode(cloudEvent.data); console.log('\nNew document:'); console.log(JSON.stringify(firestoreReceived.value, null, 2)); });
The code uses the functions framework Node.js library to create a function that processes data delivered using the cloudEvent specification.
In the editor, add the following to the
firestore-functions/package.json
file:{ "name": "firestore_functions", "version": "0.0.1", "main": "index.js", "dependencies": { "@google-cloud/functions-framework": "^3.1.3", "protobufjs": "^7.2.2", "@google-cloud/firestore": "^6.0.0" } }
Update service account permissions
Grant the Cloud Functions service agent certain permissions before deploying the function. Run the following commands in Cloud Shell.
Click Open Terminal.
Set an environment variable for the Cloud Functions service agent's service account:
SERVICE_ACCOUNT=service-$PROJECT_NUMBER@gcf-admin-robot.iam.gserviceaccount.com
To view and get artifacts from Artifact Registry, grant the
artifactregistry.reader
role to the Cloud Functions service account:gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$SERVICE_ACCOUNT --role roles/artifactregistry.reader
Note: If you receive an error from the previous step indicating that the service account does not exist, or that the request has invalid authentication credentials, perform the following steps:
Disable the Cloud Functions API:
gcloud services disable cloudfunctions.googleapis.com
Re-enable the Cloud Functions API:
gcloud services enable cloudfunctions.googleapis.com
Wait a few seconds, and then rerun the command to grant the
artifactregistry.reader
role to the Cloud Functions service account:gcloud projects add-iam-policy-binding $PROJECT_ID --member serviceAccount:$SERVICE_ACCOUNT --role roles/artifactregistry.reader
Deploy the function
To deploy the function, run the following command from Cloud Shell:
gcloud functions deploy newCustomer \ --gen2 \ --runtime=nodejs20 \ --region=us-east1 \ --trigger-location=us-east1 \ --source=. \ --entry-point=newCustomer \ --trigger-event-filters=type=google.cloud.firestore.document.v1.created \ --trigger-event-filters=database='(default)' \ --trigger-event-filters-path-pattern=document='customers/{name}'
Firestore supports the created, updated, deleted, and written events which you specify as the trigger-event-filter option to the deploy command. The path pattern states that all documents in the customers collection should be monitored.
After the command executes successfully, the command generates the URL for the function endpoint, as shown in this sample partial command output:
... state: ACTIVE updateTime: '2024-03-19T21:38:04.917134057Z' url: https://us-east1-us-east1.cloudfunctions.net/newCustomer
If you receive an error when deploying the function, wait a few minutes before retrying the deploy command. The service account permissions for Eventarc might take some time to propagate.
Test the function
Navigate to Firestore Studio in the Cloud console.
To create a new document collection, click Start collection.
For Collection ID, type customers
To generate an ID for a document in this collection, click into Document ID.
For this document, add a field with the following values:
| Field name | Field type | Field value | | --- | --- | --- | | firstname | string | Lucas |
Click Save.
To verify that your cloud function was invoked, on the Navigation menu (), under Serverless, click Cloud Functions.
Click the function name newCustomer.
Click Logs.
Verify that the log entries generated from the function code are present and display the data from the database document that you created.
You might need to click Refresh to view the latest log entries.
Click Check my progress to verify the objective.
Develop an event-driven function for new Firestore documents.
Check my progress
Task 4. Develop an event-driven function for Firestore to update a document
In this task, you develop a function that is triggered when a document is updated in the Firestore database. Your function adds a new field to the document with a value that is derived from the values of some of the other document's fields.
Write the function code
In the editor, add the following code below in the
firestore-functions/index.js
file:const Firestore = require('@google-cloud/firestore'); const firestore = new Firestore({ projectId: process.env.GOOGLE_CLOUD_PROJECT, }); functions.cloudEvent('updateCustomer', async cloudEvent => { console.log('Loading protos...'); const root = await protobuf.load('data.proto'); const DocumentEventData = root.lookupType( 'google.events.cloud.firestore.v1.DocumentEventData' ); console.log('Decoding data...'); const firestoreReceived = DocumentEventData.decode(cloudEvent.data); const resource = firestoreReceived.value.name; const affectedDoc = firestore.doc(resource.split('/documents/')[1]); // Fullname already exists, so don't update again to avoid infinite loop. if (firestoreReceived.value.fields.hasOwnProperty('fullname')) { console.log('Fullname is already present in document.'); return; } if (firestoreReceived.value.fields.hasOwnProperty('lastname')) { const lname = firestoreReceived.value.fields.lastname.stringValue; const fname = firestoreReceived.value.fields.firstname.stringValue; const fullname = `${fname} ${lname}` console.log(`Adding fullname --> ${fullname}`); await affectedDoc.update({ fullname: fullname }); } });
You can define multiple functions with their own entry points in the same project main file and deploy them separately to Cloud Functions.
With this approach, every function may share the same set of dependencies even if some of those functions do not need those dependencies.
To minimize the number of dependencies needed for a particular function and reduce it's memory requirements, it is recommended to keep each function's source code in it's own top-level directory with it's own project configuration files.
Deploy the function
To deploy the new function, run the following command from Cloud Shell:
gcloud functions deploy updateCustomer \ --gen2 \ --runtime=nodejs20 \ --region=us-east1 \ --trigger-location=us-east1 \ --source=. \ --entry-point=updateCustomer \ --trigger-event-filters=type=google.cloud.firestore.document.v1.updated \ --trigger-event-filters=database='(default)' \ --trigger-event-filters-path-pattern=document='customers/{name}'
Verify the command output indicating that the function has been deployed and the state is
Active
.
Test the function
In the Cloud Console, in Firestore Studio, select the existing documents in the
customers
collection with afirstname
field value of Lucas.For this document, click Add Field.
Add a field with the following values:
| Field name | Field type | Field value | | --- | --- | --- | | lastname | string | Sherman |
Click Save Field.
Wait for a few seconds, and then verify that you see a new field
fullname
is added to the document.This indicates that your cloud function
updateCustomer
was invoked when the document was updated.To verify that your cloud function was invoked, on the Navigation menu (), under Serverless, click Cloud Functions.
Click the function name updateCustomer.
Click Logs.
Verify that the log entries generated from the function code are present that indicate that the
fullname
field was added to the document.You might need to click Refresh to view the latest log entries.
Click Check my progress to verify the objective.
Develop an event-driven function for Firestore to update a document.
Check my progress
Task 5. Using Secrets with Cloud Functions
Secret Manager is a Google Cloud service that securely stores data like API keys, passwords, certificates, credentials, and other sensitive information. You can then access these secrets from Cloud Functions or other services for use in your function logic or service implementation.
In this task, you create and store a credential as a secret in Secret Manager. You develop a function to access the key in your function logic.
Create a secret
To create and use secrets, run the following command in Cloud Shell and enable the Secret Manager API:
gcloud services enable secretmanager.googleapis.com
Create and store a secret named
api-cred
with valuesecret_api_key
in Secret Manager:echo -n "secret_api_key" | gcloud secrets create api-cred --replication-policy="automatic" --data-file=-
The replication-policy is used to determine which regions are used to store secrets and their versions. To specify one or more regions where a secret can be replicated, you can set the replication-policy to user-managed.
Grant access
To access a secret, your function's runtime service account must be granted access to the secret.
By default, 2nd generation Cloud Functions uses the Compute Engine default service account as a function's runtime service account.
To authenticate with Secret Manager, grant the
Secret Manager Secret Accessor
role to the Compute Engine default service account:gcloud secrets add-iam-policy-binding api-cred --member=serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com --project=$PROJECT_ID --role='roles/secretmanager.secretAccessor'
Note: For production use, you should configure your function to authenticate with its own user-managed service account, that is assigned the least-permissive set of roles required to accomplish that function's tasks.
Access the secret from your function
In this subtask, you modify the previously developed newCustomer
function to access the secret.
In the editor, add the following code to the
newCustomer
function in theindex.js
file. Add the code at the end of the function after the lastconsole.log
statement in the function body:// BEGIN access a secret const fs = require('fs/promises'); try { const secret = await fs.readFile('/etc/secrets/api_cred/latest', { encoding: 'utf8' }); // use the secret. For lab testing purposes, we log the secret. console.log('secret: ', secret); } catch (err) { console.log(err); } // End access a secret
The function code reads the secret value from a file that is mounted to a volume that is accessible to the function. You configure this method of accessing the secret when you deploy the function in the next step.
Redeploy the function
In Cloud Shell, redeploy the
newCustomer
function with the secret:gcloud functions deploy newCustomer \ --gen2 \ --runtime=nodejs20 \ --region=us-east1 \ --trigger-location=us-east1 \ --source=. \ --entry-point=newCustomer \ --trigger-event-filters=type=google.cloud.firestore.document.v1.created \ --trigger-event-filters=database='(default)' \ --trigger-event-filters-path-pattern=document='customers/{name}' \ --set-secrets '/etc/secrets/api_cred/latest=api-cred:latest'
The set-secrets deploy command option specifies the mount path: /etc/secrets/api_cred, and the secret path: /latest.
By referencing a secret as a volume, your function accesses the latest secret value from Secret Manager each time the file is read from disk.
Your function can also access a specific version of the secret value as an environment variable. View the documentation on using secrets with Cloud Functions for more information.
After the function is deployed, verify that it has access to the secret:
gcloud functions describe newCustomer
The output from the
describe
command includes information about the secret. Here's a partial output from the command:... secretVolumes: - mountPath: /etc/secrets/api_cred projectId: 'qwiklabs-gcp-04-1024504e6418' secret: api-cred versions: - path: /latest version: latest ...
Test the function
To test the function, repeat the test from the previous task to add a new customer document from Firestore Studio in Cloud console.
To view the function's logs in the Cloud console, in the navigation menu, click Cloud Functions, and then click the newCustomer function name.
To view the function's logs, click Logs.
Verify that the entry to log the value of the secret key is present:
secret: secret_api_key
Click Check my progress to verify the objective.
Solution of Lab
Set up Firestore
Navigation Firestore
Click Create Database
Click Native mode (recommended), and then click Continue
Choose Region from lab
For Secure rules, select Test rules
Create and Save a Collection
Click
Start collection
.Fill in the details as follows, then click Save:
Collection ID:
customers
Document ID:
Randomly Genarate
Add Fields to the collection:
Field name:
firstname
Field type:
string
Field value:
Lucas
Click the Add a Field (+) button to add another field:
Field name:
lastname
Field type:
string
Field value:
Sherman
Click Save to finalize the collection.
Open the Cloud Shell terminal window.
export REGION=
curl -LO raw.githubusercontent.com/quiccklabs/Labs_solutions/master/Integrating%20Cloud%20Functions%20with%20Firestore/quicklab.sh
sudo chmod +x quicklab.sh
./quicklab.sh
Wait for command to execute.
Now Delete the old field and again create the same.
Field name: lastname
Field type:
string
Field value:
Sherman