Skip to content

Conversation

@khilan-crest
Copy link

Adding python cmek samples

Fixes #

Note: Before submitting a pull request, please open an issue for discussion if you are not associated with Google.

Checklist

@product-auto-label product-auto-label bot added api: secretmanager Issues related to the Secret Manager API. samples Issues that are directly related to samples. labels Jan 16, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @khilan-crest, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the Python samples for Google Cloud Secret Manager by introducing new functionalities for enhanced secret lifecycle management. It provides practical examples for configuring secrets with Customer-Managed Encryption Keys (CMEK), setting expiration dates, and establishing rotation schedules. Furthermore, the PR includes samples for interacting with secret tag bindings, allowing users to list and detach tags. These new samples cover both globally replicated and regionally managed secrets, ensuring comprehensive coverage for various deployment scenarios and improving the overall utility of the sample library.

Highlights

  • New Secret Management Samples: Added Python samples for creating and managing secrets with advanced features like Customer-Managed Encryption Keys (CMEK), expiration times, and rotation policies.
  • Tag Binding Operations: Introduced samples for listing and detaching tag bindings from secrets.
  • Regional Secret Support: Extended advanced secret management capabilities to regional secrets, including CMEK, expiration, rotation, and tag binding operations.
  • Comprehensive Update/Delete Samples: Provided corresponding samples for updating expiration and rotation settings, and deleting these configurations for both global and regional secrets.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a comprehensive set of new Python samples for Google Cloud Secret Manager, covering features like CMEK, expiration, rotation, and tagging for both global and regional secrets. The changes are well-structured and the new samples are a valuable addition. I've identified a few areas for improvement, mainly around consistency in client usage, resource path construction, and a couple of bugs in the test assertions. Addressing these points will enhance the robustness and maintainability of the new samples.

Comment on lines 19 to 55
from google.api_core import client_options
from google.cloud import secretmanager
from google.protobuf import field_mask_pb2


def delete_regional_secret_rotation(
project_id: str, secret_id: str, location_id: str
) -> None:
"""
Removes the rotation configuration from a regional secret.
Args:
project_id (str): ID of the Google Cloud project
secret_id (str): ID of the secret
location_id (str): Region where the secret is stored (e.g., "us-central1")
Example:
# Delete rotation configuration from a regional secret
delete_regional_secret_rotation(
"my-project",
"my-secret-with-rotation",
"us-central1"
)
"""
# Construct the secret name from the component parts
secret_name = (
f"projects/{project_id}/locations/{location_id}/secrets/{secret_id}"
)

# Set up the endpoint for the specific region
endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
client_option = client_options.ClientOptions(api_endpoint=endpoint)

