-
Notifications
You must be signed in to change notification settings - Fork 6.7k
feat(secretmanager): Adding python cmek samples #13749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(secretmanager): Adding python cmek samples #13749
Conversation
…python-docs-samples into python_expiretime_samples
…python-docs-samples into python_expiretime_samples
…python-docs-samples into python_expiretime_samples
…-crest/python-docs-samples into python_rotation_samples
Summary of ChangesHello @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
Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this 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.
| 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 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
)There was a problem hiding this comment.
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
| 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}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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}"There was a problem hiding this comment.
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
| assert ( | ||
| not retrieved_secret.rotation | ||
| ), f"Rotation is {repr(retrieved_secret.rotation)}, expected None or empty" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| 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}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| 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}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| assert ( | ||
| not retrieved_secret.rotation | ||
| ), f"Rotation is {repr(retrieved_secret.rotation)}, expected None or empty" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| 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 | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
)There was a problem hiding this comment.
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). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| Creates a new secret with a customer-managed encryption key (CMEK). | |
| Creates a new secret with a customer-managed encryption key (CMEK). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| 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}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| sm_client = secretmanager.SecretManagerServiceClient() | ||
|
|
||
| # Build the resource name of the parent secret. | ||
| secret_name = sm_client.secret_path(project_id, secret_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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) |
There was a problem hiding this comment.
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
…python-docs-samples into python_expiretime_samples
…-crest/python-docs-samples into python_rotation_samples
…rest/python-docs-samples into python_cmek_samples
…python-docs-samples into python_expiretime_samples
…-crest/python-docs-samples into python_rotation_samples
…rest/python-docs-samples into python_cmek_samples
…python-docs-samples into python_expiretime_samples
…-crest/python-docs-samples into python_rotation_samples
…rest/python-docs-samples into python_cmek_samples
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
nox -s py-3.9(see Test Environment Setup)nox -s lint(see Test Environment Setup)