Skip to content

Conversation

@khilan-crest
Copy link

Adding secret rotation 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 enhances the Google Cloud Secret Manager Python samples by introducing a suite of new functionalities. It provides developers with clear, executable examples for advanced secret lifecycle management, including setting and updating expiration times, configuring and modifying secret rotation policies, and integrating with Pub/Sub for notifications. Furthermore, it adds robust examples for managing resource tags on secrets, covering creation, listing, and detachment, for both global and regional secret instances.

Highlights

  • Secret Lifecycle Management: Introduced new samples for creating secrets with expiration times and configuring automatic secret rotation, along with corresponding samples to update and remove these settings.
  • Pub/Sub Integration: Added samples demonstrating how to create secrets with Pub/Sub topics for notification, which is crucial for automated secret rotation workflows.
  • Tag Management for Secrets: Provided new samples for listing and detaching tag bindings from secrets, enhancing metadata management capabilities.
  • Regional Resource Support: Extended all new lifecycle and tag management functionalities to regional Secret Manager resources, ensuring comprehensive coverage for geographically distributed deployments.
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 samples for Secret Manager, covering secret rotation, expiration, and tagging for both global and regional secrets. The changes are well-structured and the accompanying tests are thorough. I've provided a few suggestions to improve the correctness of the documentation, ensure consistency across the new samples, and address minor issues like timezone handling and typos. Overall, this is a great addition to the repository.

Args:
project_id (str): ID of the Google Cloud project
secret_id (str): ID of the secret to update
rotation_period_hours (int): New rotation period in hours
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This rotation_period_hours parameter is documented but is not present in the function signature. Since the value is hardcoded within the function, this line should be removed from the docstring to avoid confusion.

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 32 to 37
# Update the rotation period of a secret to 60 days
update_secret_rotation(
"my-project",
"my-secret-with-rotation",
"projects/my-project/topics/my-rotation-topic"
)
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 example usage is incorrect and will raise an error. It passes a topic_name which is not a parameter of the function. Additionally, the comment mentions '60 days' while the code hardcodes the rotation to 48 hours. The example should be corrected to reflect the actual implementation.

Suggested change
# Update the rotation period of a secret to 60 days
update_secret_rotation(
"my-project",
"my-secret-with-rotation",
"projects/my-project/topics/my-rotation-topic"
)
# Update the rotation period of a secret to 48 hours
update_secret_rotation(
"my-project",
"my-secret-with-rotation"
)

Copy link
Author

Choose a reason for hiding this comment

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

Done

# limitations under the License.

# [START secretmanager_create_regional_secret_with_rotation]
from datetime import datetime, timedelta
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To create timezone-aware datetime objects, timezone should be imported from the datetime module. This is important for consistency and to prevent potential timezone-related bugs.

Suggested change
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone

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 53 to 61
count = 0

print(f"Tag bindings for {secret_name}:")
for binding in bindings:
print(f"- Tag Value: {binding.tag_value}")
count += 1

if count == 0:
print(f"No tag bindings found for {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.

medium

Using a counter variable count to check if any items were processed is a bit verbose. A more Pythonic approach is to use a boolean flag to track if any bindings were found. This can improve readability.

For example:

    found_bindings = False
    print(f"Tag bindings for {secret_name}:")
    for binding in bindings:
        print(f"- Tag Value: {binding.tag_value}")
        found_bindings = True

    if not found_bindings:
        print(f"No tag bindings found for {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.

Done


# Set next rotation time to 24 hours from now
next_rotation_time = timestamp_pb2.Timestamp()
next_rotation_time.FromDatetime(datetime.now() + timedelta(hours=24))
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using datetime.now() without a timezone creates a naive datetime object, which can lead to inconsistencies. It's best practice to use timezone-aware objects, especially for APIs. Please use datetime.now(timezone.utc) for consistency and to avoid potential bugs.

Suggested change
next_rotation_time.FromDatetime(datetime.now() + timedelta(hours=24))
next_rotation_time.FromDatetime(datetime.now(timezone.utc) + timedelta(hours=24))

Copy link
Author

Choose a reason for hiding this comment

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

Done

# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
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 with other sample files in this repository, argparse should be imported inside the if __name__ == "__main__": block. Please move this import statement accordingly.

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 56 to 68
count = 0

# Use the list_tag_bindings method to get all tag bindings
request = resourcemanager_v3.ListTagBindingsRequest(parent=parent)
tag_bindings = tag_bindings_client.list_tag_bindings(request=request)

# Iterate through the results
for binding in tag_bindings:
print(f"- Tag Value: {binding.tag_value}")
count += 1

if count == 0:
print(f"No tag bindings found for {name}.")
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Using a counter variable count to check if any items were processed is a bit verbose. A more Pythonic approach is to use a boolean flag to track if any bindings were found. This can improve readability.

For example:

    found_bindings = False
    for binding in tag_bindings:
        print(f"- Tag Value: {binding.tag_value}")
        found_bindings = True

    if not found_bindings:
        print(f"No tag bindings found for {name}.")

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 1163 to 1172
retrived_secret = get_regional_secret.get_regional_secret(
project_id, location_id, secret_id
)

assert (
len(retrived_secret.topics) == 1
), f"Expected 1 topic, got {len(retrived_secret.topics)}"
assert (
retrived_secret.topics[0].name == topic_name
), f"Topic mismatch: got {retrived_secret.topics[0].name}, want {topic_name}"
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

There's a typo in the variable name retrived_secret. It should be retrieved_secret for correctness and readability.

Suggested change
retrived_secret = get_regional_secret.get_regional_secret(
project_id, location_id, secret_id
)
assert (
len(retrived_secret.topics) == 1
), f"Expected 1 topic, got {len(retrived_secret.topics)}"
assert (
retrived_secret.topics[0].name == topic_name
), f"Topic mismatch: got {retrived_secret.topics[0].name}, want {topic_name}"
retrieved_secret = get_regional_secret.get_regional_secret(
project_id, location_id, secret_id
)
assert (
len(retrieved_secret.topics) == 1
), f"Expected 1 topic, got {len(retrieved_secret.topics)}"
assert (
retrieved_secret.topics[0].name == topic_name
), f"Topic mismatch: got {retrieved_secret.topics[0].name}, want {topic_name}"

Copy link
Author

Choose a reason for hiding this comment

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

Done

# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
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 with other sample files in this repository, argparse should be imported inside the if __name__ == "__main__": block. Please move this import statement.

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 994 to 1001
retrived_secret = get_secret(project_id, secret_id)

assert (
len(retrived_secret.topics) == 1
), f"Expected 1 topic, got {len(retrived_secret.topics)}"
assert (
retrived_secret.topics[0].name == topic_name
), f"Topic mismatch: got {retrived_secret.topics[0].name}, want {topic_name}"
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

There's a typo in the variable name retrived_secret. It should be retrieved_secret for correctness and readability.

Suggested change
retrived_secret = get_secret(project_id, secret_id)
assert (
len(retrived_secret.topics) == 1
), f"Expected 1 topic, got {len(retrived_secret.topics)}"
assert (
retrived_secret.topics[0].name == topic_name
), f"Topic mismatch: got {retrived_secret.topics[0].name}, want {topic_name}"
retrieved_secret = get_secret(project_id, secret_id)
assert (
len(retrieved_secret.topics) == 1
), f"Expected 1 topic, got {len(retrieved_secret.topics)}"
assert (
retrieved_secret.topics[0].name == topic_name
), f"Topic mismatch: got {retrieved_secret.topics[0].name}, want {topic_name}"

Copy link
Author

Choose a reason for hiding this comment

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

Done

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