# Create the Secret Manager client with the regional endpoint
client = secretmanager.SecretManagerServiceClient(
client_options=client_option
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For consistency with other regional samples and for more robust code, please import secretmanager_v1 and use the secret_path helper to construct the secret resource name instead of formatting the string manually.

from google.api_core import client_options
from google.cloud import secretmanager_v1 as secretmanager
from google.protobuf import field_mask_pb2


def delete_regional_secret_rotation(
    project_id: str, secret_id: str, location_id: str
) -> None:
    """
    Removes the rotation configuration from a regional secret.

    Args:
        project_id (str): ID of the Google Cloud project
        secret_id (str): ID of the secret
        location_id (str): Region where the secret is stored (e.g., "us-central1")

    Example:
        # Delete rotation configuration from a regional secret
        delete_regional_secret_rotation(
            "my-project",
            "my-secret-with-rotation",
            "us-central1"
        )
    """
    # Set up the endpoint for the specific region
    endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
    client_option = client_options.ClientOptions(api_endpoint=endpoint)

    # Create the Secret Manager client with the regional endpoint
    client = secretmanager.SecretManagerServiceClient(
        client_options=client_option
    )

    # Construct the secret name from the component parts.
    secret_name = client.secret_path(
        project=project_id, location=location_id, secret=secret_id
    )

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client.secret_path not exist for location based paths

Comment on lines +18 to +57
from google.api_core import client_options
from google.cloud import resourcemanager_v3


def detach_regional_tag(
project_id: str, location_id: str, secret_id: str, tag_value: str
) -> None:
"""
Detaches a tag value from a regional secret.
Args:
project_id (str): ID of the Google Cloud project
location_id (str): Region where the secret is stored (e.g., "us-central1")
secret_id (str): ID of the secret
tag_value (str): Tag value to detach (e.g., "tagValues/123456789012")
Example:
# Detach a tag value from a regional secret
detach_regional_tag(
"my-project",
"us-central1",
"my-secret",
"tagValues/123456789012"
)
"""
# Set up the endpoint for the regional resource manager
rm_endpoint = f"{location_id}-cloudresourcemanager.googleapis.com"
client_option = client_options.ClientOptions(api_endpoint=rm_endpoint)

# Create the Tag Bindings client with the regional endpoint
tag_bindings_client = resourcemanager_v3.TagBindingsClient(
client_options=client_option
)

secret_name = (
f"projects/{project_id}/locations/{location_id}/secrets/{secret_id}"
)

# Format the parent resource for the tag bindings request
parent = f"//secretmanager.googleapis.com/{secret_name}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For consistency and to prevent potential errors from manual string formatting, it's better to use the secret_path helper from the Secret Manager client to construct the resource name. This requires importing secretmanager_v1.

from google.api_core import client_options
from google.cloud import resourcemanager_v3
from google.cloud import secretmanager_v1 as secretmanager


def detach_regional_tag(
    project_id: str, location_id: str, secret_id: str, tag_value: str
) -> None:
    """
    Detaches a tag value from a regional secret.

    Args:
        project_id (str): ID of the Google Cloud project
        location_id (str): Region where the secret is stored (e.g., "us-central1")
        secret_id (str): ID of the secret
        tag_value (str): Tag value to detach (e.g., "tagValues/123456789012")

    Example:
        # Detach a tag value from a regional secret
        detach_regional_tag(
            "my-project",
            "us-central1",
            "my-secret",
            "tagValues/123456789012"
        )
    """
    # Set up the endpoint for the regional resource manager
    rm_endpoint = f"{location_id}-cloudresourcemanager.googleapis.com"
    client_option = client_options.ClientOptions(api_endpoint=rm_endpoint)

    # Create the Tag Bindings client with the regional endpoint
    tag_bindings_client = resourcemanager_v3.TagBindingsClient(
        client_options=client_option
    )

    secret_name = secretmanager.SecretManagerServiceClient.secret_path(
        project=project_id, location=location_id, secret=secret_id
    )

    # Format the parent resource for the tag bindings request
    parent = f"//secretmanager.googleapis.com/{secret_name}"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client.secret_path not exist for location based paths

Comment on lines 982 to 984
assert (
not retrieved_secret.rotation
), f"Rotation is {repr(retrieved_secret.rotation)}, expected None or empty"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The assertion not retrieved_secret.rotation is incorrect for checking if a protobuf message field is empty. An empty Rotation message is not falsy. To correctly verify that the rotation configuration has been removed, you should compare it to a default Rotation instance.

Suggested change
assert (
not retrieved_secret.rotation
), f"Rotation is {repr(retrieved_secret.rotation)}, expected None or empty"
assert (
retrieved_secret.rotation == secretmanager.types.Rotation()
), f"Rotation is {repr(retrieved_secret.rotation)}, expected empty"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 19 to 55
from google.api_core import client_options
from google.cloud import secretmanager


def create_regional_secret_with_cmek(
project_id: str, secret_id: str, location_id: str, kms_key_name: str
) -> None:
"""
Creates a new regional secret encrypted with a customer-managed encryption key (CMEK).
Args:
project_id (str): ID of the Google Cloud project
secret_id (str): ID of the secret to create
location_id (str): Region where the secret should be stored (e.g., "us-central1")
kms_key_name (str): Full resource name of the KMS key
(e.g., "projects/my-project/locations/{location_id}/keyRings/{keyringname}/cryptoKeys/{keyname}")
Example:
# Create a regional secret with a customer-managed encryption key
create_regional_secret_with_cmek(
"my-project",
"my-regional-secret-with-cmek",
"us-central1",
"projects/my-project/locations/us-central1/keyRings/my-keyring/cryptoKeys/my-key"
)
"""
# Set up the endpoint for the specific region
endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
client_option = client_options.ClientOptions(api_endpoint=endpoint)

# Create the Secret Manager client with the regional endpoint
client = secretmanager.SecretManagerServiceClient(
client_options=client_option
)

# Build the resource name of the parent project with location
parent = f"projects/{project_id}/locations/{location_id}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For regional samples, it's better to explicitly import secretmanager_v1 for consistency with other regional samples in the repository. This also allows you to use the location_path helper method to construct the parent resource name, which is more robust than manual string formatting.

from google.api_core import client_options
from google.cloud import secretmanager_v1 as secretmanager


def create_regional_secret_with_cmek(
    project_id: str, secret_id: str, location_id: str, kms_key_name: str
) -> None:
    """
    Creates a new regional secret encrypted with a customer-managed encryption key (CMEK).

    Args:
        project_id (str): ID of the Google Cloud project
        secret_id (str): ID of the secret to create
        location_id (str): Region where the secret should be stored (e.g., "us-central1")
        kms_key_name (str): Full resource name of the KMS key
            (e.g., "projects/my-project/locations/{location_id}/keyRings/{keyringname}/cryptoKeys/{keyname}")

    Example:
        # Create a regional secret with a customer-managed encryption key
        create_regional_secret_with_cmek(
            "my-project",
            "my-regional-secret-with-cmek",
            "us-central1",
            "projects/my-project/locations/us-central1/keyRings/my-keyring/cryptoKeys/my-key"
        )
    """
    # Set up the endpoint for the specific region
    endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
    client_option = client_options.ClientOptions(api_endpoint=endpoint)

    # Create the Secret Manager client with the regional endpoint
    client = secretmanager.SecretManagerServiceClient(
        client_options=client_option
    )

    # Build the resource name of the parent project with location
    parent = client.location_path(project=project_id, location=location_id)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 20 to 53
from google.api_core import client_options
from google.cloud import secretmanager
from google.protobuf import timestamp_pb2


def create_regional_secret_with_expire_time(
project_id: str, secret_id: str, location_id: str
) -> None:
"""
Creates a new regional secret with an expiration time.
Args:
project_id (str): ID of the Google Cloud project
secret_id (str): ID of the secret to create
location_id (str): Region where the secret should be stored (e.g., "us-central1")
Example:
# Create a regional secret that expires in 1 hour
create_regional_secret_with_expire_time("my-project", "my-secret-with-expiry", "us-central1")
"""
# Set expiration time to 1 hour from now
expire_time = datetime.now(timezone.utc) + timedelta(hours=1)

# Set up the endpoint for the specific region
endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
client_option = client_options.ClientOptions(api_endpoint=endpoint)

# Create the Secret Manager client with the regional endpoint
client = secretmanager.SecretManagerServiceClient(
client_options=client_option
)

# Build the resource name of the parent project with location
parent = f"projects/{project_id}/locations/{location_id}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For consistency with other regional samples and for more robust code, please import secretmanager_v1 and use the location_path helper to construct the parent resource name instead of formatting the string manually.

from google.api_core import client_options
from google.cloud import secretmanager_v1 as secretmanager
from google.protobuf import timestamp_pb2


def create_regional_secret_with_expire_time(
    project_id: str, secret_id: str, location_id: str
) -> None:
    """
    Creates a new regional secret with an expiration time.

    Args:
        project_id (str): ID of the Google Cloud project
        secret_id (str): ID of the secret to create
        location_id (str): Region where the secret should be stored (e.g., "us-central1")

    Example:
        # Create a regional secret that expires in 1 hour
        create_regional_secret_with_expire_time("my-project", "my-secret-with-expiry", "us-central1")
    """
    # Set expiration time to 1 hour from now
    expire_time = datetime.now(timezone.utc) + timedelta(hours=1)

    # Set up the endpoint for the specific region
    endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
    client_option = client_options.ClientOptions(api_endpoint=endpoint)

    # Create the Secret Manager client with the regional endpoint
    client = secretmanager.SecretManagerServiceClient(
        client_options=client_option
    )

    # Build the resource name of the parent project with location
    parent = client.location_path(project=project_id, location=location_id)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 1150 to 1152
assert (
not retrieved_secret.rotation
), f"Rotation is {repr(retrieved_secret.rotation)}, expected None or empty"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The assertion not retrieved_secret.rotation is incorrect for checking if a protobuf message field is empty. An empty Rotation message is not falsy. To correctly verify that the rotation configuration has been removed, you should check for the absence of its sub-fields or compare it to a default Rotation instance.

Suggested change
assert (
not retrieved_secret.rotation
), f"Rotation is {repr(retrieved_secret.rotation)}, expected None or empty"
assert (
retrieved_secret.rotation == secretmanager_v1.types.Rotation()
), f"Rotation is {repr(retrieved_secret.rotation)}, expected empty"

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 20 to 59
from google.api_core import client_options
from google.cloud import secretmanager
from google.protobuf import field_mask_pb2, timestamp_pb2


def update_regional_secret_expiration(
project_id: str, secret_id: str, location_id: str
) -> None:
"""
Updates the expiration time of a regional secret.
Args:
project_id (str): ID of the Google Cloud project
secret_id (str): ID of the secret
location_id (str): Region where the secret is stored (e.g., "us-central1")
Example:
# Update expiration time of a regional secret
update_regional_secret_expiration(
"my-project",
"my-secret",
"us-central1"
)
"""
# Construct the secret name from the component parts
secret_name = (
f"projects/{project_id}/locations/{location_id}/secrets/{secret_id}"
)

# Set new expiration time to 2 hours from now
new_expire = datetime.now(timezone.utc) + timedelta(hours=2)

# Set up the endpoint for the specific region
endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
client_option = client_options.ClientOptions(api_endpoint=endpoint)

# Create the Secret Manager client with the regional endpoint
client = secretmanager.SecretManagerServiceClient(
client_options=client_option
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

For consistency with other regional samples and for more robust code, please import secretmanager_v1 and use the secret_path helper to construct the secret resource name instead of formatting the string manually.

from google.api_core import client_options
from google.cloud import secretmanager_v1 as secretmanager
from google.protobuf import field_mask_pb2, timestamp_pb2


def update_regional_secret_expiration(
    project_id: str, secret_id: str, location_id: str
) -> None:
    """
    Updates the expiration time of a regional secret.

    Args:
        project_id (str): ID of the Google Cloud project
        secret_id (str): ID of the secret
        location_id (str): Region where the secret is stored (e.g., "us-central1")

    Example:
        # Update expiration time of a regional secret
        update_regional_secret_expiration(
            "my-project",
            "my-secret",
            "us-central1"
        )
    """
    # Set new expiration time to 2 hours from now
    new_expire = datetime.now(timezone.utc) + timedelta(hours=2)

    # Set up the endpoint for the specific region
    endpoint = f"secretmanager.{location_id}.rep.googleapis.com"
    client_option = client_options.ClientOptions(api_endpoint=endpoint)

    # Create the Secret Manager client with the regional endpoint
    client = secretmanager.SecretManagerServiceClient(
        client_options=client_option
    )

    # Construct the secret name from the component parts.
    secret_name = client.secret_path(
        project=project_id, location=location_id, secret=secret_id
    )

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client.secret_path not exist for location based paths

project_id: str, secret_id: str, kms_key_name: str
) -> None:
"""
Creates a new secret with a customer-managed encryption key (CMEK).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The first line of the docstring is indented, which violates the PEP 257 style guide for docstrings. The summary line should not have extra indentation.

Suggested change
Creates a new secret with a customer-managed encryption key (CMEK).
Creates a new secret with a customer-managed encryption key (CMEK).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 19 to 39
from google.cloud import resourcemanager_v3


def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None:
"""
Detaches a tag value from a secret.
Args:
project_id (str): The project ID where the secret exists.
secret_id (str): The ID of the secret from which to detach the tag.
tag_value (str): The tag value to detach (e.g., "tagValues/123456789012").
Example:
# Detach a tag value from a secret
detach_tag("my-project", "my-secret", "tagValues/123456789012")
"""
# Create the Resource Manager client.
client = resourcemanager_v3.TagBindingsClient()

# Build the resource name of the parent secret.
secret_name = f"projects/{project_id}/secrets/{secret_id}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency and to prevent potential errors from manual string formatting, it's better to use the secret_path helper from the Secret Manager client to construct the resource name. This also requires importing the secretmanager client.

from google.cloud import resourcemanager_v3
from google.cloud import secretmanager


def detach_tag(project_id: str, secret_id: str, tag_value: str) -> None:
    """
    Detaches a tag value from a secret.

    Args:
        project_id (str): The project ID where the secret exists.
        secret_id (str): The ID of the secret from which to detach the tag.
        tag_value (str): The tag value to detach (e.g., "tagValues/123456789012").

    Example:
        # Detach a tag value from a secret
        detach_tag("my-project", "my-secret", "tagValues/123456789012")
    """
    # Create the Resource Manager client.
    client = resourcemanager_v3.TagBindingsClient()

    # Build the resource name of the parent secret.
    secret_name = secretmanager.SecretManagerServiceClient.secret_path(project_id, secret_id)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 41 to 44
sm_client = secretmanager.SecretManagerServiceClient()

# Build the resource name of the parent secret.
secret_name = sm_client.secret_path(project_id, secret_id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The SecretManagerServiceClient is instantiated here only to call secret_path. Since secret_path is a class method, you can call it directly on the class without creating a client instance. This avoids an unnecessary object creation and API client initialization.

Suggested change
sm_client = secretmanager.SecretManagerServiceClient()
# Build the resource name of the parent secret.
secret_name = sm_client.secret_path(project_id, secret_id)
# Build the resource name of the parent secret.
secret_name = secretmanager.SecretManagerServiceClient.secret_path(project_id, secret_id)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to create client for consistency and readability

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: secretmanager Issues related to the Secret Manager API. samples Issues that are directly related to samples.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant