Update a resource using a field mask

When updating a resource in Google Cloud, you often want to modify specific fields without replacing the entire resource. The Cloud Client Libraries for Rust support field masks so that you can specify which fields an update operation modifies and leave all other fields unchanged.

Before you begin

This document uses the Secret Manager API, though the same concepts apply to other Google Cloud services.

Before running the code in this document, complete the following prerequisites:

Dependencies

The google-cloud-wkt crate provides well-known types for Google Cloud APIs, including FieldMask. Add google-cloud-secretmanager-v1, google-cloud-wkt, anyhow, and tokio to your Cargo.toml file:

cargo add google-cloud-secretmanager-v1 google-cloud-wkt anyhow
cargo add tokio --features macros,rt-multi-thread

How field masks work

A FieldMask contains a set of symbolic field paths that identify which fields an update operation modifies or a read operation returns.

In an update operation, the service changes only the fields listed in the mask and ignores values on any unlisted fields in the request message. If you omit the field mask on an update request, the operation applies to all mutable fields.

To reset a specific field to its default value, include the field path in the mask and leave that field at its default value on the resource struct.

Update fields on a resource

To create a secret and selectively update its labels and annotations fields using a FieldMask, complete the following steps:

  1. Initialize a SecretManagerService client and create a secret with empty labels and annotations:

    let client = SecretManagerService::builder().build().await?;
    
    let secret = client
        .create_secret()
        .set_parent(format!("projects/{project_id}"))
        .set_secret_id("your-secret")
        .set_secret(
            Secret::new().set_replication(Replication::new().set_automatic(Automatic::new())),
        )
        .send()
        .await?;
    println!("CREATE = {secret:?}");

  2. Send an update_secret request that sets labels, annotations, the current etag to prevent overwriting concurrent updates, and an update mask covering "annotations" and "labels":

    let tag = |mut labels: HashMap<_, _>, msg: &str| {
        labels.insert("updated".to_string(), msg.to_string());
        labels
    };
    
    let update = client
        .update_secret()
        .set_secret(
            Secret::new()
                .set_name(&secret.name)
                .set_etag(secret.etag)
                .set_labels(tag(secret.labels, "your-label"))
                .set_annotations(tag(secret.annotations, "your-annotations")),
        )
        .set_update_mask(FieldMask::default().set_paths(["annotations", "labels"]))
        .send()
        .await?;
    println!("UPDATE = {update:?}");

    The set_update_mask call specifies the exact field paths to modify:

    .set_update_mask(FieldMask::default().set_paths(["annotations", "labels"]))

The output confirms that the operation updated both fields:

labels: {"updated": "your-label"},
...
annotations: {"updated": "your-annotations"},

Complete code

The following function combines secret creation and the field-mask update:

use google_cloud_secretmanager_v1::client::SecretManagerService;
use google_cloud_secretmanager_v1::model::replication::Automatic;
use google_cloud_secretmanager_v1::model::{Replication, Secret};
use google_cloud_wkt::FieldMask;
use std::collections::HashMap;

pub async fn sample(project_id: &str) -> anyhow::Result<()> {
    let client = SecretManagerService::builder().build().await?;

    let secret = client
        .create_secret()
        .set_parent(format!("projects/{project_id}"))
        .set_secret_id("your-secret")
        .set_secret(
            Secret::new().set_replication(Replication::new().set_automatic(Automatic::new())),
        )
        .send()
        .await?;
    println!("CREATE = {secret:?}");

    let tag = |mut labels: HashMap<_, _>, msg: &str| {
        labels.insert("updated".to_string(), msg.to_string());
        labels
    };

    let update = client
        .update_secret()
        .set_secret(
            Secret::new()
                .set_name(&secret.name)
                .set_etag(secret.etag)
                .set_labels(tag(secret.labels, "your-label"))
                .set_annotations(tag(secret.annotations, "your-annotations")),
        )
        .set_update_mask(FieldMask::default().set_paths(["annotations", "labels"]))
        .send()
        .await?;
    println!("UPDATE = {update:?}");

    Ok(())
}

Clean up

To avoid incurring charges to your Google Cloud account and to prevent ALREADY_EXISTS errors on subsequent runs, delete the your-secret secret when you're finished.